1 package cz.cuni.amis.pogamut.ut2004.tournament;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.text.SimpleDateFormat;
6 import java.util.Date;
7 import java.util.Iterator;
8 import java.util.List;
9 import java.util.logging.Level;
10 import java.util.regex.Pattern;
11
12 import org.apache.commons.io.FileUtils;
13
14 import com.martiansoftware.jsap.FlaggedOption;
15 import com.martiansoftware.jsap.JSAP;
16 import com.martiansoftware.jsap.JSAPException;
17 import com.martiansoftware.jsap.JSAPResult;
18 import com.martiansoftware.jsap.Switch;
19
20 import cz.cuni.amis.pogamut.base.utils.logging.LogCategory;
21 import cz.cuni.amis.pogamut.ut2004.tournament.deathmatch.UT2004DeathMatch;
22 import cz.cuni.amis.pogamut.ut2004.tournament.deathmatch.UT2004DeathMatch1v1;
23 import cz.cuni.amis.pogamut.ut2004.tournament.deathmatch.UT2004DeathMatchConfig;
24 import cz.cuni.amis.pogamut.ut2004.tournament.match.UT2004BotConfig;
25
26 public class UT2004DeathMatchConsole {
27
28 private static final char ARG_UT2004_HOME_DIR_SHORT = 'u';
29
30 private static final String ARG_UT2004_HOME_DIR_LONG = "ut2004-home-dir";
31
32 private static final char ARG_BOT_JARs_SHORT = 'a';
33
34 private static final String ARG_BOT_JARs_LONG = "bot-jars";
35
36 private static final char ARG_BOT_NAMEs_SHORT = 'b';
37
38 private static final String ARG_BOT_NAMEs_LONG = "bot-names";
39
40 private static final char ARG_MAP_NAME_SHORT = 'm';
41
42 private static final String ARG_MAP_NAME_LONG = "map-name";
43
44 private static final char ARG_MATCH_NAME_SHORT = 'n';
45
46 private static final String ARG_MATCH_NAME_LONG = "match-name";
47
48 private static final char ARG_RESULT_DIR_SHORT = 'r';
49
50 private static final String ARG_RESULT_DIR_LONG = "result-directory";
51
52 private static final char ARG_SERVER_NAME_SHORT = 's';
53
54 private static final String ARG_SERVER_NAME_LONG = "server-name";
55
56 private static final char ARG_FRAG_LIMIT_SHORT = 'f';
57
58 private static final String ARG_FRAG_LIMIT_LONG = "frag-limit";
59
60 private static final char ARG_TIMEOUT_MINUTES_SHORT = 't';
61
62 private static final String ARG_TIMEOUT_MINUTES_LONG = "timeout-minutes";
63
64 private static final char ARG_HUMAN_LIKE_LOG_SHORT = 'h';
65
66 private static final String ARG_HUMAN_LIKE_LOG_LONG = "human-like-log";
67
68 private static final char ARG_UT2004_PORT_SHORT = 'p';
69
70 private static final String ARG_UT2004_PORT_LONG = "ut2004-port";
71
72 private static JSAP jsap;
73
74 private static boolean headerOutput = false;
75
76 private static String ut2004HomeDir;
77
78 private static String botJars;
79
80 private static String[] botJarsSeparated;
81
82 private static String botNames;
83
84 private static String[] botNamesSeparated;
85
86 private static File[] botJarFiles;
87
88 private static String map;
89
90 private static String serverName;
91
92 private static String resultDir;
93
94 private static String matchName;
95
96 private static int fragLimit;
97
98 private static int timeoutMinutes;
99
100 private static JSAPResult config;
101
102 private static File ut2004HomeDirFile;
103
104 private static File bot1JarFile;
105
106 private static File bot2JarFile;
107
108 private static File mapsDirFile;
109
110 private static File mapFile;
111
112 private static File ut2004SystemDirFile;
113
114 private static File ut2004IniFile;
115
116 private static boolean humanLikeLog;
117
118 private static int ut2004Port;
119
120 private static void fail(String errorMessage) {
121 fail(errorMessage, null);
122 }
123
124 private static void fail(String errorMessage, Throwable e) {
125 header();
126 System.out.println("ERROR: " + errorMessage);
127 System.out.println();
128 if (e != null) {
129 e.printStackTrace();
130 System.out.println("");
131 }
132 System.out.println("Usage: java -jar ut2004-tournament-1v1....jar ");
133 System.out.println(" " + jsap.getUsage());
134 System.out.println();
135 System.out.println(jsap.getHelp());
136 System.out.println();
137 throw new RuntimeException("FAILURE: " + errorMessage);
138 }
139
140 private static void header() {
141 if (headerOutput) return;
142 System.out.println();
143 System.out.println("==================================");
144 System.out.println("Pogamut UT2004 DeathMatch Executor");
145 System.out.println("==================================");
146 System.out.println();
147 headerOutput = true;
148 }
149
150 private static void initJSAP() throws JSAPException {
151 jsap = new JSAP();
152
153 FlaggedOption opt1 = new FlaggedOption(ARG_UT2004_HOME_DIR_LONG)
154 .setStringParser(JSAP.STRING_PARSER)
155 .setRequired(true)
156 .setShortFlag(ARG_UT2004_HOME_DIR_SHORT)
157 .setLongFlag(ARG_UT2004_HOME_DIR_LONG);
158 opt1.setHelp("UT2004 home directory containing GameBots2004 (System/GameBots2004.u) present.");
159
160 jsap.registerParameter(opt1);
161
162 FlaggedOption opt2 = new FlaggedOption(ARG_BOT_JARs_LONG)
163 .setStringParser(JSAP.STRING_PARSER)
164 .setRequired(true)
165 .setShortFlag(ARG_BOT_JARs_SHORT)
166 .setLongFlag(ARG_BOT_JARs_LONG);
167 opt2.setHelp("Semicolon separated PATH/TO/JAR/file1;PATH/TO/JAR/file2 containing executable jars of bots.");
168
169 jsap.registerParameter(opt2);
170
171 FlaggedOption opt3 = new FlaggedOption(ARG_BOT_NAMEs_LONG)
172 .setStringParser(JSAP.STRING_PARSER)
173 .setRequired(true)
174 .setShortFlag(ARG_BOT_NAMEs_SHORT)
175 .setLongFlag(ARG_BOT_NAMEs_LONG);
176 opt3.setHelp("Semicolon separated name1;name2;name3 (ids) that should be given to bots.");
177
178 jsap.registerParameter(opt3);
179
180 FlaggedOption opt6 = new FlaggedOption(ARG_MAP_NAME_LONG)
181 .setStringParser(JSAP.STRING_PARSER)
182 .setRequired(true)
183 .setShortFlag(ARG_MAP_NAME_SHORT)
184 .setLongFlag(ARG_MAP_NAME_LONG);
185 opt6.setHelp("Map where the game should be played (e.g. DM-1on1-Albatross).");
186
187 jsap.registerParameter(opt6);
188
189 FlaggedOption opt7 = new FlaggedOption(ARG_MATCH_NAME_LONG)
190 .setStringParser(JSAP.STRING_PARSER)
191 .setRequired(false)
192 .setShortFlag(ARG_MATCH_NAME_SHORT)
193 .setLongFlag(ARG_MATCH_NAME_LONG)
194 .setDefault("DMMatch1v1");
195 opt7.setHelp("Name of the match == output folder for the results.");
196
197 jsap.registerParameter(opt7);
198
199 FlaggedOption opt8 = new FlaggedOption(ARG_RESULT_DIR_LONG)
200 .setStringParser(JSAP.STRING_PARSER)
201 .setRequired(false)
202 .setShortFlag(ARG_RESULT_DIR_SHORT)
203 .setLongFlag(ARG_RESULT_DIR_LONG)
204 .setDefault(".");
205 opt8.setHelp("PATH/TO/directory where to output results (does not need to exist).");
206
207 jsap.registerParameter(opt8);
208
209 FlaggedOption opt9 = new FlaggedOption(ARG_SERVER_NAME_LONG)
210 .setStringParser(JSAP.STRING_PARSER)
211 .setRequired(false)
212 .setShortFlag(ARG_SERVER_NAME_SHORT)
213 .setLongFlag(ARG_SERVER_NAME_LONG)
214 .setDefault("DMMatch1v1");
215 opt9.setHelp("Server name that should be advertised via LAN.");
216
217 jsap.registerParameter(opt9);
218
219 FlaggedOption opt10 = new FlaggedOption(ARG_FRAG_LIMIT_LONG)
220 .setStringParser(JSAP.INTEGER_PARSER)
221 .setRequired(false)
222 .setShortFlag(ARG_FRAG_LIMIT_SHORT)
223 .setLongFlag(ARG_FRAG_LIMIT_LONG)
224 .setDefault("20");
225 opt10.setHelp("Frag limit for the match.");
226
227 jsap.registerParameter(opt10);
228
229 FlaggedOption opt11 = new FlaggedOption(ARG_TIMEOUT_MINUTES_LONG)
230 .setStringParser(JSAP.INTEGER_PARSER)
231 .setRequired(false)
232 .setShortFlag(ARG_TIMEOUT_MINUTES_SHORT)
233 .setLongFlag(ARG_TIMEOUT_MINUTES_LONG)
234 .setDefault("20");
235 opt11.setHelp("Match timeout in minutes.");
236
237 jsap.registerParameter(opt11);
238
239 Switch opt12 = new Switch(ARG_HUMAN_LIKE_LOG_LONG)
240 .setShortFlag(ARG_HUMAN_LIKE_LOG_SHORT)
241 .setLongFlag(ARG_HUMAN_LIKE_LOG_LONG)
242 .setDefault("false");
243 opt12.setHelp("Whether to produce log for 'HumanLike Project' analysis.");
244
245 jsap.registerParameter(opt12);
246
247 FlaggedOption opt13 = new FlaggedOption(ARG_UT2004_PORT_LONG)
248 .setStringParser(JSAP.INTEGER_PARSER)
249 .setRequired(false)
250 .setShortFlag(ARG_UT2004_PORT_SHORT)
251 .setLongFlag(ARG_UT2004_PORT_LONG)
252 .setDefault("7777");
253 opt13.setHelp("UT2004 port for the dedicated server (1-32000).");
254
255 jsap.registerParameter(opt13);
256 }
257
258 private static void readConfig(String[] args) {
259 System.out.println("Parsing command arguments.");
260
261 try {
262 config = jsap.parse(args);
263 } catch (Exception e) {
264 fail(e.getMessage());
265 System.out.println("");
266 e.printStackTrace();
267 throw new RuntimeException("FAILURE!");
268 }
269
270 if (!config.success()) {
271 String error = "Invalid arguments specified.";
272 Iterator errorIter = config.getErrorMessageIterator();
273 if (!errorIter.hasNext()) {
274 error += "\n-- No details given.";
275 } else {
276 while (errorIter.hasNext()) {
277 error += "\n-- " + errorIter.next();
278 }
279 }
280 fail(error);
281 }
282
283 ut2004HomeDir = config.getString(ARG_UT2004_HOME_DIR_LONG);
284 botJars = config.getString(ARG_BOT_JARs_LONG);
285 botJarsSeparated = botJars == null ? null : botJars.split(";");
286 botNames = config.getString(ARG_BOT_NAMEs_LONG);
287 botNamesSeparated = botNames == null ? null : botNames.split(";");
288 map = config.getString(ARG_MAP_NAME_LONG);
289 serverName = config.getString(ARG_SERVER_NAME_LONG);
290 resultDir = config.getString(ARG_RESULT_DIR_LONG);
291 matchName = config.getString(ARG_MATCH_NAME_LONG);
292 fragLimit = config.getInt(ARG_FRAG_LIMIT_LONG);
293 timeoutMinutes = config.getInt(ARG_TIMEOUT_MINUTES_LONG);
294 humanLikeLog = config.getBoolean(ARG_HUMAN_LIKE_LOG_LONG);
295 ut2004Port = config.getInt(ARG_UT2004_PORT_LONG);
296 }
297
298 private static void sanityChecks() {
299 System.out.println("Sanity checks...");
300
301 ut2004HomeDirFile = new File(ut2004HomeDir);
302 if (!ut2004HomeDirFile.exists() || !ut2004HomeDirFile.isDirectory()) {
303 fail("UT2004 directory was not found at '" + ut2004HomeDirFile.getAbsolutePath() + "', path resolved from configuration read as '" + ut2004HomeDir + "'.");
304 }
305 System.out.println("-- UT2004 directory found at '" + ut2004HomeDirFile.getAbsolutePath() + "'");
306
307 ut2004SystemDirFile = new File(ut2004HomeDirFile, "System");
308 if (!ut2004SystemDirFile.exists() || !ut2004SystemDirFile.isDirectory()) {
309 fail("UT2004/System directory was not found at '" + ut2004SystemDirFile.getAbsolutePath() + "', invalid UT2004 installation.");
310 }
311 System.out.println("-- UT2004/System directory found at '" + ut2004SystemDirFile.getAbsolutePath() + "'");
312
313 ut2004IniFile = new File(ut2004SystemDirFile, "UT2004.ini");
314 if (!ut2004IniFile.exists() || !ut2004IniFile.isFile()) {
315 fail("UT2004/System/UT2004.ini file was not found at '" + ut2004IniFile.getAbsolutePath() + "', invalid UT2004 installation.");
316 }
317 System.out.println("-- UT2004/System/UT2004.ini file found at '" + ut2004IniFile.getAbsolutePath() + "'");
318
319 if (botJarsSeparated == null) {
320 fail("Bot jar(s) was/were not specified correctly.");
321 }
322
323 if (botNamesSeparated == null) {
324 fail("Bot name(s) was/were not specified correctly.");
325 }
326
327 if (botJarsSeparated.length != botNamesSeparated.length) {
328 fail("Bot jar(s) and name(s) numbers mismatch. I've parsed " + botJarsSeparated.length + " bot jar files != " + botNamesSeparated.length + " of bot names.");
329 }
330
331 botJarFiles = new File[botJarsSeparated.length];
332 for (int i = 0; i < botJarsSeparated.length; ++i) {
333 botJarFiles[i] = new File(botJarsSeparated[i]);
334 if (!botJarFiles[i].exists() || !botJarFiles[i].isFile()) {
335 fail("Bot" + (i+1) + " jar file was not found at '"+ botJarFiles[i].getAbsolutePath() + "', path resolved from configuration read as '" + botJarsSeparated[i] + "'.");
336 }
337 System.out.println("-- Bot" + (i+1) + " jar file found at '" + botJarFiles[i].getAbsolutePath() + "'");
338 }
339 System.out.println("-- Bot jars ok");
340
341 for (int i = 0; i < botNamesSeparated.length; ++i) {
342 if (botNamesSeparated[i] == null || botNamesSeparated[i].isEmpty()) {
343 fail("Bot " + (i+1) + " invalid name '" + botNamesSeparated[i] +"' specified.");
344 }
345 System.out.println("-- Bot" + (i+1) + " name set as '" + botNamesSeparated[i] + "'");
346 }
347 System.out.println("-- Bot names ok");
348
349 mapsDirFile = new File(ut2004HomeDirFile, "Maps");
350 if (!mapsDirFile.exists() || !mapsDirFile.isDirectory()) {
351 fail("UT2004/Maps directory was not found at '" + mapsDirFile.getAbsolutePath() + "', invalid UT2004 installation.");
352 }
353 System.out.println("-- UT2004/Maps directory found at '" + mapsDirFile.getAbsolutePath() + "'");
354
355 mapFile = new File(mapsDirFile, map + ".ut2");
356 if (!mapFile.exists() || !mapFile.isFile()) {
357 fail("Specified map '" + map + "' was not found within UT2004/Maps dir at '" + mapFile.getAbsoluteFile() + "', could not execute the match.");
358 }
359 System.out.println("-- Map '" + map + "' found at '" + mapFile.getAbsolutePath() + "'");
360
361 if (matchName == null || matchName.isEmpty()) {
362 fail("Invalid match name '" + matchName + "' specified.");
363 }
364 System.out.println("-- Match name set as '" + matchName + "'");
365
366 if (serverName == null || serverName.isEmpty()) {
367 fail("Invalid server name '" + serverName + "' specified.");
368 }
369 System.out.println("-- Server name set as '" + serverName + "'");
370
371 if (fragLimit < 1) {
372 fail("Invalid frag limit '" + fragLimit +"' specified, must be >= 1.");
373 }
374 System.out.println("-- Frag limit set as '" + fragLimit + "'");
375
376 if (timeoutMinutes < 1) {
377 fail("Invalid time limit '" + timeoutMinutes +"' specified, must be >= 1.");
378 }
379 System.out.println("-- Timeout set as '" + timeoutMinutes + "' minutes.");
380
381 if (ut2004Port < 1 || ut2004Port > 32000) {
382 fail("Invalid UT2004 port specified '" + ut2004Port + "', must be 1 <= port <= 32000.");
383 }
384 System.out.println("-- UT2004 port set as '" + ut2004Port + "'");
385
386 System.out.println("Sanity checks OK!");
387 }
388
389 private static void setUT2004Ini() {
390 Date date = new Date(System.currentTimeMillis());
391 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
392 File ut2004IniBackup = new File(ut2004SystemDirFile, "UT2004.ini." + sdf.format(date) + ".bak");
393
394 System.out.println("Backing up '" + ut2004IniFile.getAbsolutePath() + "' into '" + ut2004IniBackup.getAbsolutePath() + "' ...");
395 try {
396 FileUtils.copyFile(ut2004IniFile, ut2004IniBackup);
397 } catch (IOException e) {
398 throw new RuntimeException("Failed to backup UT2004.ini", e);
399 }
400
401 System.out.println("Reading '" + ut2004IniFile.getAbsolutePath() + "' ...");
402 List<String> lines;
403 try {
404 lines = FileUtils.readLines(ut2004IniFile);
405 } catch (IOException e) {
406 throw new RuntimeException("Failed to read UT2004.ini", e);
407 }
408 System.out.println("-- " + lines.size() + " lines read.");
409
410 System.out.println("Searching for UT2004 Port and ServerName ...");
411
412
413
414
415 int state = 0;
416
417 Pattern patternSection = Pattern.compile("^\\s*\\[\\s*[^]]*\\s*\\]\\s*$");
418 Pattern patternSectionURL = Pattern.compile("^\\s*\\[\\s*" + UT2004Ini.Section_URL + "\\s*\\]\\s*$");
419 Pattern patternSectionEngineGameReplicationInfo = Pattern.compile("^\\s*\\[\\s*" + UT2004Ini.Section_Engine_GameReplicationInfo + "\\s*\\]\\s*$");
420 Pattern patternPort = Pattern.compile("^\\s*" + UT2004Ini.Key_Port + "\\s*=\\s*.*$");
421 Pattern patternServerName = Pattern.compile("^\\s*" + UT2004Ini.Key_ServerName + "\\s*=\\s*.*$");
422 Pattern patternShortName = Pattern.compile("^\\s*" + UT2004Ini.Key_ShortName + "\\s*=\\s*.*$");
423
424 boolean portFound = false;
425 boolean nameFound = false;
426 boolean shortNameFound = false;
427
428 for (int lineNum = 0;
429
430
431 lineNum <= lines.size()
432
433
434 && (!(portFound && nameFound && shortNameFound));
435 ++lineNum
436 ) {
437
438
439
440 String line = lineNum < lines.size() ? lines.get(lineNum).trim() : null;
441
442
443 if (lineNum < lines.size() && line == null) {
444 continue;
445 }
446
447 if (lineNum == lines.size() || patternSection.matcher(line).matches()) {
448 switch (state) {
449 case 0:
450 break;
451 case 1:
452 if (!portFound) {
453 lines.add(lineNum, UT2004Ini.Key_Port + "=" + ut2004Port);
454 portFound = true;
455 ++lineNum;
456 }
457 break;
458 case 2:
459 if (!nameFound) {
460 lines.add(lineNum, UT2004Ini.Key_ServerName + "=" + serverName);
461 nameFound = true;
462 ++lineNum;
463 }
464 if (!shortNameFound) {
465 lines.add(lineNum, UT2004Ini.Key_ShortName + "=" + serverName);
466 shortNameFound = true;
467 ++lineNum;
468 }
469 break;
470 }
471 if (lineNum == lines.size()) {
472 break;
473 }
474 if (line == null) {
475 continue;
476 }
477 if (patternSectionURL.matcher(line).matches()) {
478 state = 1;
479 } else
480 if (patternSectionEngineGameReplicationInfo.matcher(line).matches()) {
481 state = 2;
482 } else {
483 state = 0;
484 }
485 continue;
486 }
487
488 switch (state) {
489 case 0:
490 continue;
491 case 1:
492 if (!portFound && patternPort.matcher(line).matches()) {
493 lines.set(lineNum, UT2004Ini.Key_Port + "=" + ut2004Port);
494 portFound = true;
495 }
496 continue;
497 case 2:
498 if (!nameFound && patternServerName.matcher(line).matches()) {
499 lines.set(lineNum, UT2004Ini.Key_ServerName + "=" + serverName);
500 nameFound = true;
501 } else
502 if (!shortNameFound && patternShortName.matcher(line).matches()) {
503 lines.set(lineNum, UT2004Ini.Key_ShortName + "=" + serverName);
504 shortNameFound = true;
505 }
506 continue;
507 default:
508 continue;
509 }
510 }
511
512 if (!portFound) {
513 throw new RuntimeException("Failed to set UT2004 port!");
514 }
515 if (!nameFound) {
516 throw new RuntimeException("Failed to set UT2004 ServerName!");
517 }
518
519 System.out.println("UT2004 Port and ServerName set.");
520 System.out.println("Rewriting '" + ut2004IniFile.getAbsolutePath() + "' ...");
521 try {
522 FileUtils.writeLines(ut2004IniFile, lines);
523 } catch (IOException e) {
524 throw new RuntimeException("Failed to write UT2004.ini", e);
525 }
526
527 System.out.println("UT2004 Port and ServerName set.");
528 }
529
530 private static void executeMatch() {
531 UT2004DeathMatchConfig config = new UT2004DeathMatchConfig();
532
533 UT2004BotConfig[] botConfigs = new UT2004BotConfig[botJarFiles.length];
534 for (int i = 0; i < botJarFiles.length; ++i) {
535 UT2004BotConfig botConfig = new UT2004BotConfig();
536 botConfig.setBotId(botNamesSeparated[i]);
537 botConfig.setPathToBotJar(botJarFiles[i].getAbsolutePath());
538 botConfig.setRedirectStdErr(true);
539 botConfig.setRedirectStdOut(true);
540 botConfigs[i] = botConfig;
541 }
542
543 config.setBot(botConfigs);
544 config.setOutputDirectory(new File(resultDir));
545 config.setMatchId(matchName);
546 config.setFragLimit(fragLimit);
547 config.setTimeLimit(timeoutMinutes);
548 config.setHumanLikeLogEnabled(humanLikeLog);
549
550 config.getUccConf().setStartOnUnusedPort(true);
551 config.getUccConf().setUnrealHome(ut2004HomeDir);
552 config.getUccConf().setGameType("BotDeathMatch");
553 config.getUccConf().setMapName(map);
554
555 System.out.println("EXECUTING MATCH!");
556
557 LogCategory log = new LogCategory(matchName);
558 UT2004DeathMatch match = new UT2004DeathMatch(config, log);
559
560 match.getLog().setLevel(Level.INFO);
561 match.getLog().addConsoleHandler();
562
563 match.run();
564 }
565
566 public static void main(String[] args) throws JSAPException {
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590 initJSAP();
591
592 header();
593
594 readConfig(args);
595
596 sanityChecks();
597
598 setUT2004Ini();
599
600 executeMatch();
601 }
602
603 }