1 package cz.cuni.amis.pogamut.ut2004.tournament.deathmatch;
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.utils.guice.AdaptableProvider;
22 import cz.cuni.amis.pogamut.base.utils.logging.LogCategory;
23 import cz.cuni.amis.pogamut.unreal.communication.messages.UnrealId;
24 import cz.cuni.amis.pogamut.ut2004.agent.module.sensor.AgentStats;
25 import cz.cuni.amis.pogamut.ut2004.analyzer.IUT2004AnalyzerObserver;
26 import cz.cuni.amis.pogamut.ut2004.analyzer.UT2004Analyzer;
27 import cz.cuni.amis.pogamut.ut2004.analyzer.stats.UT2004AnalyzerObsStats;
28 import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.StartPlayers;
29 import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.PlayerScore;
30 import cz.cuni.amis.pogamut.ut2004.server.impl.UT2004Server;
31 import cz.cuni.amis.pogamut.ut2004.tournament.botexecution.UT2004BotExecution;
32 import cz.cuni.amis.pogamut.ut2004.tournament.match.UT2004BotConfig;
33 import cz.cuni.amis.pogamut.ut2004.tournament.match.UT2004Match;
34 import cz.cuni.amis.pogamut.ut2004.tournament.match.UT2004MatchResult;
35 import cz.cuni.amis.pogamut.ut2004.utils.UCCWrapper;
36 import cz.cuni.amis.utils.ExceptionToString;
37 import cz.cuni.amis.utils.FilePath;
38 import cz.cuni.amis.utils.exception.PogamutException;
39 import cz.cuni.amis.utils.exception.PogamutIOException;
40 import cz.cuni.amis.utils.exception.PogamutInterruptedException;
41 import cz.cuni.amis.utils.flag.FlagListener;
42 import cz.cuni.amis.utils.token.IToken;
43
44 public class UT2004DeathMatch extends UT2004Match<UT2004DeathMatchConfig, UT2004DeathMatchResult> {
45
46 public UT2004DeathMatch(UT2004DeathMatchConfig config, LogCategory log) {
47 super(false, config, log);
48 }
49
50 @Override
51 protected void changeBotTeam(UT2004Server server, UnrealId botId, int desiredTeam) {
52
53 }
54
55 @Override
56 protected UT2004MatchResult waitMatchFinish(UCCWrapper ucc, UT2004Server server, UT2004Analyzer analyzer, Bots bots, long timeoutInMillis) {
57
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;
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
72 final CountDownLatch waitLatch = new CountDownLatch(1);
73 final AdaptableProvider<Boolean> oneOfBotsDiedOut = new AdaptableProvider<Boolean>(false);
74 final AdaptableProvider<Boolean> serverDiedOut = new AdaptableProvider<Boolean>(false);
75 final Map<UnrealId, PlayerScore> scores = new HashMap<UnrealId, PlayerScore>();
76
77 boolean exception = false;
78
79 try {
80 scoresListener = new IWorldEventListener<PlayerScore>() {
81
82 @Override
83 public void notify(PlayerScore event) {
84 scores.put(event.getId(), event);
85 }
86
87 };
88 server.getWorldView().addEventListener(PlayerScore.class, scoresListener);
89
90 for (UT2004BotConfig botConfig : config.getBots().values()) {
91 FlagListener<Boolean> obs = new FlagListener<Boolean>() {
92 @Override
93 public void flagChanged(Boolean changedValue) {
94 if (!changedValue) {
95
96 oneOfBotsDiedOut.set(true);
97 waitLatch.countDown();
98 }
99 }
100 };
101
102 bots.bots.get(botConfig.getBotId()).getRunning().addListener(obs);
103 customBotObservers.put(botConfig.getBotId(), obs);
104 if (!bots.bots.get(botConfig.getBotId()).getRunning().getFlag()) {
105
106 oneOfBotsDiedOut.set(true);
107 waitLatch.countDown();
108 throw new PogamutException("One of custom bots died out from the start, failure!", log, this);
109 }
110 }
111
112 serverObs = new FlagListener<IAgentState>() {
113
114 @Override
115 public void flagChanged(IAgentState changedValue) {
116 if (changedValue instanceof IAgentStateDown) {
117
118 serverDiedOut.set(true);
119 waitLatch.countDown();
120 }
121 }
122
123 };
124
125 server.getState().addListener(serverObs);
126
127 if (server.notInState(IAgentStateUp.class)) {
128
129 serverDiedOut.set(true);
130 waitLatch.countDown();
131 throw new PogamutException("Server is dead from the start, failure!", log, this);
132 }
133
134 uccObs = new FlagListener<Boolean>() {
135
136 @Override
137 public void flagChanged(Boolean changedValue) {
138 if (changedValue) {
139
140
141 serverDiedOut.set(true);
142 waitLatch.countDown();
143 }
144 }
145
146 };
147
148 ucc.getGameEnding().addListener(uccObs);
149
150 waitLatch.await(timeoutInMillis, TimeUnit.MILLISECONDS);
151 if (waitLatch.getCount() > 0) {
152
153 throw new PogamutException("TIMEOUT! The match did not end in " + (timeoutInMillis / 1000) + " secs.", log, this);
154 }
155
156 bots.matchEnd = System.currentTimeMillis();
157
158
159 if (oneOfBotsDiedOut.get()) {
160
161 try {
162 Thread.sleep(5000);
163 } catch (InterruptedException e) {
164 throw new PogamutInterruptedException("Interrupted while giving GB2004 time to tear down its connection.", log, this);
165 }
166 try {
167 server.getAct().act(new StartPlayers());
168 } catch (Exception e) {
169
170 serverDiedOut.set(true);
171 }
172 if (!serverDiedOut.get()) {
173
174 log.warning("ONE OF BOTS HAS DIED OUT, BUT SERVER IS STILL RUNNING ... POSSIBLE MATCH FAILURE!");
175 }
176 }
177 if (!serverDiedOut.get() && server.inState(IAgentStateUp.class)) {
178
179 server.kill();
180 }
181
182
183
184 if (ucc != null) {
185 try {
186 if (log != null && log.isLoggable(Level.INFO)) {
187 log.info(config.getMatchId().getToken() + ": Killing UCC...");
188 }
189 } catch (Exception e) {
190 }
191 try {
192 ucc.stop();
193 } catch (Exception e) {
194 }
195 }
196
197 List<UnrealId> winners = new ArrayList<UnrealId>(1);
198 int maxFrags = 0;
199
200
201 for (Entry<UnrealId, IToken> entry : bots.unrealId2BotId.entrySet()) {
202 PlayerScore playerScore = scores.get(entry.getKey());
203 if (playerScore == null) {
204 throw new PogamutException("Can't resolve the match result. One of the bot with botId '" + entry.getValue().getToken() + "' and corresponding unrealId '" + entry.getKey().getStringId() + "' has no score entry!", log, this);
205 }
206 if (playerScore.getScore() == maxFrags) {
207 winners.add(entry.getKey());
208 } else
209 if (playerScore.getScore() > maxFrags) {
210 maxFrags = playerScore.getScore();
211 winners.clear();
212 winners.add(entry.getKey());
213 }
214 }
215 for (Entry<UnrealId, IToken> entry : bots.nativeUnrealId2BotId.entrySet()) {
216 PlayerScore playerScore = scores.get(entry.getKey());
217 if (playerScore == null) {
218 throw new PogamutException("Can't resolve the match result. One of the bot with botId '" + entry.getValue().getToken() + "' and corresponding unrealId '" + entry.getKey().getStringId() + "' has no score entry!", log, this);
219 }
220 if (playerScore.getScore() == maxFrags) {
221 winners.add(entry.getKey());
222 } else
223 if (playerScore.getScore() > maxFrags) {
224 maxFrags = playerScore.getScore();
225 winners.clear();
226 winners.add(entry.getKey());
227 }
228 }
229
230 if (winners.size() == 0) {
231
232 throw new PogamutException("There is no winner, impossible! **puzzled**", log, this);
233 }
234 if (winners.size() > 1) {
235 StringBuffer sb = new StringBuffer();
236 sb.append("There is more than one bot with highest score == " + maxFrags + ": ");
237 boolean first = true;
238 for (UnrealId id : winners) {
239 if (first) first = false;
240 else sb.append(", ");
241 sb.append("Bot[botId=" + bots.getBotId(id) + ", unrealId=" + id.getStringId() + ", score=" + scores.get(id).getScore() + "]");
242 }
243 sb.append(".");
244 if (log != null && log.isLoggable(Level.WARNING)) {
245 log.warning(sb.toString());
246 }
247
248 throw new PogamutException(sb.toString(), log, this);
249 }
250
251 if (log != null && log.isLoggable(Level.WARNING)) {
252 log.warning(config.getMatchId().getToken() + ": MATCH FINISHED!");
253 }
254
255 return processResults(ucc, server, analyzer, bots, winners, scores);
256
257 } catch (Exception e) {
258 exception = true;
259 throw new PogamutException("Failed to perform the match!", e, log, this);
260 } finally {
261 for (Entry<IToken, FlagListener<Boolean>> entry : customBotObservers.entrySet()) {
262 bots.bots.get(entry.getKey()).getRunning().removeListener(entry.getValue());
263 }
264 server.getState().removeListener(serverObs);
265 server.getWorldView().removeEventListener(PlayerScore.class, scoresListener);
266 }
267
268 }
269
270 protected UT2004DeathMatchResult processResults(UCCWrapper ucc, UT2004Server server, UT2004Analyzer analyzer, Bots bots, List<UnrealId> winners, Map<UnrealId, PlayerScore> finalScores) {
271
272 if (log != null && log.isLoggable(Level.FINE)) {
273 log.fine(config.getMatchId().getToken() + ": Processing results...");
274 }
275
276 UT2004DeathMatchResult result = new UT2004DeathMatchResult();
277
278 result.setMatchTimeEnd(((double)bots.matchEnd - (double)bots.matchStart) / (1000));
279
280 for (Entry<UnrealId, PlayerScore> entry : finalScores.entrySet()) {
281 result.getFinalScores().put(bots.getBotId(entry.getKey()), entry.getValue());
282 }
283
284 for (Entry<IToken, IUT2004AnalyzerObserver> entry : bots.botObservers.entrySet()) {
285 if (!(entry.getValue() instanceof UT2004AnalyzerObsStats)) {
286 throw new PogamutException("There is an observer of wrong class, expecting UT2004AnalyzerObsStats, got " + entry.getValue().getClass().getSimpleName() + "!", log, this);
287 }
288 result.getBotObservers().put(entry.getKey(), (UT2004AnalyzerObsStats)entry.getValue());
289 }
290
291 List<IToken> botIds = config.getAllBotIds();
292 for (IToken botId1 : botIds) {
293 result.getTotalKills().put(botId1, 0);
294 result.getWasKilled().put(botId1, 0);
295 result.getSuicides().put(botId1, 0);
296 for (IToken botId2 : botIds) {
297 result.getKillCounts().put(botId1, botId2, 0);
298 }
299 }
300
301 for (Entry<IToken, UT2004AnalyzerObsStats> entry : result.getBotObservers().entrySet()) {
302 IToken botId = entry.getKey();
303 UT2004AnalyzerObsStats obs = entry.getValue();
304 AgentStats stats = obs.getStats();
305 for (Entry<UnrealId, Integer> killed : stats.getKilled().entrySet()) {
306 result.getKillCounts().get(botId).put(bots.getBotId(killed.getKey()), killed.getValue());
307 }
308 for (Entry<UnrealId, Integer> killedBy : stats.getKilledBy().entrySet()) {
309 if (bots.isNativeBot(killedBy.getKey())) {
310 result.getKillCounts().get(bots.getBotId(killedBy.getKey())).put(botId, killedBy.getValue());
311 }
312 }
313 result.getSuicides().put(botId, stats.getSuicides());
314 result.getKillCounts().put(botId, botId, stats.getSuicides());
315 }
316
317 for (IToken nativeBotId1 : config.getNativeBots().keySet()) {
318 for (IToken nativeBotId2 : config.getNativeBots().keySet()) {
319 if (nativeBotId1 == nativeBotId2) continue;
320 result.getKillCounts().get(nativeBotId1).put(nativeBotId2, 0);
321 }
322 result.getSuicides().put(nativeBotId1, 0);
323 }
324
325 for (IToken botId : botIds) {
326 int totalKills = 0;
327 int totalKilled = 0;
328 for (IToken other : botIds) {
329 if (botId == other) continue;
330 totalKills += result.getKillCounts().get(botId, other);
331 totalKilled += result.getKillCounts().get(other, botId);
332 }
333 result.getTotalKills().put(botId, totalKills);
334 result.getWasKilled().put(botId, totalKilled);
335 if (config.isNativeBot(botId)) {
336 result.getSuicides().put(botId, result.getFinalScores().get(botId).getDeaths() - totalKilled);
337 }
338 }
339
340 if (winners.size() <= 0) {
341 throw new PogamutException("There is no winner, impossible! **puzzled**", log, this);
342 } else
343 if (winners.size() == 1) {
344 result.setWinnerBot(bots.getBotId(winners.get(0)));
345 } else {
346 result.setDraw(true);
347 }
348
349 if (log != null && log.isLoggable(Level.WARNING)) {
350 log.warning(config.getMatchId().getToken() + ": Results processed, " + (result.isDraw() ? "DRAW!" : "winner is Bot[botId=" + result.getWinnerBot().getToken() + ", unrealId=" + bots.getUnrealId(result.getWinnerBot()).getStringId() + "]."));
351 }
352
353 return result;
354 }
355
356 protected void outputResults_step1(UT2004DeathMatchResult result, File outputDirectory) {
357 if (log != null && log.isLoggable(Level.FINE)) {
358 log.fine(config.getMatchId().getToken() + ": Outputting match result into CSV file...");
359 }
360
361 File file = new File(outputDirectory.getAbsolutePath() + File.separator + "match-" + config.getMatchId().getToken() + "-result.csv");
362 FilePath.makeDirsToFile(file);
363 try {
364 Formatter writer = new Formatter(file);
365 writer.format("MatchId;FragLimit;TimeLimit;TimeEnd;Winner\n");
366 writer.format
367 (
368 "%s;%d;%d;%.3f;%s",
369 config.getMatchId().getToken(),
370 config.getFragLimit(),
371 config.getTimeLimit(),
372 result.getMatchTimeEnd(),
373 result.isDraw() ? "DRAW" : String.valueOf(result.getWinnerBot().getToken())
374 );
375 try {
376 writer.close();
377 } catch (Exception e) {
378 }
379 } catch (IOException e) {
380 throw new PogamutIOException("Failed to write results!", e, log, this);
381 }
382
383 if (log != null && log.isLoggable(Level.INFO)) {
384 log.info(config.getMatchId().getToken() + ": Match result output into " + file.getAbsolutePath() + ".");
385 }
386
387 }
388
389 protected void outputResults_step2(UT2004DeathMatchResult result, File outputDirectory) {
390 if (log != null && log.isLoggable(Level.FINE)) {
391 log.fine(config.getMatchId().getToken() + ": Outputting match scores into CSV file...");
392 }
393
394 File file = new File(outputDirectory.getAbsolutePath() + File.separator + "match-" + config.getMatchId().getToken() + "-bot-scores.csv");
395 FilePath.makeDirsToFile(file);
396 try {
397 Formatter writer = new Formatter(file);
398
399 List<IToken> bots = new ArrayList<IToken>(config.getBots().keySet());
400 List<IToken> nativeBots = new ArrayList<IToken>(config.getNativeBots().keySet());
401
402 Collections.sort(bots, new Comparator<IToken>() {
403 @Override
404 public int compare(IToken o1, IToken o2) {
405 return o1.getToken().compareTo(o2.getToken());
406 }
407 });
408 Collections.sort(nativeBots, new Comparator<IToken>() {
409 @Override
410 public int compare(IToken o1, IToken o2) {
411 return o1.getToken().compareTo(o2.getToken());
412 }
413 });
414 result.setBots(bots);
415 result.setNativeBots(nativeBots);
416
417 writer.format("botId");
418 writer.format(";score;kills;killedByOthers;deaths;suicides");
419 for (IToken token : config.getAllBotIds()) {
420 writer.format(";");
421 writer.format(token.getToken());
422 }
423
424 for (IToken token : config.getAllBotIds()) {
425 writer.format("\n");
426 writer.format(token.getToken());
427 writer.format(";%d", result.getFinalScores().get(token).getScore());
428 writer.format(";%d", result.getTotalKills().get(token));
429 writer.format(";%d", result.getWasKilled().get(token));
430 writer.format(";%d", result.getFinalScores().get(token).getDeaths());
431 writer.format(";%d", result.getSuicides().get(token));
432 for (IToken token2 : config.getAllBotIds()) {
433 writer.format(";%d", result.getKillCounts().get(token).get(token2));
434 }
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 if (log != null && log.isLoggable(Level.INFO)) {
446 log.info(config.getMatchId().getToken() + ": Match scores output into " + file.getAbsolutePath() + ".");
447 }
448
449 }
450
451 @Override
452 protected void outputResults(UCCWrapper ucc, UT2004Server server, UT2004Analyzer analyzer, Bots bots, UT2004MatchResult result, File outputDirectory) {
453 if (!(result instanceof UT2004DeathMatchResult)) {
454 throw new PogamutException("Can't out results! Expected results of class UT2004DeathMatchResult and got " + result.getClass().getSimpleName() + "!", log, this);
455 }
456 outputResults_step1((UT2004DeathMatchResult) result, outputDirectory);
457 outputResults_step2((UT2004DeathMatchResult) result, outputDirectory);
458 }
459
460 @Override
461 public UT2004DeathMatchResult execute() {
462 try {
463 if (log != null && log.isLoggable(Level.WARNING)) {
464 log.warning(config.getMatchId().getToken() + ": Executing!");
465 }
466 } catch (Exception e) {
467 }
468
469 UCCWrapper ucc = null;
470 UT2004Server server = null;
471 Bots bots = null;
472 UT2004Analyzer analyzer = null;
473 String recordFileName = config.getMatchId().getToken() + "-replay-" + UT2004Match.getCurrentDate();
474 boolean exception = false;
475
476 try {
477
478 setupLogger();
479
480
481 validate();
482
483
484 createGB2004Ini();
485
486
487 ucc = startUCC();
488
489
490 server = startControlServer(ucc);
491
492
493 bots = startBots(ucc, server);
494
495
496 analyzer = startAnalyzer(ucc, bots, getOutputPath("bots"));
497
498
499 matchIsAboutToBegin(ucc, server, analyzer, bots);
500
501
502 restartMatch(server, bots);
503
504
505 recordReplay(server, recordFileName);
506
507
508 UT2004DeathMatchResult result = (UT2004DeathMatchResult) waitMatchFinish(ucc, server, analyzer, bots, config.getTimeLimit() * 1000 + 60 * 1000);
509
510
511 copyReplay(ucc, recordFileName, getOutputPath());
512
513
514 outputResults(ucc, server, analyzer, bots, result, getOutputPath());
515
516
517 shutdownAll(ucc, server, analyzer, bots);
518
519 ucc = null;
520 server = null;
521 analyzer = null;
522 bots = null;
523
524
525 return result;
526
527 } catch (Exception e) {
528 if (log != null && log.isLoggable(Level.SEVERE)) {
529 log.severe(ExceptionToString.process(config.getMatchId().getToken() + ": EXCEPTION!", e));
530 }
531 exception = true;
532 if (e instanceof PogamutException) throw (PogamutException)e;
533 throw new PogamutException(e, log, this);
534 } finally {
535 try {
536 if (log != null && log.isLoggable(Level.INFO)) {
537 log.info(config.getMatchId().getToken() + ": Cleaning up...");
538 }
539 } catch (Exception e) {
540 }
541
542 if (ucc != null) {
543 try {
544 if (log != null && log.isLoggable(Level.INFO)) {
545 log.info(config.getMatchId().getToken() + ": Killing UCC...");
546 }
547 } catch (Exception e) {
548 }
549 try {
550 ucc.stop();
551 } catch (Exception e) {
552 }
553 }
554 if (server != null) {
555 try {
556 if (log != null && log.isLoggable(Level.INFO)) {
557 log.info(config.getMatchId().getToken() + ": Killing UT2004Server...");
558 }
559 } catch (Exception e) {
560 }
561 try {
562 server.kill();
563 } catch (Exception e) {
564 }
565 }
566 if (bots != null) {
567 try {
568 if (log != null && log.isLoggable(Level.INFO)) {
569 log.info(config.getMatchId().getToken() + ": Killing Custom bots...");
570 }
571 } catch (Exception e) {
572 }
573 for (UT2004BotExecution exec : bots.bots.values()) {
574 try {
575 exec.stop();
576 } catch (Exception e) {
577 }
578 }
579 try {
580 if (log != null && log.isLoggable(Level.INFO)) {
581 log.info(config.getMatchId().getToken() + ": Killing Custom bot observers...");
582 }
583 } catch (Exception e) {
584 }
585 for (IUT2004AnalyzerObserver obs : bots.botObservers.values()) {
586 try {
587 obs.kill();
588 } catch (Exception e) {
589 }
590 }
591 }
592 if (analyzer != null) {
593 try {
594 if (log != null && log.isLoggable(Level.INFO)) {
595 log.info(config.getMatchId().getToken() + ": Killing UT2004Analyzer...");
596 }
597 } catch (Exception e) {
598 }
599 try {
600 analyzer.kill();
601 } catch (Exception e) {
602 }
603 }
604
605 try {
606
607 restoreGB2004IniBackup();
608 } catch (Exception e) {
609 }
610
611 try {
612 if (log != null && log.isLoggable(Level.WARNING)) {
613 if (exception) {
614 log.warning(config.getMatchId().getToken() + ": Cleaned up, MATCH FAILED!");
615 } else {
616 log.warning(config.getMatchId().getToken() + ": Cleaned up, match finished successfully.");
617 }
618 }
619 } catch (Exception e) {
620 }
621 try {
622 closeLogger();
623 } catch (Exception e) {
624
625 }
626 }
627
628 }
629
630 }