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