View Javadoc

1   package cz.cuni.amis.pogamut.ut2004.tournament.deathmatch;
2   
3   import java.io.File;
4   import java.util.ArrayList;
5   import java.util.Collections;
6   import java.util.Comparator;
7   import java.util.HashMap;
8   import java.util.List;
9   import java.util.Map;
10  
11  import org.apache.commons.io.FileUtils;
12  
13  import cz.cuni.amis.pogamut.ut2004.tournament.match.IUT2004BotConfig;
14  import cz.cuni.amis.pogamut.ut2004.tournament.match.UT2004BotConfig;
15  import cz.cuni.amis.pogamut.ut2004.tournament.match.UT2004NativeBotConfig;
16  import cz.cuni.amis.pogamut.ut2004.utils.UCCWrapperConf;
17  import cz.cuni.amis.utils.NullCheck;
18  import cz.cuni.amis.utils.exception.PogamutException;
19  import cz.cuni.amis.utils.token.IToken;
20  import cz.cuni.amis.utils.token.Tokens;
21  
22  /**
23   * Configuration for the {@link UT2004DeathMatchTournament} class.
24   * <p><p>
25   * The most interesting method is {@link UT2004DeathMatchTournamentConfig#cleanUp()} that is using recursion to generate all possible
26   * matches for enlisted bots.
27   * <p><p> 
28   * Do not forget to use {@link UT2004DeathMatchTournamentConfig#setNumBotsInOneMatch(int)}!
29   * <p><p>
30   * THREAD-UNSAFE!
31   *
32   * @author Jimmy
33   */
34  public class UT2004DeathMatchTournamentConfig {
35  	
36  	/**
37  	 * How many bots should be present in one match.
38  	 */
39  	protected int numBotsInOneMatch = 2;
40  	
41  	/**
42  	 * Unique id of the tournament.
43  	 */
44  	protected IToken tournamentId;
45  	
46  	/**
47  	 * Used as {@link UT2004DeathMatchConfig#setFragLimit(int)}.
48  	 */
49  	protected int fragLimit;
50  	
51  	/**
52  	 * Used as {@link UT2004DeathMatchConfig#setTimeLimit(int)}.
53  	 */
54  	protected int timeLimitInMinutes;
55  	
56  	protected UCCWrapperConf uccConf = new UCCWrapperConf();	
57  	
58  	/**
59  	 * Used as {@link UT2004DeathMatchConfig#setOutputDirectory(File)}.
60  	 * <p><p>
61  	 * Example: "results" + File.separator + "tournament"
62  	 */
63  	protected String outputDir;
64  		
65  	/**
66  	 * Custom (Pogamut) bots in the tournament.
67  	 */
68  	protected Map<IToken, UT2004BotConfig> bots = new HashMap<IToken, UT2004BotConfig>();
69  	
70  	/**
71  	 * Native bots in the tournament.
72  	 */
73  	protected Map<IToken, UT2004NativeBotConfig> nativeBots = new HashMap<IToken, UT2004NativeBotConfig>();
74  
75  	/**
76  	 * Parameter-less constructor, don't forget to initialize everything!	 
77  	 **/
78  	public UT2004DeathMatchTournamentConfig() {
79  		uccConf.setGameType("BotDeathMatch");
80  		uccConf.setStartOnUnusedPort(true);
81  	}
82  
83  	/**
84  	 * How many bots should be present in one match.
85  	 * @return
86  	 */
87  	public int getNumBotsInOneMatch() {
88  		return numBotsInOneMatch;
89  	}
90  
91  	/**
92  	 * Used as {@link UT2004DeathMatchConfig#setFragLimit(int)}.
93  	 * 
94  	 * @return
95  	 */
96  	public int getFragLimit() {
97  		return fragLimit;
98  	}
99  
100 	/**
101 	 * Used as {@link UT2004DeathMatchConfig#setTimeLimit(int)}.
102 	 * 
103 	 * @return
104 	 */
105 	public int getTimeLimitInMinutes() {
106 		return timeLimitInMinutes;
107 	}
108 
109 	/**
110 	 * Used for unique identification of the tournament (optional).
111 	 * 
112 	 * @return
113 	 */
114 	public IToken getTournamentId() {
115 		return tournamentId;
116 	}
117 	
118 	/**
119 	 * Used as {@link UT2004DeathMatchConfig#setOutputDirectory(File)}.
120 	 * 
121 	 * @return
122 	 */
123 	public String getOutputDir() {
124 		return outputDir;
125 	}
126 	
127 	/**
128 	 * UccConfiguration used for running ucc.exe for respective matches.
129 	 * @return
130 	 */
131 	public UCCWrapperConf getUccConf() {
132 		return uccConf;
133 	}
134 	
135 	/**
136 	 * Map with custom (Pogamut) bots that are enlisted to the tournament.
137 	 * @return
138 	 */
139 	public Map<IToken, UT2004BotConfig> getBots() {
140 		return bots;
141 	}
142 	
143 	/**
144 	 * Map with native UT2004 bots that are enlisted to the tournament.
145 	 * @return
146 	 */
147 	public Map<IToken, UT2004NativeBotConfig> getNativeBots() {
148 		return nativeBots;
149 	}
150 	
151 	public void setNumBotsInOneMatch(int numBotsInOneMatch) {
152 		this.numBotsInOneMatch = numBotsInOneMatch;
153 	}
154 
155 	public UT2004DeathMatchTournamentConfig setTournamentId(IToken id) {
156 		this.tournamentId = id;
157 		return this;
158 	}
159 	
160 	public UT2004DeathMatchTournamentConfig setTournamentId(String id) {
161 		this.tournamentId = Tokens.get(id);
162 		return this;
163 	}
164 	
165 	public UT2004DeathMatchTournamentConfig setFragLimit(int fragLimit) {
166 		this.fragLimit = fragLimit;
167 		return this;
168 	}
169 
170 	public UT2004DeathMatchTournamentConfig setTimeLimitInMinutes(int timeLimitInMinutes) {
171 		this.timeLimitInMinutes = timeLimitInMinutes;
172 		return this;
173 	}
174 
175 	public UT2004DeathMatchTournamentConfig setUnrealHome(String unrealHome) {
176 		this.uccConf.setUnrealHome(unrealHome);
177 		return this;
178 	}
179 
180 	public UT2004DeathMatchTournamentConfig setMapName(String mapName) {
181 		this.uccConf.setMapName(mapName);
182 		return this;
183 	}
184 
185 	public UT2004DeathMatchTournamentConfig setOutputDir(String outputDir) {
186 		this.outputDir = outputDir;
187 		return this;
188 	}
189 	
190 	public void setUccConf(UCCWrapperConf uccConf) {
191 		this.uccConf = uccConf;
192 	}
193 	
194 	public UT2004DeathMatchTournamentConfig setBots(Map<IToken, UT2004BotConfig> bots) {
195 		this.bots = bots;
196 		return this;
197 	}
198 	
199 	public UT2004DeathMatchTournamentConfig setNativeBots(Map<IToken, UT2004NativeBotConfig> nativeBots) {
200 		this.nativeBots = nativeBots;
201 		return this;
202 	}
203 	
204 	public UT2004DeathMatchTournamentConfig clearBots() {
205 		this.bots.clear();
206 		return this;
207 	}
208 	
209 	public UT2004DeathMatchTournamentConfig clearNativeBots() {
210 		this.nativeBots.clear();
211 		return this;
212 	}
213 	
214 	/**
215 	 * Adds NEW bot configuration into the object, checks whether there is no BotId clash (if so, throws an exception).
216 	 * @param bots
217 	 * @return
218 	 */
219 	public UT2004DeathMatchTournamentConfig addBot(UT2004BotConfig... bots) {
220 		if (bots == null) return this;
221 		for (UT2004BotConfig bot : bots) {
222 			NullCheck.check(bot.getBotId(), "bot.getBotId()");
223 			if (this.bots.containsKey(bot.getBotId())) {
224 				throw new PogamutException("Can't add another bot under the id " + bot.getBotId().getToken() + ", there is already an existing custom bot configuration under this ID. If you need to override it, use setBot().", this);
225 			}
226 			if (this.nativeBots.containsKey(bot.getBotId())) {
227 				throw new PogamutException("Can't add another bot under the id " + bot.getBotId().getToken() + ", there is already an existing native bot configuration under this ID. If you need to override it, use setBot().", this);
228 			}
229 			this.bots.put(bot.getBotId(), bot);
230 		}
231 		return this;
232 	}
233 	
234 	/**
235 	 * Sets bot configuration into the object, does not checks whether there is BotId clash.
236 	 * 
237 	 * @param bots
238 	 * @return
239 	 */
240 	public UT2004DeathMatchTournamentConfig setBot(UT2004BotConfig... bots) {
241 		if (bots == null) return this;
242 		for (UT2004BotConfig bot : bots) {
243 			NullCheck.check(bot.getBotId(), "bot.getBotId()");
244 			if (this.nativeBots.containsKey(bot.getBotId())) {
245 				throw new PogamutException("Can't add another bot under the id " + bot.getBotId().getToken() + ", there is already an existing native bot configuration under this ID. If you need to override it, use setBot().", this);
246 			}
247 			this.bots.put(bot.getBotId(), bot);
248 		}
249 		return this;
250 	}
251 	
252 	/**
253 	 * Adds NEW native bot configuration into the object, checks whether there is no BotId clash (if so, throws an exception).
254 	 * @param bots
255 	 * @return
256 	 */
257 	public UT2004DeathMatchTournamentConfig addNativeBot(UT2004NativeBotConfig... bots) {
258 		if (bots == null) return this;
259 		for (UT2004NativeBotConfig bot : bots) {
260 			NullCheck.check(bot.getBotId(), "bot.getBotId()");
261 			if (this.bots.containsKey(bot.getBotId())) {
262 				throw new PogamutException("Can't add another bot under the id " + bot.getBotId().getToken() + ", there is already an existing custom bot configuration under this ID. If you need to override it, use setBot().", this);
263 			}
264 			if (this.nativeBots.containsKey(bot.getBotId())) {
265 				throw new PogamutException("Can't add another bot under the id " + bot.getBotId().getToken() + ", there is already an existing native bot configuration under this ID. If you need to override it, use setNativeBot().", this);
266 			}
267 			this.nativeBots.put(bot.getBotId(), bot);
268 		}
269 		return this;
270 	}
271 	
272 	/**
273 	 * Sets native bot configuration into the object, does not checks whether there is BotId clash.
274 	 * @param bots
275 	 * @return
276 	 */
277 	public UT2004DeathMatchTournamentConfig setNativeBot(UT2004NativeBotConfig... bots) {
278 		if (bots == null) return this;
279 		for (UT2004NativeBotConfig bot : bots) {
280 			NullCheck.check(bot.getBotId(), "bot.getBotId()");
281 			if (this.bots.containsKey(bot.getBotId())) {
282 				throw new PogamutException("Can't add another bot under the id " + bot.getBotId().getToken() + ", there is already an existing custom bot configuration under this ID. If you need to override it, use setBot().", this);
283 			}
284 			this.nativeBots.put(bot.getBotId(), bot);
285 		}
286 		return this;
287 	}
288 	
289 	public boolean isNativeBot(IToken botId) {
290 		return nativeBots.containsKey(botId);
291 	}
292 	
293 	/**
294 	 * Returns bot configuration regardless it is custom (Pogamut) bot id or native UT2004 bot id.
295 	 * @param botId
296 	 * @return
297 	 */
298 	public IUT2004BotConfig getBotConfig(IToken botId) {
299 		UT2004BotConfig config = bots.get(botId);
300 		if (config != null) return config;
301 		return nativeBots.get(botId);
302 	}
303 
304 	/**
305 	 * WARNING: Removes directory with tournament results. 
306 	 */
307 	public void cleanUp() {
308 		try {
309 			FileUtils.deleteQuietly(new File(outputDir));
310 		} catch (Exception e) {			
311 		}		
312 	}	
313 	
314 	/**
315 	 * Creates {@link UT2004DeathMatchConfig} for bots of ids from 'chosenBots'.
316 	 * <p><p>
317 	 * Created configuration IS NOT VALIDATED!
318 	 * 
319 	 * @param chosenBots
320 	 * @return
321 	 */
322 	public UT2004DeathMatchConfig createConfiguration(List<IToken> chosenBots) {
323 		UT2004DeathMatchConfig matchConfig = new UT2004DeathMatchConfig();
324 		
325 		StringBuffer matchId = new StringBuffer();
326 		boolean first = true;
327 		for (IToken token : chosenBots) {
328 			if (first) first = false;
329 			else matchId.append("-vs-");
330 			matchId.append(token.getToken());
331 		}
332 		matchConfig.setMatchId(matchId.toString());
333 		matchConfig.setOutputDirectory(new File(outputDir));
334 				
335 		matchConfig.setFragLimit(getFragLimit());
336 		matchConfig.setTimeLimit(getTimeLimitInMinutes()); // in minutes
337 		matchConfig.setUccConf(new UCCWrapperConf(uccConf));
338 		
339 		for (IToken botId : chosenBots) {
340 			if (isNativeBot(botId)) {
341 				matchConfig.addNativeBot(new UT2004NativeBotConfig(nativeBots.get(botId)));
342 			} else {
343 				matchConfig.addBot(new UT2004BotConfig(bots.get(botId)));
344 			}
345 		}
346 		
347 		return matchConfig;
348 	}
349 	
350 	protected void generate(List<UT2004DeathMatchConfig> result, int numBots, List<IToken> botIds, int startFrom, List<IToken> chosenBots) {
351 		if (startFrom >= botIds.size()) {
352 			// no more bots to use...
353 			return;
354 		}
355 		if (chosenBots.size() + (botIds.size() - startFrom) < numBots) {
356 			// we can't generate more matches as there is not enough bots for that
357 			return;
358 		}
359 		
360 		chosenBots.add(botIds.get(startFrom));
361 		
362 		if (chosenBots.size() == numBots) {
363 			// all bots have been chosen, create configuration
364 			result.add(createConfiguration(chosenBots));
365 		} else {
366 			// continue with the recursion
367 			for (int i = startFrom+1; i < botIds.size(); ++i) {
368 				generate(result, numBots, botIds, i, chosenBots);
369 			}
370 		}
371 		
372 		chosenBots.remove(chosenBots.size()-1);
373 	}
374 	
375 	protected UT2004DeathMatchConfig[] generateMatches(int numBots) {
376 		List<IToken> botIds = new ArrayList<IToken>(bots.size() + nativeBots.size());
377 		botIds.addAll(bots.keySet());
378 		Collections.sort(botIds, new Comparator<IToken>() {
379 			@Override
380 			public int compare(IToken o1, IToken o2) {
381 				if (o1 == null) {
382 					if (o2 == null) return 0;
383 					else return -1;
384 				}
385 				if (o2 == null) return 1;
386 				return o1.getToken().compareTo(o2.getToken());
387 			}
388 		});
389 		
390 		List<IToken> nativeBotIds = new ArrayList<IToken>(nativeBots.keySet());
391 		Collections.sort(nativeBotIds, new Comparator<IToken>() {
392 			@Override
393 			public int compare(IToken o1, IToken o2) {
394 				if (o1 == null) {
395 					if (o2 == null) return 0;
396 					else return -1;
397 				}
398 				if (o2 == null) return 1;
399 				return o1.getToken().compareTo(o2.getToken());
400 			}
401 		});
402 		
403 		botIds.addAll(nativeBotIds);
404 
405 		List<IToken> chosenBots = new ArrayList<IToken>();
406 		
407 		List<UT2004DeathMatchConfig> result = new ArrayList<UT2004DeathMatchConfig>();
408 		for (int i = 0; i < bots.size(); ++i) {
409 			generate(result, numBots, botIds, i, chosenBots);
410 		}
411 		return result.toArray(new UT2004DeathMatchConfig[result.size()]);
412 	}
413 	
414 	/**
415 	 * This method will create all combinations of matche configs that consists of 'numBotsInOneMatch' bots, i.e., all 1v1 (numBotsInOneMatch == 2) or all 1v1v1 (numBotsInOneMatch == 3), etc.
416 	 * <p><p>
417 	 * It does not create matche configs where there are only native UT2004 bots, there is always at least 1 custom (Pogamut) bot 
418 	 * in every generated match configs.
419 	 * <p><p>
420 	 * Match configurations are NOT VALIDATED!
421 	 * 
422 	 * @param numBotsInOneMatch must be &gt;= 2
423 	 * @return
424 	 */
425 	public UT2004DeathMatchConfig[] createMatcheConfigs(int numBotsInOneMatch) {
426 		if (numBotsInOneMatch < 2) throw new IllegalArgumentException("numBotsInOneMatch = " + numBotsInOneMatch + " < 2 !!!");
427 		if (numBotsInOneMatch > bots.size() + nativeBots.size()) throw new IllegalArgumentException("numBotsInOneMatch = " + numBotsInOneMatch + " > " + (bots.size() + nativeBots.size()) + " = number of all (custom+native) enlisted bots");
428 		if (bots.size() == 0) throw new PogamutException("No custom (Pogamut) bots enlisted to the tournament!", this);
429 		return generateMatches(numBotsInOneMatch);
430 	}
431 	
432 	/**
433 	 * This method will create all combinations of matche configs that consists of 'numBotsInOneMatch' that is obtained
434 	 * via {@link UT2004DeathMatchTournamentConfig#getNumBotsInOneMatch()}.
435 	 * <p><p>
436 	 * It does not create matche configs where there are only native UT2004 bots, there is always at least 1 custom (Pogamut) bot 
437 	 * in every generated match configs.
438 	 * <p><p>
439 	 * Match configurations are NOT VALIDATED!
440 	 * 
441 	 * @return
442 	 */
443 	public UT2004DeathMatchConfig[] createMatcheConfigs() {
444 		return createMatcheConfigs(getNumBotsInOneMatch());
445 	}
446 	
447 }