示例#1
0
 static void init(RobotController rc) {
   int roundLimit = rc.getRoundLimit();
   Common.rc = rc;
   rand = new Random(rc.getID());
   id = rc.getID();
   myTeam = rc.getTeam();
   enemyTeam = myTeam.opponent();
   history = new MapLocation[roundLimit];
   robotType = rc.getType();
   enrollment = rc.getRoundNum();
   if (robotType != RobotType.ARCHON) birthday = enrollment - robotType.buildTurns - BUILD_LAG;
   hometown = rc.getLocation();
   sightRadius = robotType.sensorRadiusSquared;
   straightSight = (int) Math.sqrt(sightRadius);
   canMessageSignal = robotType.canMessageSignal();
   Signals.buildTarget = new MapLocation[roundLimit];
   Signals.buildStrategy = new SignalStrategy[roundLimit];
   try {
     addInfo(rc.senseRobot(id));
     myArchonHometowns = rc.getInitialArchonLocations(myTeam);
     enemyArchonHometowns = rc.getInitialArchonLocations(enemyTeam);
     int coordinates[] = new int[MAP_MAX];
     int x = 0;
     int y = 0;
     for (int i = enemyArchonHometowns.length - 1; i >= 0; --i) {
       MapLocation loc = enemyArchonHometowns[i];
       twiceCenterX += loc.x;
       twiceCenterY += loc.y;
       coordinates[loc.y] *= MAP_MAX;
       coordinates[loc.y] += loc.x + 1;
     }
     for (int i = 0; i < myArchonHometowns.length; ++i) {
       MapLocation loc = myArchonHometowns[i];
       twiceCenterX += loc.x;
       twiceCenterY += loc.y;
       x += loc.x;
       y += loc.y;
     }
     twiceCenterX /= myArchonHometowns.length;
     twiceCenterY /= myArchonHometowns.length;
     x /= myArchonHometowns.length;
     y /= myArchonHometowns.length;
     for (int i = 0; i < myArchonHometowns.length; ++i) {
       MapLocation loc = myArchonHometowns[i];
       int xCoord = coordinates[loc.y] - 1;
       coordinates[loc.y] /= MAP_MAX;
       if (loc.x != twiceCenterX - xCoord) rotation = true;
     }
     Archon.center = new MapLocation(x, y);
     myBase = new MapLocation(twiceCenterX / 2, twiceCenterY / 2).directionTo(Archon.center);
     enemyBase = myBase.opposite();
   } catch (Exception e) {
     System.out.println(e.getMessage());
     e.printStackTrace();
   }
 }
示例#2
0
  // TODO: this takes a little too long on big maps
  private void computePastrScores() {
    int mapWidth = rc.getMapWidth();
    int mapHeight = rc.getMapHeight();
    double mapSize = Math.hypot(mapWidth, mapHeight);
    MapLocation mapCenter = new MapLocation(mapWidth / 2, mapHeight / 2);

    double[][] pastrScores = new double[mapWidth][mapHeight];
    for (int y = 2; y < mapHeight - 2; y += 5) {
      for (int x = 2; x < mapWidth - 2; x += 5) {
        if (rc.senseTerrainTile(new MapLocation(x, y)) != TerrainTile.VOID) {
          MapLocation loc = new MapLocation(x, y);
          double distOurHQ = Math.sqrt(loc.distanceSquaredTo(ourHQ));
          double distTheirHQ = Math.sqrt(loc.distanceSquaredTo(theirHQ));
          if (distOurHQ < distTheirHQ) {
            int numCows = 0;
            for (int cowX = x - 2; cowX <= x + 2; cowX++) {
              for (int cowY = y - 2; cowY <= y + 2; cowY++) {
                if (rc.senseTerrainTile(new MapLocation(cowX, cowY)) != TerrainTile.VOID) {
                  numCows += cowGrowth[cowX][cowY];
                }
              }
            }
            if (numCows >= 5) {
              double distCenter = Math.sqrt(loc.distanceSquaredTo(mapCenter));
              pastrScores[x][y] =
                  numCows
                      * (1 + (1.0 * distCenter - 0.5 * distOurHQ + 0.5 * distTheirHQ) / mapSize);
            } else {
              pastrScores[x][y] = -999999; // must be at least some cows
            }
          } else {
            pastrScores[x][y] = -999999; // only make pastrs on squares closer to our HQ than theirs
          }
        } else {
          pastrScores[x][y] = -999999; // don't make pastrs on void squares
        }
      }
    }

    computedPastrScores = pastrScores;
  }
示例#3
0
 /**
  * @param rc
  * @param dirToCorner
  * @return the location of the corner in the given direction or LOCATION_NONE if it is not a
  *     corner
  * @throws GameActionException
  */
 private static MapLocation checkForCorner(RobotController rc, Direction dirToCorner)
     throws GameActionException {
   int senseRadiusMinusOneSquared =
       (int) Math.pow(Math.sqrt(rc.getType().sensorRadiusSquared) - 1, 2);
   // so that when you add one below to check for a corner, you can still sense the +1 location
   MapLocation[] nearby =
       MapLocation.getAllMapLocationsWithinRadiusSq(rc.getLocation(), senseRadiusMinusOneSquared);
   boolean isCorner = true;
   MapLocation corner = getFurthestInDirection(rc, nearby, dirToCorner);
   Direction[] nearDirections = {dirToCorner, dirToCorner.rotateLeft(), dirToCorner.rotateRight()};
   for (Direction dir : nearDirections) {
     if (rc.onTheMap(corner.add(dir))) {
       isCorner = false;
     }
   }
   return isCorner ? corner : LOCATION_NONE;
 }
  public void probeAndUpdateMapInfoModule(
      final MapInfoModule mapInfoModule,
      final MapLocation location,
      final RobotController robotController)
      throws GameActionException {

    final int probeDistance =
        (int) Math.floor(Math.sqrt(robotController.getType().sensorRadiusSquared));
    if (mapInfoModule.eastBoundaryValue == MapInfoModule.UnknownValue) {

      final MapLocation foundProbeLocation =
          this.probeDirection(Direction.EAST, probeDistance, location, robotController);
      if (foundProbeLocation != null) {

        mapInfoModule.eastBoundaryValue = foundProbeLocation.x;
      }
    }
    if (mapInfoModule.westBoundaryValue == MapInfoModule.UnknownValue) {

      final MapLocation foundProbeLocation =
          this.probeDirection(Direction.WEST, probeDistance, location, robotController);
      if (foundProbeLocation != null) {

        mapInfoModule.westBoundaryValue = foundProbeLocation.x;
      }
    }
    if (mapInfoModule.northBoundaryValue == MapInfoModule.UnknownValue) {

      final MapLocation foundProbeLocation =
          this.probeDirection(Direction.NORTH, probeDistance, location, robotController);
      if (foundProbeLocation != null) {

        mapInfoModule.northBoundaryValue = foundProbeLocation.y;
      }
    }
    if (mapInfoModule.southBoundaryValue == MapInfoModule.UnknownValue) {

      final MapLocation foundProbeLocation =
          this.probeDirection(Direction.SOUTH, probeDistance, location, robotController);
      if (foundProbeLocation != null) {

        mapInfoModule.southBoundaryValue = foundProbeLocation.y;
      }
    }
  }
示例#5
0
 private int guessTravelRounds(MapLocation start, MapLocation dest) {
   int ret =
       (int) (GameConstants.SOLDIER_MOVE_ACTION_DELAY * Math.sqrt(start.distanceSquaredTo(dest)));
   MapLocation probe = start;
   boolean inObstacle = false;
   int numObstacles = 0;
   do {
     probe = probe.add(probe.directionTo(theirHQ));
     if (rc.senseTerrainTile(probe) == TerrainTile.VOID) {
       if (!inObstacle) numObstacles++; // too big?
       inObstacle = true;
     } else {
       inObstacle = false;
     }
   } while (!probe.equals(theirHQ));
   ret += 25 * numObstacles;
   return ret;
 }
示例#6
0
 private static boolean archonIsTooClose(RobotController rc) {
   boolean tooClose = false;
   int archonReserveDistance =
       (int) (Math.sqrt(rc.getRobotCount()) * 1.4); // @Hope make this more finessed
   List<RobotInfo> robots = Arrays.asList(rc.senseNearbyRobots(archonReserveDistance + 1, myTeam));
   List<RobotInfo> archons = new ArrayList<>();
   for (RobotInfo robot : robots) {
     if (robot.type == RobotType.ARCHON) {
       archons.add(robot);
     }
   }
   MapLocation myLocation = rc.getLocation();
   for (RobotInfo archon : archons) {
     if (!tooClose && myLocation.distanceSquaredTo(archon.location) < archonReserveDistance) {
       tooClose = true;
       break;
     }
   }
   return tooClose;
 }
示例#7
0
  /**
   * Code to run every turn.
   *
   * @param rc
   */
  static void runBefore(RobotController rc) throws GameActionException {
    // -2 for build signals
    Signals.maxMessages = GameConstants.MESSAGE_SIGNALS_PER_TURN - 2;
    read = Signals.readSignals(rc);
    sent = 0;
    int turn = rc.getRoundNum();

    switch (turn - enrollment) {
      case 0:
        if (targetType != null && Signals.targetsSize > 0) {
          models.addFirst(new Target(targetType, Signals.targets[0]));
        }
        break;
      case 1:
        for (int i = 0; i < sqrt.length; ++i) sqrt[i] = Math.sqrt(i);
        break;
      case 2:
        // Sense rubble a little after construction
        // TODO: change to 3(?) to avoid overlapping with action
        senseRubble(rc);
        break;
      default:
        break;
    }

    if (canMessageSignal) {
      sendRadius = 2 * sightRadius;
      sendBoundariesLow = false;
      sendBoundariesHigh = false;
      senseParts(rc);
    }

    updateMap(rc);
    if (turn > 5) {
      robotInfos = rc.senseNearbyRobots();
      for (RobotInfo info : robotInfos) {
        addInfo(info);
      }
    }
  }
示例#8
0
  // find a safe location
  public static MapLocation findSaferLocation() // move to far spot in direction with fewest enemies
      {
    MapLocation currentLocation = rc.getLocation();
    ArrayList<Direction> directions = Utility.arrayListOfDirections();

    /*
    ArrayList<Integer> enemiesInEachDirection = new ArrayList<Integer>(10);
    //initialize the enemiesInEachDirection arraylist
    for(int i = 0; i < 10; i++)
    {
    	enemiesInEachDirection.add(0);
    }

    for(RobotInfo foe : foes)
    {
    	Direction dirToFoe = currentLocation.directionTo(foe.location);
    	int index = directions.indexOf(dirToFoe);
    	int numberOfFoesInDirection = enemiesInEachDirection.get(index);
    	enemiesInEachDirection.set(index, numberOfFoesInDirection++);
    }

    int leastEnemies = 1000000;
    int directionWithLeastEnemies = 0;
    for(int i = 0; i<enemiesInEachDirection.size(); i++)
    {
    	int numberOfEnemies = enemiesInEachDirection.get(i);
    	if(numberOfEnemies < leastEnemies)
    	{
    		directionWithLeastEnemies = i;
    		leastEnemies = numberOfEnemies;
    	}
    }

    Direction direction = directions.get(directionWithLeastEnemies);//the direction with the fewest enemies
     */

    // find if foes are within attack range
    RobotInfo[] foes = rc.senseHostileRobots(rc.getLocation(), RobotType.SCOUT.sensorRadiusSquared);
    ArrayList<RobotInfo> nearAttackRange = new ArrayList<RobotInfo>();

    for (RobotInfo foe : foes) {
      RobotType type = foe.type;
      if (type != RobotType.ARCHON
          && type != RobotType.ZOMBIEDEN
          && type != RobotType.SCOUT) // only want enemies who can attack
      {
        // if you're close to the attack range
        if (currentLocation.distanceSquaredTo(foe.location) < foe.type.attackRadiusSquared + 4) {
          nearAttackRange.add(foe);
        }
      }
    }

    // get average loc of hostiles
    // could also just run away from the closest hostile
    // neither one of those would have you go up or down if you have enemies directly to
    // your left and right
    int n_hostiles = 0;
    int x_sum = 0;
    int y_sum = 0;
    if (nearAttackRange.size() > 0) {
      for (RobotInfo robot : nearAttackRange) {
        n_hostiles++;
        MapLocation robotLoc = robot.location;
        x_sum += robotLoc.x;
        y_sum += robotLoc.y;
      }
      int x = x_sum / n_hostiles;
      int y = y_sum / n_hostiles;
      MapLocation hostileLoc = new MapLocation(x, y);
      Direction dirToMove = hostileLoc.directionTo(rc.getLocation());
      MapLocation locationToGoTo =
          currentLocation.add(dirToMove, (int) Math.sqrt(RobotType.ARCHON.sensorRadiusSquared));
      return locationToGoTo;
    }

    /* OTHER WAY TO DO IT
    //get the average direction to them
    //ArrayList<Direction> listOfDirections = Utility.arrayListOfDirections();
    int averageDirection = 0;
    for(RobotInfo foe : nearAttackRange)
    {
    	averageDirection += directions.indexOf(currentLocation.directionTo(foe.location));
    }
    if(nearAttackRange.size() > 0)
    {
    	averageDirection /= nearAttackRange.size();
    	Direction directionToEnemies = directions.get(averageDirection);

    	//move in that direction as far as you can see
    	MapLocation locationToGoTo = currentLocation.add(directionToEnemies.opposite(), (int)Math.sqrt(RobotType.ARCHON.sensorRadiusSquared));
    	return locationToGoTo;
    }
    */

    else {
      return rc.getLocation();
    }
  }
示例#9
0
  private Strategy pickStrategyByAnalyzingMap() throws GameActionException {
    // Guess how long it would take the enemy to rush a well-placed pastr
    MapLocation mapCenter = new MapLocation(rc.getMapWidth() / 2, rc.getMapHeight() / 2);
    int openPastrRushRounds =
        guessTravelRounds(theirHQ, mapCenter)
            + guessTravelRounds(mapCenter, computedBestPastrLocation);

    // my pathfinding rocks:
    int pastrTravelDelay =
        (int)
            (GameConstants.SOLDIER_MOVE_ACTION_DELAY
                * Math.sqrt(ourHQ.distanceSquaredTo(computedBestPastrLocation)));
    int pastrBuildDelay =
        (int)
            (GameConstants.HQ_SPAWN_DELAY_CONSTANT_1
                + RobotType.PASTR.captureTurns); // how long it will take for our pastr to go up
    openPastrRushRounds += pastrBuildDelay; // they can't rush until they know where to rush to

    // Guess how many rounds it would take us to win with a noise tower in the open
    double openPastrCowGrowth = estimateNearbyCowGrowth(computedBestPastrLocation);
    int openPastrRoundsNeeded = estimateTimeToWin(openPastrCowGrowth);
    int towerInefficiency = 150; // we don't start milking immediately, unfortunately
    openPastrRoundsNeeded += towerInefficiency;
    int fastTowerBuildDelay =
        RobotType.NOISETOWER.captureTurns
            + pastrTravelDelay; // account for the time needed to go there and set up a tower
    int safeTowerBuildDelay =
        RobotType.NOISETOWER.captureTurns
            + Math.max(
                81, pastrTravelDelay); // account for the time needed to go there and set up a
    // tower and wait for them to make a pastr
    int fastOpenPastrRoundsToWin = openPastrRoundsNeeded + fastTowerBuildDelay;
    int safeOpenPastrRoundsToWin = openPastrRoundsNeeded + safeTowerBuildDelay;

    // Guess how many rounds it would take us to win with a noise tower in the HQ
    double hqPastrCowGrowth = estimateNearbyCowGrowth(ourHQ);
    int hqPastrRoundsToWin = estimateTimeToWin(hqPastrCowGrowth);
    double hqSlowdown = estimateHQHerdObstacleSlowdownFactor();
    hqPastrRoundsToWin *= hqSlowdown;
    hqPastrRoundsToWin += RobotType.NOISETOWER.captureTurns;
    hqPastrRoundsToWin += towerInefficiency;

    //		Debug.indicate("map", 0, String.format("fastOpenPastrRoundsToWin = %d,
    // safeOpenPastrRoundsToWin = %d (cows = %f)", fastOpenPastrRoundsToWin,
    //				safeOpenPastrRoundsToWin, openPastrCowGrowth));
    //		Debug.indicate("map", 1, String.format("hqPastrRoundsToWin = %d (cows = %f, slowdown = %f),
    // rushRounds = %d", hqPastrRoundsToWin, hqPastrCowGrowth,
    //				hqSlowdown, openPastrRushRounds));

    Strategy strat;

    if (fastOpenPastrRoundsToWin < openPastrRushRounds) {
      // I can't imagine this actually happening, but if we can win before the rush even gets to us
      // then go for it!
      strat = Strategy.NOISE_THEN_ONE_PASTR;
    } else if (safeOpenPastrRoundsToWin < hqPastrRoundsToWin) {
      // otherwise we probably have to decide between a safe open pastr and an HQ pastr.
      // Go open pastr only if is significantly faster
      strat = Strategy.ONE_PASTR_THEN_NOISE;
    } else {
      strat = Strategy.HQ_PASTR;
    }

    return strat;
  }
示例#10
0
  public static void computeAndPublish(MapLocation here, MapLocation pastr, RobotController rc)
      throws GameActionException {
    // Useful data
    int attackRange =
        (int)
            (Math.sqrt(RobotType.NOISETOWER.attackRadiusMaxSquared)
                - 3); // -3 becauase we actually have to shoot past the square
    int attackRangeSq = attackRange * attackRange;
    int mapWidth = rc.getMapWidth();
    int mapHeight = rc.getMapHeight();
    Direction[] dirs =
        new Direction[] {
          Direction.NORTH_WEST,
          Direction.SOUTH_WEST,
          Direction.SOUTH_EAST,
          Direction.NORTH_EAST,
          Direction.NORTH,
          Direction.WEST,
          Direction.SOUTH,
          Direction.EAST
        };
    int[] dirsX = new int[] {1, 1, -1, -1, 0, 1, 0, -1};
    int[] dirsY = new int[] {1, -1, -1, 1, 1, 0, -1, 0};

    // Set up the queue
    MapLocation[] locQueue =
        new MapLocation
            [(2 * RobotType.NOISETOWER.attackRadiusMaxSquared + 1)
                * (2 * RobotType.NOISETOWER.attackRadiusMaxSquared + 1)];
    int locQueueHead = 0;
    int locQueueTail = 0;
    boolean[][] wasQueued = new boolean[GameConstants.MAP_MAX_WIDTH][GameConstants.MAP_MAX_HEIGHT];

    // Push pastr onto queue
    locQueue[locQueueTail] = pastr;
    locQueueTail++;
    wasQueued[pastr.x][pastr.y] = true;

    while (locQueueHead != locQueueTail) {
      // pop a location from the queue
      MapLocation loc = locQueue[locQueueHead];
      locQueueHead++;

      int locX = loc.x;
      int locY = loc.y;
      for (int i = 8; i-- > 0; ) {
        int x = locX + dirsX[i];
        int y = locY + dirsY[i];
        if (x > 0 && y > 0 && x < mapWidth && y < mapHeight && !wasQueued[x][y]) {
          MapLocation newLoc = new MapLocation(x, y);
          if (here.distanceSquaredTo(newLoc) <= attackRangeSq) {
            if (rc.senseTerrainTile(newLoc) != TerrainTile.VOID) {
              writeHerdDir(newLoc, dirs[i], rc);

              // push newLoc onto queue
              locQueue[locQueueTail] = newLoc;
              locQueueTail++;
              wasQueued[x][y] = true;
            }
          }
        }
      }
    }
  }
示例#11
0
public class BotNoiseTower extends Bot {
  public static void loop(RobotController theRC) throws Exception {
    init(theRC);
    while (true) {
      try {
        turn();
      } catch (Exception e) {
        e.printStackTrace();
      }
      rc.yield();
    }
  }

  protected static void init(RobotController theRC) throws GameActionException {
    Bot.init(theRC);
    // Debug.init(theRC, "herd");

    here = rc.getLocation();

    // claim the assignment to build this tower so others know not to build it
    int numPastrLocations = MessageBoard.NUM_PASTR_LOCATIONS.readInt();
    amSuppressor = true;
    for (int i = 0; i < numPastrLocations; i++) {
      MapLocation pastrLoc = MessageBoard.BEST_PASTR_LOCATIONS.readFromMapLocationList(i);
      if (rc.getLocation().isAdjacentTo(pastrLoc)) {
        amSuppressor = false;
        MessageBoard.TOWER_BUILDER_ROBOT_IDS.claimAssignment(i);
        break;
      }
    }

    if (amSuppressor) {
      int bestDistSq = 999999;
      int numSuppressors = MessageBoard.NUM_SUPPRESSORS.readInt();
      int suppressorIndex = -1;
      for (int i = 0; i < numSuppressors; i++) {
        MapLocation target = MessageBoard.SUPPRESSOR_TARGET_LOCATIONS.readFromMapLocationList(i);
        int distSq = here.distanceSquaredTo(target);
        if (distSq < bestDistSq) {
          bestDistSq = distSq;
          suppressionTarget = target;
          suppressorIndex = i;
        }
      }
      if (suppressorIndex != -1) {
        MessageBoard.SUPPRESSOR_BUILDER_ROBOT_IDS.claimAssignment(suppressorIndex);
      }
    } else {
      // Figure out the best direction to start herding in
      double[] freeCows = new double[8];
      double[][] cowGrowth = rc.senseCowGrowth();
      Direction[] dirs = Direction.values();
      for (int i = 0; i < 8; i++) {
        Direction dir = dirs[i];
        MapLocation probe = here.add(dir);
        while (Util.passable(rc.senseTerrainTile(probe))
            && here.distanceSquaredTo(probe) <= RobotType.NOISETOWER.attackRadiusMaxSquared) {
          freeCows[i] += cowGrowth[probe.x][probe.y];
          probe = probe.add(dir);
        }
      }

      double bestScore = -1;
      int bestDir = -1;
      for (int i = 0; i < 8; i++) {
        double score = freeCows[i] + freeCows[(i + 1) % 8] + freeCows[(i + 2) % 8];
        if (score > bestScore) {
          bestScore = score;
          bestDir = i;
        }
      }
      attackDir = dirs[bestDir];
    }
  }

  static MapLocation here;
  static MapLocation pastr = null;
  static boolean amSuppressor;
  static MapLocation suppressionTarget;

  private static void turn() throws GameActionException {
    if (!rc.isActive()) return;

    if (amSuppressor) {
      MapLocation[] theirPastrs = rc.sensePastrLocations(them);
      MapLocation closestEnemyPastr = Util.closest(theirPastrs, here);
      if (closestEnemyPastr != null) {
        if (rc.canAttackSquare(closestEnemyPastr)) {
          rc.attackSquare(closestEnemyPastr);
          return;
        } else {
          MapLocation closer = closestEnemyPastr.add(closestEnemyPastr.directionTo(here));
          if (rc.canAttackSquare(closer)) {
            rc.attackSquare(closer);
            return;
          }
        }
      } else {
        if (suppressionTarget != null && rc.canAttackSquare(suppressionTarget)) {
          rc.attackSquare(suppressionTarget);
          return;
        }
      }
    }

    pastr = findNearestAlliedPastr();
    if (pastr == null
        || here.distanceSquaredTo(pastr) > 2 * RobotType.NOISETOWER.attackRadiusMaxSquared) {
      pastr = here;
    }

    herdTowardPastrDumb();
    // herdTowardPastrSmart();
  }

  private static MapLocation findNearestAlliedPastr() {
    MapLocation[] pastrs = rc.sensePastrLocations(us);
    return Util.closest(pastrs, here);
  }

  static final int maxOrthogonalRadius =
      (int) Math.sqrt(RobotType.NOISETOWER.attackRadiusMaxSquared);
  static final int maxDiagonalRadius =
      (int) Math.sqrt(RobotType.NOISETOWER.attackRadiusMaxSquared / 2);
  static Direction attackDir = Direction.NORTH;
  static int radius = attackDir.isDiagonal() ? maxDiagonalRadius : maxOrthogonalRadius;
  // static final int[] nextDumbHerdDir = new int[] { 2, 3, 4, 5, 6, 7, 1, 0 };
  static final int[] nextDumbHerdDir = new int[] {1, 2, 3, 4, 5, 6, 7, 0};

  private static void herdTowardPastrDumb() throws GameActionException {
    MapLocation target = null;

    do {
      int dx = radius * attackDir.dx;
      int dy = radius * attackDir.dy;
      target = new MapLocation(pastr.x + dx, pastr.y + dy);

      radius--;
      if (radius <= (attackDir.isDiagonal() ? 3 : 5)) {
        attackDir = Direction.values()[nextDumbHerdDir[attackDir.ordinal()]];
        radius = attackDir.isDiagonal() ? maxDiagonalRadius : maxOrthogonalRadius;
      }
    } while (tooFarOffMap(target) || !rc.canAttackSquare(target));

    rc.attackSquare(target);
  }

  private static boolean tooFarOffMap(MapLocation loc) {
    int W = 2;
    return loc.x < -W
        || loc.y < -W
        || loc.x >= rc.getMapWidth() + W
        || loc.y >= rc.getMapWidth() + W;
  }

  static MapLocation smartLoc = null;
  static Direction nextStartDir = Direction.NORTH;

  static int[] edgesX =
      new int[] {-2, -1, 0, 1, 2, -3, -2, 2, 3, -3, 3, -3, 3, -3, 3, -3, -2, 2, 3, -2, -1, 0, 1, 2};
  static int[] edgesY =
      new int[] {3, 3, 3, 3, 3, 2, 2, 2, 2, 1, 1, 0, 0, -1, -1, -2, -2, -2, -2, -3, -3, -3, -3, -3};
  static int edgeTrimIndex = 0;

  private static void herdTowardPastrSmart() throws GameActionException {
    if (smartLoc == null || smartLoc.distanceSquaredTo(pastr) <= GameConstants.PASTR_RANGE) {
      smartLoc =
          pastr.add(
              nextStartDir,
              nextStartDir.isDiagonal() ? maxDiagonalRadius - 3 : maxOrthogonalRadius - 3);
      nextStartDir = nextStartDir.rotateRight();
      edgeTrimIndex = edgesX.length - 1;
      // Debug.indicate("herd", 0, "starting new spoke");
    }

    while (edgeTrimIndex >= 0) {
      MapLocation edge =
          new MapLocation(pastr.x + edgesX[edgeTrimIndex], pastr.y + edgesY[edgeTrimIndex]);
      edgeTrimIndex--;
      if (isOnMap(edge) && rc.canSenseSquare(edge) && rc.senseCowsAtLocation(edge) > 1000) {
        MapLocation targetSquare = edge.add(pastr.directionTo(edge));
        if (rc.canAttackSquare(targetSquare)) {
          rc.attackSquareLight(targetSquare);
          return;
        }
      }
    }

    while (!rc.canAttackSquare(smartLoc) || !isOnMap(smartLoc)) {
      Direction moveDir = smartLoc.directionTo(pastr);
      Direction computedMoveDir = HerdPattern.readHerdDir(smartLoc, rc);
      if (computedMoveDir != null) moveDir = computedMoveDir;
      smartLoc = smartLoc.add(moveDir);
      if (here.distanceSquaredTo(smartLoc) > RobotType.NOISETOWER.attackRadiusMaxSquared) {
        smartLoc = null;
        return;
      }
      if (smartLoc.equals(pastr))
        return; // otherwise in some situations we could get an infinite loop
    }

    Direction herdDir = smartLoc.directionTo(pastr);
    Direction computedHerdDir = HerdPattern.readHerdDir(smartLoc, rc);
    if (computedHerdDir != null) herdDir = computedHerdDir;

    MapLocation targetSquare = smartLoc.add(Util.opposite(herdDir), 3);
    // Debug.indicate("herd", 2, "want to attack " + targetSquare.toString());
    if (rc.canAttackSquare(targetSquare)) rc.attackSquare(targetSquare);
    smartLoc = smartLoc.add(herdDir);
  }
}