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