# 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) 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 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 defendingFood = self.getFoodYouAreDefending(gameState).asList() team = sorted(self.getTeam(gameState)) rank = team.index(self.index) if self.index in team else 0 if defendingFood: centerY = sum(y for x, y in defendingFood) / float(len(defendingFood)) spread = max(2, self.height // 5) targetY = centerY - spread if rank == 0 else centerY + spread else: 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)