Source: dynamic_role_switching_20260430_093820

RawBack
# 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
    timeLeft = self.getTimeLeft(gameState)
    score = self.getScore(gameState)

    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.shouldEndgameReturn(score, timeLeft, carrying, homeDistance)
    )
    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 shouldEndgameReturn(self, score, timeLeft, carrying, homeDistance):
    if carrying <= 0:
      return timeLeft < homeDistance + 20
    if score <= 1 and timeLeft < 260:
      return True
    if score <= 3 and timeLeft < max(180, 4 * homeDistance + 80):
      return True
    return timeLeft < homeDistance + 20

  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)