# myTeam.py
# ---------
# Licensing Information: You are free to use or extend these projects for
# educational purposes provided that (1) you do not distribute or publish
# solutions, (2) you retain this notice, and (3) you provide clear
# attribution to UC Berkeley, including a link to http://ai.berkeley.edu.
#
# Attribution Information: The Pacman AI projects were developed at UC Berkeley.
# The core projects and autograders were primarily created by John DeNero
# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
from captureAgents import CaptureAgent
import random
import util
from game import Directions
from util import nearestPoint
def createTeam(firstIndex, secondIndex, isRed,
first='FoodReturnOffensiveAgent',
second='PortalChokepointDefender'):
return [eval(first)(firstIndex), eval(second)(secondIndex)]
class ReflexCaptureAgent(CaptureAgent):
def registerInitialState(self, gameState):
self.start = gameState.getAgentPosition(self.index)
CaptureAgent.registerInitialState(self, gameState)
self.width = gameState.data.layout.width
self.height = gameState.data.layout.height
self.portals = self.computePortals(gameState)
def chooseAction(self, gameState):
actions = gameState.getLegalActions(self.index)
values = [self.evaluate(gameState, action) 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 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 computePortals(self, gameState):
walls = gameState.getWalls()
if self.red:
homeX = self.width // 2 - 1
enemyX = self.width // 2
else:
homeX = self.width // 2
enemyX = self.width // 2 - 1
portals = []
fallback = []
for y in range(1, self.height - 1):
if not walls[homeX][y]:
fallback.append((homeX, y))
if not walls[enemyX][y]:
portals.append((homeX, y))
return portals or fallback or [self.start]
def isHomeSide(self, pos):
if pos is None:
return True
if self.red:
return pos[0] < self.width // 2
return pos[0] >= self.width // 2
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 activeEnemyGhostDistances(self, gameState, myPos):
distances = []
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:
distances.append(self.getMazeDistance(myPos, enemyPos))
return distances
class FoodReturnOffensiveAgent(ReflexCaptureAgent):
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)
ghostDistances = self.activeEnemyGhostDistances(successor, myPos)
closestGhost = min(ghostDistances) if ghostDistances else None
features['successorScore'] = self.getScore(successor)
features['foodRemaining'] = len(foodList)
features['distanceToFood'] = self.minDistance(myPos, foodList)
features['distanceHome'] = self.minDistance(myPos, self.portals)
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
carrying = myState.numCarrying
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'] + 25:
features['shouldReturn'] = 1
return features
def getWeights(self, gameState, action):
features = self.getFeatures(gameState, action)
weights = {
'successorScore': 200,
'foodRemaining': -100,
'distanceToFood': -4,
'distanceToCapsule': -2,
'ghostDistance': 2,
'immediateDanger': -1000,
'nearGhost': -200,
'distanceHome': 0,
'shouldReturn': 0,
'stop': -100,
'reverse': -3,
}
if features['shouldReturn']:
weights['distanceHome'] = -16
weights['distanceToFood'] = -1
if features['nearGhost'] or features['immediateDanger']:
weights['distanceToCapsule'] = -8
return weights
class PortalChokepointDefender(ReflexCaptureAgent):
def registerInitialState(self, gameState):
ReflexCaptureAgent.registerInitialState(self, gameState)
self.currentTarget = self.chooseFoodClusterPortal(gameState)
self.lastMissingFood = None
def chooseAction(self, gameState):
self.updateMissingFood(gameState)
self.currentTarget = self.chooseDefensiveTarget(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['outsideHome'] = 1
elif not self.isHomeSide(myPos):
features['outsideHome'] = 1
features['numInvaders'] = len(invaders)
if invaders:
invaderDistance = min(
self.getMazeDistance(myPos, invader.getPosition())
for invader in invaders
)
features['invaderDistance'] = invaderDistance
if myState.scaredTimer > 0 and invaderDistance <= 2:
features['scaredTooClose'] = 1
elif self.currentTarget is not None:
features['distanceToTarget'] = self.getMazeDistance(myPos, self.currentTarget)
if invaders and self.currentTarget is not None:
features['distanceToTarget'] = self.getMazeDistance(myPos, self.currentTarget)
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):
weights = {
'numInvaders': -1000,
'onDefense': 180,
'outsideHome': -1200,
'invaderDistance': -28,
'distanceToTarget': -7,
'scaredTooClose': -500,
'stop': -120,
'reverse': -2,
}
if gameState.getAgentState(self.index).scaredTimer > 0:
weights['invaderDistance'] = 8
weights['distanceToTarget'] = -9
return weights
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 updateMissingFood(self, gameState):
previous = self.getPreviousObservation()
if previous is None:
return
previousFood = set(self.getFoodYouAreDefending(previous).asList())
currentFood = set(self.getFoodYouAreDefending(gameState).asList())
missing = list(previousFood - currentFood)
if missing:
myPos = gameState.getAgentPosition(self.index)
self.lastMissingFood = min(
missing,
key=lambda food: self.getMazeDistance(myPos, food)
)
def chooseDefensiveTarget(self, gameState):
myPos = gameState.getAgentPosition(self.index)
invaders = self.visibleInvaders(gameState)
if invaders:
return self.chooseInvaderTarget(gameState, invaders, myPos)
if self.lastMissingFood is not None:
if myPos is not None and self.getMazeDistance(myPos, self.lastMissingFood) <= 1:
self.lastMissingFood = None
else:
return self.lastMissingFood
return self.chooseFoodClusterPortal(gameState)
def chooseInvaderTarget(self, gameState, invaders, myPos):
myState = gameState.getAgentState(self.index)
bestTarget = None
bestScore = None
for invader in invaders:
invaderPos = invader.getPosition()
nearestPortal = min(
self.portals,
key=lambda portal: self.getMazeDistance(invaderPos, portal)
)
myToInvader = self.getMazeDistance(myPos, invaderPos)
myToPortal = self.getMazeDistance(myPos, nearestPortal)
invaderToPortal = self.getMazeDistance(invaderPos, nearestPortal)
if myState.scaredTimer > 0:
target = nearestPortal
score = myToPortal
elif myToInvader <= 4 or myToInvader <= invaderToPortal + 1:
target = invaderPos
score = myToInvader
else:
target = nearestPortal
score = myToPortal + 0.5 * invaderToPortal
if bestScore is None or score < bestScore:
bestTarget = target
bestScore = score
return bestTarget
def chooseFoodClusterPortal(self, gameState):
defendingFood = self.getFoodYouAreDefending(gameState).asList()
if not defendingFood:
return min(self.portals, key=lambda portal: self.getMazeDistance(self.start, portal))
centerX = sum(x for x, y in defendingFood) / float(len(defendingFood))
centerY = sum(y for x, y in defendingFood) / float(len(defendingFood))
return min(
self.portals,
key=lambda portal: abs(portal[0] - centerX) + abs(portal[1] - centerY)
)