View Javadoc

1   package cz.cuni.amis.pogamut.ut2004.hideandseek.tournament;
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.impl.AgentId;
18  import cz.cuni.amis.pogamut.base.agent.state.level0.IAgentState;
19  import cz.cuni.amis.pogamut.base.agent.state.level1.IAgentStateDown;
20  import cz.cuni.amis.pogamut.base.agent.state.level1.IAgentStateUp;
21  import cz.cuni.amis.pogamut.base.agent.state.level2.IAgentStateRunning;
22  import cz.cuni.amis.pogamut.base.utils.guice.AdaptableProvider;
23  import cz.cuni.amis.pogamut.base.utils.logging.LogCategory;
24  import cz.cuni.amis.pogamut.unreal.communication.messages.UnrealId;
25  import cz.cuni.amis.pogamut.ut2004.agent.params.UT2004AgentParameters;
26  import cz.cuni.amis.pogamut.ut2004.analyzer.IUT2004AnalyzerObserver;
27  import cz.cuni.amis.pogamut.ut2004.analyzer.UT2004Analyzer;
28  import cz.cuni.amis.pogamut.ut2004.analyzer.UT2004AnalyzerFullObserver;
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.PlayerMessage;
32  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.Self;
33  import cz.cuni.amis.pogamut.ut2004.factory.guice.remoteagent.UT2004ServerFactory;
34  import cz.cuni.amis.pogamut.ut2004.hideandseek.server.HSBotRecord;
35  import cz.cuni.amis.pogamut.ut2004.hideandseek.server.UT2004HSServer;
36  import cz.cuni.amis.pogamut.ut2004.hideandseek.server.UT2004HSServerModule;
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.NullCheck;
46  import cz.cuni.amis.utils.collections.MyCollections;
47  import cz.cuni.amis.utils.exception.PogamutException;
48  import cz.cuni.amis.utils.exception.PogamutIOException;
49  import cz.cuni.amis.utils.exception.PogamutInterruptedException;
50  import cz.cuni.amis.utils.flag.FlagListener;
51  import cz.cuni.amis.utils.token.IToken;
52  import cz.cuni.amis.utils.token.Tokens;
53  
54  public class UT2004HideAndSeek extends UT2004Match<UT2004HideAndSeekConfig, UT2004HideAndSeekResult> {
55  	
56  	private String origFixedSeekerName = null;
57  
58  	public UT2004HideAndSeek(UT2004HideAndSeekConfig config, LogCategory log) {
59  		super(false, config, log);		
60  	}
61  	
62  	@Override
63  	protected void changeBotTeam(UT2004Server server, UnrealId botId, int desiredTeam) {
64  		// THERE IS NO NEED TO CHANGE BOT TEAM IN DEATHMATCH!
65  	}
66  
67  	@Override
68  	protected UT2004MatchResult waitMatchFinish(UCCWrapper ucc, UT2004Server server, UT2004Analyzer analyzer, Bots bots, long timeoutInMillis) {
69  		// usually the GB2004 dies out whenever match ends -> just wait till server does not fail + timeout + observe bots
70  		
71  		if (log != null && log.isLoggable(Level.WARNING)) {
72  			log.warning(config.getMatchId().getToken() + ": Waiting for the match to finish...");
73  		}
74  		
75  		// round times + 1 min per round for restart + 5 mins extra
76  		long minTimeoutMillis = (long)(Math.round(config.getHsConfig().getRoundCount() * config.getHsConfig().getRoundTimeUT() * 1000 + config.getHsConfig().getRoundCount() * 60 * 1000 + 5 * 60 * 1000));
77  		
78  		if (minTimeoutMillis > timeoutInMillis) {
79  			timeoutInMillis = minTimeoutMillis;
80  			log.warning(config.getMatchId().getToken() + ": match global timeout set to " + (minTimeoutMillis/1000) + " seconds");
81  		}
82  		
83  		Map<IToken, FlagListener<Boolean>> customBotObservers = new HashMap<IToken, FlagListener<Boolean>>(config.getBots().size());
84  		FlagListener<IAgentState> serverObs = null;
85  		FlagListener<Boolean> uccObs = null;
86  		FlagListener<Boolean> hsGameRunning = null;
87  		
88  		final CountDownLatch waitLatch = new CountDownLatch(1);
89  		final AdaptableProvider<Boolean> oneOfBotsDiedOut = new AdaptableProvider<Boolean>(false);
90  		final AdaptableProvider<Boolean> serverDiedOut = new AdaptableProvider<Boolean>(false);
91  		
92  		boolean exception = false;
93  		
94  		try {			
95  			for (UT2004BotConfig botConfig : config.getBots().values()) {
96  				FlagListener<Boolean> obs = new FlagListener<Boolean>() {
97  					@Override
98  					public void flagChanged(Boolean changedValue) {
99  						if (!changedValue) {
100 							// bot has died out
101 							oneOfBotsDiedOut.set(true);
102 							waitLatch.countDown();
103 						}
104 					}
105 				};
106 				
107 				bots.bots.get(botConfig.getBotId()).getRunning().addListener(obs);
108 				customBotObservers.put(botConfig.getBotId(), obs);
109 				if (!bots.bots.get(botConfig.getBotId()).getRunning().getFlag()) {
110 					// bot has died out
111 					oneOfBotsDiedOut.set(true);
112 					waitLatch.countDown();
113 					throw new PogamutException("One of custom bots died out from the start, failure!", log, this);
114 				}			
115 			}
116 			
117 			serverObs = new FlagListener<IAgentState>() {
118 	
119 				@Override
120 				public void flagChanged(IAgentState changedValue) {					
121 					if (changedValue instanceof IAgentStateDown) {
122 						// server has died out ... consider match to be over...
123 						serverDiedOut.set(true);
124 						waitLatch.countDown();
125 					}
126 				}
127 				
128 			};
129 			
130 			server.getState().addListener(serverObs);
131 								
132 			if (server.notInState(IAgentStateUp.class)) {
133 				// server has died out ... consider match to be over...
134 				serverDiedOut.set(true);
135 				waitLatch.countDown();
136 				throw new PogamutException("Server is dead from the start, failure!", log, this);
137 			}
138 			
139 			if (!((UT2004HSServer)server).isGameRunning().getFlag()) {
140 				serverDiedOut.set(true);
141 				waitLatch.countDown();
142 				throw new PogamutException("Hide&Seek game is not running at the beginning, invalid!", log, this);
143 			}
144 			
145 			hsGameRunning = new FlagListener<Boolean>() {
146 
147 				@Override
148 				public void flagChanged(Boolean changedValue) {
149 					if (!changedValue) {
150 						// TAG GAME HAS ENDED
151 						waitLatch.countDown();
152 					}
153 				}
154 				
155 			};
156 			
157 			((UT2004HSServer)server).isGameRunning().addListener(hsGameRunning);
158 			
159 			uccObs = new FlagListener<Boolean>() {
160 
161 				@Override
162 				public void flagChanged(Boolean changedValue) {
163 					if (changedValue) {
164 						// GAME IS ENDING!
165 						// Consider match to be over...
166 						serverDiedOut.set(true);
167 						waitLatch.countDown();
168 					}
169 				}
170 				
171 			};
172 			
173 			ucc.getGameEnding().addListener(uccObs);
174 			
175 			waitLatch.await(timeoutInMillis, TimeUnit.MILLISECONDS);
176 			if (waitLatch.getCount() > 0) {
177 				// TIMEOUT!
178 				throw new PogamutException("TIMEOUT! The match did not end in " + (timeoutInMillis / 1000) + " secs.", log, this);
179 			}
180 			
181 			bots.matchEnd = System.currentTimeMillis();
182 					
183 			// WHAT HAS HAPPENED?
184 			if (oneOfBotsDiedOut.get()) {
185 				// check whether the server is down as well... but let GB2004 to process it
186 				try {
187 					Thread.sleep(5000);
188 				} catch (InterruptedException e) {
189 					throw new PogamutInterruptedException("Interrupted while giving GB2004 time to tear down its connection.", log, this);
190 				}
191 				try {
192 					server.getAct().act(new StartPlayers());
193 				} catch (Exception e) {
194 					// YEP, server is down
195 					serverDiedOut.set(true);
196 				}
197 				if (!serverDiedOut.get()) {
198 					// NO SERVER IS STILL RUNNING
199 					log.warning("ONE OF BOTS HAS DIED OUT, BUT SERVER IS STILL RUNNING ... POSSIBLE MATCH FAILURE!");
200 				}
201 			}
202 			if (((UT2004HSServer)server).getGameFailed().getFlag()) {
203 				throw new PogamutException("UT2004HSServer reported failure!", log, this);
204 			}
205 			if (!serverDiedOut.get() && server.inState(IAgentStateUp.class)) {
206 				// Server is still running? ... This will likely to always happen as frag limit is targetFragCount+10 !!! 
207 				// Kill it...
208 				server.kill();
209 			}
210 			// server is DEAD -> assume that the match has ended
211 			
212 			// KILL UCC TO ENSURE NOTHING WILL CHANGE AFTER THAT
213 			if (ucc != null) {
214 				try {
215 					if (log != null && log.isLoggable(Level.INFO)) {
216 						log.info(config.getMatchId().getToken() + ": Killing UCC...");
217 					} 
218 				} catch (Exception e) {				
219 				}
220 				try {
221 					ucc.stop();
222 				} catch (Exception e) {					
223 				}
224 			}
225 			
226 			// OBTAIN BOT RECORDS
227 			UT2004HSServer hsServer = (UT2004HSServer)server;
228 			Map<IToken, HSBotRecord<PlayerMessage>> botRecordMap = new HashMap<IToken, HSBotRecord<PlayerMessage>>();
229 			List<HSBotRecord<PlayerMessage>> botRecords = new ArrayList<HSBotRecord<PlayerMessage>>();
230 			for (Entry<UnrealId, HSBotRecord<PlayerMessage>> entryRecord : hsServer.getBotRecords().entrySet()) {				
231 				if (entryRecord.getValue().getPlayer() == null || entryRecord.getValue().getPlayer().getJmx() == null) continue;
232 				IToken botId = bots.unrealId2BotId.get(entryRecord.getKey());
233 				botRecordMap.put(botId, entryRecord.getValue());
234 				botRecords.add(entryRecord.getValue());
235 			}
236 			
237 			if (botRecords.size() == 0) {
238 				throw new PogamutException("There are not bots results, it seems like the match has not been even played ???", log, this);
239 			}
240 			
241 			// SORT botRecords DESCENDING
242 			Collections.sort(botRecords, new Comparator<HSBotRecord<PlayerMessage>>() {
243 				@Override
244 				public int compare(HSBotRecord<PlayerMessage> o1, HSBotRecord<PlayerMessage> o2) {
245 					return o2.getScore() - o1.getScore();
246 				}
247 			});
248 			
249 			// DETERMINE WINNERs
250 			List<IToken> winners = new ArrayList<IToken>(1);			
251 			int maxScore = botRecords.get(0).getScore();			
252 			for (HSBotRecord<PlayerMessage> botRecord : botRecords) {
253 				if (maxScore == botRecord.getScore()) {
254 					IToken botId = bots.unrealId2BotId.get(botRecord.getBotId());
255 					winners.add(botId);
256 				} else {
257 					// maxScore contains MAX SCORE indeed (botRecords are sorted ~ descending)
258 					// => maxScore > botRecord.getScore()
259 					break;
260 				}
261 			}
262 			
263 			// CHECK WINNERS
264 			if (winners.size() == 0) {
265 				// no one has won??? IMPOSSIBLE!
266 				throw new PogamutException("There is no WINNER, impossible! **puzzled**", log, this);
267 			}
268 			if (winners.size() > 1) {
269 				StringBuffer sb = new StringBuffer();
270 				sb.append("There is more than one winner with highest score == " + maxScore + ": ");
271 				boolean first = true;
272 				for (IToken id : winners) {
273 					if (first) first = false;
274 					else sb.append(", ");
275 					sb.append("Bot[botId=" + id + ", unrealId=" + bots.botId2UnrealId.get(id) + ", score=" + botRecordMap.get(id).getScore() + "]");
276 				}
277 				sb.append(".");
278 				if (log != null && log.isLoggable(Level.WARNING)) {
279 					log.warning(sb.toString());
280 				}
281 			}
282 			
283 			if (log != null && log.isLoggable(Level.WARNING)) {
284 				log.warning(config.getMatchId().getToken() + ": MATCH FINISHED!");
285 			}
286 			
287 			return processResults(ucc, server, analyzer, bots, winners, botRecordMap);
288 			
289 		} catch (Exception e) {
290 			exception = true;
291 			throw new PogamutException("Failed to perform the match!", e, log, this);
292 		} finally {
293 			if (hsGameRunning != null) {
294 				((UT2004HSServer)server).isGameRunning().removeListener(hsGameRunning);
295 				hsGameRunning = null;
296 			}
297 			for (Entry<IToken, FlagListener<Boolean>> entry : customBotObservers.entrySet()) {
298 				bots.bots.get(entry.getKey()).getRunning().removeListener(entry.getValue());
299 			}
300 			server.getState().removeListener(serverObs);
301 		}		
302 
303 	}
304 	
305 	protected UT2004HideAndSeekResult processResults(UCCWrapper ucc, UT2004Server server, UT2004Analyzer analyzer, Bots bots, List<IToken> winners, Map<IToken, HSBotRecord<PlayerMessage>> botRecords) {
306 		if (log != null && log.isLoggable(Level.FINE)) {
307 			log.fine(config.getMatchId().getToken() + ": Processing results...");
308 		}
309 		
310 		config.getHsConfig().setFixedSeekerName(origFixedSeekerName);
311 		
312 		UT2004HideAndSeekResult result = new UT2004HideAndSeekResult();
313 		
314 		// BOTS
315 		result.setBots(MyCollections.asList(bots.botId2UnrealId.keySet()));
316 		
317 		// BOT IDS
318 		result.setBotIds(bots.botId2UnrealId);
319 		
320 		// BOT OBSERVERS
321 		for (Entry<IToken, IUT2004AnalyzerObserver> entry : bots.botObservers.entrySet()) {
322 			if (!(entry.getValue() instanceof UT2004AnalyzerObsStats)) {
323 				throw new PogamutException("There is an observer of wrong class, expecting UT2004AnalyzerObsStats, got " + entry.getValue().getClass().getSimpleName() + "!", log, this);
324 			}
325 			result.getBotObservers().put(entry.getKey(), (UT2004AnalyzerObsStats)entry.getValue());
326 		}
327 		
328 		// WINNERS
329 		result.setWinners(winners);
330 		
331 		// MATCH TIME
332 		result.setMatchTime(((double)bots.matchEnd - (double)bots.matchStart) / (1000));
333 		
334 		// SCORES
335 		result.setScoreDetails(botRecords);
336 				
337 		if (log != null && log.isLoggable(Level.WARNING)) {
338 			log.warning(config.getMatchId().getToken() + ": Results processed, #Winners = " + result.getWinners().size() + ", Winners score = " + result.getWinnerScore());
339 		}
340 		
341 		return result;
342 	}
343 	
344 	protected void outputResults_step1(UT2004HideAndSeekResult result, File outputDirectory) {
345 		if (log != null && log.isLoggable(Level.FINE)) {
346 			log.fine(config.getMatchId().getToken() + ": Outputting match result into CSV file...");
347 		}
348 		
349 		File file = new File(outputDirectory.getAbsolutePath() + File.separator + "match-" + config.getMatchId().getToken() + "-result.csv");
350 		FilePath.makeDirsToFile(file);
351 		try {
352 			Formatter writer = new Formatter(file);
353 			writer.format("MatchId;WinnerScore;MatchTimeSecs;" + config.getHsConfig().getCSVHeader() + "");
354 			for (int i = 0; i < result.getWinners().size(); ++i) {
355 				writer.format(";Winner" + (i+1));
356 			}
357 			writer.format("\n");
358 			writer.format
359 					(
360 						"%s;%d;%.3f;",
361 						config.getMatchId().getToken(),
362 						result.getWinnerScore(),
363 						result.getMatchTime()
364 					);
365 			config.getHsConfig().formatCSVLine(writer);
366 			
367 			for (IToken winner : result.getWinners()) {
368 				writer.format(";%s", winner.getToken());
369 			}
370 			try {
371 				writer.close();
372 			} catch (Exception e) {			
373 			}
374 		} catch (IOException e) {
375 			throw new PogamutIOException("Failed to write results!", e, log, this);
376 		}
377 		
378 		if (log != null && log.isLoggable(Level.INFO)) {
379 			log.info(config.getMatchId().getToken() + ": Match result output into " + file.getAbsolutePath() + ".");
380 		}		
381 	}
382 	
383 	protected void outputResults_step2(UT2004HideAndSeekResult result, File outputDirectory) {
384 		if (log != null && log.isLoggable(Level.FINE)) {
385 			log.fine(config.getMatchId().getToken() + ": Outputting match scores into CSV file...");
386 		}
387 		
388 		File file = new File(outputDirectory.getAbsolutePath() + File.separator + "match-" + config.getMatchId().getToken() + "-bot-scores.csv");
389 		FilePath.makeDirsToFile(file);
390 		try {
391 			Formatter writer = new Formatter(file);
392 			
393 			List<IToken> bots = new ArrayList<IToken>(result.getBots());
394 			
395 			Collections.sort(bots, new Comparator<IToken>() {
396 				@Override
397 				public int compare(IToken o1, IToken o2) {
398 					return o1.getToken().compareTo(o2.getToken());
399 				}				
400 			});
401 			
402 			writer.format("BotId;SeekerCount;RunnerCount;Score");
403 			
404 			writer.format(";RunnerCapturedBySeekerScore;RunnerSpottedBySeekerScore;RunnerSafeScore;RunnerSurvivedScore;RunnerFoulScore");
405 			writer.format(";SeekerCapturedRunnerScore;SeekerSpottedRunnerScore;SeekerLetRunnerSurviveScore;SeekerLetRunnerEscapeScore");
406 			
407 			for (IToken token : bots) {
408 				writer.format(";ThisRunnerCapturedBySeeker_" + token.getToken() + "_Count");
409 				writer.format(";ThisRunnerSpottedBySeeker_" + token.getToken() + "_Count");				
410 				writer.format(";ThisRunnerSafeCountWhileSeeker_" + token.getToken() + "_Count");
411 				writer.format(";ThisRunnerSurvivedWhileSeeker_" + token.getToken() + "_Count");
412 				writer.format(";ThisRunnerFoulWhileSeeker_" + token.getToken() + "_Count");
413 				
414 				writer.format(";ThisSeekerCapturedRunner_" + token.getToken() + "_Count");
415 				writer.format(";ThisSeekerSpottedRunner_" + token.getToken() + "_Count");
416 				writer.format(";ThisSeekerLetRunner_" + token.getToken() + "_SurviveCount");
417 				writer.format(";ThisSeekerLetRunner_" + token.getToken() + "_EscapeCount");
418 								
419 			}
420 			
421 			for (IToken token1 : bots) {
422 				writer.format("\n");
423 				writer.format(token1.getToken());
424 				
425 				HSBotRecord<PlayerMessage> record = result.getScoreDetails().get(token1);
426 				
427 				writer.format(";%d", record.getSeekerCount());
428 				writer.format(";%d", record.getRunnerCount());
429 				writer.format(";%d", record.getScore());
430 				
431 				writer.format(";%d", record.getRunnerCapturedBySeekerScore());
432 				writer.format(";%d", record.getRunnerSpottedBySeekerScore());				
433 				writer.format(";%d", record.getRunnerSafeScore());
434 				writer.format(";%d", record.getRunnerSurvivedScore());
435 				writer.format(";%d", record.getRunnerFoulScore());
436 				
437 				writer.format(";%d", record.getSeekerCapturedRunnerScore());
438 				writer.format(";%d", record.getSeekerSpottedRunnerScore());
439 				writer.format(";%d", record.getSeekerLetRunnerSurviveScore());
440 				writer.format(";%d", record.getSeekerLetRunnerEscapeScore());
441 				
442 				for (IToken token2 : bots) {
443 					UnrealId bot2Id = result.getBotIds().get(token2);
444 					writer.format(";%d", record.getRunnerCapturedBySeekerCount().get(bot2Id));
445 					writer.format(";%d", record.getRunnerSpottedBySeekerCount().get(bot2Id));
446 					writer.format(";%d", record.getRunnerSafeCount().get(bot2Id));
447 					writer.format(";%d", record.getRunnerSurvivedCount().get(bot2Id));
448 					writer.format(";%d", record.getRunnerFoulCount().get(bot2Id));
449 					
450 					writer.format(";%d", record.getSeekerCapturedRunnerCount().get(bot2Id));
451 					writer.format(";%d", record.getSeekerSpottedRunnerCount().get(bot2Id));
452 					writer.format(";%d", record.getSeekerLetRunnerSurviveCount().get(bot2Id));
453 					writer.format(";%d", record.getSeekerLetRunnerEscapeCount().get(bot2Id));					
454 				}				
455 			}
456 			
457 			try {
458 				writer.close();
459 			} catch (Exception e) {			
460 			}
461 		} catch (IOException e) {
462 			throw new PogamutIOException("Failed to write results!", e, log, this);
463 		}
464 		
465 		if (log != null && log.isLoggable(Level.INFO)) {
466 			log.info(config.getMatchId().getToken() + ": Match scores output into " + file.getAbsolutePath() + ".");
467 		}
468 	}
469 	
470 	@Override
471 	protected void outputResults(UCCWrapper ucc, UT2004Server server, UT2004Analyzer analyzer, Bots bots, UT2004MatchResult result,	File outputDirectory) {
472 		if (!(result instanceof UT2004HideAndSeekResult)) {
473 			throw new PogamutException("Can't out results! Expected results of class UT2004HideAndSeekResult and got " + result.getClass().getSimpleName() + "!", log, this);
474 		}
475 		outputResults_step1((UT2004HideAndSeekResult) result, outputDirectory);
476 		outputResults_step2((UT2004HideAndSeekResult) result, outputDirectory);
477 	}
478 	
479 	/**
480 	 * Usually STEP 4 ... after the UCC has started up, you usually want to connect to it to confirm it is up and running
481 	 * and be able to observe any changes in the environment / alter the environment, etc.
482 	 * <p><p>
483 	 * This method may need to be override to provide custom implementation of {@link UT2004Server} interface, i.e.,
484 	 * provide your custom control server. Current implementation is using {@link UT2004Server}.
485 	 * <p><p>
486 	 * Raises exception in case of any error.
487 	 * 
488 	 * @param ucc MUST NOT BE NULL
489 	 * @return running control server
490 	 */
491 	@SuppressWarnings("rawtypes")
492 	@Override
493 	protected UT2004Server startControlServer(UCCWrapper ucc) {
494 		if (log != null && log.isLoggable(Level.FINE)) {
495 			log.fine(config.getMatchId().getToken() + ": Starting UT2004Server...");
496 		}
497 		NullCheck.check(ucc, "ucc");
498         UT2004HSServerModule module = new UT2004HSServerModule();
499         UT2004ServerFactory factory = new UT2004ServerFactory(module);        
500         UT2004Server server = (UT2004Server) factory.newAgent(new UT2004AgentParameters().setAgentId(new AgentId(config.getMatchId().getToken()+"-UT2004Server")).setWorldAddress(ucc.getServerAddress()));
501         
502         server.start();
503         
504         if (!server.inState(IAgentStateRunning.class)) {
505         	throw new PogamutException("Failed to start UT2004HSServer!", log, this);
506         }
507         
508 		if (log != null && log.isLoggable(Level.INFO)) {
509 			log.info(config.getMatchId().getToken() + ": UT2004Server started.");
510 		}
511 		return server;
512 	}
513 	
514 	/**
515 	 * STEP 7 ... start the hide-and-seek game
516 	 * 
517 	 * @param ucc MUST NOT BE NULL
518 	 * @param server MUST NOT BE NULL
519 	 * @param analyzer may be null
520 	 * @param bots MUST NOT BE NULL
521 	 */
522 	protected void matchIsAboutToBegin(UCCWrapper ucc, UT2004Server server, UT2004Analyzer analyzer, Bots bots) {
523 		super.matchIsAboutToBegin(ucc, server, analyzer, bots);
524 		
525 		// WAIT FOR BOTS TO CATCH UP
526 		try {
527 			Thread.sleep(5000);
528 		} catch (InterruptedException e) {
529 			throw new PogamutInterruptedException(e, this);
530 		}
531 		
532 		// FIXED SEEKER?
533 		if (config.getHsConfig().isFixedSeeker()) {
534 			// YES!
535 			// We have to check for the real bot name of the IToken of fixed seeker
536 			String fixedSeekerString = config.getHsConfig().getFixedSeekerName();
537 			IToken fixedSeekerToken = Tokens.get(fixedSeekerString);
538 			if (!bots.botObservers.containsKey(fixedSeekerToken)) {
539 				throw new RuntimeException("Failed to find '" + fixedSeekerString + "' as token " + fixedSeekerToken + " within observer tokens, cannot ensure FIXED SEEKER settings.");
540 			}
541 			UT2004AnalyzerFullObserver observer = (UT2004AnalyzerFullObserver) bots.botObservers.get(fixedSeekerToken);
542 			if (observer == null) {
543 				throw new PogamutException("Failed to find '" + fixedSeekerString + "' as token " + fixedSeekerToken + " within observer map, cannot ensure FIXED SEEKER settings.", log, this);
544 			}
545 			Self fixedSeekerSelf = observer.getBotSelf();
546 			if (fixedSeekerSelf == null) {
547 				throw new PogamutException("Failed to obtain fixed seeker '" + fixedSeekerString + "' SELF from the observer, cannot ensure FIXED SEEKER settings.", log, this);
548 			}
549 			String fixedSeekerTrueName = fixedSeekerSelf.getName();
550 			if (fixedSeekerTrueName == null) {
551 				throw new PogamutException("Failed to obtain true name of the fixed seeker '" + fixedSeekerString + "' SELF.getName() is null, cannot ensure FIXED SEEKER settings.", log, this);
552 			}
553 			if (fixedSeekerTrueName.contains("[")) {
554 				fixedSeekerTrueName = fixedSeekerTrueName.substring(0, fixedSeekerTrueName.indexOf("[")).trim();
555 			}
556 			if (fixedSeekerTrueName.isEmpty()) {
557 				throw new PogamutException("Failed to obtain true name of the fixed seeker '" + fixedSeekerString + "', true name cannot be enclosed by [] brackets!", log, this);
558 			}
559 			if (log != null && log.isLoggable(Level.WARNING)) {
560 				log.warning("Fixed seeker '" + fixedSeekerString + "' true bot name is '" + fixedSeekerTrueName + "', configuring fixed seeker for this name.");
561 			}
562 			origFixedSeekerName = config.getHsConfig().getFixedSeekerName();
563 			config.getHsConfig().setFixedSeekerName(fixedSeekerTrueName);
564 			
565 			int fixedSeekerTrueNameCount = 0;
566 			for (UT2004AnalyzerFullObserver obs : ((Map<IToken, UT2004AnalyzerFullObserver>)(Map)bots.botObservers).values()) {
567 				Self self = obs.getBotSelf();
568 				if (self == null) {
569 					throw new PogamutException("One bot observer does not contain SELF, cannot ensure FIXED SEEKER settings.", log, this);
570 				}
571 				if (self.getName() == null) {
572 					throw new PogamutException("One bot observer has SELF.getName() == NULL, cannot ensure FIXED SEEKER settings.", log, this);
573 				}
574 				if (self.getName().startsWith(fixedSeekerTrueName)) {
575 					++fixedSeekerTrueNameCount;
576 				}
577 			}
578 			if (fixedSeekerTrueNameCount != 1) {
579 				throw new PogamutException("There are INVALID number of bots that has name prefixed with seeker name '" + fixedSeekerTrueName + ": " + fixedSeekerTrueNameCount + ". Cannot ensure FIXED SEEKER settings.", log, this);
580 			}
581 		}
582 		
583 		// CONFIGURE OBSERVER PORT
584 		config.getHsConfig().setObserverPort(ucc.getObserverPort());
585 		
586 		// START THE TAG GAME
587 		UT2004HSServer hsServer = (UT2004HSServer) server;		
588 		hsServer.startGame(config.getHsConfig());
589 	}		
590 
591 	@Override
592 	public UT2004HideAndSeekResult execute() {
593 		try {
594 			if (log != null && log.isLoggable(Level.WARNING)) {
595 				log.warning(config.getMatchId().getToken() + ": Executing!");
596 			} 
597 		} catch (Exception e) {				
598 		}
599 		
600 		UCCWrapper ucc = null;
601 		UT2004Server server = null;
602 		Bots bots = null;
603 		UT2004Analyzer analyzer = null;
604 		String recordFileName = config.getMatchId().getToken() + "-replay-" + UT2004Match.getCurrentDate();
605 		boolean exception = false;
606 		
607 		try {
608 			// STEP 0
609 			setupLogger();
610 			
611 			// STEP 1
612 			validate();
613 			
614 			// STEP 2
615 			createGB2004Ini();
616 			
617 			// STEP 3
618 			ucc = startUCC();
619 			
620 			// STEP 4
621 			server = startControlServer(ucc);
622 			
623 			// STEP 5
624 			bots = startBots(ucc, server);
625 			
626 			// STEP 6
627 			analyzer = startAnalyzer(ucc, bots, getOutputPath("bots"), config.isHumanLikeLogEnabled());
628 						
629 			// STEP 8
630 			restartMatch(server, bots);
631 
632 			// STEP 9			
633 			recordReplay(server, recordFileName);
634 			
635 			// STEP 7
636 			matchIsAboutToBegin(ucc, server, analyzer, bots); // <-- this will start HideAndSeekGame ... do that after the match restart + after start recording the replay!			
637 			
638 			// STEP 9.5
639 			// timeout == 0 -> will be determined within waitMatchFinish
640 			UT2004HideAndSeekResult result = (UT2004HideAndSeekResult) waitMatchFinish(ucc, server, analyzer, bots, 0);
641 			
642 			// STEP 11
643 			copyReplay(ucc, recordFileName, getOutputPath());
644 
645 			// STEP 12
646 			outputResults(ucc, server, analyzer, bots, result, getOutputPath());
647 			
648 			// STEP 13
649 			shutdownAll(ucc, server, analyzer, bots);
650 			
651 			ucc = null;
652 			server = null;
653 			analyzer = null;
654 			bots = null;
655 			
656 			// WE'RE DONE! ... all that is left is a possible cleanup...
657 			return result;
658 			
659 		} catch (Exception e) {
660 			if (log != null && log.isLoggable(Level.SEVERE)) {
661 				log.severe(ExceptionToString.process(config.getMatchId().getToken() + ": EXCEPTION!", e));
662 			}
663 			exception = true;
664 			if (e instanceof PogamutException) throw (PogamutException)e;
665 			throw new PogamutException(e, log, this);
666 		} finally {	
667 			try {
668 				if (log != null && log.isLoggable(Level.INFO)) {
669 					log.info(config.getMatchId().getToken() + ": Cleaning up...");
670 				} 
671 			} catch (Exception e) {				
672 			}
673 			
674 			if (ucc != null) {
675 				try {
676 					if (log != null && log.isLoggable(Level.INFO)) {
677 						log.info(config.getMatchId().getToken() + ": Killing UCC...");
678 					} 
679 				} catch (Exception e) {				
680 				}
681 				try {
682 					ucc.stop();
683 				} catch (Exception e) {					
684 				}
685 			}
686 			if (server != null) {
687 				try {
688 					if (log != null && log.isLoggable(Level.INFO)) {
689 						log.info(config.getMatchId().getToken() + ": Killing UT2004Server...");
690 					} 
691 				} catch (Exception e) {				
692 				}
693 				try {
694 					server.kill();
695 				} catch (Exception e) {					
696 				}
697 			}
698 			if (bots != null) {
699 				try {
700 					if (log != null && log.isLoggable(Level.INFO)) {
701 						log.info(config.getMatchId().getToken() + ": Killing Custom bots...");
702 					} 
703 				} catch (Exception e) {				
704 				}
705 				for (UT2004BotExecution exec : bots.bots.values()) {
706 					try {
707 						exec.stop();					
708 					} catch (Exception e) {					
709 					}
710 				}
711 				try {
712 					if (log != null && log.isLoggable(Level.INFO)) {
713 						log.info(config.getMatchId().getToken() + ": Killing Custom bot observers...");
714 					} 
715 				} catch (Exception e) {				
716 				}
717 				for (IUT2004AnalyzerObserver obs : bots.botObservers.values()) {
718 					try {
719 						obs.kill();
720 					} catch (Exception e) {						
721 					}
722 				}
723 			if (analyzer != null) {
724 			}
725 				try {
726 					if (log != null && log.isLoggable(Level.INFO)) {
727 						log.info(config.getMatchId().getToken() + ": Killing UT2004Analyzer...");
728 					} 
729 				} catch (Exception e) {				
730 				}
731 				try {
732 					analyzer.kill();
733 				} catch (Exception e) {					
734 				}
735 			}	
736 			
737 			try {
738 				// STEP 10
739 				restoreGB2004IniBackup();
740 			} catch (Exception e) {				
741 			}
742 			
743 			try {
744 				if (log != null && log.isLoggable(Level.WARNING)) {
745 					if (exception) {
746 						log.warning(config.getMatchId().getToken() + ": Cleaned up, MATCH FAILED!");
747 					} else { 
748 						log.warning(config.getMatchId().getToken() + ": Cleaned up, match finished successfully.");
749 					}
750 				} 
751 			} catch (Exception e) {				
752 			}
753 			try {
754 				closeLogger();
755 			} catch (Exception e) {
756 				
757 			}
758 		}
759 		
760 	}
761 
762 }