View Javadoc

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.concurrent.Callable;
13  import java.util.logging.Level;
14  import java.util.logging.LogRecord;
15  
16  import cz.cuni.amis.pogamut.base.utils.logging.ILogPublisher;
17  import cz.cuni.amis.pogamut.base.utils.logging.LogCategory;
18  import cz.cuni.amis.pogamut.ut2004.tournament.match.UT2004Match;
19  import cz.cuni.amis.pogamut.ut2004.tournament.match.UT2004MatchConfig;
20  import cz.cuni.amis.utils.FilePath;
21  import cz.cuni.amis.utils.NullCheck;
22  import cz.cuni.amis.utils.exception.PogamutException;
23  import cz.cuni.amis.utils.exception.PogamutIOException;
24  import cz.cuni.amis.utils.maps.HashMapMap;
25  import cz.cuni.amis.utils.maps.LazyMap;
26  import cz.cuni.amis.utils.token.IToken;
27  import cz.cuni.amis.utils.token.Token;
28  import cz.cuni.amis.utils.token.Tokens;
29  
30  /**
31   * Simple class that allows to run 1 match multiple times automatically suffixing ID of the match (name of the output directory)
32   * with numbers.
33   * <p><p>
34   * THREAD-UNSAFE!
35   * 
36   * @author Jimmy
37   */
38  public class UT2004DeathMatchRepeater implements Callable<List<UT2004DeathMatchResult>>, Runnable {
39  
40  	protected UT2004DeathMatchConfig matchConfig;
41  	protected int repeats;
42  
43  	protected List<UT2004DeathMatchResult> results = new ArrayList<UT2004DeathMatchResult>();
44  	protected List<Throwable> exceptions = new ArrayList<Throwable>();
45  	private LogCategory log;
46  	
47  	/**
48  	 * Parameter-less constructor, don't forget to initialize {@link UT2004DeathMatchRepeater#setMatchConfig(UT2004Match)} and
49  	 * {@link UT2004DeathMatchRepeater#setRepeats(int)}.
50  	 */
51  	public UT2004DeathMatchRepeater() {
52  	}
53  	
54  	/**
55  	 * Parameter-less constructor, don't forget to initialize {@link UT2004DeathMatchRepeater#setMatchConfig(UT2004Match)} and
56  	 * {@link UT2004DeathMatchRepeater#setRepeats(int)}.
57  	 */
58  	public UT2004DeathMatchRepeater(LogCategory log) {
59  		this.log = log;
60  	}
61  	
62  	public UT2004DeathMatchRepeater(UT2004DeathMatchConfig match, int repeats, LogCategory log) {
63  		NullCheck.check(match, "match");
64  		this.matchConfig = match;
65  		this.repeats = repeats;
66  		if (this.repeats < 0) {
67  			throw new IllegalArgumentException("repeats = " + repeats + " < 0, can't be!");
68  		}		
69  		this.log = log;
70  	}
71  
72  	protected String getNum(int i, int max) {
73  		String result = String.valueOf(i);
74  		String maxStr = String.valueOf(max);
75  		while (result.length() < maxStr.length()) {
76  			result = "0" + result;
77  		}
78  		return result;
79  	}
80  	
81  	protected Token getToken(IToken orig, int i, int max) {
82  		return Tokens.get(orig.getToken() + "-" + getNum(i+1, max) + "_of_" + max);
83  	}
84  
85  	/**
86  	 * Log that is being used.
87  	 * @return
88  	 */
89  	public LogCategory getLog() {
90  		return log;
91  	}
92  
93  	public void setLog(LogCategory log) {
94  		this.log = log;
95  	}
96  
97  	/**
98  	 * Which match we're going to evaluate.
99  	 * @return
100 	 */
101 	public UT2004DeathMatchConfig getMatchConfig() {
102 		return matchConfig;
103 	}
104 
105 	public void setMatchConfig(UT2004DeathMatchConfig matchConfig) {
106 		this.matchConfig = matchConfig;
107 	}
108 
109 	/**
110 	 * How many times we will repeat the {@link UT2004DeathMatchRepeater#getMatchConfig()}.
111 	 * @return
112 	 */
113 	public int getRepeats() {
114 		return repeats;
115 	}
116 
117 	public void setRepeats(int repeats) {
118 		this.repeats = repeats;
119 	}
120 
121 	/**
122 	 * After the {@link UT2004DeathMatchRepeater#run()} or {@link UT2004DeathMatchRepeater#call()} returns list of exceptions that
123 	 * had happened during matches (Array is guaranteed to have the length of {@link UT2004DeathMatchRepeater#repeats}, some 
124 	 * items may be null. Usually all.)
125 	 * <p><p>
126 	 * Immutable.
127 	 * 
128 	 * @return
129 	 */
130 	public List<Throwable> getExceptions() {
131 		return Collections.unmodifiableList(exceptions);
132 	}
133 
134 	/**
135 	 * After the {@link UT2004DeathMatchRepeater#run()} or {@link UT2004DeathMatchRepeater#call()} returns list of match results 
136 	 * (Array is guaranteed to have the length of {@link UT2004DeathMatchRepeater#repeats}, some items may be null == exception
137 	 * happened).
138 	 * <p><p>
139 	 * Immutable.
140 	 * 
141 	 * @return
142 	 */
143 	public List<UT2004DeathMatchResult> getResults() {
144 		return Collections.unmodifiableList(results);
145 	}
146 
147 	@Override
148 	public List<UT2004DeathMatchResult> call() throws Exception {
149 		call();
150 		return getResults();
151 	}
152 
153 	@Override
154 	public void run() {
155 		if (this.matchConfig == null) {
156 			throw new PogamutException("No match set into the repeater!", this);
157 		}
158 		if (this.repeats < 0) {
159 			throw new PogamutException("repeats = " + repeats + " < 0, can't be!", this);
160 		}
161 		IToken token = matchConfig.getMatchId();
162 		for (int i = 0; i < repeats; ++i) {
163 			if (log != null && log.isLoggable(Level.INFO)) {
164 				log.info("Running " + token.getToken() + " match " + (i+1) + " / " + repeats + " ...");
165 			}
166 			matchConfig.setMatchId(getToken(token, i, repeats));
167 			UT2004DeathMatch match = new UT2004DeathMatch(matchConfig, new LogCategory(matchConfig.getMatchId().getToken()));
168 			match.getLog().addHandler(new ILogPublisher() {
169 				@Override
170 				public void close() throws SecurityException {
171 				}
172 				@Override
173 				public void flush() {
174 				}
175 				@Override
176 				public void publish(LogRecord record) {
177 					if (UT2004DeathMatchRepeater.this.log != null) {
178 						UT2004DeathMatchRepeater.this.log.log(record);
179 					}
180 				}
181 			});	
182 			match.cleanUp();
183 			boolean exception = false;
184 			UT2004DeathMatchResult result = null;
185 			try {
186 				result = (UT2004DeathMatchResult) match.call();
187 			} catch (Exception e) {
188 				exception = true;
189 				results.add(null);
190 				exceptions.add(e);
191 			}
192 			if (!exception) {
193 				results.add(result);
194 				exceptions.add(null);
195 			}
196 		}
197 		matchConfig.setMatchId(token);
198 		outputAggregatedResults();
199 	}
200 
201 	protected void outputAggregatedResults() {
202 		if (log != null && log.isLoggable(Level.FINE)) {
203 			log.fine(matchConfig.getMatchId().getToken() + ": Outputting aggregated match results into CSV files...");
204 		}
205 		IToken token = matchConfig.getMatchId();
206 		File outputDirectory = new File(matchConfig.getOutputDirectory().getAbsolutePath() + File.separator + matchConfig.getMatchId().getToken() + "-results");
207 		outputDirectory.mkdirs();
208 		outputAggregatedResults(outputDirectory);
209 		if (log != null && log.isLoggable(Level.INFO)) {
210 			UT2004MatchConfig config = matchConfig;
211 			log.info(config.getMatchId().getToken() + ": Aggregated match results output into CSV files.");
212 		}
213 	}
214 	
215 	protected void outputAggregatedResults(File outputDirectory) {
216 		UT2004MatchConfig config = matchConfig;
217 		
218 		// BOT IDS
219 		
220 		List<IToken> botIds = config.getAllBotIds();
221 		
222 		// WINS / DRAWS
223 		
224 		Map<IToken, Integer> wins = new LazyMap<IToken, Integer>() {
225 			@Override
226 			protected Integer create(IToken key) { return 0; }
227 		};
228 		Map<IToken, Integer> draws = new LazyMap<IToken, Integer>() {
229 			@Override
230 			protected Integer create(IToken key) { return 0; }
231 		};
232 		
233 		int matchFinished = 0; // HOW MANY MATCHES FINISHED WITHOUT EXCEPTION...
234 		for (UT2004DeathMatchResult result : results) {
235 			if (result == null) continue;
236 			++matchFinished;
237 			if (result.isDraw()) {
238 				for (IToken botId : botIds) {
239 					draws.put(botId, 1+draws.get(botId));
240 				}
241 			} else {
242 				wins.put(result.getWinnerBot(), 1+wins.get(result.getWinnerBot()));
243 			}
244 		}
245 
246 		// KILLS
247 		
248 		HashMapMap<IToken, IToken, Integer> kills = new HashMapMap<IToken, IToken, Integer>();
249 		HashMap<IToken, Integer> scores = new HashMap<IToken, Integer>();
250 		HashMap<IToken, Integer> deaths = new HashMap<IToken, Integer>();
251 		HashMap<IToken, Integer> totalKills = new HashMap<IToken, Integer>();
252 		HashMap<IToken, Integer> totalKilled = new HashMap<IToken, Integer>();
253 		for (IToken botId1 : botIds) {
254 			scores.put(botId1, 0);
255 			deaths.put(botId1, 0);
256 			totalKills.put(botId1, 0);
257 			for (IToken botId2 : botIds) {
258 				kills.put(botId1, botId2, 0);
259 			}
260 		}
261 		for (UT2004DeathMatchResult result : results) {
262 			if (result == null) continue;
263 			for (IToken botId1 : botIds) {
264 				scores.put(botId1, scores.get(botId1) + result.getFinalScores().get(botId1).getScore());
265 				deaths.put(botId1, deaths.get(botId1) + result.getFinalScores().get(botId1).getDeaths());
266 				totalKills.put(botId1, totalKills.get(botId1) + result.getTotalKills().get(botId1));
267 				for (IToken botId2 : botIds) {
268 					kills.put(botId1, botId2, kills.get(botId1, botId2) + result.getKillCounts().get(botId1, botId2));					
269 				}
270 			}
271 		}
272 		
273 		//
274 		// OUTPUTTING AGGREGATED DATA
275 		//
276 		
277 		File resultFile = new File(outputDirectory.getAbsolutePath() + File.separator + "match-" + repeats + "x-" + config.getMatchId().getToken() + "-scores-aggregated.csv");
278 		FilePath.makeDirsToFile(resultFile);
279 		try {
280 			Formatter writer = new Formatter(resultFile);
281 			writer.format("botId;matches;matchFinished;win;winRatio;draw;drawRatio;lose;loseRatio;score;scoreAvg;kills;killsAvg;killedByOthers;killedByOthersAvg;deaths;deathsAvg;suicides;suicidesAvg");
282 			if (matchFinished > 0) {
283 				for (IToken botId : botIds) {
284 					writer.format(";");
285 					writer.format(botId.getToken());
286 					writer.format(";");
287 					writer.format(botId.getToken() + "Avg");
288 				}
289 				for (IToken botId : botIds) {
290 					writer.format
291 					(
292 						"\n%s;%d;%d;%d;%.3f;%d;%.3f;%d;%.3f;%d;%.3f;%d;%.3f;%d;%.3f;%d;%.3f;%d;%.3f",
293 						// BOT ID
294 						botId.getToken(),
295 						// MATCHES
296 						repeats,
297 						// MATCH FINISHED,
298 						matchFinished,
299 						// WIN
300 						wins.get(botId),
301 						((double)wins.get(botId)) / ((double)matchFinished),
302 						// DRAW
303 						draws.get(botId),
304 						((double)draws.get(botId)) / ((double)matchFinished),
305 						// LOSE					
306 						matchFinished - wins.get(botId) - draws.get(botId),
307 						((double)(matchFinished - wins.get(botId) - draws.get(botId))) / ((double)matchFinished),
308 						// SCORE
309 						scores.get(botId), 
310 						((double)scores.get(botId))/((double)matchFinished),
311 						// KILLS
312 						totalKills.get(botId),
313 						((double)totalKills.get(botId))/matchFinished,
314 						// KILLED BY OTHERS
315 						(deaths.get(botId) - kills.get(botId, botId)), // killed by others = how many times we died - suicides
316 						((double)(deaths.get(botId) - kills.get(botId, botId))) / ((double)matchFinished),
317 						// DEATHS
318 						deaths.get(botId),
319 						((double)deaths.get(botId)) / ((double)matchFinished),
320 						// SUICIDES
321 						kills.get(botId, botId), 
322 						((double)kills.get(botId, botId)) /((double)matchFinished)
323 					);
324 					for (IToken botId2 : botIds) {
325 						writer.format(";%d",   kills.get(botId).get(botId2));
326 						writer.format(";%.3f", ((double)kills.get(botId).get(botId2))/((double)matchFinished));
327 					}
328 				}
329 			} else {
330 				writer.format("NO MATCH FINISHED, ALL HAVE ENDED WITH AN EXCEPTION!");
331 			}
332 			try {
333 				writer.close();
334 			} catch (Exception e) {			
335 			}
336 		} catch (IOException e) {
337 			throw new PogamutIOException("Failed to write results!", e, log, this);
338 		}
339 		
340 		//
341 		// OUTPUTTING DATA per BOT
342 		//
343 		
344 		for (IToken botId1 : botIds) {
345 			File botFile = new File(outputDirectory.getAbsolutePath() + File.separator + "match-" + repeats + "x-" + config.getMatchId().getToken() + "-scores-" + botId1.getToken() + ".csv");
346 			FilePath.makeDirsToFile(botFile);
347 			try {
348 				Formatter writer = new Formatter(botFile);
349 				writer.format("match;matches;matchFinished;score;kills;killedByOthers;deaths;suicides");
350 				for (IToken botId2 : botIds) {
351 					writer.format(";");
352 					writer.format(botId2.getToken());
353 				}
354 				int i = 0;
355 				for (UT2004DeathMatchResult result : results) {
356 					++i;
357 					if (result == null) continue;					
358 					writer.format
359 					(
360 						"\n%d;%d;%d;%d;%d;%d;%d;%d",
361 						// MATCH NUMBER
362 						i,
363 						// MATCHES
364 						repeats,
365 						// MATCH FINISHED
366 						matchFinished,
367 						// SCORE
368 						result.getFinalScores().get(botId1).getScore(), 
369 						// KILLS
370 						result.getTotalKills().get(botId1),
371 						// KILLED BY OTHERS
372 						result.getWasKilled().get(botId1),
373 						// DEATHS
374 						result.getFinalScores().get(botId1).getDeaths(),
375 						// SUICIDES
376 						result.getSuicides().get(botId1)
377 					);
378 					for (IToken botId2 : botIds) {
379 						writer.format(";%d", result.getKillCounts().get(botId1).get(botId2));
380 					}
381 				}
382 				try {
383 					writer.close();
384 				} catch (Exception e) {			
385 				}
386 			} catch (IOException e) {
387 				throw new PogamutIOException("Failed to write results!", e, log, this);
388 			}
389 		}
390 		
391 		//
392 		// OUTPUTTING DATA per NATIVE BOT
393 		//
394 		
395 		List<IToken> customBots = new ArrayList<IToken>(config.getBots().keySet());
396 		Collections.sort(customBots, new Comparator<IToken>() {
397 			@Override
398 			public int compare(IToken o1, IToken o2) {
399 				return o1.getToken().compareTo(o2.getToken());
400 			}
401 		});
402 		
403 		if (results.size() > 0) {
404 			for (IToken botId1 : customBots) {
405 				File botFile = new File(outputDirectory.getAbsolutePath() + File.separator + "match-" + repeats + "x-" + config.getMatchId().getToken() + "-stats-" + botId1.getToken() + ".csv");
406 				FilePath.makeDirsToFile(botFile);
407 				try {
408 					Formatter writer = new Formatter(botFile);
409 					writer.format("match;");
410 					results.get(0).getBotObservers().get(botId1).getStats().outputHeader(writer);
411 					int i = 0;
412 					for (UT2004DeathMatchResult result : results) {
413 						++i;
414 						if (result == null) continue;
415 						writer.format("%d;", i);
416 						result.getBotObservers().get(botId1).getStats().outputStatLine(writer, result.getMatchTimeEnd());
417 					}
418 					try {
419 						writer.close();
420 					} catch (Exception e) {			
421 					}
422 				} catch (IOException e) {
423 					throw new PogamutIOException("Failed to write results!", e, log, this);
424 				}
425 			}
426 		}
427 	}
428 	
429 	
430 	
431 }