# 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]) )