Source: blended_roi_intercept_v01_20260430_044344

RawBack
# Blended ROI/intercept team for CS470 Assignment 3.

from captureAgents import CaptureAgent
import heapq
import random
from game import Directions
from util import nearestPoint


def createTeam(firstIndex, secondIndex, isRed,
               first='BlendedCourierAgent',
               second='PortalInterceptAgent'):
  return [globals()[first](firstIndex), globals()[second](secondIndex)]


class LayoutAgent(CaptureAgent):
  DIRECTIONS = (
    (Directions.NORTH, (0, 1)),
    (Directions.SOUTH, (0, -1)),
    (Directions.EAST, (1, 0)),
    (Directions.WEST, (-1, 0)),
  )

  def registerInitialState(self, gameState):
    self.start = gameState.getAgentPosition(self.index)
    self.width = gameState.data.layout.width
    self.height = gameState.data.layout.height
    self.compactLayout = self.width <= 22 and self.height <= 8
    CaptureAgent.registerInitialState(self, gameState)
    self.homeEntries = self.computeHomeEntries(gameState)
    self.patrolEntries = self.computePatrolEntries()

  def getSuccessor(self, gameState, action):
    successor = gameState.generateSuccessor(self.index, action)
    pos = successor.getAgentState(self.index).getPosition()
    if pos != nearestPoint(pos):
      return successor.generateSuccessor(self.index, action)
    return successor

  def computeHomeEntries(self, gameState):
    walls = gameState.getWalls()
    x = self.width // 2 - 1 if self.red else self.width // 2
    entries = []
    for y in range(1, self.height - 1):
      if not walls[x][y]:
        entries.append((x, y))
    return entries or [self.start]

  def computePatrolEntries(self):
    entries = sorted(self.homeEntries, key=lambda p: p[1])
    if len(entries) <= 2:
      return entries
    lower = entries[max(0, len(entries) // 3)]
    upper = entries[min(len(entries) - 1, (2 * len(entries)) // 3)]
    if lower == upper:
      return [entries[len(entries) // 2]]
    return [lower, upper]

  def chooseBestAction(self, gameState, scorer):
    actions = gameState.getLegalActions(self.index)
    values = [scorer(action) for action in actions]
    bestValue = max(values)
    bestActions = [a for a, v in zip(actions, values) if v == bestValue]
    if Directions.STOP in bestActions and len(bestActions) > 1:
      bestActions.remove(Directions.STOP)
    return random.choice(bestActions)

  def minDistance(self, pos, targets):
    if pos is None or not targets:
      return 0
    return min(self.getMazeDistance(pos, target) for target in targets)

  def nearestTarget(self, pos, targets):
    if pos is None or not targets:
      return None
    return min(targets, key=lambda target: self.getMazeDistance(pos, target))

  def activeGhostPositions(self, gameState):
    positions = []
    for opponent in self.getOpponents(gameState):
      enemy = gameState.getAgentState(opponent)
      pos = enemy.getPosition()
      if pos is not None and not enemy.isPacman and enemy.scaredTimer <= 1:
        positions.append(nearestPoint(pos))
    return positions

  def activeGhostDistances(self, gameState, pos):
    if pos is None:
      return []
    return [self.getMazeDistance(pos, ghost) for ghost in self.activeGhostPositions(gameState)]

  def visibleInvaders(self, gameState):
    enemies = [gameState.getAgentState(i) for i in self.getOpponents(gameState)]
    return [enemy for enemy in enemies if enemy.isPacman and enemy.getPosition() is not None]

  def reversePenalty(self, gameState, action, amount=3):
    reverse = Directions.REVERSE[gameState.getAgentState(self.index).configuration.direction]
    return amount if action == reverse else 0

  def isOpen(self, pos, walls):
    x, y = pos
    return 0 <= x < self.width and 0 <= y < self.height and not walls[x][y]

  def neighborPositions(self, pos, walls):
    x, y = pos
    result = []
    for action, (dx, dy) in self.DIRECTIONS:
      nxt = (x + dx, y + dy)
      if self.isOpen(nxt, walls):
        result.append((action, nxt))
    return result

  def firstPlannedAction(self, gameState, target, avoidGhosts=True):
    start = gameState.getAgentPosition(self.index)
    if start is None or target is None:
      return None
    start = nearestPoint(start)
    target = nearestPoint(target)
    if start == target:
      return None

    walls = gameState.getWalls()
    ghosts = self.activeGhostPositions(gameState) if avoidGhosts else []
    frontier = []
    bestCost = {start: 0}
    counter = 0
    heapq.heappush(frontier, (self.getMazeDistance(start, target), 0, counter, start, None))

    limit = self.width * self.height * 4
    while frontier and counter < limit:
      priority, cost, _, pos, firstAction = heapq.heappop(frontier)
      if cost != bestCost.get(pos):
        continue
      if pos == target:
        return firstAction
      for action, nxt in self.neighborPositions(pos, walls):
        stepCost = 1 + self.plannerRisk(nxt, ghosts)
        newCost = cost + stepCost
        if newCost >= bestCost.get(nxt, 999999):
          continue
        counter += 1
        bestCost[nxt] = newCost
        first = action if firstAction is None else firstAction
        heapq.heappush(frontier, (
          newCost + self.getMazeDistance(nxt, target),
          newCost,
          counter,
          nxt,
          first
        ))
    return None

  def plannerRisk(self, pos, ghosts):
    risk = 0
    for ghost in ghosts:
      distance = self.getMazeDistance(pos, ghost)
      if distance <= 1:
        risk += 90
      elif distance <= 2:
        risk += 35
      elif distance <= 5:
        risk += 6 * (6 - distance)
    return risk

  def likelyDied(self, gameState, successor):
    oldState = gameState.getAgentState(self.index)
    newState = successor.getAgentState(self.index)
    oldPos = oldState.getPosition()
    newPos = newState.getPosition()
    if oldState.isPacman and newPos == self.start and oldPos != self.start:
      return True
    if oldState.numCarrying > 0 and newState.numCarrying == 0:
      return self.getScore(successor) <= self.getScore(gameState)
    return False

  def compactRoleAction(self, gameState):
    self.updateCompactLastEatenFood(gameState)
    mode = self.compactTeamMode(gameState)
    role = self.compactRole(gameState, mode)
    targets = self.compactTargets(gameState, mode, role)
    avoidDanger = role in ('offense', 'conservative')

    action = self.firstActionToTargets(gameState, targets, avoidDanger)
    if action is not None:
      return action
    if avoidDanger:
      action = self.firstActionToTargets(gameState, targets, False)
      if action is not None:
        return action
    return self.compactFallbackAction(gameState, targets, role)

  def compactTeamMode(self, gameState):
    invaders = self.visibleInvaders(gameState)
    maxCarry = max([invader.numCarrying for invader in invaders] or [0])
    score = self.getScore(gameState)
    timeLeft = gameState.data.timeleft
    if len(invaders) >= 2 or maxCarry >= 3:
      return 'defense'
    if score < -5 and not invaders:
      return 'offense'
    if score > 5 or timeLeft < 150:
      return 'conservative'
    return 'split'

  def compactRole(self, gameState, mode):
    if mode == 'split':
      team = sorted(self.getTeam(gameState))
      if self.index == team[0]:
        return 'offense'
      return 'defense'
    return mode

  def compactTargets(self, gameState, mode, role):
    if role == 'defense':
      return self.compactDefenseTargets(gameState)
    if role == 'conservative':
      return self.compactConservativeTargets(gameState)
    return self.compactOffenseTargets(gameState, mode)

  def compactOffenseTargets(self, gameState, mode):
    myState = gameState.getAgentState(self.index)
    myPos = gameState.getAgentPosition(self.index)
    food = self.getFood(gameState).asList()
    capsules = self.getCapsules(gameState)
    homeDistance = self.minDistance(myPos, self.homeEntries)
    ghosts = self.activeGhostDistances(gameState, myPos)
    closestGhost = min(ghosts) if ghosts else None
    returnThreshold = 4 if mode == 'offense' else 3

    shouldReturn = (
      myState.numCarrying >= returnThreshold or
      len(food) <= 2 or
      (myState.numCarrying > 0 and closestGhost is not None and closestGhost <= 5) or
      gameState.data.timeleft < homeDistance + 24
    )
    if shouldReturn:
      return self.sortedTargets(myPos, self.homeEntries)
    if capsules and closestGhost is not None and closestGhost <= 5:
      return self.sortedTargets(myPos, capsules)
    if food:
      return self.compactFoodTargets(myPos, food)
    return self.sortedTargets(myPos, self.homeEntries)

  def compactFoodTargets(self, myPos, food):
    if myPos is None:
      return list(food)[:4]
    scored = []
    for target in food:
      distance = self.getMazeDistance(myPos, target)
      homeDepth = self.minDistance(target, self.homeEntries)
      cluster = 0
      for other in food:
        if other != target and abs(target[0] - other[0]) + abs(target[1] - other[1]) <= 3:
          cluster += 1
      scored.append((distance + 0.3 * homeDepth - 1.8 * cluster, distance, target))
    scored.sort()
    return [target for score, distance, target in scored[:4]]

  def compactDefenseTargets(self, gameState):
    myPos = gameState.getAgentPosition(self.index)
    invaders = self.visibleInvaders(gameState)
    if invaders:
      invaders.sort(
        key=lambda invader: (
          -invader.numCarrying,
          self.getMazeDistance(myPos, invader.getPosition())
        )
      )
      return [invader.getPosition() for invader in invaders]
    if getattr(self, 'lastEatenFood', None) is not None:
      if myPos is None or self.getMazeDistance(myPos, self.lastEatenFood) > 1:
        return [self.lastEatenFood]
      self.lastEatenFood = None
    return [self.compactPatrolTarget(gameState)]

  def compactConservativeTargets(self, gameState):
    myState = gameState.getAgentState(self.index)
    if self.visibleInvaders(gameState):
      return self.compactDefenseTargets(gameState)
    if myState.numCarrying > 0 or myState.isPacman:
      return self.sortedTargets(gameState.getAgentPosition(self.index), self.homeEntries)
    return [self.compactPatrolTarget(gameState)]

  def compactPatrolTarget(self, gameState):
    myPos = gameState.getAgentPosition(self.index)
    if getattr(self, 'lastEatenFood', None) is not None:
      if myPos is None or self.getMazeDistance(myPos, self.lastEatenFood) > 1:
        return self.lastEatenFood
      self.lastEatenFood = None
    team = sorted(self.getTeam(gameState))
    rank = team.index(self.index) if self.index in team else 0
    if self.patrolEntries:
      return self.patrolEntries[rank % len(self.patrolEntries)]
    targetY = self.height * (rank + 1) / float(len(team) + 1)
    return min(self.homeEntries, key=lambda p: abs(p[1] - targetY))

  def updateCompactLastEatenFood(self, gameState):
    if not hasattr(self, 'lastEatenFood'):
      self.lastEatenFood = None
    previous = self.getPreviousObservation()
    if previous is None:
      return
    previousFood = set(self.getFoodYouAreDefending(previous).asList())
    currentFood = set(self.getFoodYouAreDefending(gameState).asList())
    eaten = list(previousFood - currentFood)
    if eaten:
      myPos = gameState.getAgentPosition(self.index)
      if myPos is None:
        self.lastEatenFood = eaten[0]
      else:
        self.lastEatenFood = min(eaten, key=lambda p: self.getMazeDistance(myPos, p))

  def sortedTargets(self, pos, targets):
    if pos is None:
      return list(targets)
    return sorted(targets, key=lambda target: self.getMazeDistance(pos, target))

  def firstActionToTargets(self, gameState, targets, avoidDanger):
    if not targets:
      return None
    start = gameState.getAgentPosition(self.index)
    if start is None:
      return None
    start = nearestPoint(start)
    targetSet = set(nearestPoint(target) for target in targets if target is not None)
    if not targetSet:
      return None

    legalActions = [a for a in gameState.getLegalActions(self.index) if a != Directions.STOP]
    random.shuffle(legalActions)
    walls = gameState.getWalls()
    danger = self.activeGhostPositions(gameState)
    queue = []
    visited = set([start])

    for action in legalActions:
      nxt = self.nextPosition(start, action)
      if not self.isOpen(nxt, walls):
        continue
      if avoidDanger and self.isDangerous(nxt, danger):
        continue
      if nxt in targetSet:
        return action
      queue.append((nxt, action))
      visited.add(nxt)

    head = 0
    while head < len(queue):
      pos, firstAction = queue[head]
      head += 1
      for action, (dx, dy) in self.DIRECTIONS:
        nxt = (pos[0] + dx, pos[1] + dy)
        if nxt in visited or not self.isOpen(nxt, walls):
          continue
        if avoidDanger and self.isDangerous(nxt, danger):
          continue
        if nxt in targetSet:
          return firstAction
        visited.add(nxt)
        queue.append((nxt, firstAction))
    return None

  def nextPosition(self, pos, action):
    for direction, (dx, dy) in self.DIRECTIONS:
      if direction == action:
        return (pos[0] + dx, pos[1] + dy)
    return pos

  def isDangerous(self, pos, dangerPositions):
    for danger in dangerPositions:
      if abs(pos[0] - danger[0]) + abs(pos[1] - danger[1]) <= 1:
        return True
    return False

  def compactFallbackAction(self, gameState, targets, role):
    def scorer(action):
      successor = self.getSuccessor(gameState, action)
      myState = successor.getAgentState(self.index)
      pos = myState.getPosition()
      value = 120 * self.getScore(successor)
      if targets and pos is not None:
        value -= self.minDistance(pos, targets)
      if role in ('offense', 'conservative'):
        ghosts = self.activeGhostDistances(successor, pos)
        if ghosts:
          closest = min(ghosts)
          if myState.isPacman and closest <= 1:
            value -= 1000
          else:
            value += min(closest, 5)
      if myState.isPacman and role == 'defense':
        value -= 500
      if action == Directions.STOP:
        value -= 100
      value -= self.reversePenalty(gameState, action, 3)
      return value
    return self.chooseBestAction(gameState, scorer)


class BlendedCourierAgent(LayoutAgent):
  def chooseAction(self, gameState):
    if self.compactLayout:
      return self.compactRoleAction(gameState)
    target, returning = self.chooseTarget(gameState)
    action = self.firstPlannedAction(gameState, target, True)
    if action is not None and not self.badFirstStep(gameState, action):
      return action
    return self.chooseBestAction(gameState, lambda a: self.scoreAction(gameState, a, target, returning))

  def chooseTarget(self, gameState):
    myState = gameState.getAgentState(self.index)
    myPos = myState.getPosition()
    food = self.getFood(gameState).asList()
    home = self.nearestTarget(myPos, self.homeEntries)
    ghosts = self.activeGhostDistances(gameState, myPos)
    closestGhost = min(ghosts) if ghosts else None

    if myState.numCarrying > 0:
      if myState.numCarrying >= self.carryThreshold(gameState):
        return home, True
      if myState.isPacman and closestGhost is not None and closestGhost <= 5:
        return home, True
      if gameState.data.timeleft < self.minDistance(myPos, self.homeEntries) + 24:
        return home, True
    if len(food) <= 2 or not food:
      return home, True

    capsules = self.getCapsules(gameState)
    if capsules and closestGhost is not None and closestGhost <= 5 and myState.isPacman:
      return self.nearestTarget(myPos, capsules), False
    return self.bestFoodTarget(gameState, myPos, food), False

  def carryThreshold(self, gameState):
    if self.compactLayout:
      return 1
    if self.getScore(gameState) >= 3:
      return 2
    if self.width >= 30 and self.height >= 15:
      return 4
    return 3

  def bestFoodTarget(self, gameState, myPos, food):
    ghosts = self.activeGhostPositions(gameState)
    scored = []
    for target in food:
      distance = self.getMazeDistance(myPos, target)
      homeDistance = self.minDistance(target, self.homeEntries)
      cluster = self.foodClusterBonus(target, food)
      risk = self.foodRisk(target, distance, ghosts)
      overlap = self.teammateOverlapPenalty(gameState, target, distance)
      cost = distance + 0.55 * homeDistance + risk + overlap - cluster
      scored.append((cost, distance, homeDistance, target))
    scored.sort()
    return scored[0][3]

  def foodClusterBonus(self, target, food):
    bonus = 0.0
    for other in food:
      if other == target:
        continue
      distance = abs(target[0] - other[0]) + abs(target[1] - other[1])
      if distance <= 2:
        bonus += 2.2
      elif distance <= 4:
        bonus += 1.0
      elif distance <= 6:
        bonus += 0.35
    return min(7.5, bonus)

  def foodRisk(self, target, myDistance, ghosts):
    risk = 0.0
    for ghost in ghosts:
      ghostDistance = self.getMazeDistance(ghost, target)
      margin = ghostDistance - myDistance
      if margin <= 0:
        risk += 18.0 + min(12.0, -margin * 2.0)
      elif margin <= 2:
        risk += 8.0 - 2.0 * margin
      elif margin <= 4:
        risk += 2.0
    return min(38.0, risk)

  def teammateOverlapPenalty(self, gameState, target, myDistance):
    penalty = 0.0
    for teammate in self.getTeam(gameState):
      if teammate == self.index:
        continue
      pos = gameState.getAgentPosition(teammate)
      if pos is None:
        continue
      teammateDistance = self.getMazeDistance(pos, target)
      if teammateDistance + 1 < myDistance:
        penalty += 8.0
      elif teammateDistance <= myDistance + 1:
        penalty += 3.0
    return penalty

  def badFirstStep(self, gameState, action):
    if action == Directions.STOP:
      return True
    successor = self.getSuccessor(gameState, action)
    if self.likelyDied(gameState, successor):
      return True
    myState = successor.getAgentState(self.index)
    ghosts = self.activeGhostDistances(successor, myState.getPosition())
    return myState.isPacman and ghosts and min(ghosts) <= 1

  def scoreAction(self, gameState, action, target, returning):
    successor = self.getSuccessor(gameState, action)
    myState = successor.getAgentState(self.index)
    myPos = myState.getPosition()
    food = self.getFood(successor).asList()
    ghosts = self.activeGhostDistances(successor, myPos)
    closestGhost = min(ghosts) if ghosts else None

    value = 250 * self.getScore(successor)
    value -= 105 * len(food)
    if target is not None:
      value -= 4 * self.getMazeDistance(myPos, target)
    homeDistance = self.minDistance(myPos, self.homeEntries)
    if returning or myState.numCarrying > 0:
      value -= (12 + 3 * myState.numCarrying) * homeDistance
    if closestGhost is not None:
      value += 2 * closestGhost
      if myState.isPacman and closestGhost <= 2:
        value -= 1200 * (3 - closestGhost)
      elif myState.isPacman and closestGhost <= 5:
        value -= 210
    if action == Directions.STOP:
      value -= 500
    value -= self.reversePenalty(gameState, action, 5)
    if self.likelyDied(gameState, successor):
      value -= 9000
    return value


class PortalInterceptAgent(LayoutAgent):
  def registerInitialState(self, gameState):
    LayoutAgent.registerInitialState(self, gameState)
    self.lastEatenFood = None
    self.currentTarget = self.choosePatrolTarget(gameState)
    self.mode = 'patrol'

  def chooseAction(self, gameState):
    if self.compactLayout:
      return self.compactRoleAction(gameState)
    self.updateLastEatenFood(gameState)
    self.currentTarget, self.mode = self.chooseTarget(gameState)
    if self.mode == 'support':
      action = self.firstPlannedAction(gameState, self.currentTarget, True)
      if action is not None and not self.badSupportStep(gameState, action):
        return action
    return self.chooseBestAction(gameState, lambda a: self.scoreDefenseAction(gameState, a))

  def chooseTarget(self, gameState):
    invader = self.primaryInvader(gameState)
    if invader is not None:
      myPos = gameState.getAgentPosition(self.index)
      invaderPos = invader.getPosition()
      portal = self.nearestTarget(invaderPos, self.homeEntries)
      distToInvader = self.getMazeDistance(myPos, invaderPos)
      invaderToPortal = self.getMazeDistance(invaderPos, portal)
      catchSoon = distToInvader <= 2 or distToInvader <= invaderToPortal
      nearEscape = invaderToPortal <= max(4, self.width // 6)
      atPortal = self.getMazeDistance(myPos, portal) <= 1
      if catchSoon or (invader.numCarrying < 2 and atPortal and distToInvader <= invaderToPortal + 2):
        return invaderPos, 'chase'
      if invader.numCarrying >= 2 or nearEscape or invaderToPortal < distToInvader:
        return portal, 'hold'
      return invaderPos, 'chase'

    if self.lastEatenFood is not None:
      myPos = gameState.getAgentPosition(self.index)
      if myPos is not None and self.getMazeDistance(myPos, self.lastEatenFood) <= 1:
        self.lastEatenFood = None
      else:
        return self.lastEatenFood, 'inspect'

    supportTarget = self.chooseSupportTarget(gameState)
    if supportTarget is not None:
      return supportTarget, 'support'
    return self.choosePatrolTarget(gameState), 'patrol'

  def chooseSupportTarget(self, gameState):
    if self.compactLayout or gameState.data.timeleft <= 260:
      return None
    myState = gameState.getAgentState(self.index)
    myPos = myState.getPosition()
    if myPos is None:
      return None
    if myState.numCarrying > 0:
      return self.nearestTarget(myPos, self.homeEntries)
    if self.getScore(gameState) > 0 and gameState.data.timeleft > 500:
      return None
    ghosts = self.activeGhostDistances(gameState, myPos)
    if myState.isPacman and ghosts and min(ghosts) <= 5:
      return self.nearestTarget(myPos, self.homeEntries)
    food = self.getFood(gameState).asList()
    if not food:
      return None
    target = min(
      food,
      key=lambda p: (
        self.getMazeDistance(myPos, p) + self.minDistance(p, self.homeEntries),
        self.getMazeDistance(myPos, p)
      )
    )
    roundTrip = self.getMazeDistance(myPos, target) + self.minDistance(target, self.homeEntries)
    if roundTrip > self.width + 6:
      return None
    return target

  def scoreDefenseAction(self, gameState, action):
    successor = self.getSuccessor(gameState, action)
    myState = successor.getAgentState(self.index)
    myPos = myState.getPosition()
    target = self.currentTarget or self.choosePatrolTarget(successor)
    targetDistance = self.getMazeDistance(myPos, target)

    value = 0
    if self.mode == 'support':
      value += 400 * self.getScore(successor)
      value -= 18 * targetDistance
      if myState.numCarrying > 0:
        value -= 25 * self.minDistance(myPos, self.homeEntries)
      ghosts = self.activeGhostDistances(successor, myPos)
      if myState.isPacman and ghosts and min(ghosts) <= 5:
        value -= 900 * (6 - min(ghosts))
    else:
      if myState.isPacman:
        value -= 1200
      value -= 12 * targetDistance

    invaders = self.visibleInvaders(successor)
    if invaders:
      closest = min(self.getMazeDistance(myPos, inv.getPosition()) for inv in invaders)
      if self.mode == 'chase':
        value -= 10 * closest
      if closest <= 1 and myState.scaredTimer == 0:
        value += 500
      if myState.scaredTimer > 0 and closest <= 2:
        value -= 250

    if action == Directions.STOP:
      if self.mode == 'hold' and targetDistance == 0:
        value -= 1
      else:
        value -= 100
    value -= self.reversePenalty(gameState, action, 2)
    return value

  def badSupportStep(self, gameState, action):
    successor = self.getSuccessor(gameState, action)
    myState = successor.getAgentState(self.index)
    ghosts = self.activeGhostDistances(successor, myState.getPosition())
    return myState.isPacman and ghosts and min(ghosts) <= 1

  def primaryInvader(self, gameState):
    invaders = self.visibleInvaders(gameState)
    if not invaders:
      return None
    myPos = gameState.getAgentPosition(self.index)
    return max(
      invaders,
      key=lambda inv: (
        inv.numCarrying,
        -self.getMazeDistance(myPos, inv.getPosition())
      )
    )

  def updateLastEatenFood(self, gameState):
    previous = self.getPreviousObservation()
    if previous is None:
      return
    previousFood = set(self.getFoodYouAreDefending(previous).asList())
    currentFood = set(self.getFoodYouAreDefending(gameState).asList())
    eaten = list(previousFood - currentFood)
    if eaten:
      myPos = gameState.getAgentPosition(self.index)
      if myPos is None:
        self.lastEatenFood = eaten[0]
      else:
        self.lastEatenFood = min(eaten, key=lambda p: self.getMazeDistance(myPos, p))

  def choosePatrolTarget(self, gameState):
    defendingFood = self.getFoodYouAreDefending(gameState).asList()
    if not defendingFood:
      return random.choice(self.homeEntries)
    center = (
      sum(x for x, y in defendingFood) / float(len(defendingFood)),
      sum(y for x, y in defendingFood) / float(len(defendingFood))
    )
    base = min(self.homeEntries, key=lambda p: abs(p[0] - center[0]) + abs(p[1] - center[1]))
    team = sorted(self.getTeam(gameState))
    if self.patrolEntries and self.index in team and len(self.patrolEntries) > 1:
      rank = team.index(self.index)
      return self.patrolEntries[rank % len(self.patrolEntries)]
    return base