View Javadoc

1   package cz.cuni.amis.pogamut.ut2004.tournament.dm.table;
2   
3   import java.io.File;
4   import java.io.FileWriter;
5   import java.io.IOException;
6   import java.io.PrintWriter;
7   import java.text.SimpleDateFormat;
8   import java.util.Date;
9   import java.util.Iterator;
10  import java.util.List;
11  import java.util.logging.Level;
12  import java.util.regex.Pattern;
13  
14  import org.apache.commons.io.FileUtils;
15  
16  import com.martiansoftware.jsap.FlaggedOption;
17  import com.martiansoftware.jsap.JSAP;
18  import com.martiansoftware.jsap.JSAPException;
19  import com.martiansoftware.jsap.JSAPResult;
20  import com.martiansoftware.jsap.Switch;
21  
22  import cz.cuni.amis.pogamut.base.utils.logging.LogCategory;
23  import cz.cuni.amis.pogamut.ut2004.tournament.UT2004Ini;
24  import cz.cuni.amis.pogamut.ut2004.tournament.deathmatch.UT2004DeathMatch;
25  import cz.cuni.amis.pogamut.ut2004.tournament.deathmatch.UT2004DeathMatch1v1;
26  import cz.cuni.amis.pogamut.ut2004.tournament.deathmatch.UT2004DeathMatchConfig;
27  import cz.cuni.amis.pogamut.ut2004.tournament.match.UT2004BotConfig;
28  
29  public class Main {
30  	
31  	private static final char ARG_UT2004_HOME_DIR_SHORT = 'u';
32  	
33  	private static final String ARG_UT2004_HOME_DIR_LONG = "ut2004-home-dir";
34  	
35  	private static final char ARG_BOT_JARs_SHORT = 'a';
36  	
37  	private static final String ARG_BOT_JARs_LONG = "bot-jars";
38  	
39  	private static final char ARG_BOT_NAMEs_SHORT = 'b';
40  	
41  	private static final String ARG_BOT_NAMEs_LONG = "bot-names";
42  	
43  	private static final char ARG_MAP_NAME_SHORT = 'm';
44  	
45  	private static final String ARG_MAP_NAME_LONG = "map-name";
46  		
47  	private static final char ARG_RESULT_DIR_SHORT = 'r';
48  	
49  	private static final String ARG_RESULT_DIR_LONG = "result-directory";
50  	
51  	private static final char ARG_SERVER_NAME_SHORT = 's';
52  	
53  	private static final String ARG_SERVER_NAME_LONG = "server-name";
54  	
55  	private static final char ARG_FRAG_LIMIT_SHORT = 'f';
56  	
57  	private static final String ARG_FRAG_LIMIT_LONG = "frag-limit";
58  	
59  	private static final char ARG_TIMEOUT_MINUTES_SHORT = 't';
60  	
61  	private static final String ARG_TIMEOUT_MINUTES_LONG = "timeout-minutes";
62  	
63  	private static final char ARG_HUMAN_LIKE_LOG_SHORT = 'h';
64  	
65  	private static final String ARG_HUMAN_LIKE_LOG_LONG = "human-like-log";
66  	
67  	private static final char ARG_UT2004_PORT_SHORT = 'p';
68  	
69  	private static final String ARG_UT2004_PORT_LONG = "ut2004-port";
70  	
71  	private static final char ARG_CONCURRENT_THREADS_SHORT = 'c';
72  	
73  	private static final String ARG_CONCURRENT_THREADS_LONG = "threads";
74  	
75  	private static final char ARG_GENERATE_BATCH_FILES_SHORT = 'g';
76  	
77  	private static final String ARG_GENERATE_BATCH_FILES_LONG = "generate-batch-files";
78  	
79  	private static final char ARG_DEBUG_SHORT = 'd';
80  	
81  	private static final String ARG_DEBUG_LONG = "debug";
82  	
83  	private static final char ARG_CONTINUE_SHORT = 'o';
84  	
85  	private static final String ARG_CONTINUE_LONG = "continue";
86  
87  	private static final long MATCH_START_INTERLEAVE_MILLIS = 2 * 60 * 1000;
88  
89  	private static JSAP jsap;
90  
91  	private static boolean headerOutput = false;
92  
93  	private static String ut2004HomeDir;
94  
95  	private static String botJars;
96  	
97  	private static String[] botJarsSeparated;
98  
99  	private static String botNames;
100 	
101 	private static String[] botNamesSeparated;
102 	
103 	private static File[] botJarFiles;
104 	
105 	private static String map;
106 
107 	private static String serverName;
108 
109 	private static String resultDir;
110 
111 	private static int fragLimit;
112 	
113 	private static int timeoutMinutes;
114 
115 	private static JSAPResult config;
116 
117 	private static File ut2004HomeDirFile;
118 
119 	private static File mapsDirFile;
120 
121 	private static File mapFile;
122 
123 	private static File ut2004SystemDirFile;
124 
125 	private static File ut2004IniFile;
126 
127 	private static boolean humanLikeLog;
128 
129 	private static int ut2004Port;	
130 	
131 	private static int threadCount;
132 	
133 	private static boolean generateBatchFiles;
134 	
135 	private static boolean debug;
136 	
137 	private static boolean shouldContinue;
138 	
139 	private static void fail(String errorMessage) {
140 		fail(errorMessage, null);
141 	}
142 
143 	private static void fail(String errorMessage, Throwable e) {
144 		header();
145 		System.out.println("ERROR: " + errorMessage);
146 		System.out.println();
147 		if (e != null) {
148 			e.printStackTrace();
149 			System.out.println("");
150 		}		
151         System.out.println("Usage: java -jar ut2004-tournament-dm-table.jar ");
152         System.out.println("                " + jsap.getUsage());
153         System.out.println();
154         System.out.println(jsap.getHelp());
155         System.out.println();
156         throw new RuntimeException("FAILURE: " + errorMessage);
157 	}
158 
159 	private static void header() {
160 		if (headerOutput) return;
161 		System.out.println();
162 		System.out.println("========================================");
163 		System.out.println("Pogamut UT2004 DeathMatch Table Executor");
164 		System.out.println("========================================");
165 		System.out.println();
166 		headerOutput = true;
167 	}
168 	
169 	private static void info(String msg) {
170 		System.out.println("[INFO]    " + msg);
171 	}
172 	
173 	private static void warning(String msg) {
174 		System.out.println("[WARNING] " + msg);
175 	}
176 	
177 	private static void severe(String msg) {
178 		System.out.println("[SEVERE]  " + msg);
179 	}
180 	
181 	private static void initJSAP() throws JSAPException {
182 		jsap = new JSAP();
183 		    	
184     	FlaggedOption opt1 = new FlaggedOption(ARG_UT2004_HOME_DIR_LONG)
185         	.setStringParser(JSAP.STRING_PARSER)
186         	.setRequired(true) 
187         	.setShortFlag(ARG_UT2004_HOME_DIR_SHORT)
188         	.setLongFlag(ARG_UT2004_HOME_DIR_LONG);    
189         opt1.setHelp("UT2004 home directory containing GameBots2004 (System/GameBots2004.u) present.");
190         
191         jsap.registerParameter(opt1);
192         
193         FlaggedOption opt2 = new FlaggedOption(ARG_BOT_JARs_LONG)
194 	    	.setStringParser(JSAP.STRING_PARSER)
195 	    	.setRequired(true) 
196 	    	.setShortFlag(ARG_BOT_JARs_SHORT)
197 	    	.setLongFlag(ARG_BOT_JARs_LONG);    
198 	    opt2.setHelp("Semicolon separated PATH/TO/JAR/file1;PATH/TO/JAR/file2 containing executable jars of bots.");
199 	
200 	    jsap.registerParameter(opt2);
201 	    
202 	    FlaggedOption opt3 = new FlaggedOption(ARG_BOT_NAMEs_LONG)
203 	    	.setStringParser(JSAP.STRING_PARSER)
204 	    	.setRequired(true) 
205 	    	.setShortFlag(ARG_BOT_NAMEs_SHORT)
206 	    	.setLongFlag(ARG_BOT_NAMEs_LONG);    
207 	    opt3.setHelp("Semicolon separated name1;name2;name3 (ids) that should be given to bots.");
208 	
209 	    jsap.registerParameter(opt3);
210     
211 	    FlaggedOption opt6 = new FlaggedOption(ARG_MAP_NAME_LONG)
212 	    	.setStringParser(JSAP.STRING_PARSER)
213 	    	.setRequired(true) 
214 	    	.setShortFlag(ARG_MAP_NAME_SHORT)
215 	    	.setLongFlag(ARG_MAP_NAME_LONG);    
216 	    opt6.setHelp("Map where the game should be played (e.g. DM-1on1-Albatross).");
217 	
218 	    jsap.registerParameter(opt6);
219         
220 	    FlaggedOption opt8 = new FlaggedOption(ARG_RESULT_DIR_LONG)
221 	    	.setStringParser(JSAP.STRING_PARSER)
222 	    	.setRequired(false) 
223 	    	.setShortFlag(ARG_RESULT_DIR_SHORT)
224 	    	.setLongFlag(ARG_RESULT_DIR_LONG)
225 	    	.setDefault(".");
226 	    opt8.setHelp("PATH/TO/directory where to output results (does not need to exist).");
227 	
228 	    jsap.registerParameter(opt8);
229 	    
230 	    FlaggedOption opt9 = new FlaggedOption(ARG_SERVER_NAME_LONG)
231 			.setStringParser(JSAP.STRING_PARSER)
232 			.setRequired(false) 
233 			.setShortFlag(ARG_SERVER_NAME_SHORT)
234 			.setLongFlag(ARG_SERVER_NAME_LONG)
235 			.setDefault("DMMatch1v1");
236 		opt9.setHelp("Server name that should be advertised via LAN.");
237 		
238 		jsap.registerParameter(opt9);
239 		
240 		FlaggedOption opt10 = new FlaggedOption(ARG_FRAG_LIMIT_LONG)
241 			.setStringParser(JSAP.INTEGER_PARSER)
242 			.setRequired(false) 
243 			.setShortFlag(ARG_FRAG_LIMIT_SHORT)
244 			.setLongFlag(ARG_FRAG_LIMIT_LONG)
245 			.setDefault("20");
246 		opt10.setHelp("Frag limit for the match.");
247 		
248 		jsap.registerParameter(opt10);
249 		
250 		FlaggedOption opt11 = new FlaggedOption(ARG_TIMEOUT_MINUTES_LONG)
251 			.setStringParser(JSAP.INTEGER_PARSER)
252 			.setRequired(false) 
253 			.setShortFlag(ARG_TIMEOUT_MINUTES_SHORT)
254 			.setLongFlag(ARG_TIMEOUT_MINUTES_LONG)
255 			.setDefault("20");
256 		opt11.setHelp("Match timeout in minutes.");
257 		
258 		jsap.registerParameter(opt11);
259 		
260 		Switch opt12 = new Switch(ARG_HUMAN_LIKE_LOG_LONG)
261 			.setShortFlag(ARG_HUMAN_LIKE_LOG_SHORT)
262 			.setLongFlag(ARG_HUMAN_LIKE_LOG_LONG)
263 			.setDefault("false");
264 		opt12.setHelp("Whether to produce log for 'HumanLike Project' analysis.");
265 		
266 		jsap.registerParameter(opt12);
267 		
268 		FlaggedOption opt13 = new FlaggedOption(ARG_UT2004_PORT_LONG)
269 			.setStringParser(JSAP.INTEGER_PARSER)
270 			.setRequired(false) 
271 			.setShortFlag(ARG_UT2004_PORT_SHORT)
272 			.setLongFlag(ARG_UT2004_PORT_LONG)
273 			.setDefault("7777");
274 		opt13.setHelp("UT2004 port for the dedicated server (1-32000).");
275 		
276 		jsap.registerParameter(opt13);
277 		
278 		FlaggedOption opt14 = new FlaggedOption(ARG_CONCURRENT_THREADS_LONG)
279 			.setStringParser(JSAP.INTEGER_PARSER)
280 			.setRequired(false) 
281 			.setShortFlag(ARG_CONCURRENT_THREADS_SHORT)
282 			.setLongFlag(ARG_CONCURRENT_THREADS_LONG)
283 			.setDefault("1");
284 		opt14.setHelp("How many matches should be executed in parallel.");
285 		
286 		jsap.registerParameter(opt14);
287 		
288 		Switch opt15 = new Switch(ARG_GENERATE_BATCH_FILES_LONG)
289 			.setShortFlag(ARG_GENERATE_BATCH_FILES_SHORT)
290 			.setLongFlag(ARG_GENERATE_BATCH_FILES_LONG);
291 		opt15.setHelp("Generate batch file for every match executed (requires ut2004-tournament.jar and java on PATH to execute then).");
292 		
293 		jsap.registerParameter(opt15);
294 		
295 		Switch opt16 = new Switch(ARG_DEBUG_LONG)
296 			.setShortFlag(ARG_DEBUG_SHORT)
297 			.setLongFlag(ARG_DEBUG_LONG);
298 		opt16.setHelp("DEBUG - Whether to output stdout/err of respective executed bots.");
299 	
300 		jsap.registerParameter(opt16);
301 		
302 		Switch opt17 = new Switch(ARG_CONTINUE_LONG)
303 			.setShortFlag(ARG_CONTINUE_SHORT)
304 			.setLongFlag(ARG_CONTINUE_LONG);
305 		opt17.setHelp("Whether to skip matches for which the result folder already exists.");
306 	
307 		jsap.registerParameter(opt17);
308 	}
309 
310 	private static void readConfig(String[] args) {
311 		System.out.println("Parsing command arguments.");
312 		
313 		try {
314 	    	config = jsap.parse(args);
315 	    } catch (Exception e) {
316 	    	fail(e.getMessage());
317 	    	System.out.println("");
318 	    	e.printStackTrace();
319 	    	throw new RuntimeException("FAILURE!");
320 	    }
321 		
322 		if (!config.success()) {
323 			String error = "Invalid arguments specified.";
324 			Iterator errorIter = config.getErrorMessageIterator();
325 			if (!errorIter.hasNext()) {
326 				error += "\n-- No details given.";
327 			} else {
328 				while (errorIter.hasNext()) {
329 					error += "\n-- " + errorIter.next();
330 				}
331 			}
332 			fail(error);
333     	}
334 		
335 		ut2004HomeDir = config.getString(ARG_UT2004_HOME_DIR_LONG);
336 		botJars = config.getString(ARG_BOT_JARs_LONG);
337 	    botJarsSeparated = botJars == null ? null : botJars.split(";");
338 	    botNames = config.getString(ARG_BOT_NAMEs_LONG);
339 	    botNamesSeparated = botNames == null ? null : botNames.split(";");
340 	    map = config.getString(ARG_MAP_NAME_LONG);
341 	    serverName = config.getString(ARG_SERVER_NAME_LONG);
342 	    resultDir = config.getString(ARG_RESULT_DIR_LONG);
343 	    fragLimit = config.getInt(ARG_FRAG_LIMIT_LONG);
344 	    timeoutMinutes = config.getInt(ARG_TIMEOUT_MINUTES_LONG);
345 	    humanLikeLog = config.getBoolean(ARG_HUMAN_LIKE_LOG_LONG);
346 	    ut2004Port = config.getInt(ARG_UT2004_PORT_LONG);
347 	    threadCount = config.getInt(ARG_CONCURRENT_THREADS_LONG);
348 	    generateBatchFiles = config.getBoolean(ARG_GENERATE_BATCH_FILES_LONG);
349 	    debug = config.getBoolean(ARG_DEBUG_LONG);
350 	    shouldContinue = config.getBoolean(ARG_CONTINUE_LONG);
351 	}
352 	
353 	private static void sanityChecks() {
354 		System.out.println("Sanity checks...");
355 		
356 	    ut2004HomeDirFile = new File(ut2004HomeDir);
357 	    if (!ut2004HomeDirFile.exists() || !ut2004HomeDirFile.isDirectory()) {
358 	    	fail("UT2004 directory was not found at '" + ut2004HomeDirFile.getAbsolutePath() + "', path resolved from configuration read as '" + ut2004HomeDir + "'.");
359 	    }
360 	    System.out.println("-- UT2004 directory found at '" + ut2004HomeDirFile.getAbsolutePath() + "'");
361 	    
362 	    ut2004SystemDirFile = new File(ut2004HomeDirFile, "System");
363 	    if (!ut2004SystemDirFile.exists() || !ut2004SystemDirFile.isDirectory()) {
364 	    	fail("UT2004/System directory was not found at '" + ut2004SystemDirFile.getAbsolutePath() + "', invalid UT2004 installation.");
365 	    }
366 	    System.out.println("-- UT2004/System directory found at '" + ut2004SystemDirFile.getAbsolutePath() + "'");
367 	    
368 	    ut2004IniFile = new File(ut2004SystemDirFile, "UT2004.ini");
369 	    if (!ut2004IniFile.exists() || !ut2004IniFile.isFile()) {
370 	    	fail("UT2004/System/UT2004.ini file was not found at '" + ut2004IniFile.getAbsolutePath() + "', invalid UT2004 installation.");
371 	    }
372 	    System.out.println("-- UT2004/System/UT2004.ini file found at '" + ut2004IniFile.getAbsolutePath() + "'");
373 	    
374 	    if (botJarsSeparated == null) {
375 			fail("Bot jar(s) was/were not specified correctly.");
376 		}
377 		
378 		if (botNamesSeparated == null) {
379 			fail("Bot name(s) was/were not specified correctly.");
380 		}
381 		
382 		if (botJarsSeparated.length != botNamesSeparated.length) {
383 			fail("Bot jar(s) and name(s) numbers mismatch. I've parsed " + botJarsSeparated.length + " bot jar files != " + botNamesSeparated.length + " of bot names.");
384 		}
385 		
386 		if (botJarsSeparated.length < 2) {
387 			fail("Not enough bot jars/names specified: " + botJarsSeparated.length + " < 2");
388 		}
389 		
390 		botJarFiles = new File[botJarsSeparated.length];
391 	    for (int i = 0; i < botJarsSeparated.length; ++i) {
392 	    	botJarFiles[i] = new File(botJarsSeparated[i]);
393 	    	if (!botJarFiles[i].exists() || !botJarFiles[i].isFile()) {
394 		    	fail("Bot" + (i+1) + " jar file was not found at '"+ botJarFiles[i].getAbsolutePath() + "', path resolved from configuration read as '" + botJarsSeparated[i] + "'.");
395 		    }
396 	    	System.out.println("-- Bot" + (i+1) + " jar file found at '" + botJarFiles[i].getAbsolutePath() + "'");
397 	    }
398 	    System.out.println("-- Bot jars ok");
399 	   	
400 	    for (int i = 0; i < botNamesSeparated.length; ++i) {
401 	    	if (botNamesSeparated[i] == null || botNamesSeparated[i].isEmpty()) {
402 	    		fail("Bot " + (i+1) + " invalid name '" + botNamesSeparated[i] +"' specified.");
403 	    	}
404 	    	System.out.println("-- Bot" + (i+1) + " name set as '" + botNamesSeparated[i] + "'");
405 	    }
406 	    System.out.println("-- Bot names ok");
407 	    
408 	    mapsDirFile = new File(ut2004HomeDirFile, "Maps");
409 	    if (!mapsDirFile.exists() || !mapsDirFile.isDirectory()) {
410 	    	fail("UT2004/Maps directory was not found at '" + mapsDirFile.getAbsolutePath() + "', invalid UT2004 installation.");
411 	    }
412 	    System.out.println("-- UT2004/Maps directory found at '" + mapsDirFile.getAbsolutePath() + "'");
413 	    
414 	    mapFile = new File(mapsDirFile, map + ".ut2");
415 	    if (!mapFile.exists() || !mapFile.isFile()) {
416 	    	fail("Specified map '" + map + "' was not found within UT2004/Maps dir at '" + mapFile.getAbsoluteFile() + "', could not execute the match.");
417 	    }
418 	    System.out.println("-- Map '" + map + "' found at '" + mapFile.getAbsolutePath() + "'");
419 	    	    
420 	    if (serverName == null || serverName.isEmpty()) {
421 	    	fail("Invalid server name '" + serverName + "' specified.");
422 	    }
423 	    System.out.println("-- Server name set as '" + serverName + "'");
424 	    
425 	    if (fragLimit < 1) {
426 	    	fail("Invalid frag limit '" + fragLimit +"' specified, must be >= 1.");
427 	    }
428 	    System.out.println("-- Frag limit set as '" + fragLimit + "'");
429 	    
430 	    if (timeoutMinutes < 1) {
431 	    	fail("Invalid time limit '" + timeoutMinutes +"' specified, must be >= 1.");
432 	    }
433 	    System.out.println("-- Timeout set as '" + timeoutMinutes + "' minutes.");
434 	    
435 	    if (ut2004Port < 1 || ut2004Port > 32000) {
436 	    	fail("Invalid UT2004 port specified '" + ut2004Port + "', must be 1 <= port <= 32000.");
437 	    }
438 	    System.out.println("-- UT2004 port set as '" + ut2004Port + "'");
439 	    
440 	    if (0 > threadCount || threadCount > 20) {
441 	    	fail("Invalid parallel thread count " + threadCount + ", must be 20 >= N > 0.");
442 	    }	    
443 	    
444 	    System.out.println("Sanity checks OK!");
445 	}
446 	
447 	private static void setUT2004Ini() {
448 		Date date = new Date(System.currentTimeMillis());
449 		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
450 		File ut2004IniBackup = new File(ut2004SystemDirFile, "UT2004.ini." + sdf.format(date) + ".bak");
451 		
452 		System.out.println("Backing up '" + ut2004IniFile.getAbsolutePath() + "' into '" + ut2004IniBackup.getAbsolutePath() + "' ...");
453 		try {
454 			FileUtils.copyFile(ut2004IniFile, ut2004IniBackup);
455 		} catch (IOException e) {
456 			throw new RuntimeException("Failed to backup UT2004.ini", e);
457 		}
458 				
459 		System.out.println("Reading '" + ut2004IniFile.getAbsolutePath() + "' ...");
460 		List<String> lines;
461 		try {
462 			lines = FileUtils.readLines(ut2004IniFile);
463 		} catch (IOException e) {
464 			throw new RuntimeException("Failed to read UT2004.ini", e);
465 		}
466 		System.out.println("-- " + lines.size() + " lines read.");
467 		
468 		System.out.println("Searching for UT2004 Port and ServerName ...");
469 		
470 		// 0 -> nothing
471 		// 1 -> section URL
472 		// 2 -> section Engine.GameReplicationInfo
473 		int state = 0;
474 		
475 		Pattern patternSection = Pattern.compile("^\\s*\\[\\s*[^]]*\\s*\\]\\s*$");
476 		Pattern patternSectionURL = Pattern.compile("^\\s*\\[\\s*" + UT2004Ini .Section_URL + "\\s*\\]\\s*$");
477 		Pattern patternSectionEngineGameReplicationInfo = Pattern.compile("^\\s*\\[\\s*" + UT2004Ini.Section_Engine_GameReplicationInfo + "\\s*\\]\\s*$");
478 		Pattern patternPort = Pattern.compile("^\\s*" + UT2004Ini.Key_Port + "\\s*=\\s*.*$");
479 		Pattern patternServerName = Pattern.compile("^\\s*" + UT2004Ini.Key_ServerName + "\\s*=\\s*.*$");
480 		Pattern patternShortName = Pattern.compile("^\\s*" + UT2004Ini.Key_ShortName + "\\s*=\\s*.*$");
481 		
482 		boolean portFound = false;
483 		boolean nameFound = false;
484 		boolean shortNameFound = false;
485 		
486 		for (int lineNum = 0; 
487 				// IF lineNum == lines.size() 
488 				// => perform one more state switch to add possibly missing keys
489 			 lineNum <= lines.size()
490 			    // IF all set
491 				// => break;
492 			 && (!(portFound && nameFound && shortNameFound)); 
493 			++lineNum
494 		) {		
495 						
496 			
497 			// current line probed
498 			String line = lineNum < lines.size() ? lines.get(lineNum).trim() : null;
499 			
500 			// sanity check
501 			if (lineNum < lines.size() && line == null) {
502 				continue;
503 			}
504 			
505 			if (lineNum == lines.size() || patternSection.matcher(line).matches()) {
506 				switch (state) {
507 				case 0:
508 					break;
509 				case 1:
510 					if (!portFound) {
511 						lines.add(lineNum, UT2004Ini.Key_Port + "=" + ut2004Port);
512 						portFound = true;
513 						++lineNum;
514 					}
515 					break;
516 				case 2:
517 					if (!nameFound) {
518 						lines.add(lineNum, UT2004Ini.Key_ServerName + "=" + serverName);
519 						nameFound = true;
520 						++lineNum;
521 					}
522 					if (!shortNameFound) {
523 						lines.add(lineNum, UT2004Ini.Key_ShortName + "=" + serverName);
524 						shortNameFound = true;
525 						++lineNum;
526 					}
527 					break;
528 				}
529 				if (lineNum == lines.size()) {
530 					break;
531 				}
532 				if (line == null) {
533 					continue;
534 				}
535 				if (patternSectionURL.matcher(line).matches()) {
536 					state = 1;					
537 				} else
538 				if (patternSectionEngineGameReplicationInfo.matcher(line).matches()) {
539 					state = 2;
540 				} else {
541 					state = 0;
542 				}
543 				continue;
544 			}
545 			
546 			switch (state) {
547 			case 0: 
548 				continue;
549 			case 1: 
550 				if (!portFound && patternPort.matcher(line).matches()) {
551 					lines.set(lineNum, UT2004Ini.Key_Port + "=" + ut2004Port);
552 					portFound = true;
553 				}
554 				continue;
555 			case 2:
556 				if (!nameFound && patternServerName.matcher(line).matches()) {
557 					lines.set(lineNum, UT2004Ini.Key_ServerName + "=" + serverName);
558 					nameFound = true;
559 				} else
560 				if (!shortNameFound && patternShortName.matcher(line).matches()) {
561 					lines.set(lineNum, UT2004Ini.Key_ShortName + "=" + serverName);
562 					shortNameFound = true;
563 				}
564 				continue;
565 			default:
566 				continue;
567 			}
568 		}
569 		
570 		if (!portFound) {
571 			throw new RuntimeException("Failed to set UT2004 port!");
572 		}
573 		if (!nameFound) {
574 			throw new RuntimeException("Failed to set UT2004 ServerName!");
575 		}
576 		
577 		System.out.println("Rewriting '" + ut2004IniFile.getAbsolutePath() + "' ...");
578 		try {
579 			FileUtils.writeLines(ut2004IniFile, lines);
580 		} catch (IOException e) {
581 			throw new RuntimeException("Failed to write UT2004.ini", e);
582 		}		
583 		
584 		System.out.println("UT2004 Port and ServerName set.");
585 	}
586 	
587 	private static void generateBatchFile(String matchName, String bot1Jar, String bot1Id, String bot2Jar, String bot2Id) {
588 		String fileName = "match-" + bot1Id + "-vs-" + bot2Id + ".bat";
589 		File file = new File(fileName);
590 		if (file.exists()) {
591 			if (file.isFile()) {
592 				warning("Batch file already exists, skipping: " + file.getAbsolutePath());				
593  			} else {
594  				severe("Batch file already exists, but it is not file, skipping: " + file.getAbsolutePath());
595  			}
596 			return;			
597 		}
598 		try {
599 			FileWriter writer = new FileWriter(file);
600 			PrintWriter print = new PrintWriter(writer);
601 			
602 			print.println("java -jar ut2004-tournament.jar ^");
603 			print.println("  -u " + ut2004HomeDir + " ^");
604 			print.println("  -a " + bot1Jar + ";" + bot2Jar + " ^");
605 			print.println("  -b " + bot1Id + ";" + bot2Id + " ^");
606 			print.println("  -m " + map + " ^");
607 			print.println("  -r " + resultDir + " ^");
608 			print.println("  -n " + matchName + " ^");
609 			print.println("  -s DMServer-" + matchName + " ^");
610 			print.println("  -f " + fragLimit + " ^");
611 			print.println("  -t " + timeoutMinutes);
612 			
613 			try {
614 				print.close();
615 			} catch (Exception e) {				
616 			}
617 			try { 
618 				writer.close();
619 			} catch (Exception e) {				
620 			}
621 		} catch (Exception e) {
622 			
623 		}
624 	}
625 	
626 	private static String getMatchName(String bot1Id, String bot2Id) {
627 		return "Match-" + bot1Id + "-vs-" + bot2Id;
628 	}
629 	
630 	private static void generateBatchFiles() {
631 		for (int i = 0; i < botJarsSeparated.length; ++i) {
632 			String bot1Id = botNamesSeparated[i];
633 			String bot1Jar = botJarsSeparated[i];
634 			for (int j = i+1; j < botJarsSeparated.length; ++j) {
635 				String bot2Id = botNamesSeparated[j];
636 				String bot2Jar = botJarsSeparated[j];
637 				String matchName = getMatchName(bot1Id, bot2Id);
638 				generateBatchFile(matchName, bot1Jar, bot1Id, bot2Jar, bot2Id);
639 			}
640 		}
641 	}
642 	
643 	private static UT2004DeathMatch executeMatch(Match match) {		
644 		UT2004DeathMatchConfig config = new UT2004DeathMatchConfig();
645 		
646 		UT2004BotConfig bot1Config = new UT2004BotConfig();
647 		bot1Config.setBotId(match.bot1Id);
648 		bot1Config.setPathToBotJar(match.bot1Jar);
649 		bot1Config.setRedirectStdErr(debug);
650 		bot1Config.setRedirectStdOut(debug);
651 		
652 		UT2004BotConfig bot2Config = new UT2004BotConfig();
653 		bot2Config.setBotId(match.bot2Id);
654 		bot2Config.setPathToBotJar(match.bot2Jar);
655 		bot2Config.setRedirectStdErr(debug);
656 		bot2Config.setRedirectStdOut(debug);
657 		
658 		config.setBot(bot1Config, bot2Config);
659 		config.setOutputDirectory(new File(resultDir));
660 	    config.setMatchId(match.matchName);
661 	    config.setFragLimit(fragLimit);
662 	    config.setTimeLimit(timeoutMinutes);
663 	    config.setHumanLikeLogEnabled(humanLikeLog);
664 	    
665 	    config.getUccConf().setStartOnUnusedPort(true);
666 	    config.getUccConf().setUnrealHome(ut2004HomeDir);
667 	    config.getUccConf().setGameType("BotDeathMatch");
668 	    config.getUccConf().setMapName(map);
669 	     
670 	    System.out.println("EXECUTING MATCH!");
671 
672 	    LogCategory log = new LogCategory(match.matchName);
673 	    UT2004DeathMatch ut2004Match = new UT2004DeathMatch(config, log);
674 	    
675 	    ut2004Match.getLog().setLevel(Level.INFO);
676 	    ut2004Match.getLog().addConsoleHandler();
677 	    
678 	    ut2004Match.run();
679 	    
680 	    return ut2004Match;
681 	}
682 	
683 	private static class Match {
684 		
685 		String matchName;
686 		String bot1Jar;
687 		String bot1Id;
688 		String bot2Jar;
689 		String bot2Id;
690 		
691 		public Match(String matchName, String bot1Jar, String bot1Id, String bot2Jar, String bot2Id) {
692 			super();
693 			this.matchName = matchName;
694 			this.bot1Jar = bot1Jar;
695 			this.bot1Id = bot1Id;
696 			this.bot2Jar = bot2Jar;
697 			this.bot2Id = bot2Id;
698 		}
699 	}
700 	
701 	private static class ExecuteOne extends Thread {
702 
703 		private Match match;
704 		
705 		private UT2004DeathMatch result;
706 		
707 		public boolean done = false;
708 		
709 		public boolean success = true;
710 
711 		public ExecuteOne(Match match) {
712 			super(match.matchName);
713 			this.match = match;
714 		}
715 		
716 		@Override
717 		public void run() {
718 			try {
719 				info("STARTING: " + match.matchName);				
720 				result = executeMatch(match);
721 			} catch (Exception e) {				
722 				success = false;
723 			} finally {
724 				done = true;
725 				if (success) {
726 					info("ENDED-SUCCESS: " + match.matchName);
727 				} else {
728 					info("ENDED-FAILURE: " + match.matchName);
729 				}
730 			}
731 		}
732 		
733 	}
734 	
735 	private static void executeInParallel(Match[] matches, int threadCount) throws InterruptedException {
736 		int currentMatch = 0;
737 		int liveThreads = 0;
738 		
739 		int successes = 0;
740 		int skipped = 0;
741 		
742 		ExecuteOne[] threads = new ExecuteOne[threadCount];
743 		
744 		while (currentMatch < matches.length || liveThreads > 0) {
745 			
746 			for (int i = 0; i < threads.length; ++i) {
747 				if (threads[i] != null) {
748 					if (!threads[i].done) {
749 						// THREAD IS ALIVE AND MATCH IS RUNNING
750 						continue;
751 					}
752 					// THREAD HAS FINISHED, MATCH HAS ENDED
753 					--liveThreads;
754 					// CHECK SUCCESS
755 					if (threads[i].success) {
756 						++successes;
757 						
758 					}
759 					// DELETE THREAD
760 					threads[i] = null;					
761 				}
762 				
763 				assert(threads[i] == null);
764 
765 				if (shouldContinue) {
766 					while (currentMatch < matches.length) {
767 						Match match = matches[currentMatch];
768 						File resultFolder = new File(new File(resultDir), match.matchName);
769 						if (resultFolder.exists() && resultFolder.isDirectory()) {
770 							warning("Result folder for the match " + match.matchName + " already exists, skipping this match.");
771 							++skipped;
772 						} else {
773 							break;
774 						}
775 						++currentMatch;
776 					}
777 				}
778 				
779 				if (currentMatch < matches.length) {
780 					threads[i] = new ExecuteOne(matches[currentMatch]);
781 					++currentMatch;
782 					++liveThreads;
783 					threads[i].start();
784 					Thread.sleep(MATCH_START_INTERLEAVE_MILLIS); // INTERLEAVE RESPECTIVE MATCH START
785 				}
786 			}
787 			
788 			Thread.sleep(1000); // CHECK FOR MATCH END EVERY 1 sec
789 		}
790 			
791 		if (successes == matches.length - skipped) {
792 			info("ALL SUCCEEDED!");	
793 			if (shouldContinue) {
794 				info("Succeeded: " + successes + ", Skipped: " + skipped);
795 			}						
796 		} else {
797 			severe("Some matches failed, only " + successes + "/" + (matches.length - skipped) + " has succeed.");
798 			if (shouldContinue) {
799 				severe("Skipped: " + skipped);
800 			}
801 		}
802 	}
803 	
804 	private static void executeMatches() throws InterruptedException {
805 		
806 		int matchCount = botJarsSeparated.length * (botJarsSeparated.length-1) / 2;		
807 		Match[] matches = new Match[matchCount];
808 		
809 		int k = 0;
810 		for (int i = 0; i < botJarsSeparated.length-1; ++i) {
811 			String bot1Id = botNamesSeparated[i];
812 			String bot1Jar = botJarsSeparated[i];
813 			
814 			for (int j = i+1; j < botJarsSeparated.length; ++j) {
815 				String bot2Id = botNamesSeparated[j];
816 				String bot2Jar = botJarsSeparated[j];
817 			
818 				String matchName = getMatchName(bot1Id, bot2Id);
819 				
820 				matches[k] = new Match(matchName, bot1Jar, bot1Id, bot2Jar, bot2Id);
821 						
822 			    ++k;
823 			}
824 		}
825 
826 		executeInParallel(matches, threadCount);
827 	}
828 
829 	public static void main(String[] args) throws JSAPException {
830 //      FOR TESTING		
831 //		args = new String[] {
832 //			"-u",
833 //			"D:\\Games\\UT2004-Devel",
834 //			"-a",
835 //			"D:\\Workspaces\\Pogamut-Trunk\\Main\\PogamutUT2004Examples\\04-HunterBot\\target\\ut2004-04-hunter-bot-3.6.1-SNAPSHOT.one-jar.jar;D:\\Workspaces\\Pogamut-Trunk\\Main\\PogamutUT2004Examples\\04-HunterBot\\target\\ut2004-04-hunter-bot-3.6.1-SNAPSHOT.one-jar.jar;D:\\Workspaces\\Pogamut-Trunk\\Main\\PogamutUT2004Examples\\04-HunterBot\\target\\ut2004-04-hunter-bot-3.6.1-SNAPSHOT.one-jar.jar",
836 //			"-b",
837 //			"HunterBot1;HunterBot2;HunterBot3",
838 //			"-r",
839 //			"./results",
840 //			"-s",
841 //			"HunterServer",
842 //			"-m",
843 //			"DM-1on1-Albatross",
844 //			"-f",
845 //			"3",
846 //			"-t",
847 //			"3",
848 //			"-h", // human-like log			
849 //		    "-c",
850 //		    "5",
851 //		    "-g", // generate batch-files
852 //	        "-d", // debug
853 //            "-o"  // continue		
854 //		};
855 		
856 		initJSAP();
857 	    
858 	    header();
859 	    
860 	    readConfig(args);
861 	    
862 	    sanityChecks();
863 	    
864 	    if (generateBatchFiles) {
865 	    	generateBatchFiles();
866 	    }
867 	    
868 	    setUT2004Ini();
869 	    
870 	    try {
871 	    	executeMatches();
872 	    } catch (Exception e) {
873 	    	fail("ERROR", e);
874 	    }
875 	}
876 	
877 }