/******************************************************************************* * 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 * - Bugfix: updateMovement() checked for distanceRemaining > 1 instead of * distanceRemaining > 0 if slowingDown and moveDirection == -1 * - Bugfix: Substituted wait(10000) with wait() in execute() method, so * that robots do not hang when game is paused * - Bugfix: Teleportation when turning the robot to 0 degrees while forcing * the robot towards the bottom * - Added setPaintEnabled() and isPaintEnabled() * - Added setSGPaintEnabled() and isSGPaintEnabled() * - Replaced the colorIndex with bodyColor, gunColor, and radarColor * - Replaced the setColors() with setBodyColor(), setGunColor(), and * setRadarColor() * - Added bulletColor, scanColor, setBulletColor(), and setScanColor() and * removed getColorIndex() * - Optimizations * - Ported to Java 5 * - Bugfix: HitRobotEvent.isMyFault() returned false despite the fact that * the robot was moving toward the robot it collides with. This was the * case when distanceRemaining == 0 * - Removed isDead field as the robot state is used as replacement * - Added isAlive() method * - Added constructor for creating a new robot with a name only * - Added the set() that copies a RobotRecord into this robot in order to * support the replay feature * - Fixed synchronization issues with several member fields * - Added features to support the new JuniorRobot class * - Added cleanupStaticFields() for clearing static fields on robots * - Added getMaxTurnRate() * - Added turnAndMove() in order to support the turnAheadLeft(), * turnAheadRight(), turnBackLeft(), and turnBackRight() for the * JuniorRobot, which moves the robot in a perfect curve that follows a * circle * - Changed the behaviour of checkRobotCollision() so that HitRobotEvents * are only created and sent to robot when damage do occur. Previously, a * robot could receive HitRobotEvents even when no damage was done * - Renamed scanReset() to rescan() * - Added getStatusEvents() * - Added getGraphicsProxy(), getPaintEvents() * Luis Crespo * - Added states * Titus Chen * - Bugfix: Hit wall and teleporting problems with checkWallCollision() * Robert D. Maupin * - Replaced old collection types like Vector and Hashtable with * synchronized List and HashMap * Nathaniel Troutman * - Added cleanup() method for cleaning up references to internal classes * to prevent circular references causing memory leaks * Pavel Savara * - Re-work of robot interfaces * - hosting related logic moved to robot proxy * - interlocked synchronization * - (almost) minimized surface between RobotPeer and RobotProxy to serializable messages. *******************************************************************************/ package net.sf.robocode.battle.peer; import static java.lang.Math.PI; import static java.lang.Math.abs; import static java.lang.Math.atan2; import static java.lang.Math.cos; import static java.lang.Math.min; import static java.lang.Math.sin; import static java.lang.Math.tan; import static net.sf.robocode.io.Logger.logMessage; import static robocode.util.Utils.normalAbsoluteAngle; import static robocode.util.Utils.normalNearAbsoluteAngle; import static robocode.util.Utils.normalRelativeAngle; import java.awt.geom.Arc2D; import java.awt.geom.Rectangle2D; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import net.sf.robocode.battle.Battle; import net.sf.robocode.host.IHostManager; import net.sf.robocode.host.RobotStatics; import net.sf.robocode.host.events.EventManager; import net.sf.robocode.host.events.EventQueue; import net.sf.robocode.host.proxies.IHostingRobotProxy; import net.sf.robocode.io.Logger; import net.sf.robocode.peer.BadBehavior; import net.sf.robocode.peer.BulletStatus; import net.sf.robocode.peer.DebugProperty; import net.sf.robocode.peer.ExecCommands; import net.sf.robocode.peer.ExecResults; import net.sf.robocode.peer.IRobotPeer; import net.sf.robocode.peer.BulletStatus; import net.sf.robocode.peer.TeamMessage; import net.sf.robocode.repository.IRobotRepositoryItem; import net.sf.robocode.security.HiddenAccess; import net.sf.robocode.serialization.RbSerializer; import robocode.DeathEvent; import robocode.Event; import robocode.HitRobotEvent; import robocode.HitWallEvent; import robocode.RobotStatus; import robocode.Rules; import robocode.ScannedRobotEvent; import robocode.SkippedTurnEvent; import robocode.WinEvent; import robocode.control.RandomFactory; import robocode.control.RobotSpecification; import robocode.control.snapshot.BulletState; import robocode.control.snapshot.RobotState; import robocode.exception.AbortedException; import robocode.exception.DeathException; import robocode.exception.WinException; /** * RobotPeer is an object that deals with game mechanics and rules, and makes * sure that robots abides the rules. * * @author Mathew A. Nelson (original) * @author Flemming N. Larsen (contributor) * @author Luis Crespo (contributor) * @author Titus Chen (contributor) * @author Robert D. Maupin (contributor) * @author Nathaniel Troutman (contributor) * @author Pavel Savara (contributor) * @author Patrick Cupka (contributor) * @author Julian Kent (contributor) * @author "Positive" (contributor) */ public final class RobotPeer extends RobotPeerWeaponSystem implements IRobotPeerBattle, IRobotPeer { private static final int MAX_SKIPPED_TURNS = 30; private static final int MAX_SKIPPED_TURNS_WITH_IO = 240; private final RobotSpecification robotSpecification; private IHostingRobotProxy robotProxy; private AtomicReference<RobotStatus> status = new AtomicReference<RobotStatus>(); private AtomicReference<List<TeamMessage>> teamMessages = new AtomicReference<List<TeamMessage>>( new ArrayList<TeamMessage>()); private int skippedTurns; private boolean isIORobot; private boolean isPaintEnabled; private boolean sgPaintEnabled; private final Arc2D scanArc; private final RbSerializer rbSerializer; protected boolean scan; protected final AtomicBoolean isSleeping = new AtomicBoolean(false); private final AtomicBoolean halt = new AtomicBoolean(false); public boolean isSleeping() { return isSleeping.get(); } public boolean getHalt() { return halt.get(); } public void setHalt(boolean value) { halt.set(value); } public RobotPeer(Battle battle, IHostManager hostManager, RobotSpecification robotSpecification, int duplicate, TeamPeer team, int index, int contestantIndex) { super(battle); if (team != null) { team.add(this); } rbSerializer = new RbSerializer(); scanArc = new Arc2D.Double(); teamPeer = team; state = RobotState.ACTIVE; battleRules = battle.getBattleRules(); this.robotSpecification = robotSpecification; boolean isLeader = teamPeer != null && teamPeer.size() == 1; String teamName = team == null ? null : team.getName(); List<String> teamMembers = team == null ? null : team.getMemberNames(); statics = new RobotStatics(robotSpecification, duplicate, isLeader, battleRules, teamName, teamMembers, index, contestantIndex); statistics = new RobotStatistics(this, battle.getRobotsCount()); robotProxy = (IHostingRobotProxy) hostManager.createRobotProxy(robotSpecification, statics, this); } public void print(Throwable ex) { println(ex.toString()); StackTraceElement[] trace = ex.getStackTrace(); for (StackTraceElement aTrace : trace) { println("\tat " + aTrace); } Throwable ourCause = ex.getCause(); if (ourCause != null) { print(ourCause); } } public void printProxy(String s) { synchronized (proxyText) { proxyText.append(s); } } public String readOutText() { synchronized (proxyText) { final String robotText = battleText.toString() + proxyText.toString(); battleText.setLength(0); proxyText.setLength(0); return robotText; } } public RobotStatistics getRobotStatistics() { return statistics; } public ContestantStatistics getStatistics() { return statistics; } public RobotSpecification getRobotSpecification() { return robotSpecification; } // ------------------- // statics // ------------------- public boolean isDroid() { return statics.isDroid(); } public boolean isJuniorRobot() { return statics.isJuniorRobot(); } public boolean isInteractiveRobot() { return statics.isInteractiveRobot(); } public boolean isPaintRobot() { return statics.isPaintRobot(); } public boolean isAdvancedRobot() { return statics.isAdvancedRobot(); } public boolean isTeamRobot() { return statics.isTeamRobot(); } public String getShortName() { return statics.getShortName(); } public String getVeryShortName() { return statics.getVeryShortName(); } public int getIndex() { return statics.getIndex(); } public int getContestIndex() { return statics.getContestIndex(); } // ------------------- // status // ------------------- public void setPaintEnabled(boolean enabled) { isPaintEnabled = enabled; } public boolean isPaintEnabled() { return isPaintEnabled; } public void setSGPaintEnabled(boolean enabled) { sgPaintEnabled = enabled; } public boolean isSGPaintEnabled() { return sgPaintEnabled; } public Arc2D getScanArc() { return scanArc; } // ------------------- // robot space // ------------------- public double getRadarHeading() { return radarHeading; } public int getBodyColor() { return commands.get().getBodyColor(); } public int getRadarColor() { return commands.get().getRadarColor(); } public int getScanColor() { return commands.get().getScanColor(); } // ------------ // team // ------------ public String getTeamName() { return statics.getTeamName(); } public boolean isTeamLeader() { return statics.isTeamLeader(); } ByteBuffer bidirectionalBuffer; protected RobotStatistics statistics; protected final TeamPeer teamPeer; protected boolean inCollision; protected AtomicReference<EventQueue> events = new AtomicReference<EventQueue>(new EventQueue()); protected final AtomicBoolean isRunning = new AtomicBoolean(false); protected double lastGunHeading; protected double lastRadarHeading; public void setupBuffer(ByteBuffer bidirectionalBuffer) { this.bidirectionalBuffer = bidirectionalBuffer; } public void setupThread() { Thread.currentThread().setName(getName()); } public void executeImplSerial() throws IOException { ExecCommands commands = (ExecCommands) rbSerializer.deserialize(bidirectionalBuffer); final ExecResults results = executeImpl(commands); bidirectionalBuffer.clear(); rbSerializer.serializeToBuffer(bidirectionalBuffer, RbSerializer.ExecResults_TYPE, results); } public void waitForBattleEndImplSerial() throws IOException { ExecCommands commands = (ExecCommands) rbSerializer.deserialize(bidirectionalBuffer); final ExecResults results = waitForBattleEndImpl(commands); bidirectionalBuffer.clear(); rbSerializer.serializeToBuffer(bidirectionalBuffer, RbSerializer.ExecResults_TYPE, results); } public final ExecResults executeImpl(ExecCommands newCommands) { validateCommands(newCommands); if (!isExecFinishedAndDisabled) { // from robot to battle commands.set(new ExecCommands(newCommands, true)); printProxy(newCommands.getOutputText()); } else { // slow down spammer try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } // If we are stopping, yet the robot took action (in onWin or onDeath), stop now. if (battle.isAborted()) { isExecFinishedAndDisabled = true; throw new AbortedException(); } if (isDead()) { isExecFinishedAndDisabled = true; throw new DeathException(); } if (getHalt()) { isExecFinishedAndDisabled = true; if (isWinner) { throw new WinException(); } else { throw new AbortedException(); } } waitForNextTurn(); // from battle to robot final ExecCommands resCommands = new ExecCommands(this.commands.get(), false); final RobotStatus resStatus = status.get(); final boolean shouldWait = battle.isAborted() || (battle.isLastRound() && isWinner()); return new ExecResults(resCommands, resStatus, readoutEvents(), readoutTeamMessages(), readoutProjectiles(), getHalt(), shouldWait, isPaintEnabled()); } public final ExecResults waitForBattleEndImpl(ExecCommands newCommands) { if (!getHalt()) { // from robot to battle commands.set(new ExecCommands(newCommands, true)); printProxy(newCommands.getOutputText()); waitForNextTurn(); } // from battle to robot final ExecCommands resCommands = new ExecCommands(this.commands.get(), false); final RobotStatus resStatus = status.get(); final boolean shouldWait = battle.isAborted() || (battle.isLastRound() && !isWinner()); readoutTeamMessages(); // throw away return new ExecResults(resCommands, resStatus, readoutEvents(), new ArrayList<TeamMessage>(), readoutProjectiles(), getHalt(), shouldWait, false); } private void validateCommands(ExecCommands newCommands) { if (Double.isNaN(newCommands.getMaxTurnRate())) { println("You cannot setMaxTurnRate to: " + newCommands.getMaxTurnRate()); } newCommands.setMaxTurnRate(Math.min(abs(newCommands.getMaxTurnRate()), Rules.MAX_TURN_RATE_RADIANS)); if (Double.isNaN(newCommands.getMaxVelocity())) { println("You cannot setMaxVelocity to: " + newCommands.getMaxVelocity()); } newCommands.setMaxVelocity(Math.min(abs(newCommands.getMaxVelocity()), Rules.MAX_VELOCITY)); } private List<Event> readoutEvents() { return events.getAndSet(new EventQueue()); } private List<TeamMessage> readoutTeamMessages() { return teamMessages.getAndSet(new ArrayList<TeamMessage>()); } private List<BulletStatus> readoutProjectiles() { return projectileUpdates.getAndSet(new ArrayList<BulletStatus>()); } private void waitForNextTurn() { synchronized (isSleeping) { // Notify the battle that we are now asleep. // This ends any pending wait() call in battle.runRound(). // Should not actually take place until we release the lock in wait(), below. isSleeping.set(true); isSleeping.notifyAll(); // Notifying battle that we're asleep // Sleeping and waiting for battle to wake us up. try { isSleeping.wait(); } catch (InterruptedException e) { // We are expecting this to happen when a round is ended! // Immediately reasserts the exception by interrupting the caller thread itself Thread.currentThread().interrupt(); } isSleeping.set(false); // Notify battle thread, which is waiting in // our wakeup() call, to return. // It's quite possible, by the way, that we'll be back in sleep (above) // before the battle thread actually wakes up isSleeping.notifyAll(); } } // ----------- // called on battle thread // ----------- public void waitWakeup() { synchronized (isSleeping) { if (isSleeping()) { // Wake up the thread isSleeping.notifyAll(); try { isSleeping.wait(10000); } catch (InterruptedException e) { // Immediately reasserts the exception by interrupting the caller thread itself Thread.currentThread().interrupt(); } } } } public void waitWakeupNoWait() { synchronized (isSleeping) { if (isSleeping()) { // Wake up the thread isSleeping.notifyAll(); } } } public void waitSleeping(long millisWait, int nanosWait) { synchronized (isSleeping) { // It's quite possible for simple robots to // complete their processing before we get here, // so we test if the robot is already asleep. if (!isSleeping()) { try { for (long i = millisWait; i > 0 && !isSleeping() && isRunning(); i--) { isSleeping.wait(0, 999999); } if (!isSleeping() && isRunning()) { isSleeping.wait(0, nanosWait); } } catch (InterruptedException e) { // Immediately reasserts the exception by interrupting the caller thread itself Thread.currentThread().interrupt(); logMessage("Wait for " + getName() + " interrupted."); } } } } public void setSkippedTurns() { if (isSleeping() || !isRunning() || battle.isDebugging() || isPaintEnabled()) { skippedTurns = 0; } else { println("SYSTEM: " + getShortName() + " skipped turn " + battle.getTime()); skippedTurns++; events.get().clear(false); if (!isDead()) { addEvent(new SkippedTurnEvent(battle.getTime())); } if ((!isIORobot && (skippedTurns > MAX_SKIPPED_TURNS)) || (isIORobot && (skippedTurns > MAX_SKIPPED_TURNS_WITH_IO))) { println("SYSTEM: " + getShortName() + " has not performed any actions in a reasonable amount of time."); println("SYSTEM: No score will be generated."); setHalt(true); waitWakeupNoWait(); punishBadBehavior(BadBehavior.SKIPPED_TOO_MANY_TURNS); robotProxy.forceStopThread(); } } } public void initializeRound(List<RobotPeer> robots, double[][] initialRobotPositions) { boolean valid = false; if (initialRobotPositions != null) { if (statics.getIndex() >= 0 && statics.getIndex() < initialRobotPositions.length) { double[] pos = initialRobotPositions[statics.getIndex()]; x = pos[0]; y = pos[1]; bodyHeading = pos[2]; gunHeading = radarHeading = bodyHeading; updateBoundingBox(); valid = validSpot(robots); } } if (!valid) { final Random random = RandomFactory.getRandom(); for (int j = 0; j < 1000; j++) { x = RobotPeerEngine.WIDTH + random.nextDouble() * (battleRules.getBattlefieldWidth() - 2 * RobotPeerEngine.WIDTH); y = RobotPeerEngine.HEIGHT + random.nextDouble() * (battleRules.getBattlefieldHeight() - 2 * RobotPeerEngine.HEIGHT); bodyHeading = 2 * Math.PI * random.nextDouble(); gunHeading = radarHeading = bodyHeading; updateBoundingBox(); if (validSpot(robots)) { break; } } } setState(RobotState.ACTIVE); isWinner = false; velocity = 0; if (statics.isTeamLeader() && statics.isDroid()) { energy = 220; } else if (statics.isTeamLeader()) { energy = 200; } else if (statics.isDroid()) { energy = 120; } else { energy = 100; } gunHeat = 3; setHalt(false); isExecFinishedAndDisabled = false; isEnergyDrained = false; scan = false; inCollision = false; scanArc.setAngleStart(0); scanArc.setAngleExtent(0); scanArc.setFrame(-100, -100, 1, 1); skippedTurns = 0; status = new AtomicReference<RobotStatus>(); readoutEvents(); readoutTeamMessages(); readoutProjectiles(); battleText.setLength(0); proxyText.setLength(0); // Prepare new execution commands, but copy the colors from the last commands. // Bugfix [2628217] - Robot Colors don't stick between rounds. ExecCommands newExecCommands = new ExecCommands(); newExecCommands.copyColors(commands.get()); commands = new AtomicReference<ExecCommands>(newExecCommands); } public void startRound(long waitMillis, int waitNanos) { Logger.logMessage(".", false); statistics.initialize(); ExecCommands newExecCommands = new ExecCommands(); // Copy the colors from the last commands. // Bugfix [2628217] - Robot Colors don't stick between rounds. newExecCommands.copyColors(commands.get()); currentCommands = newExecCommands; int others = battle.getActiveRobots() - (isAlive() ? 1 : 0); RobotStatus stat = HiddenAccess.createStatus(energy, x, y, bodyHeading, gunHeading, radarHeading, velocity, currentCommands.getBodyTurnRemaining(), currentCommands.getRadarTurnRemaining(), currentCommands.getGunTurnRemaining(), currentCommands.getDistanceRemaining(), gunHeat, others, battle.getRoundNum(), battle.getNumRounds(), battle.getTime()); status.set(stat); robotProxy.startRound(currentCommands, stat); synchronized (isSleeping) { try { // Wait for the robot to go to sleep (take action) isSleeping.wait(waitMillis, waitNanos); } catch (InterruptedException e) { logMessage("Wait for " + getName() + " interrupted."); // Immediately reasserts the exception by interrupting the caller thread itself Thread.currentThread().interrupt(); } } if (!isSleeping() && !battle.isDebugging()) { logMessage("\n" + getName() + " still has not started after " + waitMillis + " ms... giving up."); } } public void performLoadCommands() { currentCommands = commands.get(); fire(currentCommands.getProjectiles()); if (currentCommands.isScan()) { scan = true; } if (currentCommands.isIORobot()) { isIORobot = true; } if (currentCommands.isMoved()) { currentCommands.setMoved(false); } } public void performScan(List<RobotPeer> robots) { if (isDead()) { return; } turnedRadarWithGun = false; // scan if (scan) { scan(lastRadarHeading, robots); turnedRadarWithGun = (lastGunHeading == lastRadarHeading) && (gunHeading == radarHeading); scan = false; } // dispatch messages if (statics.isTeamRobot() && teamPeer != null) { for (TeamMessage teamMessage : currentCommands.getTeamMessages()) { for (RobotPeer member : teamPeer) { if (checkDispatchToMember(member, teamMessage.recipient)) { member.addTeamMessage(teamMessage); } } } } currentCommands = null; lastHeading = -1; lastGunHeading = -1; lastRadarHeading = -1; } private void addTeamMessage(TeamMessage message) { final List<TeamMessage> queue = teamMessages.get(); queue.add(message); } private boolean checkDispatchToMember(RobotPeerWeaponSystem member, String recipient) { if (member.isAlive()) { if (recipient == null) { if (member != this) { return true; } } else { final int nl = recipient.length(); final String currentName = member.statics.getName(); if ((currentName.length() >= nl && currentName.substring(0, nl).equals(recipient))) { return true; } final String currentClassName = member.statics.getFullClassName(); if ((currentClassName.length() >= nl && currentClassName.substring(0, nl).equals(recipient))) { return true; } } } return false; } private void scan(double lastRadarHeading, List<RobotPeer> robots) { if (statics.isDroid()) { return; } double startAngle = lastRadarHeading; double scanRadians = getRadarHeading() - startAngle; // Check if we passed through 360 if (scanRadians < -PI) { scanRadians = 2 * PI + scanRadians; } else if (scanRadians > PI) { scanRadians = scanRadians - 2 * PI; } // In our coords, we are scanning clockwise, with +y up // In java coords, we are scanning counterclockwise, with +y down // All we need to do is adjust our angle by -90 for this to work. startAngle -= PI / 2; startAngle = normalAbsoluteAngle(startAngle); scanArc.setArc(x - Rules.RADAR_SCAN_RADIUS, y - Rules.RADAR_SCAN_RADIUS, 2 * Rules.RADAR_SCAN_RADIUS, 2 * Rules.RADAR_SCAN_RADIUS, 180.0 * startAngle / PI, 180.0 * scanRadians / PI, Arc2D.PIE); for (RobotPeer otherRobot : robots) { if (!(otherRobot == null || otherRobot == this || otherRobot.isDead()) && intersects(scanArc, otherRobot.boundingBox)) { double dx = otherRobot.x - x; double dy = otherRobot.y - y; double angle = atan2(dx, dy); double dist = Math.hypot(dx, dy); final ScannedRobotEvent event = new ScannedRobotEvent(getNameForEvent(otherRobot), otherRobot.energy, normalRelativeAngle(angle - getBodyHeading()), dist, otherRobot.getBodyHeading(), otherRobot.getVelocity()); addEvent(event); } } } private boolean intersects(Arc2D arc, Rectangle2D rect) { return (rect.intersectsLine(arc.getCenterX(), arc.getCenterY(), arc.getStartPoint().getX(), arc.getStartPoint().getY())) || arc.intersects(rect); } public void setRunning(boolean value) { isRunning.set(value); } public void punishBadBehavior(BadBehavior badBehavior) { kill(); // Bug fix [2828479] - Missed onRobotDeath events statistics.setInactive(); final IRobotRepositoryItem repositoryItem = (IRobotRepositoryItem) HiddenAccess.getFileSpecification( robotSpecification); StringBuffer message = new StringBuffer(getName()).append(' '); boolean disableInRepository = false; // Per default, robots are not disabled in the repository switch (badBehavior) { case CANNOT_START: message.append("could not be started or loaded."); disableInRepository = true; // Disable in repository when it cannot be started anyways break; case UNSTOPPABLE: message.append("cannot be stopped."); break; case SKIPPED_TOO_MANY_TURNS: message.append("has skipped too many turns."); break; case SECURITY_VIOLATION: message.append("has caused a security violation."); disableInRepository = true; // No mercy here! break; } if (disableInRepository) { repositoryItem.setValid(false); message.append(" This ").append(repositoryItem.isTeam() ? "team" : "robot").append( " has been banned and will not be allowed to participate in battles."); } logMessage(message.toString()); } public void setWinner(boolean newWinner) { isWinner = newWinner; } public void waitForStop() { robotProxy.waitForStopThread(); } /** * Clean things up removing all references to the robot. */ public void cleanup() { battle = null; if (robotProxy != null) { robotProxy.cleanup(); robotProxy = null; } if (statistics != null) { statistics.cleanup(); statistics = null; } status = null; commands = null; events = null; teamMessages = null; projectileUpdates = null; battleText.setLength(0); proxyText.setLength(0); statics = null; battleRules = null; } public Object getGraphicsCalls() { return commands.get().getGraphicsCalls(); } public boolean isTryingToPaint() { return commands.get().isTryingToPaint(); } public List<DebugProperty> getDebugProperties() { return commands.get().getDebugProperties(); } public void publishStatus(long currentTurn) { final ExecCommands currentCommands = commands.get(); int others = battle.getActiveRobots() - (isAlive() ? 1 : 0); RobotStatus stat = HiddenAccess.createStatus(energy, x, y, bodyHeading, gunHeading, radarHeading, velocity, currentCommands.getBodyTurnRemaining(), currentCommands.getRadarTurnRemaining(), currentCommands.getGunTurnRemaining(), currentCommands.getDistanceRemaining(), gunHeat, others, battle.getRoundNum(), battle.getNumRounds(), battle.getTime()); status.set(stat); } public int compareTo(ContestantPeer cp) { double myScore = statistics.getTotalScore(); double hisScore = cp.getStatistics().getTotalScore(); if (statistics.isInRound()) { myScore += statistics.getCurrentScore(); hisScore += cp.getStatistics().getCurrentScore(); } if (myScore < hisScore) { return -1; } if (myScore > hisScore) { return 1; } return 0; } @Override public String toString() { return statics.getShortName() + "(" + (int) energy + ") X" + (int) x + " Y" + (int) y + " " + state.toString() + (isSleeping() ? " sleeping " : "") + (isRunning() ? " running" : "") + (getHalt() ? " halted" : ""); } public String getName() { return statics.getName(); } public String getAnonymousName() { return statics.getAnonymousName(); } public TeamPeer getTeamPeer() { return teamPeer; } public boolean isTeamMate(RobotPeerEngine otherRobot) { if (getTeamPeer() != null) { for (RobotPeerEngine mate : getTeamPeer()) { if (otherRobot == mate) { return true; } } } return false; } public String getNameForEvent(RobotPeer otherRobot) { if (battleRules.getHideEnemyNames() && !isTeamMate(otherRobot)) { return otherRobot.getAnonymousName(); } return otherRobot.getName(); } private void checkRobotCollision(List<RobotPeer> robots) { inCollision = false; for (RobotPeer otherRobot : robots) { if (!(otherRobot == null || otherRobot == this || otherRobot.isDead()) && boundingBox.intersects(otherRobot.boundingBox)) { // Bounce back double angle = atan2(otherRobot.x - x, otherRobot.y - y); double movedx = velocity * sin(bodyHeading); double movedy = velocity * cos(bodyHeading); boolean atFault; double bearing = normalRelativeAngle(angle - bodyHeading); if ((velocity > 0 && bearing > -PI / 2 && bearing < PI / 2) || (velocity < 0 && (bearing < -PI / 2 || bearing > PI / 2))) { inCollision = true; atFault = true; velocity = 0; currentCommands.setDistanceRemaining(0); x -= movedx; y -= movedy; boolean teamFire = (teamPeer != null && teamPeer == otherRobot.teamPeer); if (!teamFire) { statistics.scoreRammingDamage(otherRobot.getName()); } this.updateEnergy(-Rules.ROBOT_HIT_DAMAGE); otherRobot.updateEnergy(-Rules.ROBOT_HIT_DAMAGE); if (otherRobot.energy == 0) { if (otherRobot.isAlive()) { otherRobot.kill(); if (!teamFire) { final double bonus = statistics.scoreRammingKill(otherRobot.getName()); if (bonus > 0) { println( "SYSTEM: Ram bonus for killing " + this.getNameForEvent(otherRobot) + ": " + (int) (bonus + .5)); } } } } addEvent( new HitRobotEvent(getNameForEvent(otherRobot), normalRelativeAngle(angle - bodyHeading), otherRobot.energy, atFault)); otherRobot.addEvent( new HitRobotEvent(getNameForEvent(this), normalRelativeAngle(PI + angle - otherRobot.getBodyHeading()), energy, false)); } } } if (inCollision) { setState(RobotState.HIT_ROBOT); } } private void checkWallCollision() { boolean hitWall = false; double fixx = 0, fixy = 0; double angle = 0; if (x > getBattleFieldWidth() - HALF_WIDTH_OFFSET) { hitWall = true; fixx = getBattleFieldWidth() - HALF_WIDTH_OFFSET - x; angle = normalRelativeAngle(PI / 2 - bodyHeading); } if (x < HALF_WIDTH_OFFSET) { hitWall = true; fixx = HALF_WIDTH_OFFSET - x; angle = normalRelativeAngle(3 * PI / 2 - bodyHeading); } if (y > getBattleFieldHeight() - HALF_HEIGHT_OFFSET) { hitWall = true; fixy = getBattleFieldHeight() - HALF_HEIGHT_OFFSET - y; angle = normalRelativeAngle(-bodyHeading); } if (y < HALF_HEIGHT_OFFSET) { hitWall = true; fixy = HALF_HEIGHT_OFFSET - y; angle = normalRelativeAngle(PI - bodyHeading); } if (hitWall) { addEvent(new HitWallEvent(angle)); // only fix both x and y values if hitting wall at an angle if ((bodyHeading % (Math.PI / 2)) != 0) { double tanHeading = tan(bodyHeading); // if it hits bottom or top wall if (fixx == 0) { fixx = fixy * tanHeading; } // if it hits a side wall else if (fixy == 0) { fixy = fixx / tanHeading; } // if the robot hits 2 walls at the same time (rare, but just in case) else if (abs(fixx / tanHeading) > abs(fixy)) { fixy = fixx / tanHeading; } else if (abs(fixy * tanHeading) > abs(fixx)) { fixx = fixy * tanHeading; } } x += fixx; y += fixy; x = (HALF_WIDTH_OFFSET >= x) ? HALF_WIDTH_OFFSET : ((getBattleFieldWidth() - HALF_WIDTH_OFFSET < x) ? getBattleFieldWidth() - HALF_WIDTH_OFFSET : x); y = (HALF_HEIGHT_OFFSET >= y) ? HALF_HEIGHT_OFFSET : ((getBattleFieldHeight() - HALF_HEIGHT_OFFSET < y) ? getBattleFieldHeight() - HALF_HEIGHT_OFFSET : y); // Update energy, but do not reset inactiveTurnCount if (statics.isAdvancedRobot()) { setEnergy(energy - Rules.getWallHitDamage(velocity), false); } updateBoundingBox(); currentCommands.setDistanceRemaining(0); velocity = 0; } if (hitWall) { setState(RobotState.HIT_WALL); } } private double getBattleFieldHeight() { return battleRules.getBattlefieldHeight(); } private double getBattleFieldWidth() { return battleRules.getBattlefieldWidth(); } public final void performMove(List<RobotPeer> robots, double zapEnergy) { // Reset robot state to active if it is not dead if (isDead()) { return; } setState(RobotState.ACTIVE); updateGunHeat(); lastHeading = bodyHeading; lastGunHeading = gunHeading; lastRadarHeading = radarHeading; final double lastX = x; final double lastY = y; if (!inCollision) { updateHeading(); } updateGunHeading(); updateRadarHeading(); updateMovement(); // At this point, robot has turned then moved. // We could be touching a wall or another bot... // First and foremost, we can never go through a wall: checkWallCollision(); // Now check for robot collision checkRobotCollision(robots); // Scan false means robot did not call scan() manually. // But if we're moving, scan if (!scan) { scan = (lastHeading != bodyHeading || lastGunHeading != gunHeading || lastRadarHeading != radarHeading || lastX != x || lastY != y); } if (isDead()) { return; } // zap if (zapEnergy != 0) { zap(zapEnergy); } } public void addEvent(Event event) { if (isRunning()) { final EventQueue queue = events.get(); if ((queue.size() > EventManager.MAX_QUEUE_SIZE) && !(event instanceof DeathEvent || event instanceof WinEvent || event instanceof SkippedTurnEvent)) { println( "Not adding to " + statics.getShortName() + "'s queue, exceeded " + EventManager.MAX_QUEUE_SIZE + " events in queue."); // clean up old stuff queue.clear(battle.getTime() - EventManager.MAX_EVENT_STACK); return; } queue.add(event); } } protected void updateRadarHeading() { if (currentCommands.getRadarTurnRemaining() > 0) { if (currentCommands.getRadarTurnRemaining() < Rules.RADAR_TURN_RATE_RADIANS) { radarHeading += currentCommands.getRadarTurnRemaining(); currentCommands.setRadarTurnRemaining(0); } else { radarHeading += Rules.RADAR_TURN_RATE_RADIANS; currentCommands.setRadarTurnRemaining( currentCommands.getRadarTurnRemaining() - Rules.RADAR_TURN_RATE_RADIANS); } } else if (currentCommands.getRadarTurnRemaining() < 0) { if (currentCommands.getRadarTurnRemaining() > -Rules.RADAR_TURN_RATE_RADIANS) { radarHeading += currentCommands.getRadarTurnRemaining(); currentCommands.setRadarTurnRemaining(0); } else { radarHeading -= Rules.RADAR_TURN_RATE_RADIANS; currentCommands.setRadarTurnRemaining( currentCommands.getRadarTurnRemaining() + Rules.RADAR_TURN_RATE_RADIANS); } } radarHeading = normalAbsoluteAngle(radarHeading); } public void kill() { battle.resetInactiveTurnCount(10.0); if (isAlive()) { addEvent(new DeathEvent()); if (statics.isTeamLeader()) { for (RobotPeer teammate : teamPeer) { if (!(teammate.isDead() || teammate == this)) { teammate.updateEnergy(-30); BulletPeer sBullet = new BulletPeer(this, battleRules, -1); sBullet.setState(BulletState.HIT_VICTIM); sBullet.setX(teammate.x); sBullet.setY(teammate.y); sBullet.setVictim(teammate); sBullet.setPower(4); battle.addBullet(sBullet); } } } battle.registerDeathRobot(this); // 'fake' bullet for explosion on self final ExplosionPeer fake = new ExplosionPeer(this, battleRules); battle.addBullet(fake); } updateEnergy(-energy); setState(RobotState.DEAD); } protected void zap(double zapAmount) { if (energy == 0) { kill(); return; } energy -= abs(zapAmount); if (energy < .1) { energy = 0; currentCommands.setDistanceRemaining(0); currentCommands.setBodyTurnRemaining(0); } } protected void updateHeading() { boolean normalizeHeading = true; double turnRate = min(currentCommands.getMaxTurnRate(), (.4 + .6 * (1 - (abs(velocity) / Rules.MAX_VELOCITY))) * Rules.MAX_TURN_RATE_RADIANS); if (currentCommands.getBodyTurnRemaining() > 0) { if (currentCommands.getBodyTurnRemaining() < turnRate) { bodyHeading += currentCommands.getBodyTurnRemaining(); gunHeading += currentCommands.getBodyTurnRemaining(); radarHeading += currentCommands.getBodyTurnRemaining(); if (currentCommands.isAdjustGunForBodyTurn()) { currentCommands.setGunTurnRemaining( currentCommands.getGunTurnRemaining() - currentCommands.getBodyTurnRemaining()); } if (currentCommands.isAdjustRadarForBodyTurn()) { currentCommands.setRadarTurnRemaining( currentCommands.getRadarTurnRemaining() - currentCommands.getBodyTurnRemaining()); } currentCommands.setBodyTurnRemaining(0); } else { bodyHeading += turnRate; gunHeading += turnRate; radarHeading += turnRate; currentCommands.setBodyTurnRemaining(currentCommands.getBodyTurnRemaining() - turnRate); if (currentCommands.isAdjustGunForBodyTurn()) { currentCommands.setGunTurnRemaining(currentCommands.getGunTurnRemaining() - turnRate); } if (currentCommands.isAdjustRadarForBodyTurn()) { currentCommands.setRadarTurnRemaining(currentCommands.getRadarTurnRemaining() - turnRate); } } } else if (currentCommands.getBodyTurnRemaining() < 0) { if (currentCommands.getBodyTurnRemaining() > -turnRate) { bodyHeading += currentCommands.getBodyTurnRemaining(); gunHeading += currentCommands.getBodyTurnRemaining(); radarHeading += currentCommands.getBodyTurnRemaining(); if (currentCommands.isAdjustGunForBodyTurn()) { currentCommands.setGunTurnRemaining( currentCommands.getGunTurnRemaining() - currentCommands.getBodyTurnRemaining()); } if (currentCommands.isAdjustRadarForBodyTurn()) { currentCommands.setRadarTurnRemaining( currentCommands.getRadarTurnRemaining() - currentCommands.getBodyTurnRemaining()); } currentCommands.setBodyTurnRemaining(0); } else { bodyHeading -= turnRate; gunHeading -= turnRate; radarHeading -= turnRate; currentCommands.setBodyTurnRemaining(currentCommands.getBodyTurnRemaining() + turnRate); if (currentCommands.isAdjustGunForBodyTurn()) { currentCommands.setGunTurnRemaining(currentCommands.getGunTurnRemaining() + turnRate); } if (currentCommands.isAdjustRadarForBodyTurn()) { currentCommands.setRadarTurnRemaining(currentCommands.getRadarTurnRemaining() + turnRate); } } } else { normalizeHeading = false; } if (normalizeHeading) { if (currentCommands.getBodyTurnRemaining() == 0) { bodyHeading = normalNearAbsoluteAngle(bodyHeading); } else { bodyHeading = normalAbsoluteAngle(bodyHeading); } } if (Double.isNaN(bodyHeading)) { Logger.realErr.println("HOW IS HEADING NAN HERE"); } } public boolean isRunning() { return isRunning.get(); } }