View Javadoc

1   package cz.cuni.amis.pogamut.ut2004.tournament.teamdeathmatch;
2   
3   import java.io.File;
4   import java.io.IOException;
5   import java.util.ArrayList;
6   import java.util.Collections;
7   import java.util.Comparator;
8   import java.util.Formatter;
9   import java.util.HashMap;
10  import java.util.List;
11  import java.util.Map;
12  import java.util.Map.Entry;
13  import java.util.concurrent.CountDownLatch;
14  import java.util.concurrent.TimeUnit;
15  import java.util.logging.Level;
16  
17  import cz.cuni.amis.pogamut.base.agent.state.level0.IAgentState;
18  import cz.cuni.amis.pogamut.base.agent.state.level1.IAgentStateDown;
19  import cz.cuni.amis.pogamut.base.agent.state.level1.IAgentStateUp;
20  import cz.cuni.amis.pogamut.base.communication.worldview.event.IWorldEventListener;
21  import cz.cuni.amis.pogamut.base.communication.worldview.object.IWorldObjectEvent;
22  import cz.cuni.amis.pogamut.base.communication.worldview.object.IWorldObjectListener;
23  import cz.cuni.amis.pogamut.base.utils.guice.AdaptableProvider;
24  import cz.cuni.amis.pogamut.base.utils.logging.LogCategory;
25  import cz.cuni.amis.pogamut.unreal.communication.messages.UnrealId;
26  import cz.cuni.amis.pogamut.ut2004.agent.module.sensor.AgentStats;
27  import cz.cuni.amis.pogamut.ut2004.analyzer.IUT2004AnalyzerObserver;
28  import cz.cuni.amis.pogamut.ut2004.analyzer.UT2004Analyzer;
29  import cz.cuni.amis.pogamut.ut2004.analyzer.stats.UT2004AnalyzerObsStats;
30  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.StartPlayers;
31  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.MapFinished;
32  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.PlayerScore;
33  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.TeamScore;
34  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.TeamScoreMessage;
35  import cz.cuni.amis.pogamut.ut2004.server.impl.UT2004Server;
36  import cz.cuni.amis.pogamut.ut2004.tournament.botexecution.UT2004BotExecution;
37  import cz.cuni.amis.pogamut.ut2004.tournament.match.UT2004BotConfig;
38  import cz.cuni.amis.pogamut.ut2004.tournament.match.UT2004Match;
39  import cz.cuni.amis.pogamut.ut2004.tournament.match.result.UT2004MatchResult;
40  import cz.cuni.amis.pogamut.ut2004.utils.UCCWrapper;
41  import cz.cuni.amis.utils.ExceptionToString;
42  import cz.cuni.amis.utils.FilePath;
43  import cz.cuni.amis.utils.exception.PogamutException;
44  import cz.cuni.amis.utils.exception.PogamutIOException;
45  import cz.cuni.amis.utils.exception.PogamutInterruptedException;
46  import cz.cuni.amis.utils.flag.FlagListener;
47  import cz.cuni.amis.utils.token.IToken;
48  
49  public class UT2004TeamDeathMatch extends UT2004Match<UT2004TeamDeathMatchConfig, UT2004TeamDeathMatchResult> {
50  
51  	protected int targetScoreLimit = 0;
52  	
53  	public UT2004TeamDeathMatch(UT2004TeamDeathMatchConfig config, LogCategory log) {
54  		super(true, config, log);		
55  	}
56  
57  	@Override
58  	protected UT2004MatchResult waitMatchFinish(UCCWrapper ucc, UT2004Server server, UT2004Analyzer analyzer, Bots bots, long timeoutInMillis) {
59  		// usually the GB2004 dies out whenever match ends -> just wait till server does not fail + timeout + observe bots
60  		
61  		if (log != null && log.isLoggable(Level.WARNING)) {
62  			log.warning(config.getMatchId().getToken() + ": Waiting for the match to finish...");
63  		}
64  		
65  		if (config.getTimeLimit() * 60 * 1000 + 5 * 60 * 1000 > timeoutInMillis) {
66  			timeoutInMillis = config.getTimeLimit() * 60 * 1000 + 5 * 60 * 1000; // give additional 5 minutes to UT2004 to restart GB2004
67  		}
68  		
69  		Map<IToken, FlagListener<Boolean>> customBotObservers = new HashMap<IToken, FlagListener<Boolean>>(config.getBots().size());
70  		FlagListener<IAgentState> serverObs = null;
71  		FlagListener<Boolean> uccObs = null;
72  		IWorldEventListener<PlayerScore> scoresListener = null;
73  		IWorldObjectListener<TeamScore> teamScoresListener = null;
74  		IWorldEventListener<MapFinished> mapFinishedListener = null;
75  		
76  		final CountDownLatch waitLatch = new CountDownLatch(1);
77  		final AdaptableProvider<Boolean> oneOfBotsDiedOut = new AdaptableProvider<Boolean>(false);
78  		final AdaptableProvider<Boolean> serverDiedOut    = new AdaptableProvider<Boolean>(false);
79  		final Map<UnrealId, PlayerScore> scores           = new HashMap<UnrealId, PlayerScore>();
80  		final Map<Integer, TeamScore>    teamScores       = new HashMap<Integer, TeamScore>();
81  		
82  		boolean exception = false;
83  		
84  		try {
85  			teamScores.put(0, new TeamScoreMessage(UnrealId.get("TEAM0"), 0, 0));
86  			teamScores.put(1, new TeamScoreMessage(UnrealId.get("TEAM1"), 1, 0));
87  			serverDiedOut.set(false);
88  			
89  			scoresListener = new IWorldEventListener<PlayerScore>() {
90  
91  				@Override
92  				public void notify(PlayerScore event) {
93  					scores.put(event.getId(), event);
94  				}
95  				
96  			};
97  			server.getWorldView().addEventListener(PlayerScore.class, scoresListener);
98  			
99  			teamScoresListener = new IWorldObjectListener<TeamScore>() {
100 
101 				@Override
102 				public void notify(IWorldObjectEvent<TeamScore> event) {
103 					if (event.getObject() == null) return;
104 					int team = event.getObject().getTeam();
105 					teamScores.put(team, event.getObject());
106 					
107 					if (event.getObject().getScore() >= targetScoreLimit) {
108 						// TARGET SCORE REACHED BY ONE OF THE TEAMS!
109 						waitLatch.countDown();
110 					}
111 				}
112 				
113 			};
114 			server.getWorldView().addObjectListener(TeamScore.class, teamScoresListener);
115 
116 			
117 			for (UT2004BotConfig botConfig : config.getBots().values()) {
118 				FlagListener<Boolean> obs = new FlagListener<Boolean>() {
119 					@Override
120 					public void flagChanged(Boolean changedValue) {
121 						if (!changedValue) {
122 							// bot has died out
123 							oneOfBotsDiedOut.set(true);
124 							waitLatch.countDown();
125 						}
126 					}
127 				};
128 				
129 				bots.bots.get(botConfig.getBotId()).getRunning().addListener(obs);
130 				customBotObservers.put(botConfig.getBotId(), obs);
131 				if (!bots.bots.get(botConfig.getBotId()).getRunning().getFlag()) {
132 					// bot has died out
133 					oneOfBotsDiedOut.set(true);
134 					waitLatch.countDown();
135 					throw new PogamutException("One of custom bots died out from the start, failure!", log, this);
136 				}			
137 			}
138 			
139 			serverObs = new FlagListener<IAgentState>() {
140 	
141 				@Override
142 				public void flagChanged(IAgentState changedValue) {
143 					if (changedValue instanceof IAgentStateDown) {
144 						// server has died out ... consider match to be over...
145 						serverDiedOut.set(true);
146 						waitLatch.countDown();
147 					}
148 				}
149 				
150 			};
151 			
152 			server.getState().addListener(serverObs);
153 			
154 			mapFinishedListener = new IWorldEventListener<MapFinished>() {
155 				@Override
156 				public void notify(MapFinished event) {
157 					log.info("MapFinished event received.");
158 					waitLatch.countDown();
159 				}
160 			};
161 			
162 			server.getWorldView().addEventListener(MapFinished.class, mapFinishedListener);
163 			
164 			if (server.notInState(IAgentStateUp.class)) {
165 				// server has died out ... consider match to be over...
166 				serverDiedOut.set(true);
167 				waitLatch.countDown();
168 				throw new PogamutException("Server is dead from the start, failure!", log, this);
169 			}
170 			
171 			uccObs = new FlagListener<Boolean>() {
172 
173 				@Override
174 				public void flagChanged(Boolean changedValue) {
175 					if (changedValue) {
176 						// GAME IS ENDING!
177 						// Consider match to be over...
178 						serverDiedOut.set(true);
179 						waitLatch.countDown();
180 					}
181 				}
182 				
183 			};
184 			
185 			ucc.getGameEnding().addListener(uccObs);
186 			
187 			waitLatch.await(timeoutInMillis, TimeUnit.MILLISECONDS);
188 			if (waitLatch.getCount() > 0) {
189 				// TIMEOUT!
190 				throw new PogamutException("TIMEOUT! The match did not end in " + (timeoutInMillis / 1000) + " secs.", log, this);
191 			}
192 			
193 			bots.matchEnd = System.currentTimeMillis();
194 			
195 			// RESTORE THE CONFIG...
196 			getConfig().setScoreLimit(targetScoreLimit); 
197 			
198 			// WHAT HAS HAPPENED?
199 			if (oneOfBotsDiedOut.get()) {
200 				// check whether the server is down as well... but let GB2004 to process it
201 				try {
202 					Thread.sleep(5000);
203 				} catch (InterruptedException e) {
204 					throw new PogamutInterruptedException("Interrupted while giving GB2004 time to tear down its connection.", log, this);
205 				}
206 				try {
207 					server.getAct().act(new StartPlayers());
208 				} catch (Exception e) {
209 					// YEP, server is down
210 					serverDiedOut.set(true);
211 				}
212 				if (!serverDiedOut.get()) {
213 					// NO SERVER IS STILL RUNNING
214 					log.warning("ONE OF BOTS HAS DIED OUT, BUT SERVER IS STILL RUNNING ... POSSIBLE MATCH FAILURE!");
215 				}
216 			}
217 			if (!serverDiedOut.get() && server.inState(IAgentStateUp.class)) {
218 				// server is still running? Kill it...
219 				server.kill();
220 			}
221 			// server is DEAD -> assume that the match has ended
222 			
223 			// KILL UCC TO ENSURE NOTHING WILL CHANGE AFTER THAT
224 			if (ucc != null) {
225 				try {
226 					if (log != null && log.isLoggable(Level.INFO)) {
227 						log.info(config.getMatchId().getToken() + ": Killing UCC...");
228 					} 
229 				} catch (Exception e) {				
230 				}
231 				try {
232 					ucc.stop();
233 				} catch (Exception e) {					
234 				}
235 			}
236 			
237 			List<Integer> winners = new ArrayList<Integer>(1);
238 			int maxScore = 0;
239 			
240 			// PROCESS THE RESULT
241 			for (Entry<Integer, TeamScore> entry : teamScores.entrySet()) {
242 				if (entry.getValue() == null || entry.getValue().getScore() == null) {
243 					throw new PogamutException("There is a team '" + entry.getKey() + "' that has NULL score!", this);
244 				}
245 				if (entry.getValue().getScore() == maxScore) {
246 					winners.add(entry.getValue().getTeam());
247 				} else
248 				if (entry.getValue().getScore() > maxScore) {
249 					winners.clear();
250 					winners.add(entry.getValue().getTeam());
251 					maxScore = entry.getValue().getScore();
252 				}
253 			}
254 			
255 			if (winners.size() == 0) {
256 				// no one has reached FragLimit
257 				throw new PogamutException("There is no winner, impossible! **puzzled**", log, this);
258 			}
259 			if (winners.size() > 1) {
260 				StringBuffer sb = new StringBuffer();
261 				sb.append("There is more than one team with highest score == " + maxScore + ": ");
262 				boolean first = true;
263 				for (Integer id : winners) {
264 					if (first) first = false;
265 					else sb.append(", ");
266 					sb.append("Team[" + id + "]");
267 				}
268 				sb.append(".");
269 				if (log != null && log.isLoggable(Level.WARNING)) {
270 					log.warning(sb.toString());
271 				}
272 			}
273 			
274 			if (log != null && log.isLoggable(Level.WARNING)) {
275 				log.warning(config.getMatchId().getToken() + ": MATCH FINISHED!");
276 			}
277 			
278 			return processResults(ucc, server, analyzer, bots, winners, scores, teamScores);
279 			
280 		} catch (Exception e) {
281 			exception = true;
282 			throw new PogamutException("Failed to perform the match!", e, log, this);
283 		} finally {
284 			for (Entry<IToken, FlagListener<Boolean>> entry : customBotObservers.entrySet()) {
285 				bots.bots.get(entry.getKey()).getRunning().removeListener(entry.getValue());
286 			}
287 			server.getState().removeListener(serverObs);
288 			server.getWorldView().removeEventListener(PlayerScore.class, scoresListener);
289 		}		
290 
291 	}
292 	
293 	protected UT2004TeamDeathMatchResult processResults(UCCWrapper ucc, UT2004Server server, UT2004Analyzer analyzer, Bots bots, List<Integer> winners, Map<UnrealId, PlayerScore> finalScores, Map<Integer, TeamScore> teamScores) {
294 		
295 		if (log != null && log.isLoggable(Level.FINE)) {
296 			log.fine(config.getMatchId().getToken() + ": Processing results...");
297 		}
298 		
299 		UT2004TeamDeathMatchResult result = new UT2004TeamDeathMatchResult();
300 		
301 		result.setMatchTimeEnd(((double)bots.matchEnd - (double)bots.matchStart) / (1000));
302 		
303 		for (Entry<Integer, TeamScore> entry : teamScores.entrySet()) {
304 			result.getTeamScores().put(entry.getKey(), entry.getValue());
305 		}
306 		
307 		for (Entry<UnrealId, PlayerScore> entry : finalScores.entrySet()) {
308 			result.getFinalScores().put(bots.getBotId(entry.getKey()), entry.getValue());
309 		}
310 		
311 		for (Entry<IToken, IUT2004AnalyzerObserver> entry : bots.botObservers.entrySet()) {
312 			if (!(entry.getValue() instanceof UT2004AnalyzerObsStats)) {
313 				throw new PogamutException("There is an observer of wrong class, expecting UT2004AnalyzerObsStats, got " + entry.getValue().getClass().getSimpleName() + "!", log, this);
314 			}
315 			result.getBotObservers().put(entry.getKey(), (UT2004AnalyzerObsStats)entry.getValue());
316 		}
317 		
318 		List<IToken> botIds = config.getAllBotIds();
319 		for (IToken botId1 : botIds) {
320 			result.getNames().put(botId1, bots.names.get(bots.getUnrealId(botId1)));
321 			result.getTotalKills().put(botId1, 0);
322 			result.getWasKilled().put(botId1, 0);
323 			result.getSuicides().put(botId1, 0);
324 			for (IToken botId2 : botIds) {
325 				result.getKillCounts().put(botId1, botId2, 0);
326 			}
327 		}
328 		
329 		for (Entry<IToken, UT2004AnalyzerObsStats> entry : result.getBotObservers().entrySet()) {
330 			IToken botId = entry.getKey();
331 			UT2004AnalyzerObsStats obs = entry.getValue();
332 			AgentStats stats = obs.getStats();
333 			for (Entry<UnrealId, Integer> killed : stats.getKilled().entrySet()) {
334 				result.getKillCounts().get(botId).put(bots.getBotId(killed.getKey()), killed.getValue());				
335 			}
336 			for (Entry<UnrealId, Integer> killedBy : stats.getKilledBy().entrySet()) {
337 				if (bots.isNativeBot(killedBy.getKey())) {
338 					result.getKillCounts().get(bots.getBotId(killedBy.getKey())).put(botId, killedBy.getValue());
339 				}
340 			}
341 			result.getSuicides().put(botId, stats.getSuicides());
342 			result.getKillCounts().put(botId, botId, stats.getSuicides());
343 		}
344 		
345 		for (IToken nativeBotId1 : config.getNativeBots().keySet()) {
346 			for (IToken nativeBotId2 : config.getNativeBots().keySet()) {
347 				if (nativeBotId1 == nativeBotId2) continue;
348 				result.getKillCounts().get(nativeBotId1).put(nativeBotId2, 0);
349 			}
350 			result.getSuicides().put(nativeBotId1, 0);
351 		}
352 		
353 		for (IToken botId : botIds) {
354 			int totalKills = 0;
355 			int totalKilled = 0;
356 			for (IToken other : botIds) {
357 				if (botId == other) continue;
358 				totalKills += result.getKillCounts().get(botId, other);
359 				totalKilled += result.getKillCounts().get(other, botId);
360 			}
361 			result.getTotalKills().put(botId, totalKills);
362 			result.getWasKilled().put(botId, totalKilled);
363 			if (config.isNativeBot(botId) || config.isHuman(botId)) {
364 				result.getSuicides().put(botId, result.getFinalScores().get(botId).getDeaths() - totalKilled);
365 			}
366 		}
367 		
368 		if (winners.size() <= 0) {
369 			throw new PogamutException("There is no winner, impossible! **puzzled**", log, this);
370 		} else 
371 		if (winners.size() == 1) {
372 			result.setWinnerTeam(winners.get(0));
373 		} else {
374 			result.setDraw(true);
375 		}
376 		
377 		if (log != null && log.isLoggable(Level.WARNING)) {
378 			log.warning(config.getMatchId().getToken() + ": Results processed, " + (result.isDraw() ? "DRAW!" : "winner is Team[" + winners.get(0) + "]."));
379 		}
380 		
381 		return result;
382 	}
383 	
384 	protected void outputResults_step1(UT2004TeamDeathMatchResult result, File outputDirectory) {
385 		if (log != null && log.isLoggable(Level.FINE)) {
386 			log.fine(config.getMatchId().getToken() + ": Outputting match result into CSV file...");
387 		}
388 		
389 		File file = new File(outputDirectory.getAbsolutePath() + File.separator + "match-" + config.getMatchId().getToken() + "-result.csv");
390 		FilePath.makeDirsToFile(file);
391 		try {
392 			Formatter writer = new Formatter(file);
393 			writer.format("MatchId;ScoreLimit;TimeLimit;TimeEnd;Winner\n");
394 			writer.format
395 					(
396 						"%s;%d;%d;%.3f;%s",
397 						config.getMatchId().getToken(),
398 						config.getScoreLimit(),
399 						config.getTimeLimit(),
400 						result.getMatchTimeEnd(),
401 						result.isDraw() ? "DRAW" : "TEAM" + String.valueOf(result.getWinnerTeam())
402 					);
403 			try {
404 				writer.close();
405 			} catch (Exception e) {			
406 			}
407 		} catch (IOException e) {
408 			throw new PogamutIOException("Failed to write results!", e, log, this);
409 		}
410 		
411 		if (log != null && log.isLoggable(Level.INFO)) {
412 			log.info(config.getMatchId().getToken() + ": Match result output into " + file.getAbsolutePath() + ".");
413 		}
414 		
415 	}
416 	
417 	protected void outputResults_step2(UT2004TeamDeathMatchResult result, File outputDirectory) {
418 		if (log != null && log.isLoggable(Level.FINE)) {
419 			log.fine(config.getMatchId().getToken() + ": Outputting match scores into CSV file...");
420 		}
421 		
422 		File file = new File(outputDirectory.getAbsolutePath() + File.separator + "match-" + config.getMatchId().getToken() + "-team-scores.csv");
423 		FilePath.makeDirsToFile(file);
424 		try {
425 			Formatter writer = new Formatter(file);
426 			
427 			List<TeamScore> teams = new ArrayList<TeamScore>();
428 			for (TeamScore score : result.getTeamScores().values()) {
429 				teams.add(score);
430 			}
431 			
432 			Collections.sort(teams, new Comparator<TeamScore>() {
433 				@Override
434 				public int compare(TeamScore o1, TeamScore o2) {
435 					return o1.getTeam().compareTo(o2.getTeam());
436 				}				
437 			});
438 			
439 			writer.format("teamId");
440 			writer.format(";score");
441 			
442 			for (TeamScore score : teams) {
443 				writer.format("\n");
444 				writer.format("%s", "TEAM" + score.getTeam());
445 				writer.format(";%d", score.getScore());								
446 			}
447 			
448 			try {
449 				writer.close();
450 			} catch (Exception e) {			
451 			}
452 		} catch (IOException e) {
453 			throw new PogamutIOException("Failed to write results!", e, log, this);
454 		}
455 		
456 		file = new File(outputDirectory.getAbsolutePath() + File.separator + "match-" + config.getMatchId().getToken() + "-bot-scores.csv");
457 		FilePath.makeDirsToFile(file);
458 		try {
459 			Formatter writer = new Formatter(file);
460 			
461 			List<IToken> bots = new ArrayList<IToken>(config.getBots().keySet());
462 			List<IToken> nativeBots = new ArrayList<IToken>(config.getNativeBots().keySet());
463 			List<IToken> humans = new ArrayList<IToken>(config.getHumans().keySet());
464 			
465 			Collections.sort(bots, new Comparator<IToken>() {
466 				@Override
467 				public int compare(IToken o1, IToken o2) {
468 					return o1.getToken().compareTo(o2.getToken());
469 				}				
470 			});
471 			Collections.sort(nativeBots, new Comparator<IToken>() {
472 				@Override
473 				public int compare(IToken o1, IToken o2) {
474 					return o1.getToken().compareTo(o2.getToken());
475 				}				
476 			});
477 			Collections.sort(humans, new Comparator<IToken>() {
478 				@Override
479 				public int compare(IToken o1, IToken o2) {
480 					return o1.getToken().compareTo(o2.getToken());
481 				}				
482 			});
483 			result.setBots(bots);
484 			result.setNativeBots(nativeBots);
485 			result.setHumans(humans);
486 			
487 			writer.format("botId");
488 			writer.format(";name;score;kills;killedByOthers;deaths;suicides");
489 			for (IToken token : config.getAllBotIds()) {
490 				writer.format(";");
491 				writer.format(token.getToken());
492 			}
493 			
494 			for (IToken token : config.getAllBotIds()) {
495 				writer.format("\n");
496 				writer.format(token.getToken());
497 				writer.format(";%s", result.getNames().get(token));
498 				writer.format(";%d", result.getFinalScores().get(token).getScore());
499 				writer.format(";%d", result.getTotalKills().get(token));
500 				writer.format(";%d", result.getWasKilled().get(token));
501 				writer.format(";%d", result.getFinalScores().get(token).getDeaths());
502 				writer.format(";%d", result.getSuicides().get(token));				
503 				for (IToken token2 : config.getAllBotIds()) {
504 					writer.format(";%d", result.getKillCounts().get(token).get(token2));
505 				}				
506 			}
507 			
508 			try {
509 				writer.close();
510 			} catch (Exception e) {			
511 			}
512 		} catch (IOException e) {
513 			throw new PogamutIOException("Failed to write results!", e, log, this);
514 		}
515 		
516 		if (log != null && log.isLoggable(Level.INFO)) {
517 			log.info(config.getMatchId().getToken() + ": Match scores output into " + file.getAbsolutePath() + ".");
518 		}
519 		
520 	}
521 	
522 	@Override
523 	protected void outputResults(UCCWrapper ucc, UT2004Server server, UT2004Analyzer analyzer, Bots bots, UT2004MatchResult result,	File outputDirectory) {
524 		if (!(result instanceof UT2004TeamDeathMatchResult)) {
525 			throw new PogamutException("Can't out results! Expected results of class UT2004TeamDeathMatchResult and got " + result.getClass().getSimpleName() + "!", log, this);
526 		}
527 		outputResults_step1((UT2004TeamDeathMatchResult) result, outputDirectory);
528 		outputResults_step2((UT2004TeamDeathMatchResult) result, outputDirectory);
529 	}
530 	
531 	@Override
532 	public UT2004TeamDeathMatchResult execute() {
533 		try {
534 			if (log != null && log.isLoggable(Level.WARNING)) {
535 				log.warning(config.getMatchId().getToken() + ": Executing!");
536 			} 
537 		} catch (Exception e) {				
538 		}
539 		
540 		UCCWrapper ucc = null;
541 		UT2004Server server = null;
542 		Bots bots = null;
543 		UT2004Analyzer analyzer = null;
544 		String recordFileName = config.getMatchId().getToken() + "-replay-" + UT2004Match.getCurrentDate();
545 		boolean exception = false;
546 		
547 		// HACK!!!
548 		// We must set frag limit to actually BIGGER NUMBER because otherwise GB2004 would drop the connection sooner before telling us that some bot
549 		// has achieved required score :-/
550 		targetScoreLimit = getConfig().getScoreLimit();
551 		getConfig().setScoreLimit(targetScoreLimit + 10); 
552 
553 		
554 		try {
555 			// STEP 0
556 			setupLogger();
557 			
558 			// STEP 1
559 			validate();
560 
561 			// STEP 2.1
562 			createUT2004Ini();
563 			
564 			// STEP 2.2
565 			createGB2004Ini();
566 			
567 			// STEP 3
568 			ucc = startUCC();
569 			
570 			// STEP 4
571 			server = startControlServer(ucc);
572 			
573 			// STEP 5.1
574 			bots = startBots(ucc, server);
575 			
576 			// STEP 5.2
577 			waitHumanPlayers(server, bots);
578 			
579 			// STEP 6
580 			analyzer = startAnalyzer(ucc, bots, getOutputPath("bots"), false);
581 			
582 			// STEP 7
583 			matchIsAboutToBegin(ucc, server, analyzer, bots);
584 			
585 			// STEP 8
586 			restartMatch(server, bots);
587 			
588 			// STEP 9			
589 			recordReplay(server, recordFileName);
590 			
591 			// STEP 9.5
592 			UT2004TeamDeathMatchResult result = (UT2004TeamDeathMatchResult) waitMatchFinish(ucc, server, analyzer, bots, config.getTimeLimit() * 1000 + 60 * 1000);
593 			
594 			// STEP 11
595 			copyReplay(ucc, recordFileName, getOutputPath());
596 
597 			// STEP 12
598 			outputResults(ucc, server, analyzer, bots, result, getOutputPath());
599 			
600 			// STEP 13
601 			shutdownAll(ucc, server, analyzer, bots);
602 			
603 			ucc = null;
604 			server = null;
605 			analyzer = null;
606 			bots = null;
607 			
608 			// WE'RE DONE! ... all that is left is a possible cleanup...
609 			return result;
610 			
611 		} catch (Exception e) {
612 			if (log != null && log.isLoggable(Level.SEVERE)) {
613 				log.severe(ExceptionToString.process(config.getMatchId().getToken() + ": EXCEPTION!", e));
614 			}
615 			exception = true;
616 			if (e instanceof PogamutException) throw (PogamutException)e;
617 			throw new PogamutException(e, log, this);
618 		} finally {		
619 			try {
620 				if (log != null && log.isLoggable(Level.INFO)) {
621 					log.info(config.getMatchId().getToken() + ": Cleaning up...");
622 				} 
623 			} catch (Exception e) {				
624 			}
625 			
626 			if (ucc != null) {
627 				try {
628 					if (log != null && log.isLoggable(Level.INFO)) {
629 						log.info(config.getMatchId().getToken() + ": Killing UCC...");
630 					} 
631 				} catch (Exception e) {				
632 				}
633 				try {
634 					ucc.stop();
635 				} catch (Exception e) {					
636 				}
637 			}
638 			if (server != null) {
639 				try {
640 					if (log != null && log.isLoggable(Level.INFO)) {
641 						log.info(config.getMatchId().getToken() + ": Killing UT2004Server...");
642 					} 
643 				} catch (Exception e) {				
644 				}
645 				try {
646 					server.kill();
647 				} catch (Exception e) {					
648 				}
649 			}
650 			if (bots != null) {
651 				try {
652 					if (log != null && log.isLoggable(Level.INFO)) {
653 						log.info(config.getMatchId().getToken() + ": Killing Custom bots...");
654 					} 
655 				} catch (Exception e) {				
656 				}
657 				for (UT2004BotExecution exec : bots.bots.values()) {
658 					try {
659 						exec.stop();					
660 					} catch (Exception e) {					
661 					}
662 				}
663 				try {
664 					if (log != null && log.isLoggable(Level.INFO)) {
665 						log.info(config.getMatchId().getToken() + ": Killing Custom bot observers...");
666 					} 
667 				} catch (Exception e) {				
668 				}
669 				for (IUT2004AnalyzerObserver obs : bots.botObservers.values()) {
670 					try {
671 						obs.kill();
672 					} catch (Exception e) {						
673 					}
674 				}
675 			}
676 			if (analyzer != null) {
677 				try {
678 					if (log != null && log.isLoggable(Level.INFO)) {
679 						log.info(config.getMatchId().getToken() + ": Killing UT2004Analyzer...");
680 					} 
681 				} catch (Exception e) {				
682 				}
683 				try {
684 					analyzer.kill();
685 				} catch (Exception e) {					
686 				}
687 			}	
688 			
689 			try {
690 				// STEP 10.1
691 				restoreUT2004IniBackup();
692 			} catch (Exception e) {				
693 			}
694 			
695 			try {
696 				// STEP 10.2
697 				restoreGB2004IniBackup();
698 			} catch (Exception e) {				
699 			}
700 			
701 			try {
702 				if (log != null && log.isLoggable(Level.WARNING)) {
703 					if (exception) {
704 						log.warning(config.getMatchId().getToken() + ": Cleaned up, MATCH FAILED!");
705 					} else { 
706 						log.warning(config.getMatchId().getToken() + ": Cleaned up, match finished successfully.");
707 					}
708 				} 
709 			} catch (Exception e) {				
710 			}
711 			try {
712 				closeLogger();
713 			} catch (Exception e) {
714 				
715 			}
716 		}
717 		
718 	}
719 
720 }