# 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 >= 4 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): 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) )