#! /usr/bin/python
import sys, os, pygame, random

# =====================================================================
# GLOBAL VARIABLES
gameScreen = None   # the main display screen
scrRefreshRate = 250 # the pause (in milliseconds) between updates
keepPlaying = True  # flag to identify if the game should continue
backImage = None
terrainList = []

# define the size of the pygame display window
scrSize = scrWidth, scrHeight = 480,480
mapRows, mapCols = 15, 15 # the size of the map in rows by columns
tilePixels = 32
defaultMap = [
      ['w','w','w','w','w','w','w','w','w','w','w','w','w','w','w'],
      ['w','g','g','g','g','g','g','g','g','g','w','g','g','g','w'],
      ['w','g','w','g','g','w','w','w','w','g','w','g','w','f','w'],
      ['w','g','w','g','g','w','g','g','g','g','w','g','w','w','w'],
      ['w','g','w','w','w','w','g','w','w','w','w','g','g','g','w'],
      ['w','g','g','w','g','g','g','g','g','g','w','g','g','g','w'],
      ['w','g','g','w','g','g','g','g','l','g','w','w','w','g','w'],
      ['w','g','g','w','g','g','l','l','l','g','g','g','g','g','w'],
      ['w','g','w','w','w','w','l','l','l','l','g','g','g','g','w'],
      ['w','g','w','g','g','g','g','l','w','g','g','w','w','w','w'],
      ['w','g','w','g','g','g','g','g','w','g','g','w','g','g','w'],
      ['w','g','w','w','w','w','w','g','w','g','w','w','g','g','w'],
      ['w','g','g','g','g','w','g','g','w','g','w','w','g','w','w'],
      ['w','g','w','g','g','g','g','g','w','g','g','g','g','g','w'],
      ['w','w','w','w','w','w','w','w','w','w','w','w','w','w','w']
   ]

# =====================================================================
# SETUP ROUTINE    
#    - initializes the pygame display screen and background image

def gameSetup():

   # specify the global variables the setup routine needs to access
   global gameScreen, scrSize, backImage, terrainList, mapRows, mapCols

   # initialize the randon number generator
   random.seed()

   # initialize the display screen
   pygame.init()
   gameScreen = pygame.display.set_mode(scrSize)
   backImage = pygame.image.load('backdrop.gif')
   gameScreen.blit(backImage, (0,0))

   # now build the terrain objects from the text map,
   # a seperate object is created for each tile on the map
   r = 0
   while r < mapRows:
      c = 0
      while c < mapCols:
         terrainList.append(Terrain(defaultMap[r][c], r, c))
         c += 1
      r += 1

# ====================================================================
# TERRAIN OBJECT
#    terrain constitutes the background of the game map,
#        e.g. grass, walls, water, etc
#
#    each terrain object has several properties:
#        - the image loaded for that terrain object
#        - the terrain object's pixel position on the display
#        - the terrain object's map square (row, column)
#        - the terrain type (grass, lake, wall)
class Terrain(pygame.sprite.Sprite):

   # the constructor (initialization routine) 
   def __init__(self, terrainType, r, c):

      # initialize a pygame sprite for the object
      pygame.sprite.Sprite.__init__(self)

      # establish the terrain attributes
      global tilePixels
      self.terrainType = terrainType
      self.row = r
      self.col = c
      self.position = self.x, self.y = r*tilePixels, c*tilePixels
      if terrainType == 'g':
         self.image = pygame.image.load('grass.gif')
      elif terrainType == 'w':
         self.image = pygame.image.load('wall.gif')
      elif terrainType == 'f':
         self.image = pygame.image.load('finish.gif')
      else:
         self.image = pygame.image.load('lake.gif')
      self.rect = self.image.get_rect()

      # draw the terrain
      global gameScreen
      gameScreen.blit(self.image, (self.x, self.y))

   def update(self):
      # redraws the terrain item
      self.rect.topleft = self.x,self.y

# =====================================================================
# CHARACTER OBJECT

class Character(pygame.sprite.Sprite):

   # the constructor (initialization routine) 
   def __init__(self, image, r, c, mdir, moveAlg, cid):

      # initialize a pygame sprite for the character
      pygame.sprite.Sprite.__init__(self)

      # record the character's unique id
      self.characterID = cid

      # load an image for the character
      self.image = pygame.image.load(image)
      self.rect = self.image.get_rect()

      # set up the initial position for the character,
      #    both as a map square (row,col) 
      #     and as a display (pixel) position (x,y)
      global tilePixels
      self.mapLocation = self.row, self.col = r, c
      self.position = self.x, self.y = r*tilePixels, c*tilePixels

      # set up the initial direction for the character
      self.movingDir = mdir

      # record the movement plotting algorithm the character should use
      #   (e.g. 'random' movement, 'clockwise' movement, etc)
      self.plotting = moveAlg


   # check for a winner (adj to finish flag)
   def checkForWin(self):
      global keepPlaying
      win = False
      if (defaultMap[self.row+1][self.col] == 'f'):
         win = True
      elif (defaultMap[self.row-1][self.col] == 'f'):
         win = True
      elif (defaultMap[self.row][self.col-1] == 'f'):
         win = True
      elif (defaultMap[self.row][self.col+1] == 'f'):
         win = True
      if win:
         print "***************************"
         print "!!!!! AI number", self.characterID, "won !!!!!"
         print "***************************"
         pygame.time.delay(2000)
         keepPlaying = False
         return True
      else:
         return False

   # the update routine adjusts the character's current position 
   #     and image based on its direction and the local terrain
   def update(self):

      # calculate the object's new position based on its old position, 
      #    its current direction, and the local map terrain
      global defaultMap, tilePixels
      
      # end the game if a character has found the flag   
      if self.checkForWin():
         return
         
      # figure out where the character should move next,
      #    based on their plotting algorithm
      if (self.plotting == 'clockwise'):
         # characters attempting to follow the walls around the maze
	 #    should always run their plotting algorithm
         self.plotDirection()

      elif (self.plotting == 'random'):
         # characters using random plotting have roughly a 1/3 chance
	 #    of replotting their direction (i.e. randomly changing
	 #    which direction they're going)
         if (random.randint(0,100) < 34):
            self.plotDirection()

	 # otherwise, characters using random plotting will try to
	 #    keep going in the same direction if possible
	 # if their way turns out to be blocked then they'll
	 #    randomly plot a new direction
         elif not self.checkAndMove(self.movingDir):
               self.plotDirection()

      # position the image correctly
      self.rect = self.image.get_rect()
      self.rect.topleft = self.x,self.y = self.row*tilePixels, self.col*tilePixels


   # check to see if you are able to move in the specified direction (n,s,e,w)
   #    if it is possible, i.e. if the target tile is grass,
   #       then move, set your direction movement, and return true
   #    otherwise return false
   def checkAndMove(self, d):
      global defaultMap
      if (d == 'n'):
         if (defaultMap[self.row-1][self.col] == 'g'):
            self.row -= 1
            self.movingDir = 'n'
            return True
      elif (d == 's'):
         if (defaultMap[self.row+1][self.col] == 'g'):
            self.row += 1
            self.movingDir = 's'
            return True
      elif (d == 'e'):
         if (defaultMap[self.row][self.col+1] == 'g'):
            self.col += 1
            self.movingDir = 'e'
            return True
      elif (d == 'w'):
         if (defaultMap[self.row][self.col-1] == 'g'):
            self.col -= 1
            self.movingDir = 'w'
            return True
      return False

   # plotDirection calculates a new facing for an AI based on
   #     both the surrounding terrain and the AI's destination
   def plotDirection(self):
      global defaultMap

      # in random movement, there is an equal chance of the AI
      #    attempting to move in each of the four directions
      if (self.plotting == 'random'):
         choice = random.randint(0,100)
         if (choice < 25):
            self.checkAndMove('n')
         elif (choice < 50):
            self.checkAndMove('e')
         elif (choice < 75):
            self.checkAndMove('w')
         elif (defaultMap[self.row+1][self.col] == 'g'):
            self.checkAndMove('s')
            
      # in clockwise movement the AI basically tries to hug the wall:
      #    the AI tries to turn clockwise from its current facing,
      #    but if that is blocked the AI tries to keep moving
      #        in its old direction
      #    if that is also blocked,
      #       then the AI tries to turn still further
      #    and if that is also blocked then the AI goes in
      #       the one direction left 
      elif (self.plotting == 'clockwise'):
         if (self.movingDir == 'n'):
            # was moving north, see if we can turn east
            if self.checkAndMove('e'): return
            # otherwise see if we can go north
            elif self.checkAndMove('n'): return
            # otherwise see if we can go west
            elif self.checkAndMove('w'): return
            # otherwise go south
            else: self.checkAndMove('s')
         
         elif (self.movingDir == 'e'):
            # was moving east, see if we can turn south
            if self.checkAndMove('s'):  return
            # otherwise see if we can go east
            elif self.checkAndMove('e'):  return
            # otherwise see if we can go north
            elif self.checkAndMove('n'): return
            # otherwise go west
            else: self.checkAndMove('w')
               
         elif (self.movingDir == 's'):
            # was moving south, see if we can turn west
            if self.checkAndMove('w'): return
            # otherwise see if we can go south
            elif self.checkAndMove('s'): return
            # otherwise see if we can go east
            elif self.checkAndMove('e'): return
            # otherwise go north
            else: self.checkAndMove('n')
               
         else:
            # was moving west, see if we can turn north
            if self.checkAndMove('n'): return
            # otherwise see if we can go west
            elif self.checkAndMove('w'): return
            # otherwise see if we can go south
            elif self.checkAndMove('s'): return
            # otherwise go east
            else: self.checkAndMove('e')


# =====================================================================
# EVENT HANDLING ROUTINE
#    - processes any pending in-game events

def processEvents():

   # specify which global variables the routine needs access to
   global keepPlaying

   # process each pending event
   for event in pygame.event.get():

      # if the user closed the window set keepPlaying to False
      #    to tell the game to quit playing
      if event.type == pygame.QUIT:
         keepPlaying = False

      # check if the user has pressed a key
      elif event.type == pygame.KEYDOWN:

         # the escape and q keys quit the game
         if event.key == pygame.K_ESCAPE:
            keepPlaying = False
         elif event.key == pygame.K_q:
            keepPlaying = False


# =====================================================================
# MAIN GAME CONTROL ROUTINE
#    - sets up the game and runs the main game update loop
#      until instructed to quit

def main():

   # identify any global variables the main routine needs to access
   global gameScreen, backImage, scrRefreshRate, keepPlaying

   # initialize pygame and the game's display screen
   gameSetup()

   # create a list of characters to add to the display,
   # giving each of them an image, map row, map column, 
   #    facing direction, movement plotting style, and unique id
   characterList = [ 
               Character('ghost1.gif', 1, 1, 'e', 'random', 0),
               Character('ghost2.gif', mapCols - 2, 1, 'n', 'clockwise', 1),
          ]

   # create a group out of the list of character objects
   characterGroup = pygame.sprite.RenderUpdates(*characterList)

   # create a group out of the list of terrain objects (map tiles)   
   terrainGroup = pygame.sprite.RenderUpdates(*terrainList)

   # run the main game loop
   keepPlaying = True
   while keepPlaying:

      # handle any pending events
      processEvents()

      # clear both the character group and the terrain group
      # update both groups
      # get a list of changed sections of the screen
      #     as a result of changes to the characters and tiles
      # and redraw the display
      characterGroup.clear(gameScreen, backImage)
      terrainGroup.clear(gameScreen, backImage)
      characterGroup.update()
      terrainGroup.update()
      updateSections = terrainGroup.draw(gameScreen)
      updateSections += characterGroup.draw(gameScreen)
      pygame.display.update(updateSections)
      pygame.display.flip()

      # pause before initiating the next loop cycle
      pygame.time.delay(scrRefreshRate)

   # shut down the game
   pygame.display.quit()
   sys.exit()


# =====================================================================
# INITIATE THE GAME
#    - calls the main() routine
if __name__ == "__main__":
   main()

