Package net.sf.robocode.battle.peer

Source Code of net.sf.robocode.battle.peer.RobotPeer

/**
* Copyright (c) 2001-2014 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
*/
package net.sf.robocode.battle.peer;


import static net.sf.robocode.io.Logger.logMessage;
import net.sf.robocode.battle.Battle;
import net.sf.robocode.battle.BoundingRectangle;
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.*;
import net.sf.robocode.repository.IRobotItem;
import net.sf.robocode.security.HiddenAccess;
import net.sf.robocode.serialization.RbSerializer;
import robocode.*;
import robocode.control.RandomFactory;
import robocode.control.RobotSetup;
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;
import static robocode.util.Utils.*;

import java.awt.geom.Arc2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;

import static java.lang.Math.*;

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;


/**
* 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)
* @author "BD123" (contributor)
*/
public final class RobotPeer implements IRobotPeerBattle, IRobotPeer {

  public static final int
      WIDTH = 36,
      HEIGHT = 36;

  private static final int
      HALF_WIDTH_OFFSET = WIDTH / 2,
      HALF_HEIGHT_OFFSET = HEIGHT / 2;

  private static final int MAX_SKIPPED_TURNS = 30;
  private static final int MAX_SKIPPED_TURNS_WITH_IO = 240;

  private Battle battle;
  private RobotStatistics statistics;
  private final TeamPeer teamPeer;
  private final RobotSpecification robotSpecification;

  private IHostingRobotProxy robotProxy;
  private AtomicReference<RobotStatus> status = new AtomicReference<RobotStatus>();
  private AtomicReference<ExecCommands> commands = new AtomicReference<ExecCommands>();
  private AtomicReference<EventQueue> events = new AtomicReference<EventQueue>(new EventQueue());
  private AtomicReference<List<TeamMessage>> teamMessages = new AtomicReference<List<TeamMessage>>(
      new ArrayList<TeamMessage>());
  private AtomicReference<List<BulletStatus>> bulletUpdates = new AtomicReference<List<BulletStatus>>(
      new ArrayList<BulletStatus>());

  // thread is running
  private final AtomicBoolean isRunning = new AtomicBoolean(false);

  private final StringBuilder battleText = new StringBuilder(1024);
  private final StringBuilder proxyText = new StringBuilder(1024);
  private RobotStatics statics;
  private BattleRules battleRules;

  // for battle thread, during robots processing
  private ExecCommands currentCommands;
  private double lastHeading;
  private double lastGunHeading;
  private double lastRadarHeading;

  private double energy;
  private double velocity;
  private double bodyHeading;
  private double radarHeading;
  private double gunHeading;
  private double gunHeat;
  private double x;
  private double y;

  private boolean scan;
  private boolean turnedRadarWithGun; // last round

  private boolean isIORobot;
  private boolean isPaintEnabled;
  private boolean sgPaintEnabled;

  // waiting for next tick
  private final AtomicBoolean isSleeping = new AtomicBoolean(false);
  private final AtomicBoolean halt = new AtomicBoolean(false);

  // last and current execution time and detecting skipped turns
  private int lastExecutionTime = -1;
  private int currentExecutionTime;

  private boolean isExecFinishedAndDisabled;
  private boolean isEnergyDrained;
  private boolean isWinner;
  private boolean inCollision;
  private boolean isOverDriving;

  private RobotState state;
  private final Arc2D scanArc;
  private final BoundingRectangle boundingBox;
  private final RbSerializer rbSerializer;

  public RobotPeer(Battle battle, IHostManager hostManager, RobotSpecification robotSpecification, int duplicate, TeamPeer team, int robotIndex) {
    super();

    this.battle = battle;
    this.robotSpecification = robotSpecification;

    this.rbSerializer = new RbSerializer();

    this.boundingBox = new BoundingRectangle();
    this.scanArc = new Arc2D.Double();
    this.teamPeer = team;
    this.state = RobotState.ACTIVE;
    this.battleRules = battle.getBattleRules();

    if (team != null) {
      team.add(this);
    }
    String teamName;
    List<String> teamMembers;
    boolean isTeamLeader;
    int teamIndex;

    if (teamPeer == null) {
      teamName = null;
      teamMembers = null;
      isTeamLeader = false;
      teamIndex = -1; // Must be set to -1 when robot is not in a team
    } else {
      teamName = team.getName();
      teamMembers = team.getMemberNames();
      isTeamLeader = team.size() == 1; // That is current team size, more might follow later. First robot is leader
      teamIndex = team.getTeamIndex();
    }

    this.statics = new RobotStatics(robotSpecification, duplicate, isTeamLeader, battleRules, teamName, teamMembers,
        robotIndex, teamIndex);
    this.statistics = new RobotStatistics(this, battle.getRobotsCount());

    this.robotProxy = (IHostingRobotProxy) hostManager.createRobotProxy(robotSpecification, statics, this);
  }

  public void println(String s) {
    synchronized (proxyText) {
      battleText.append(s);
      battleText.append("\n");
    }
  }

  private void print(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 isJuniorRobot() {
    return statics.isJuniorRobot();
  }

  public boolean isAdvancedRobot() {
    return statics.isAdvancedRobot();
  }

  public boolean isTeamRobot() {
    return statics.isTeamRobot();
  }

  public boolean isDroid() {
    return statics.isDroid();
  }

  public boolean isSentryRobot() {
    return statics.isSentryRobot();
  }

  public boolean isInteractiveRobot() {
    return statics.isInteractiveRobot();
  }

  public boolean isPaintRobot() {
    return statics.isPaintRobot();
  }

  public String getName() {
    return statics.getName();
  }

  public String getAnnonymousName() {
    return statics.getAnnonymousName();
  }

  public String getShortName() {
    return statics.getShortName();
  }

  public String getVeryShortName() {
    return statics.getVeryShortName();
  }

  public int getRobotIndex() {
    return statics.getRobotIndex();
  }

  public int getTeamIndex() {
    return statics.getTeamIndex();
  }

  public int getContestantIndex() {
    return getTeamIndex() >= 0 ? getTeamIndex() : getRobotIndex();
  }

  // -------------------
  // 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 RobotState getState() {
    return state;
  }

  public void setState(RobotState state) {
    this.state = state;
  }

  public boolean isDead() {
    return state == RobotState.DEAD;
  }

  public boolean isAlive() {
    return state != RobotState.DEAD;
  }

  public boolean isWinner() {
    return isWinner;
  }

  public boolean isRunning() {
    return isRunning.get();
  }

  public boolean isSleeping() {
    return isSleeping.get();
  }

  public boolean isHalt() {
    return halt.get();
  }

  public void setHalt(boolean value) {
    halt.set(value);
  }

  public BoundingRectangle getBoundingBox() {
    return boundingBox;
  }

  public Arc2D getScanArc() {
    return scanArc;
  }

  // -------------------
  // robot space
  // -------------------

  public double getGunHeading() {
    return gunHeading;
  }

  public double getBodyHeading() {
    return bodyHeading;
  }

  public double getRadarHeading() {
    return radarHeading;
  }

  public double getVelocity() {
    return velocity;
  }

  public double getX() {
    return x;
  }

  public double getY() {
    return y;
  }

  public double getEnergy() {
    return energy;
  }

  public double getGunHeat() {
    return gunHeat;
  }

  public int getBodyColor() {
    return commands.get().getBodyColor();
  }

  public int getRadarColor() {
    return commands.get().getRadarColor();
  }

  public int getGunColor() {
    return commands.get().getGunColor();
  }

  public int getBulletColor() {
    return commands.get().getBulletColor();
  }

  public int getScanColor() {
    return commands.get().getScanColor();
  }

  // ------------
  // team
  // ------------

  public TeamPeer getTeamPeer() {
    return teamPeer;
  }

  public String getTeamName() {
    return statics.getTeamName();
  }

  public boolean isTeamLeader() {
    return statics.isTeamLeader();
  }

  boolean isTeamMate(RobotPeer otherRobot) {
    if (getTeamPeer() != null) {
      for (RobotPeer mate : getTeamPeer()) {
        if (otherRobot == mate) {
          return true;
        }
      } 
    }
    return false;
  }
 
  // -----------
  // execute
  // -----------

  private ByteBuffer bidirectionalBuffer;

  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));
      print(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 (isHalt()) {
      isExecFinishedAndDisabled = true;
      if (isWinner) {
        throw new WinException();
      } else {
        throw new AbortedException();
      }
    }

    waitForNextTurn();

    checkSkippedTurn();

    // 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(), readoutBullets(),
        isHalt(), shouldWait, isPaintEnabled());
  }

  public final ExecResults waitForBattleEndImpl(ExecCommands newCommands) {
    if (!isHalt()) {
      // from robot to battle
      commands.set(new ExecCommands(newCommands, true));
      print(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>(), readoutBullets(),
        isHalt(), 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> readoutBullets() {
    return bulletUpdates.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();
        }
      }
    }
  }

  private 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 checkSkippedTurn() {
    // Store last and current execution time for detecting skipped turns
    lastExecutionTime = currentExecutionTime;
    currentExecutionTime = battle.getTime();

    int numSkippedTurns = (currentExecutionTime - lastExecutionTime) - 1;
    if (numSkippedTurns >= 1) {
      events.get().clear(false);

      if (isAlive()) {
        for (int skippedTurn = lastExecutionTime + 1; skippedTurn < currentExecutionTime; skippedTurn++) {
          addEvent(new SkippedTurnEvent(skippedTurn));
          println("SYSTEM: " + getShortName() + " skipped turn " + skippedTurn);
        }
      }

      if ((!isIORobot && (numSkippedTurns > MAX_SKIPPED_TURNS))
          || (isIORobot && (numSkippedTurns > 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, RobotSetup[] initialRobotSetups) {
    boolean valid = false;

    if (initialRobotSetups != null) {
      int robotIndex = statics.getRobotIndex();

      if (robotIndex >= 0 && robotIndex < initialRobotSetups.length) {
        RobotSetup setup = initialRobotSetups[robotIndex];
        if (setup != null) {
          x = setup.getX();
          y = setup.getY();
          bodyHeading = gunHeading = radarHeading = setup.getHeading();

          updateBoundingBox();
          valid = validSpot(robots);
        }
      }
    }

    if (!valid) {
      final Random random = RandomFactory.getRandom();

      double maxWidth = battleRules.getBattlefieldWidth() - RobotPeer.WIDTH;
      double maxHeight = battleRules.getBattlefieldHeight() - RobotPeer.HEIGHT;

      double halfRobotWidth = RobotPeer.WIDTH / 2;
      double halfRobotHeight = RobotPeer.HEIGHT / 2;

      int sentryBorderSize = battle.getBattleRules().getSentryBorderSize();
      int sentryBorderMoveWidth = sentryBorderSize - RobotPeer.WIDTH;
      int sentryBorderMoveHeight = sentryBorderSize - RobotPeer.HEIGHT;

      for (int j = 0; j < 1000; j++) {
        double rndX = random.nextDouble();
        double rndY = random.nextDouble();

        if (isSentryRobot()) {
          boolean placeOnHorizontalBar = random.nextDouble()
              <= ((double) battleRules.getBattlefieldWidth()
                  / (battleRules.getBattlefieldWidth() + battleRules.getBattlefieldHeight()));

          if (placeOnHorizontalBar) {
            x = halfRobotWidth + rndX * maxWidth;
            y = halfRobotHeight + (sentryBorderMoveHeight * (rndY * 2.0 - 1.0) + maxHeight) % maxHeight;
          } else {
            y = halfRobotHeight + rndY * maxHeight;
            x = halfRobotWidth + (sentryBorderMoveWidth * (rndX * 2.0 - 1.0) + maxWidth) % maxWidth;
          }
        } else {
          if (battle.countActiveSentries() > 0) {
            int safeZoneWidth = battleRules.getBattlefieldWidth() - 2 * sentryBorderSize;
            int safeZoneHeight = battleRules.getBattlefieldHeight() - 2 * sentryBorderSize;

            x = sentryBorderSize + RobotPeer.WIDTH + rndX * (safeZoneWidth - 2 * RobotPeer.WIDTH);
            y = sentryBorderSize + RobotPeer.HEIGHT + rndY * (safeZoneHeight - 2 * RobotPeer.HEIGHT);
          } else {       
            x = RobotPeer.WIDTH + rndX * (battleRules.getBattlefieldWidth() - 2 * RobotPeer.WIDTH);
            y = RobotPeer.HEIGHT + rndY * (battleRules.getBattlefieldHeight() - 2 * RobotPeer.HEIGHT);
          }
        }

        bodyHeading = 2 * Math.PI * random.nextDouble();
        gunHeading = radarHeading = bodyHeading;
        updateBoundingBox();

        if (validSpot(robots)) {
          break;
        }
      }
    }

    setState(RobotState.ACTIVE);

    isWinner = false;
    velocity = 0;

    energy = 100;
    if (statics.isSentryRobot()) {
      energy += 400;
    }
    if (statics.isTeamLeader()) {
      energy += 100;
    }
    if (statics.isDroid()) {
      energy += 20;
    }

    gunHeat = 3;

    setHalt(false);
    isExecFinishedAndDisabled = false;
    isEnergyDrained = false;

    scan = false;

    inCollision = false;

    scanArc.setAngleStart(0);
    scanArc.setAngleExtent(0);
    scanArc.setFrame(-100, -100, 1, 1);

    lastExecutionTime = -1;

    status = new AtomicReference<RobotStatus>();

    readoutEvents();
    readoutTeamMessages();
    readoutBullets();

    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);
  }

  private boolean validSpot(List<RobotPeer> robots) {
    for (RobotPeer otherRobot : robots) {
      if (otherRobot != null && otherRobot != this) {
        if (getBoundingBox().intersects(otherRobot.getBoundingBox())) {
          return false;
        }
      }
    }
    return true;
  }

  public void startRound(long waitMillis, int waitNanos) {
    Logger.logMessage(".", false);

    statistics.reset();

    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.countActiveParticipants() - (isAlive() ? 1 : 0);
    int numSentries = battle.countActiveSentries();

    RobotStatus stat = HiddenAccess.createStatus(energy, x, y, bodyHeading, gunHeading, radarHeading, velocity,
        currentCommands.getBodyTurnRemaining(), currentCommands.getRadarTurnRemaining(),
        currentCommands.getGunTurnRemaining(), currentCommands.getDistanceRemaining(), gunHeat, others, numSentries,
        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();

    fireBullets(currentCommands.getBullets());

    if (currentCommands.isScan()) {
      scan = true;
    }

    if (currentCommands.isIORobot()) {
      isIORobot = true;
    }

    if (currentCommands.isMoved()) {
      currentCommands.setMoved(false);
    }
  }

  private void fireBullets(List<BulletCommand> bulletCommands) {
    BulletPeer newBullet = null;

    for (BulletCommand bulletCmd : bulletCommands) {
      if (Double.isNaN(bulletCmd.getPower())) {
        println("SYSTEM: You cannot call fire(NaN)");
        continue;
      }
      if (gunHeat > 0 || energy == 0) {
        return;
      }

      double firePower = min(energy,
          min(max(bulletCmd.getPower(), Rules.MIN_BULLET_POWER), Rules.MAX_BULLET_POWER));

      updateEnergy(-firePower);

      gunHeat += Rules.getGunHeat(firePower);

      newBullet = new BulletPeer(this, battleRules, bulletCmd.getBulletId());

      newBullet.setPower(firePower);
      if (!turnedRadarWithGun || !bulletCmd.isFireAssistValid() || statics.isAdvancedRobot()) {
        newBullet.setHeading(gunHeading);
      } else {
        newBullet.setHeading(bulletCmd.getFireAssistAngle());
      }
      newBullet.setX(x);
      newBullet.setY(y);
    }
    // there is only last bullet in one turn
    if (newBullet != null) {
      // newBullet.update(robots, bullets);
      battle.addBullet(newBullet);
    }
  }

  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();

    // If this robot is a border sentry robot then check if it hits its "range border"
    if (isSentryRobot()) {
      checkSentryOutsideBorder();
    }

    // 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 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(RobotPeer 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;
  }

  public String getNameForEvent(RobotPeer otherRobot) {
    if (battleRules.getHideEnemyNames() && !isTeamMate(otherRobot)) {
      return otherRobot.getAnnonymousName();
    }
    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 && !otherRobot.isSentryRobot()) {
            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 && !otherRobot.isSentryRobot()) {
                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() {
    int minX = 0 + HALF_WIDTH_OFFSET;
    int minY = 0 + HALF_HEIGHT_OFFSET;
    int maxX = (int) getBattleFieldWidth() - HALF_WIDTH_OFFSET;
    int maxY = (int) getBattleFieldHeight() - HALF_HEIGHT_OFFSET;

    boolean hitWall = false;
    double adjustX = 0, adjustY = 0;
    double angle = 0;

    if (x < minX) {
      hitWall = true;
      adjustX = minX - x;
      angle = normalRelativeAngle(3 * PI / 2 - bodyHeading);

    } else if (x > maxX) {
      hitWall = true;
      adjustX = maxX - x;
      angle = normalRelativeAngle(PI / 2 - bodyHeading);

    } else if (y < minY) {
      hitWall = true;
      adjustY = minY - y;
      angle = normalRelativeAngle(PI - bodyHeading);

    } else if (y > maxY) {
      hitWall = true;
      adjustY = maxY - y;
      angle = normalRelativeAngle(-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 (adjustX == 0) {
          adjustX = adjustY * tanHeading;
        } // if it hits a side wall
        else if (adjustY == 0) {
          adjustY = adjustX / tanHeading;
        } // if the robot hits 2 walls at the same time (rare, but just in case)
        else if (abs(adjustX / tanHeading) > abs(adjustY)) {
          adjustY = adjustX / tanHeading;
        } else if (abs(adjustY * tanHeading) > abs(adjustX)) {
          adjustX = adjustY * tanHeading;
        }
      }
      x += adjustX;
      y += adjustY;

      if (x < minX) {
        x = minX;
      } else if (x > maxX) {
        x = maxX;
      }
      if (y < minY) {
        y = minY;
      } else if (y > maxY) {
        y = maxY;
      }

      // Update energy, but do not reset inactiveTurnCount
      if (statics.isAdvancedRobot()) {
        setEnergy(energy - Rules.getWallHitDamage(velocity), false);
      }

      updateBoundingBox();

      currentCommands.setDistanceRemaining(0);
      velocity = 0;

      setState(RobotState.HIT_WALL);
    }
  }

  private void checkSentryOutsideBorder() {
    int range = battle.getBattleRules().getSentryBorderSize();

    int minX = range - HALF_WIDTH_OFFSET;
    int minY = range - HALF_HEIGHT_OFFSET;
    int maxX = (int) getBattleFieldWidth() - range + HALF_WIDTH_OFFSET;
    int maxY = (int) getBattleFieldHeight() - range + HALF_HEIGHT_OFFSET;

    boolean hitWall = false;
    double adjustX = 0, adjustY = 0;
    double angle = 0;

    boolean isOutsideBorder = x > minX && x < maxX && y > minY && y < maxY;
   
    if (isOutsideBorder) {
      if ((x - minX) <= Rules.MAX_VELOCITY) {
        hitWall = true;
        adjustX = minX - x;
        angle = normalRelativeAngle(PI / 2 - bodyHeading);

      } else if ((maxX - x) <= Rules.MAX_VELOCITY) {
        hitWall = true;
        adjustX = maxX - x;
        angle = normalRelativeAngle(3 * PI / 2 - bodyHeading);

      } else if ((y - minY) <= Rules.MAX_VELOCITY) {
        hitWall = true;
        adjustY = minY - y;
        angle = normalRelativeAngle(-bodyHeading);

      } else if ((maxY - y) <= Rules.MAX_VELOCITY) {
        hitWall = true;
        adjustY = maxY - 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 (adjustX == 0) {
          adjustX = adjustY * tanHeading;
        } // if it hits a side wall
        else if (adjustY == 0) {
          adjustY = adjustX / tanHeading;
        } // if the robot hits 2 walls at the same time (rare, but just in case)
        else if (abs(adjustX / tanHeading) > abs(adjustY)) {
          adjustY = adjustX / tanHeading;
        } else if (abs(adjustY * tanHeading) > abs(adjustX)) {
          adjustX = adjustY * tanHeading;
        }
      }

      x += adjustX;
      y += adjustY;

      if (isOutsideBorder) {
        if ((x - minX) <= Rules.MAX_VELOCITY) {
          x = minX;
        } else if ((maxX - x) <= Rules.MAX_VELOCITY) {
          x = maxX;
        }
        if ((y - minY) <= Rules.MAX_VELOCITY) {
          y = minY;
        } else if ((maxY - y) <= Rules.MAX_VELOCITY) {
          y = maxY;
        }
      }

      // Update energy, but do not reset inactiveTurnCount
      if (statics.isAdvancedRobot()) {
        setEnergy(energy - Rules.getWallHitDamage(velocity), false);
      }

      updateBoundingBox();

      currentCommands.setDistanceRemaining(0);
      velocity = 0;

      setState(RobotState.HIT_WALL);
    }
  }

  private double getBattleFieldHeight() {
    return battleRules.getBattlefieldHeight();
  }

  private double getBattleFieldWidth() {
    return battleRules.getBattlefieldWidth();
  }

  private void updateBoundingBox() {
    boundingBox.setRect(x - HALF_WIDTH_OFFSET, y - HALF_HEIGHT_OFFSET, WIDTH, HEIGHT);
  }

  // TODO: Only add events to robots that are alive? + Remove checks if the Robot is alive before adding the event?
  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);
      } else {
        queue.add(event);
      }
    }
  }

  private void updateGunHeading() {
    if (currentCommands.getGunTurnRemaining() > 0) {
      if (currentCommands.getGunTurnRemaining() < Rules.GUN_TURN_RATE_RADIANS) {
        gunHeading += currentCommands.getGunTurnRemaining();
        radarHeading += currentCommands.getGunTurnRemaining();
        if (currentCommands.isAdjustRadarForGunTurn()) {
          currentCommands.setRadarTurnRemaining(
              currentCommands.getRadarTurnRemaining() - currentCommands.getGunTurnRemaining());
        }
        currentCommands.setGunTurnRemaining(0);
      } else {
        gunHeading += Rules.GUN_TURN_RATE_RADIANS;
        radarHeading += Rules.GUN_TURN_RATE_RADIANS;
        currentCommands.setGunTurnRemaining(currentCommands.getGunTurnRemaining() - Rules.GUN_TURN_RATE_RADIANS);
        if (currentCommands.isAdjustRadarForGunTurn()) {
          currentCommands.setRadarTurnRemaining(
              currentCommands.getRadarTurnRemaining() - Rules.GUN_TURN_RATE_RADIANS);
        }
      }
    } else if (currentCommands.getGunTurnRemaining() < 0) {
      if (currentCommands.getGunTurnRemaining() > -Rules.GUN_TURN_RATE_RADIANS) {
        gunHeading += currentCommands.getGunTurnRemaining();
        radarHeading += currentCommands.getGunTurnRemaining();
        if (currentCommands.isAdjustRadarForGunTurn()) {
          currentCommands.setRadarTurnRemaining(
              currentCommands.getRadarTurnRemaining() - currentCommands.getGunTurnRemaining());
        }
        currentCommands.setGunTurnRemaining(0);
      } else {
        gunHeading -= Rules.GUN_TURN_RATE_RADIANS;
        radarHeading -= Rules.GUN_TURN_RATE_RADIANS;
        currentCommands.setGunTurnRemaining(currentCommands.getGunTurnRemaining() + Rules.GUN_TURN_RATE_RADIANS);
        if (currentCommands.isAdjustRadarForGunTurn()) {
          currentCommands.setRadarTurnRemaining(
              currentCommands.getRadarTurnRemaining() + Rules.GUN_TURN_RATE_RADIANS);
        }
      }
    }
    gunHeading = normalAbsoluteAngle(gunHeading);
  }

  private 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");
    }
  }

  private 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);
  }

  /**
   * Updates the robots movement.
   *
   * This is Nat Pavasants method described here:
   *   http://robowiki.net/wiki/User:Positive/Optimal_Velocity#Nat.27s_updateMovement
   */
  private void updateMovement() {
    double distance = currentCommands.getDistanceRemaining();

    if (Double.isNaN(distance)) {
      distance = 0;
    }

    velocity = getNewVelocity(velocity, distance);

    // If we are over-driving our distance and we are now at velocity=0
    // then we stopped.
    if (isNear(velocity, 0) && isOverDriving) {
      currentCommands.setDistanceRemaining(0);
      distance = 0;
      isOverDriving = false;
    }

    // If we are moving normally and the breaking distance is more
    // than remaining distance, enabled the overdrive flag.
    if (Math.signum(distance * velocity) != -1) {
      if (getDistanceTraveledUntilStop(velocity) > Math.abs(distance)) {
        isOverDriving = true;
      } else {
        isOverDriving = false;
      }
    }

    currentCommands.setDistanceRemaining(distance - velocity);

    if (velocity != 0) {
      x += velocity * sin(bodyHeading);
      y += velocity * cos(bodyHeading);
      updateBoundingBox();
    }
  }

  private double getDistanceTraveledUntilStop(double velocity) {
    double distance = 0;

    velocity = Math.abs(velocity);
    while (velocity > 0) {
      distance += (velocity = getNewVelocity(velocity, 0));
    }
    return distance;
  }

  /**
   * Returns the new velocity based on the current velocity and distance to move.
   *
   * @param velocity the current velocity
   * @param distance the distance to move
   * @return the new velocity based on the current velocity and distance to move
   *
   * This is Patrick Cupka (aka Voidious), Julian Kent (aka Skilgannon), and Positive's method described here:
   *   http://robowiki.net/wiki/User:Voidious/Optimal_Velocity#Hijack_2
   */
  private double getNewVelocity(double velocity, double distance) {
    if (distance < 0) {
      // If the distance is negative, then change it to be positive
      // and change the sign of the input velocity and the result
      return -getNewVelocity(-velocity, -distance);
    }

    final double goalVel;

    if (distance == Double.POSITIVE_INFINITY) {
      goalVel = currentCommands.getMaxVelocity();
    } else {
      goalVel = Math.min(getMaxVelocity(distance), currentCommands.getMaxVelocity());
    }

    if (velocity >= 0) {
      return Math.max(velocity - Rules.DECELERATION, Math.min(goalVel, velocity + Rules.ACCELERATION));
    }
    // else
    return Math.max(velocity - Rules.ACCELERATION, Math.min(goalVel, velocity + maxDecel(-velocity)));
  }

  private final static double getMaxVelocity(double distance) {
    final double decelTime = Math.max(1, Math.ceil(// sum of 0... decelTime, solving for decelTime using quadratic formula
        (Math.sqrt((4 * 2 / Rules.DECELERATION) * distance + 1) - 1) / 2));

    if (decelTime == Double.POSITIVE_INFINITY) {
      return Rules.MAX_VELOCITY;
    }

    final double decelDist = (decelTime / 2.0) * (decelTime - 1) // sum of 0..(decelTime-1)
        * Rules.DECELERATION;

    return ((decelTime - 1) * Rules.DECELERATION) + ((distance - decelDist) / decelTime);
  }

  private static double maxDecel(double speed) {
    double decelTime = speed / Rules.DECELERATION;
    double accelTime = (1 - decelTime);

    return Math.min(1, decelTime) * Rules.DECELERATION + Math.max(0, accelTime) * Rules.ACCELERATION;
  }

  private void updateGunHeat() {
    gunHeat -= battleRules.getGunCoolingRate();
    if (gunHeat < 0) {
      gunHeat = 0;
    }
  }

  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(), otherRobot.isSentryRobot());

        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);
  }

  private void zap(double zapAmount) {
    if (energy == 0) {
      kill();
      return;
    }
    energy -= abs(zapAmount);
    if (energy < .1) {
      energy = 0;
      currentCommands.setDistanceRemaining(0);
      currentCommands.setBodyTurnRemaining(0);
    }
  }

  public void setRunning(boolean value) {
    isRunning.set(value);
  }

  public void drainEnergy() {
    setEnergy(0, true);
    isEnergyDrained = true;
  }

  public void punishBadBehavior(BadBehavior badBehavior) {
    kill(); // Bug fix [2828479] - Missed onRobotDeath events

    statistics.setInactive();

    final IRobotItem repositoryItem = (IRobotItem) 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());
  }

  void updateEnergy(double delta) {
    if ((!isExecFinishedAndDisabled && !isEnergyDrained) || delta < 0) {
      setEnergy(energy + delta, true);
    }
  }

  private void setEnergy(double newEnergy, boolean resetInactiveTurnCount) {
    if (resetInactiveTurnCount && (energy != newEnergy)) {
      battle.resetInactiveTurnCount(energy - newEnergy);
    }
    energy = newEnergy;
    if (energy < .01) {
      energy = 0;
      ExecCommands localCommands = commands.get();

      localCommands.setDistanceRemaining(0);
      localCommands.setBodyTurnRemaining(0);
    }
  }

  public void setWinner(boolean newWinner) {
    isWinner = newWinner;
  }

  public void kill() {
    battle.resetInactiveTurnCount(10.0);
    if (isAlive()) {
      addEvent(new DeathEvent());
      if (statics.isTeamLeader()) {
        for (RobotPeer teammate : teamPeer) {
          if (teammate.isAlive() && teammate != this) {
            teammate.updateEnergy(-30);

            BulletPeer sBullet = new BulletPeer(this, battleRules, 0);
            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);
  }

  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;
    bulletUpdates = 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.countActiveParticipants() - (isDead() || isSentryRobot() ? 0 : 1);
    int numSentries = battle.countActiveSentries();

    RobotStatus stat = HiddenAccess.createStatus(energy, x, y, bodyHeading, gunHeading, radarHeading, velocity,
        currentCommands.getBodyTurnRemaining(), currentCommands.getRadarTurnRemaining(),
        currentCommands.getGunTurnRemaining(), currentCommands.getDistanceRemaining(), gunHeat, others, numSentries,
        battle.getRoundNum(), battle.getNumRounds(), battle.getTime());

    status.set(stat);
  }

  void addBulletStatus(BulletStatus bulletStatus) {
    if (isAlive()) {
      bulletUpdates.get().add(bulletStatus);
    }
  }

  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" : "") + (isHalt() ? " halted" : "");
  }
}
TOP

Related Classes of net.sf.robocode.battle.peer.RobotPeer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.