# your_best.py
# Food ROI targeting candidate for CS470 Assignment 3.
from captureAgents import CaptureAgent
import random
import util
from game import Directions
from util import nearestPoint
def createTeam(firstIndex, secondIndex, isRed,
first='FoodROIOffensiveAgent',
second='FoodROISupportAgent'):
return [eval(first)(firstIndex), eval(second)(secondIndex)]
class ReflexCaptureAgent(CaptureAgent):
def registerInitialState(self, gameState):
self.start = gameState.getAgentPosition(self.index)
self.width = gameState.data.layout.width
self.height = gameState.data.layout.height
CaptureAgent.registerInitialState(self, gameState)
self.homeEntries = self.getHomeEntries(gameState)
def chooseAction(self, gameState):
actions = gameState.getLegalActions(self.index)
values = [self.evaluate(gameState, 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 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 evaluate(self, gameState, action):
return self.getFeatures(gameState, action) * self.getWeights(gameState, action)
def getFeatures(self, gameState, action):
features = util.Counter()
successor = self.getSuccessor(gameState, action)
features['successorScore'] = self.getScore(successor)
return features
def getWeights(self, gameState, action):
return {'successorScore': 1.0}
def getHomeEntries(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 minDistance(self, pos, targets):
if not pos or not targets:
return 0
return min(self.getMazeDistance(pos, target) for target in targets)
def activeEnemyGhosts(self, gameState):
ghosts = []
for opponent in self.getOpponents(gameState):
enemy = gameState.getAgentState(opponent)
enemyPos = enemy.getPosition()
if enemyPos is None:
continue
if not enemy.isPacman and enemy.scaredTimer <= 1:
ghosts.append((opponent, enemyPos))
return ghosts
def activeEnemyGhostDistances(self, gameState, myPos):
return [self.getMazeDistance(myPos, pos)
for opponent, pos in self.activeEnemyGhosts(gameState)]
class FoodROIOffensiveAgent(ReflexCaptureAgent):
def registerInitialState(self, gameState):
ReflexCaptureAgent.registerInitialState(self, gameState)
self.currentFoodTarget = None
self.currentReturnMode = False
def chooseAction(self, gameState):
myState = gameState.getAgentState(self.index)
myPos = myState.getPosition()
foodList = self.getFood(gameState).asList()
homeDistance = self.minDistance(myPos, self.homeEntries)
ghostDistances = self.activeEnemyGhostDistances(gameState, myPos)
closestGhost = min(ghostDistances) if ghostDistances else None
self.currentFoodTarget = self.bestFoodTarget(gameState, myPos, foodList)
self.currentReturnMode = (
myState.numCarrying >= 3 or
len(foodList) <= 2 or
(myState.numCarrying > 0 and closestGhost is not None and closestGhost <= 5) or
gameState.data.timeleft < homeDistance + 20
)
return ReflexCaptureAgent.chooseAction(self, gameState)
def bestFoodTarget(self, gameState, myPos, foodList):
if not myPos or not foodList:
return None
clusterBonuses = self.foodClusterBonuses(foodList)
ghosts = self.activeEnemyGhosts(gameState)
bestFood = None
bestCost = None
for food in foodList:
myDistance = self.getMazeDistance(myPos, food)
homeDistance = self.minDistance(food, self.homeEntries)
cost = (
myDistance +
0.6 * homeDistance +
self.ghostRiskPenalty(food, myDistance, ghosts) -
clusterBonuses.get(food, 0.0) +
self.teammateOverlapPenalty(gameState, food, myDistance)
)
if bestCost is None or cost < bestCost:
bestCost = cost
bestFood = food
elif cost == bestCost and bestFood is not None:
if myDistance < self.getMazeDistance(myPos, bestFood):
bestFood = food
return bestFood
def foodClusterBonuses(self, foodList):
bonuses = {}
for food in foodList:
bonus = 0.0
for other in foodList:
if other == food:
continue
distance = abs(food[0] - other[0]) + abs(food[1] - other[1])
if distance <= 2:
bonus += 2.0
elif distance <= 4:
bonus += 1.0
elif distance <= 6:
bonus += 0.4
bonuses[food] = min(7.0, bonus)
return bonuses
def ghostRiskPenalty(self, food, myDistance, ghosts):
if not ghosts:
return 0.0
penalty = 0.0
for opponent, ghostPos in ghosts:
ghostDistance = self.getMazeDistance(ghostPos, food)
margin = ghostDistance - myDistance
if margin <= 0:
penalty += 16.0 + min(10.0, abs(margin) * 2.0)
elif margin <= 2:
penalty += 9.0 - 2.0 * margin
elif margin <= 4:
penalty += 2.5
return min(35.0, penalty)
def teammateOverlapPenalty(self, gameState, food, myDistance):
penalty = 0.0
for teammate in self.getTeam(gameState):
if teammate == self.index:
continue
teammatePos = gameState.getAgentPosition(teammate)
if teammatePos is None:
continue
teammateDistance = self.getMazeDistance(teammatePos, food)
if teammateDistance + 1 < myDistance:
penalty += 8.0
elif teammateDistance <= myDistance + 1:
penalty += 3.0
team = sorted(self.getTeam(gameState))
if len(team) >= 2:
midpoint = self.height / 2.0
lowerLaneAgent = team[0]
if self.index == lowerLaneAgent and food[1] > midpoint:
penalty += 1.5
elif self.index != lowerLaneAgent and food[1] < midpoint:
penalty += 1.5
return penalty
def getFeatures(self, gameState, action):
features = util.Counter()
successor = self.getSuccessor(gameState, action)
myState = successor.getAgentState(self.index)
myPos = myState.getPosition()
foodList = self.getFood(successor).asList()
capsules = self.getCapsules(successor)
carrying = myState.numCarrying
ghostDistances = self.activeEnemyGhostDistances(successor, myPos)
closestGhost = min(ghostDistances) if ghostDistances else None
features['successorScore'] = self.getScore(successor)
features['foodRemaining'] = len(foodList)
if self.currentFoodTarget is not None:
features['distanceToFood'] = self.getMazeDistance(myPos, self.currentFoodTarget)
else:
features['distanceToFood'] = self.minDistance(myPos, foodList)
features['distanceHome'] = self.minDistance(myPos, self.homeEntries)
features['distanceToCapsule'] = self.minDistance(myPos, capsules)
if action == Directions.STOP:
features['stop'] = 1
reverse = Directions.REVERSE[gameState.getAgentState(self.index).configuration.direction]
if action == reverse:
features['reverse'] = 1
if closestGhost is not None:
features['ghostDistance'] = closestGhost
if closestGhost <= 2:
features['immediateDanger'] = 1
elif closestGhost <= 5:
features['nearGhost'] = 1
if carrying >= 3 or len(foodList) <= 2:
features['shouldReturn'] = 1
if carrying > 0 and closestGhost is not None and closestGhost <= 5:
features['shouldReturn'] = 1
if gameState.data.timeleft < features['distanceHome'] + 20:
features['shouldReturn'] = 1
return features
def getWeights(self, gameState, action):
weights = {
'successorScore': 200,
'foodRemaining': -100,
'distanceToFood': -3,
'distanceToCapsule': -2,
'ghostDistance': 2,
'immediateDanger': -1000,
'nearGhost': -180,
'stop': -100,
'reverse': -3,
'shouldReturn': 0,
'distanceHome': 0,
}
if self.currentReturnMode:
weights['distanceHome'] = -15
weights['distanceToFood'] = -1
weights['distanceToCapsule'] = -1
return weights
class FoodROISupportAgent(FoodROIOffensiveAgent):
def registerInitialState(self, gameState):
FoodROIOffensiveAgent.registerInitialState(self, gameState)
self.lastEatenFood = None
self.patrolTarget = self.choosePatrolTarget(gameState)
self.supportEnabled = self.height > 7
def chooseAction(self, gameState):
self.updateLastEatenFood(gameState)
myState = gameState.getAgentState(self.index)
invaders = self.visibleInvaders(gameState)
if invaders and not myState.isPacman:
myPos = gameState.getAgentPosition(self.index)
target = min(
[invader.getPosition() for invader in invaders],
key=lambda p: self.getMazeDistance(myPos, p)
)
return self.chooseDefensiveMove(gameState, target, chase=True)
if self.lastEatenFood is not None and not myState.isPacman and self.getScore(gameState) >= 1:
return self.chooseDefensiveMove(gameState, self.lastEatenFood, chase=False)
if not self.supportEnabled:
return self.chooseDefensiveMove(gameState, self.patrolTarget, chase=False)
if myState.numCarrying == 0 and self.getScore(gameState) < 1:
return self.chooseDefensiveMove(gameState, self.patrolTarget, chase=False)
return FoodROIOffensiveAgent.chooseAction(self, gameState)
def chooseDefensiveMove(self, gameState, target, chase):
actions = gameState.getLegalActions(self.index)
values = [self.defensiveMoveScore(gameState, action, target, chase) for action in actions]
bestValue = max(values)
bestActions = [action for action, value in zip(actions, values) if value == bestValue]
if Directions.STOP in bestActions and len(bestActions) > 1:
bestActions.remove(Directions.STOP)
return random.choice(bestActions)
def defensiveMoveScore(self, gameState, action, target, chase):
successor = self.getSuccessor(gameState, action)
myState = successor.getAgentState(self.index)
myPos = myState.getPosition()
score = 0.0
if myState.isPacman:
score -= 1000.0
else:
score += 120.0
if target is not None:
weight = 25.0 if chase else 4.0
score -= weight * self.getMazeDistance(myPos, target)
if action == Directions.STOP:
score -= 100.0
reverse = Directions.REVERSE[gameState.getAgentState(self.index).configuration.direction]
if action == reverse:
score -= 3.0
return score
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 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)
self.lastEatenFood = min(eaten, key=lambda p: self.getMazeDistance(myPos, p))
elif 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
def choosePatrolTarget(self, gameState):
defendingFood = self.getFoodYouAreDefending(gameState).asList()
if not defendingFood:
return random.choice(self.homeEntries)
foodCenter = (
sum(x for x, y in defendingFood) / float(len(defendingFood)),
sum(y for x, y in defendingFood) / float(len(defendingFood))
)
return min(
self.homeEntries,
key=lambda p: abs(p[0] - foodCenter[0]) + abs(p[1] - foodCenter[1])
)
class PatrolDefensiveAgent(ReflexCaptureAgent):
def registerInitialState(self, gameState):
ReflexCaptureAgent.registerInitialState(self, gameState)
self.patrolTarget = self.choosePatrolTarget(gameState)
self.lastEatenFood = None
def chooseAction(self, gameState):
self.updateLastEatenFood(gameState)
invaders = self.visibleInvaders(gameState)
if invaders:
myPos = gameState.getAgentPosition(self.index)
self.patrolTarget = min(
[a.getPosition() for a in invaders],
key=lambda p: self.getMazeDistance(myPos, p)
)
elif self.lastEatenFood is not None:
self.patrolTarget = self.lastEatenFood
elif self.patrolTarget is None:
self.patrolTarget = self.choosePatrolTarget(gameState)
return ReflexCaptureAgent.chooseAction(self, gameState)
def getFeatures(self, gameState, action):
features = util.Counter()
successor = self.getSuccessor(gameState, action)
myState = successor.getAgentState(self.index)
myPos = myState.getPosition()
invaders = self.visibleInvaders(successor)
features['onDefense'] = 1
if myState.isPacman:
features['onDefense'] = 0
features['numInvaders'] = len(invaders)
if invaders:
features['invaderDistance'] = min(
self.getMazeDistance(myPos, invader.getPosition()) for invader in invaders
)
else:
features['distanceToPatrol'] = self.getMazeDistance(myPos, self.patrolTarget)
if action == Directions.STOP:
features['stop'] = 1
reverse = Directions.REVERSE[gameState.getAgentState(self.index).configuration.direction]
if action == reverse:
features['reverse'] = 1
return features
def getWeights(self, gameState, action):
return {
'numInvaders': -1000,
'onDefense': 120,
'invaderDistance': -20,
'distanceToPatrol': -4,
'stop': -100,
'reverse': -2,
}
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 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)
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)
foodCenter = (
sum(x for x, y in defendingFood) / float(len(defendingFood)),
sum(y for x, y in defendingFood) / float(len(defendingFood))
)
return min(
self.homeEntries,
key=lambda p: abs(p[0] - foodCenter[0]) + abs(p[1] - foodCenter[1])
)