# 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).
# Student side autograding was added by Brad Miller, Nick Hay, and
# Pieter Abbeel (pabbeel@cs.berkeley.edu).
from captureAgents import CaptureAgent
import random
import util
from game import Directions
from util import nearestPoint
#################
# Team creation #
#################
def createTeam(firstIndex, secondIndex, isRed,
first='DynamicRoleAgent', second='DynamicRoleAgent'):
return [eval(first)(firstIndex), eval(second)(secondIndex)]
##########
# Agents #
##########
class DynamicRoleAgent(CaptureAgent):
"""Reflex agent with score/time/invader based team role switching."""
def registerInitialState(self, gameState):
self.start = gameState.getAgentPosition(self.index)
self.width = gameState.data.layout.width
self.height = gameState.data.layout.height
self.lastEatenFood = None
CaptureAgent.registerInitialState(self, gameState)
self.homeEntries = self.computeHomeEntries(gameState)
self.chokeTargets = self.computeChokeTargets()
def chooseAction(self, gameState):
self.updateLastEatenFood(gameState)
mode = self.decideTeamMode(gameState)
role = self.decideRole(gameState, mode)
actions = gameState.getLegalActions(self.index)
values = [self.evaluateAction(gameState, action, mode, role)
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 decideTeamMode(self, gameState):
invaders = self.visibleInvaders(gameState)
maxInvaderCarrying = 0
if invaders:
maxInvaderCarrying = max(invader.numCarrying for invader in invaders)
score = self.getScore(gameState)
timeLeft = self.getTimeLeft(gameState)
if len(invaders) >= 2:
return 'defense'
if maxInvaderCarrying >= 3:
return 'defense'
if score < -5 and not invaders:
return 'offense'
if score > 5 or timeLeft < 150:
return 'conservative'
return 'split'
def decideRole(self, gameState, mode):
if mode == 'split':
team = sorted(self.getTeam(gameState))
if self.index == team[0]:
return 'offense'
return 'defense'
return mode
def evaluateAction(self, gameState, action, mode, role):
if role == 'offense':
value = self.evaluateOffense(gameState, action, mode)
elif role == 'conservative':
value = self.evaluateConservative(gameState, action)
else:
value = self.evaluateDefense(gameState, action)
return value + self.commonActionScore(gameState, action)
def evaluateOffense(self, gameState, action, mode):
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
homeDistance = self.minDistance(myPos, self.homeEntries)
carrying = myState.numCarrying
value = 100 * self.getScore(successor)
value -= 100 * len(foodList)
if foodList:
value -= 4 * self.minDistance(myPos, foodList)
returnThreshold = 4 if mode == 'offense' else 3
shouldReturn = (
carrying >= returnThreshold or
len(foodList) <= 2 or
(carrying > 0 and closestGhost is not None and closestGhost <= 5) or
self.getTimeLeft(gameState) < homeDistance + 20
)
if shouldReturn:
value += 70 * carrying
value -= 16 * homeDistance
elif capsules and closestGhost is not None and closestGhost <= 5:
value -= 7 * self.minDistance(myPos, capsules)
if closestGhost is not None:
if closestGhost <= 1:
value -= 2000
elif closestGhost <= 2:
value -= 550
else:
value += min(closestGhost, 6) * 4
return value
def evaluateDefense(self, gameState, action):
successor = self.getSuccessor(gameState, action)
myState = successor.getAgentState(self.index)
myPos = myState.getPosition()
invaders = self.visibleInvaders(successor)
homeDistance = self.minDistance(myPos, self.homeEntries)
value = 120 * self.getScore(successor)
if myState.isPacman:
value -= 500
value -= 22 * homeDistance
else:
value += 120
if invaders:
invaderDistances = [
self.getMazeDistance(myPos, invader.getPosition())
for invader in invaders
]
minInvaderDistance = min(invaderDistances)
value -= 1000 * len(invaders)
value -= 28 * minInvaderDistance
highCarryInvaders = [invader for invader in invaders
if invader.numCarrying >= 3]
if highCarryInvaders:
value -= 12 * min(
self.getMazeDistance(myPos, invader.getPosition())
for invader in highCarryInvaders
)
if myState.scaredTimer > 1 and minInvaderDistance <= 2:
value -= 250
else:
target = self.defensivePatrolTarget(gameState)
value -= 5 * self.getMazeDistance(myPos, target)
return value
def evaluateConservative(self, gameState, action):
successor = self.getSuccessor(gameState, action)
myState = successor.getAgentState(self.index)
myPos = myState.getPosition()
invaders = self.visibleInvaders(successor)
carrying = myState.numCarrying
homeDistance = self.minDistance(myPos, self.homeEntries)
if invaders:
return self.evaluateDefense(gameState, action) - 200 * len(invaders)
value = 150 * self.getScore(successor)
if carrying > 0:
value += 90 * carrying
value -= 28 * homeDistance
elif myState.isPacman:
value -= 750
value -= 25 * homeDistance
else:
value += 160
value -= 7 * self.getMazeDistance(myPos, self.defensivePatrolTarget(gameState))
return value
def commonActionScore(self, gameState, action):
value = 0
if action == Directions.STOP:
value -= 100
currentDirection = gameState.getAgentState(self.index).configuration.direction
if action == Directions.REVERSE[currentDirection]:
value -= 3
return value
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 computeChokeTargets(self):
entries = sorted(self.homeEntries, key=lambda p: p[1])
if len(entries) <= 2:
return entries
groups = []
current = [entries[0]]
for entry in entries[1:]:
if entry[1] == current[-1][1] + 1:
current.append(entry)
else:
groups.append(current)
current = [entry]
groups.append(current)
if len(groups) >= 2:
lower = groups[0][len(groups[0]) // 2]
upper = groups[-1][len(groups[-1]) // 2]
return [lower, upper]
lowerIndex = max(0, len(entries) // 3)
upperIndex = min(len(entries) - 1, (2 * len(entries)) // 3)
if lowerIndex == upperIndex and upperIndex + 1 < len(entries):
upperIndex += 1
return [entries[lowerIndex], entries[upperIndex]]
def defensivePatrolTarget(self, gameState):
myPos = gameState.getAgentPosition(self.index)
if self.lastEatenFood 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.chokeTargets:
targetIndex = rank % len(self.chokeTargets)
return self.chokeTargets[targetIndex]
targetY = self.height * (rank + 1) / float(len(team) + 1)
return min(self.homeEntries, key=lambda p: abs(p[1] - targetY))
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 activeEnemyGhostDistances(self, gameState, myPos):
distances = []
if myPos is None:
return 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
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 minDistance(self, pos, targets):
if pos is None or not targets:
return 0
return min(self.getMazeDistance(pos, target) for target in targets)
def getTimeLeft(self, gameState):
return getattr(gameState.data, 'timeleft', 9999)