/******************************************************************************* * Copyright (c) 2001, 2010 Mathew A. Nelson and Robocode contributors * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://robocode.sourceforge.net/license/epl-v10.html * * Contributors: * Mathew A. Nelson * - Initial API and implementation * Flemming N. Larsen * - Code cleanup & optimizations * - Removed getBattleView().setDoubleBuffered(false) as BufferStrategy is * used now * - Replaced FileSpecificationVector, RobotPeerVector, and * RobotClassManagerVector with plain Vector * - Added check for if GUI is enabled before using graphical components * - Added restart() method * - Ported to Java 5 * - Added support for the replay feature * - Removed the clearBattleProperties() * - Updated to use methods from FileUtil and Logger, which replaces methods * that have been (re)moved from the robocode.util.Utils class * - Added PauseResumeListener interface, addListener(), removeListener(), * notifyBattlePaused(), notifyBattleResumed() for letting listeners * receive notifications when the game is paused or resumed * - Added missing functionality in to support team battles in * startNewBattle(BattleSpecification spec, boolean replay) * - Added missing close() on FileInputStreams and FileOutputStreams * - isPaused() is now synchronized * - Extended sendResultsToListener() to handle teams as well as robots * - Added setDefaultBattleProperties() for resetting battle properties * - Removed the showResultsDialog parameter from the stop() method * - Added null pointer check to the sendResultsToListener() method * - Enhanced the getBattleFilename() to look into the battle dir and also * add the .battle file extension to the returned file name if this is * missing * - Removed battleRunning field, isBattleRunning(), and setBattle() * - Bugfix: Multiple battle threads could run in the same time when the * battle thread was started in startNewBattle() * Luis Crespo * - Added debug step feature, including the nextTurn(), shouldStep(), * startNewRound() * Robert D. Maupin * - Replaced old collection types like Vector and Hashtable with * synchronized List and HashMap * Nathaniel Troutman * - Bugfix: Added cleanup() to prevent memory leaks by removing circular * references * Pavel Savara * - now driven by BattleObserver and commands to battle * - initial code of battle recorder and player *******************************************************************************/ package net.sf.robocode.battle; import net.sf.robocode.battle.events.BattleEventDispatcher; import net.sf.robocode.core.Container; import net.sf.robocode.host.ICpuManager; import net.sf.robocode.host.IHostManager; import net.sf.robocode.io.FileUtil; import net.sf.robocode.io.Logger; import static net.sf.robocode.io.Logger.logError; import static net.sf.robocode.io.Logger.logMessage; import net.sf.robocode.recording.BattlePlayer; import net.sf.robocode.recording.IRecordManager; import net.sf.robocode.repository.IRepositoryManager; import net.sf.robocode.settings.ISettingsManager; import robocode.Event; import robocode.control.BattleSpecification; import robocode.control.RandomFactory; import robocode.control.RobotSpecification; import robocode.control.events.BattlePausedEvent; import robocode.control.events.BattleResumedEvent; import robocode.control.events.IBattleListener; import java.io.*; import java.util.concurrent.atomic.AtomicBoolean; /** * @author Mathew A. Nelson (original) * @author Flemming N. Larsen (contributor) * @author Luis Crespo (contributor) * @author Robert D. Maupin (contributor) * @author Nathaniel Troutman (contributor) * @author Pavel Savara (contributor) */ public class BattleManager implements IBattleManager { private final ISettingsManager properties; private final IHostManager hostManager; private final ICpuManager cpuManager; private final IRecordManager recordManager; private final IRepositoryManager repositoryManager; private volatile IBattle battle; private BattleProperties battleProperties = new BattleProperties(); private final BattleEventDispatcher battleEventDispatcher; private String battleFilename; private String battlePath; private int pauseCount = 0; private final AtomicBoolean isManagedTPS = new AtomicBoolean(false); public BattleManager(ISettingsManager properties, IRepositoryManager repositoryManager, IHostManager hostManager, ICpuManager cpuManager, BattleEventDispatcher battleEventDispatcher, IRecordManager recordManager) { this.properties = properties; this.recordManager = recordManager; this.repositoryManager = repositoryManager; this.cpuManager = cpuManager; this.hostManager = hostManager; this.battleEventDispatcher = battleEventDispatcher; Logger.setLogListener(battleEventDispatcher); } public synchronized void cleanup() { if (battle != null) { battle.waitTillOver(); battle.cleanup(); battle = null; } } // Called when starting a new battle from GUI public void startNewBattle(BattleProperties battleProperties, boolean waitTillOver, boolean enableCLIRecording) { this.battleProperties = battleProperties; final RobotSpecification[] robots = repositoryManager.loadSelectedRobots(battleProperties.getSelectedRobots()); startNewBattleImpl(robots, waitTillOver, enableCLIRecording); } // Called from the RobocodeEngine public void startNewBattle(BattleSpecification spec, String initialPositions, boolean waitTillOver, boolean enableCLIRecording) { battleProperties = new BattleProperties(); battleProperties.setBattlefieldWidth(spec.getBattlefield().getWidth()); battleProperties.setBattlefieldHeight(spec.getBattlefield().getHeight()); battleProperties.setGunCoolingRate(spec.getGunCoolingRate()); battleProperties.setInactivityTime(spec.getInactivityTime()); battleProperties.setNumRounds(spec.getNumRounds()); battleProperties.setHideEnemyNames(spec.getHideEnemyNames()); battleProperties.setSelectedRobots(spec.getRobots()); battleProperties.setInitialPositions(initialPositions); final RobotSpecification[] robots = repositoryManager.loadSelectedRobots(spec.getRobots()); startNewBattleImpl(robots, waitTillOver, enableCLIRecording); } private void startNewBattleImpl(RobotSpecification[] battlingRobotsList, boolean waitTillOver, boolean enableCLIRecording) { stop(true); logMessage("Preparing battle..."); final boolean recording = (properties.getOptionsCommonEnableReplayRecording() && System.getProperty("TESTING", "none").equals("none")) || enableCLIRecording; if (recording) { recordManager.attachRecorder(battleEventDispatcher); } else { recordManager.detachRecorder(); } // resets seed for deterministic behavior of Random final String seed = System.getProperty("RANDOMSEED", "none"); if (!seed.equals("none")) { // init soon as it reads random cpuManager.getCpuConstant(); RandomFactory.resetDeterministic(Long.valueOf(seed)); } Battle realBattle = Container.createComponent(Battle.class); realBattle.setup(battlingRobotsList, battleProperties, isPaused()); battle = realBattle; Thread battleThread = new Thread(Thread.currentThread().getThreadGroup(), realBattle); battleThread.setPriority(Thread.NORM_PRIORITY); battleThread.setName("Battle Thread"); realBattle.setBattleThread(battleThread); if (!System.getProperty("NOSECURITY", "false").equals("true")) { hostManager.addSafeThread(battleThread); } // Start the realBattle thread battleThread.start(); // Wait until the realBattle is running and ended. // This must be done as a new realBattle could be started immediately after this one causing // multiple realBattle threads to run at the same time, which must be prevented! realBattle.waitTillStarted(); if (waitTillOver) { realBattle.waitTillOver(); } } public void waitTillOver() { if (battle != null) { battle.waitTillOver(); } } private void replayBattle() { if (!recordManager.hasRecord()) { return; } logMessage("Preparing replay..."); if (battle != null && battle.isRunning()) { battle.stop(true); } Logger.setLogListener(battleEventDispatcher); recordManager.detachRecorder(); battle = Container.createComponent(BattlePlayer.class); Thread battleThread = new Thread(Thread.currentThread().getThreadGroup(), battle); battleThread.setPriority(Thread.NORM_PRIORITY); battleThread.setName("BattlePlayer Thread"); // Start the battlePlayer thread battleThread.start(); } public String getBattleFilename() { return battleFilename; } public void setBattleFilename(String newBattleFilename) { if (newBattleFilename != null) { battleFilename = newBattleFilename.replace((File.separatorChar == '/') ? '\\' : '/', File.separatorChar); if (battleFilename.indexOf(File.separatorChar) < 0) { try { battleFilename = FileUtil.getBattlesDir().getCanonicalPath() + File.separatorChar + battleFilename; } catch (IOException ignore) {} } if (!battleFilename.endsWith(".battle")) { battleFilename += ".battle"; } } } public String getBattlePath() { if (battlePath == null) { battlePath = System.getProperty("BATTLEPATH"); if (battlePath == null) { battlePath = "battles"; } battlePath = new File(FileUtil.getCwd(), battlePath).getAbsolutePath(); } return battlePath; } public void saveBattleProperties() { if (battleProperties == null) { logError("Cannot save null battle properties"); return; } if (battleFilename == null) { logError("Cannot save battle to null path, use setBattleFilename()"); return; } FileOutputStream out = null; try { out = new FileOutputStream(battleFilename); battleProperties.store(out, "Battle Properties"); } catch (IOException e) { logError("IO Exception saving battle properties: " + e); } finally { FileUtil.cleanupStream(out); } } public BattleProperties loadBattleProperties() { BattleProperties res = new BattleProperties(); FileInputStream in = null; try { in = new FileInputStream(getBattleFilename()); res.load(in); } catch (FileNotFoundException e) { logError("No file " + battleFilename + " found, using defaults."); } catch (IOException e) { logError("Error while reading " + getBattleFilename() + ": " + e); } finally { FileUtil.cleanupStream(in); } return res; } public BattleProperties getBattleProperties() { if (battleProperties == null) { battleProperties = new BattleProperties(); } return battleProperties; } public void setDefaultBattleProperties() { battleProperties = new BattleProperties(); } public boolean isManagedTPS() { return isManagedTPS.get(); } public void setManagedTPS(boolean value) { isManagedTPS.set(value); } public synchronized void addListener(IBattleListener listener) { battleEventDispatcher.addListener(listener); } public synchronized void removeListener(IBattleListener listener) { battleEventDispatcher.removeListener(listener); } public synchronized void stop(boolean waitTillEnd) { if (battle != null && battle.isRunning()) { battle.stop(waitTillEnd); } } public synchronized void restart() { // Start new battle. The old battle is automatically stopped startNewBattle(battleProperties, false, false); } public synchronized void replay() { replayBattle(); } private boolean isPaused() { return (pauseCount != 0); } public synchronized void togglePauseResumeBattle() { if (isPaused()) { resumeBattle(); } else { pauseBattle(); } } public synchronized void pauseBattle() { if (++pauseCount == 1) { if (battle != null && battle.isRunning()) { battle.pause(); } else { battleEventDispatcher.onBattlePaused(new BattlePausedEvent()); } } } public synchronized void pauseIfResumedBattle() { if (pauseCount == 0) { pauseCount++; if (battle != null && battle.isRunning()) { battle.pause(); } else { battleEventDispatcher.onBattlePaused(new BattlePausedEvent()); } } } public synchronized void resumeIfPausedBattle() { if (pauseCount == 1) { pauseCount--; if (battle != null && battle.isRunning()) { battle.resume(); } else { battleEventDispatcher.onBattleResumed(new BattleResumedEvent()); } } } public synchronized void resumeBattle() { if (--pauseCount < 0) { pauseCount = 0; logError("SYSTEM: pause game bug!"); } else if (pauseCount == 0) { if (battle != null && battle.isRunning()) { battle.resume(); } else { battleEventDispatcher.onBattleResumed(new BattleResumedEvent()); } } } /** * Steps for a single turn, then goes back to paused */ public synchronized void nextTurn() { if (battle != null && battle.isRunning()) { battle.step(); } } public synchronized void prevTurn() { if (battle != null && battle.isRunning() && battle instanceof BattlePlayer) { ((BattlePlayer) battle).stepBack(); } } public synchronized void killRobot(int robotIndex) { if (battle != null && battle.isRunning() && battle instanceof Battle) { ((Battle) battle).killRobot(robotIndex); } } public synchronized void setPaintEnabled(int robotIndex, boolean enable) { if (battle != null && battle.isRunning()) { battle.setPaintEnabled(robotIndex, enable); } } public synchronized void setSGPaintEnabled(int robotIndex, boolean enable) { if (battle != null && battle.isRunning() && battle instanceof Battle) { ((Battle) battle).setSGPaintEnabled(robotIndex, enable); } } public synchronized void sendInteractiveEvent(Event event) { if (battle != null && battle.isRunning() && !isPaused() && battle instanceof Battle) { ((Battle) battle).sendInteractiveEvent(event); } } }