sonarAgents.py


from game import Agent
from game import Directions
import sonar
import random
import util
from dynamicInferenceModule import ExactDynamicInferenceModule
from game import Actions

DISTANCE_RANGE = 5 # Must be odd
DISTANCE_VALUES = [i - (DISTANCE_RANGE - 1)/2 for i in range(DISTANCE_RANGE)]
DISPLAY = None

def noisyDist(pos1, pos2):
  return util.manhattanDistance(pos1, pos2) + random.choice(DISTANCE_VALUES)

class SonarAgent(Agent):
  def observationFunction(self, state):
    # Adds the sonar signal
    obs = state.deepCopy()
    pos = obs.getPacmanPosition()
    n = obs.getNumAgents()
    distances = [0] + [noisyDist(pos, obs.getGhostPosition(i)) for i in range(1, n)]
    obs.ghostDistances = distances

    # Update the display
    words = []
    for alive, dist in zip(obs.getLivingGhosts(), distances)[1:]:
      if alive: words.append(dist)
      else: words.append('XXX')
    import __main__
    global DISPLAY
    DISPLAY = __main__._display
    DISPLAY.infoPane.updateGhostDistances(words)
    # We should also delete ghost states, but we'll just trust our agents not to cheat
    return obs
  
  def registerInitialState(self, state):
    self.trackers = [None] # Empty tracker for Pacman
    for ghostState in state.getGhostStates():
      self.trackers.append(GhostTracker(state, ghostState))
    
  def getGhostPositionDistributions(self, state):
    distributions = []
    for index, liveGhost in enumerate(state.getLivingGhosts()[1:]):
      index += 1
      if liveGhost:
        tracker, distance = self.trackers[index], state.ghostDistances[index]
        tracker.observe(state.getPacmanPosition(), distance)
        distribution = tracker.getStateDistribution()
        distributions.append(distribution)
        tracker.elapseTime()
      else:
        distributions.append(util.Counter())
    DISPLAY.updateDistributions(distributions)
    return distributions
  
class GhostTracker:
  def __init__(self, state, ghostState):
    self.adapter = GhostbustersGameAdapter(state, ghostState)
    self.inference = ExactDynamicInferenceModule(self.adapter)
    self.inference.initialize()
    
  def observe(self, pacPosition, distance):
    return self.inference.observe( (pacPosition, distance) )
    
  def elapseTime(self):
    return self.inference.elapseTime()
    
  def getStateDistribution(self):
    dist = self.inference.getBeliefDistribution()
    c = util.Counter()
    for key, value in dist.items():
      c[key[0]] = value
    return c

class GhostbustersGameAdapter:
  def __init__(self, state, ghostState):
    self.startingGhostPosition = ghostState.getPosition()
    self.walls = state.data.layout.walls

  def getInitialDistribution(self):
    c = util.Counter()
    c[(self.startingGhostPosition, )] = 1
    return c

  def getGhostTuples(self):
    return [( (x,y), ) for x in range(self.walls.width) for y in range(self.walls.height)]
  
  def getReadingDistributionGivenGhostTuple(self, shipTuple, location):
    ghostPos = shipTuple[0]
    c = util.Counter()
    trueDistance = util.manhattanDistance(ghostPos, location)
    for d in DISTANCE_VALUES:
      c[d + trueDistance] += 1.0 / DISTANCE_RANGE
    return c
  
  def getGhostTupleDistributionGivenPreviousGhostTuple(self, oldGhosts):
    ghostPos = oldGhosts[0]
    neighbors = Actions.getLegalNeighbors(ghostPos, self.walls)
    c = util.Counter()
    for n in neighbors:
      c[(n,)] = 1
    c.normalize()
    return c

class SonarKeyboardAgent(SonarAgent):
  """
  An agent controlled by the keyboard.
  """
  # NOTE: Arrow keys also work.
  WEST_KEY  = 'a' 
  EAST_KEY  = 'd' 
  NORTH_KEY = 'w' 
  SOUTH_KEY = 's'

  def __init__( self, index = 0 ):
    
    self.lastMove = Directions.STOP
    self.index = index
    self.keys = []
    
  def getAction( self, state):
    from graphicsDisplay import keys_waiting
    from graphicsDisplay import keys_pressed
    keys = keys_waiting() + keys_pressed()
    if keys != []:
      self.keys = keys
    
    legal = state.getLegalActions(self.index)
    move = self.getMove(legal)
    
    if move == Directions.STOP:
      # Try to move in the same direction as before
      if self.lastMove in legal:
        move = self.lastMove
        
    if move not in legal:
      move = random.choice(legal)
      
    self.lastMove = move
    return move

  def getMove(self, legal):
    move = Directions.STOP
    if   (self.WEST_KEY in self.keys or 'Left' in self.keys) and Directions.WEST in legal:  move = Directions.WEST
    if   (self.EAST_KEY in self.keys or 'Right' in self.keys) and Directions.EAST in legal: move = Directions.EAST
    if   (self.NORTH_KEY in self.keys or 'Up' in self.keys) and Directions.NORTH in legal:   move = Directions.NORTH
    if   (self.SOUTH_KEY in self.keys or 'Down' in self.keys) and Directions.SOUTH in legal: move = Directions.SOUTH
    return move

class TrackingKeyboardAgent(SonarKeyboardAgent):
  def getAction(self, state):
    self.getGhostPositionDistributions(state)
    return SonarKeyboardAgent.getAction(self, state)

import distanceCalculator
class GreedyTrackingAgent(SonarAgent):
  def registerInitialState(self, state):
    SonarAgent.registerInitialState(self, state)
    self.distancer = distanceCalculator.Distancer(state.data.layout)
    
  def getAction(self, state):
    distanceCalculator.waitOnDistanceCalculator(0.5)
    legal = state.getLegalActions(self.index)
    distributions = self.getGhostPositionDistributions(state)
    modes = []
    for dist in distributions:
      if len(dist.keys()) > 0:
        modes.append(dist.argMax())
    if len(modes) == 0: return random.choice(legal)
    
    options = []
    for l in legal:
      succ = state.generatePacmanSuccessor(l)
      pos = succ.getPacmanPosition()
      closestMode = min([self.distancer.getDistance(pos, mode) for mode in modes]) 
      options.append((closestMode, l))
    return min(options)[1]