View Javadoc

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