terça-feira, julho 09, 2013

Advanced Python Class: "PacMan" (extra-project)






Necessary Primitives/Structures:


Lists — Lists 


Lists can be constructed as a sequence of objects inside square brackets; e.g; [2,4,6]. The list function also converts other types of sequence data such as strings into lists.
Sequence operations such as indexing l[i], concatenation +, length len(l) and slicing apply to lists.
As opposed to strings, an element of a list can be mutated via assignment l[i] = x.
Lecture examples - None
More examples - Structure, True-False Quiz, Silly Words, Rainbow Canvas, Digital Numbers 

Points and vectors — Motion 


A point in 2D is represented by a pair of Cartesian coordinates.
A vector in 2D is represented by a pair of numbers (its horizontal and vertical components).
Taking the difference of two points (componentwise) yields a vector.
Vectors can be added and scaled (componentwise). Points should not be added or scaled.
Adding a point and a vector (componentwise) yields a new point. This operation can be used to animate moving points.
Lecture examples - Timer Control, Formula Control 
More examples - Drawing Vectors 

Distance computations — Collisions and reflections 


The distance between two points p0 and p1 is 
√(p0[0]-p1[0])2+(p0[1]-p1[1])2 .
The distance between a point and a circle and the distance between two circles follows from this formula.
The set of points [x,y] that satisfy the equation a*x + b*y + c == 0 is a line. The distance from this line to a point p is 
(a*p[0] + b*p[1] + c)/ √a2 + b2 
The distance from a circle to a line is the distance from the center of the circle to the line minus the radius.

Reflections — Collisions and reflections 


The direction of reflection for a ball bouncing off of a wall depends on the incoming velocity vector v and the normal vector to the wall at the point of contact.
The incoming velocity vector can be decomposed into v = vp + vn where vn is the component of v orthogonal to wall and vp is the component parallel to to the wall.
In this model, the reflected vector is v = vp - vn.
This model simplifies for horizontal and vertical walls. In particular, reflection simply negates one component of the velocity vector v.

Keyboard events — Keyboard input 


SimpleGUI suppports two event handlers for keyboard events.
The key down event handler is registered via set_keydown_handler.
The key up event handler is registered via set_keyup_handler.
The variable passed to each of these handlers is a number that can be compared at the constants in KEY_MAP to determine which key has been pressed.

Positional control — Keyboard input 


We can control the position of a point p directly using key board events.
The horizontal and vertical components of a point p's position can be increment/decrement via p[i] += c in response to key presses.

Velocity control — Velocity control 


Basic physics relates the position of a point p and its velocity v via the equation p += dt * v where dt is a small time step.
In this model, we can control the motion of p via keyup/keydown events that increment/decrement the two components of the velocity vector v via v[i] += c.


Mutable vs. immutable data — Programming tips #4 


Numbers, Booleans and strings are immutable and can not be modified. Only new copies of these kinds of data can be made.
On the other hand, parts of a list can be mutated via assignment to individual elements of the list.
Assignment of an entire list to a variable generates a reference that refers to the list. Subsequent assignment may generate multiple references to the same list.
Mutating a list with multiple references modifies all references to the list. This capability is very useful, but can generate subtle errors.


Code Implementation:

######################################
## Implementation of Pac-Man #########
##     Manuel Augusto Antao  #########
##     Python Class          #########
##     Rice University       #########
##     Extra-Project         #########
######################################

import simplegui
import math
import random
import user17_9jVc7rMrHiUCj84

# Images
board_image = simplegui.load_image("http://www.sitehacks.com/iipp/pacman/board.png?99")
ghost_image = simplegui.load_image("http://www.sitehacks.com/iipp/pacman/ghosts.png?5")
pacman_image = simplegui.load_image("http://www.sitehacks.com/iipp/pacman/pacman.png?5")
edible_image = simplegui.load_image("http://www.sitehacks.com/iipp/pacman/edibles.png?5")

# sounds
'''
Some or all of the sounds were obtained from the following sites:
http://www.playonloop.com/help-and-faqs/#faq5
http://www.noiseforfun.com/help-and-faqs/#faq5
http://www.sweetsoundeffects.com
'''
intro_sound = simplegui.load_sound("https://www.dropbox.com/s/ebm4oe6qgxwtw3i/Pacman%20Opening%20Song.mp3?dl=1")
pellet_sound = simplegui.load_sound("http://www.sitehacks.com/iipp/pacman/eatpellet.ogg?dl=1")
gotcha_sound = simplegui.load_sound("https://www.dropbox.com/s/okabhdekblptzaf/Pacman%20Dies.mp3?dl=1")
edible_sound = simplegui.load_sound("https://www.dropbox.com/s/1mcyemcpucwism8/Pacman%20Eating%20Cherry.mp3?dl=1")
eating_ghost_sound = simplegui.load_sound("https://www.dropbox.com/s/j6igmizn067frdo/Pacman%20Eating%20Ghost.mp3?dl=1")
frightened_sound = simplegui.load_sound("http://www.sitehacks.com/iipp/pacman/ateapill.ogg?dl=1")
one_up_sound = simplegui.load_sound("https://www.dropbox.com/s/ebg5xsryua99yz9/Pacman%20Extra%20Live.mp3?dl=1")

# direction_to_vel dictionary {direction: (forbidden_direction, velocity, angle)}
direction_to_vel = {"left" : ("right", (-1, 0), math.pi),
                     "right": ("left", (1, 0), 0),
                     "up"   : ("down", (0, -1), 3 * math.pi / 2),
                     "down" : ("up", (0, 1), math.pi / 2)}

# levels (waves)
levels = [[7, 27, 34, 54, 59, 79, 84],
          [7, 27, 34, 54, 59, 1092, 1093],
          [5, 25, 30, 50, 55, 1092, 1093]]

######################
# initialize globals #
######################
board = user17_9jVc7rMrHiUCj84.board
WIDTH = 448
HEIGHT = 576
PAC_INIT_POS = (14*16, 26.5*16)
pac_time = 0
cur_direction = "left"
global_timer = 0
global_dot_counter = 0
score = 0
eaten_pellets = []
level = 1
frightened_timer = 0
previous_mode = "scatter"
eaten_ghosts = []
lives = 3
flicker_timer = 0
release_ghost_timer = 0
edible_exists = False
edible_timer = 0
eatable_spawn_list = [70, 170]
started = False
popups = set()
game_timer = 0
one_up = False
eaten_fruits = [-1, -1, -1, -1, -1, -1, -1]
fruit_count = 0

###########################
# define helper functions #
###########################
def reset():
    """resets everything"""
    global board, pacman, blinky, pinky, inky, clyde, ghost_list, ghosts_in_house, edible, popups, global_timer, global_dot_counter, eaten_pellets, frightened_timer, previous_mode, eaten_ghosts, flicker_timer, release_ghost_timer, edible_exists, edible_timer, eatable_spawn_list, game_timer, cur_direction
 
    for tile in eaten_pellets:
        board[tile[0]][tile[1]] = 2
    board[6][1] = 3
    board[6][26] = 3
    board[26][1] = 3
    board[26][26] = 3
    pacman = Pacman()
    blinky = Blinky()
    pinky = Pinky()
    inky = Inky()
    clyde = Clyde()
    ghost_list = [blinky, pinky, inky, clyde]
    ghosts_in_house = [pinky, inky, clyde]
    edible = Edible()
    popups = set()
 
    global_timer = 0
    global_dot_counter = 0
    eaten_pellets = []
    frightened_timer = 0
    previous_mode = "scatter"
    flicker_timer = 0
    eatable_spawn_list = [70, 170]
    eaten_ghosts = []
    release_ghost_timer = 0
    edible_exists = False
    edible_timer = 0
    game_timer = 0
    cur_direction = "left"
    Ghost.set_mode("scatter")
 
def process_group(group, canvas):
    """Processes and draws a group"""
    for obj in group:
        obj.draw(canvas)
             
def dist(point1, point2):
    """return the distance between two points"""
    return math.sqrt((point1[0] - point2[0])**2 + (point1[1] - point2[1])**2)

def kill_pacman():
    """handle pacman's death"""
    global lives, global_timer, temp, global_dot_counter
    # decrement lives
    lives -= 1
    # play gotcha_sound
    gotcha_sound.play()
    # stop pacman where it is
    pacman.set_vel([0, 0])
    # remove ghosts from the field
    for ghost in list(ghost_list):
        ghost_list.remove(ghost)
    # reset Ghost.mode
    Ghost.set_mode("scatter")
    # kill pacman :[
    pacman.set_alive(False)
    temp = global_timer
    # reset global_dot_counter
    global_dot_counter = 1
##################
# define classes #
##################
class Pacman:
    def __init__(self):
        self.pos = list(PAC_INIT_POS)
        self.direction = "left"
        self.vel = [direction_to_vel[self.direction][1][0], direction_to_vel[self.direction][1][1]]
        self.orientation = direction_to_vel[self.direction][2]
        self.sprite_index = 0
        self.alive = True
    def draw(self, canvas):
        """draw Pacman on the board"""
        canvas.draw_image(pacman_image, (13 + 26 * self.sprite_index, 13), (26, 26), self.pos, (26, 26), self.orientation)
     
        # update
        if self.alive:
            self.update()
     
    def update(self):
        global pac_time, score, release_ghost_timer
        """Update Pacman position"""
        # move
        self.pos[0] = (self.pos[0] + 2 * self.vel[0]) % 448
        self.pos[1] += 2 * self.vel[1]
        # animate
        if self.alive:
            pac_time = (pac_time + 1) % 100
            if pac_time % 5 == 0:
                self.sprite_index = (self.sprite_index + 1) % 2
     
        if self.pos[0] % 16 == 8 and self.pos[1] % 16 == 8:
            cur_block = [self.pos[1] // 16, self.pos[0] // 16]
            # stop moving if pacman is against a wall
            if board[cur_block[0] + self.vel[1]][(cur_block[1] + self.vel[0]) % 28] == 0:
                self.vel = [0, 0]
            # eat pellets
            if board[cur_block[0]][cur_block[1]] in (2, 3):
                eaten_pellets.append(cur_block)
                # update score
                if board[cur_block[0]][cur_block[1]] == 2:
                    score += 10
                else:
                    score += 50
                    Ghost.set_mode("frightened")
                # reset release_ghost_timer
                release_ghost_timer = 0
                # play pellet_sound
                pellet_sound.play()

    def get_pos(self):
        """return position"""
        return self.pos
 
    def get_vel(self):
        """return velocity"""
        return self.vel
 
    def get_orientation(self):
        """return orientation"""
        return self.orientation
 
    def get_direction(self):
        """return direction"""
        return self.direction
 
    def get_sprite_index(self):
        """return sprite index"""
        return self.sprite_index
 
    def is_alive(self):
        """check if pacman is alive"""
        return pacman.alive
 
    def set_pos(self, pos):
        """set position"""
        self.pos = pos
 
    def set_vel(self, vel):
        """set velocity"""
        self.vel = vel
 
    def set_orientation(self, angle):
        """set orientation"""
        self.orientation = angle
     
    def set_direction(self, direction):
        """set direction"""
        self.direction = direction
     
    def set_sprite_index(self, index):
        """set sprite index"""
        self.sprite_index = index
     
    def set_alive(self, boolean):
        """set pacman's status"""
        self.alive = boolean
     
class Ghost:
    mode = "scatter"
    default_vel = 2
    def __init__(self):
        self.sprite_index = 0
        self.direction = "left"
        self.vel = direction_to_vel[self.direction][1]
        self.vel_mag = Ghost.default_vel
        self.dot_count = 0
        self.has_turned = False
     
    def draw(self, canvas):
        """draw ghost on the canvas"""
        canvas.draw_image(ghost_image, [13 + 26 * self.sprite_index, 13 + 26 * self.ghost_index], [26, 26], self.pos, [26, 26])
     
        # update ghost if not in house
        if self not in ghosts_in_house:
            self.update()
        #canvas.draw_circle([self.tile[1] * 16, self.tile[0] * 16], 3, 1, "Red", "Red")
    def update(self):
        """update ghost"""
        global eaten_ghosts_count, score, lives, frightened_timer, flicker_timer
        # turn into disembodied eyes when eaten by pacman
        if self in list(eaten_ghosts):
            # resurrect if eaten ghost reaches house
            if self.in_house:
                eaten_ghosts.remove(self)
                self.sprite_index = 0
            else:
                self.sprite_index = 3
        if self.pos[0] % 16 == 8 and self.pos[1] % 16 == 8:
            # if getting out of house, set target tile to the tile just outside the gate
            if self.in_house == True:
                self.tile = [14, 13]
            # else, if eaten by pacman, rush to house
            elif self in eaten_ghosts:
                self.tile = [17, 13]
            # else, if scatter mode, set scatter tile as the target tile
            elif Ghost.mode == "scatter":
                self.tile = self.scatter_tile
            # otherwise, update target tile
            else:
                self.tile_update()
         
            # handle movement
            cur_block = [self.pos[1] // 16, self.pos[0] // 16]
            available_directions = []
            # check if passing the gates
            if board[cur_block[0]][cur_block[1]] == 4:
                self.in_house = not self.in_house
             
            # make a list of forbidden blocks
            forbidden_blocks = [0]
            if not self.in_house and self not in eaten_ghosts:
                forbidden_blocks.append(4)
            # check all possible directions
            for direction in direction_to_vel.keys():
                # except the direction the ghost is coming from
                if direction != direction_to_vel[self.direction][0]:
                    if board[cur_block[0] + direction_to_vel[direction][1][1]][(cur_block[1] + direction_to_vel[direction][1][0]) % 28] not in forbidden_blocks:
                        available_directions.append(direction)
         
            # frightened mode
            if Ghost.mode == "frightened":
                # :)
                # make it move randomly
                if self not in eaten_ghosts:
                    chosen_direction = random.choice(available_directions)
                    self.direction = chosen_direction
                # else, make it move to the house  
                else:
                    # move to house if not in house
                    if not self.in_house:
                        min = 10000 #a large number
                        for direction in available_directions:
                            if dist([(cur_block[0] + direction_to_vel[direction][1][1]), (cur_block[1] + direction_to_vel[direction][1][0])], self.tile) < min:
                                min = dist([(cur_block[0] + direction_to_vel[direction][1][1]), (cur_block[1] + direction_to_vel[direction][1][0])], self.tile)
                                chosen_direction = direction
                        self.direction = chosen_direction
                        # increase velocity
                        self.vel_mag = 4
                     
                 
             
            # chase or scatter mode
            else:
                # make it turn
                if not self.has_turned:
                    self.turn()
                # make it chase
                min = 10000 #a large number
                for direction in available_directions:
                    if dist([(cur_block[0] + direction_to_vel[direction][1][1]), (cur_block[1] + direction_to_vel[direction][1][0])], self.tile) < min:
                        min = dist([(cur_block[0] + direction_to_vel[direction][1][1]), (cur_block[1] + direction_to_vel[direction][1][0])], self.tile)
                        chosen_direction = direction
                self.direction = chosen_direction
                 
     
            # slow down ghosts in the tunnel
            if self not in eaten_ghosts:
                if board[cur_block[0]][cur_block[1]] == 5:
                    self.vel_mag = 1
                else:
                    self.vel_mag = Ghost.default_vel
                 
        # check if eaten by pacman
        if [pacman.get_pos()[1] // 16, pacman.get_pos()[0] // 16] == [self.pos[1] // 16, self.pos[0] // 16]:
            if Ghost.mode == "frightened":
                if self not in eaten_ghosts:
                    eaten_ghosts.append(self)
                    eaten_ghosts_count += 1
                    popup = Popup(list(pacman.get_pos()), ((2**eaten_ghosts_count) * 100))
                    popups.add(popup)
                    eating_ghost_sound.play()
                    score += (2**eaten_ghosts_count) * 100
            else:
                if self not in eaten_ghosts:
                    if pacman.is_alive():
                        kill_pacman()
     
        self.pos[0] = (self.pos[0] + self.vel_mag * direction_to_vel[self.direction][1][0]) % 448
        self.pos[1] += self.vel_mag * direction_to_vel[self.direction][1][1]
         
     
    def turn(self):
        """turn the ghost in the direction it is coming from"""
        self.has_turned = True
        self.direction = direction_to_vel[self.direction][0]
 
    def get_pos(self):
        """return position"""
        return self.pos
 
    def get_direction(self):
        """return direction"""
        return self.direction
 
    def get_index(self):
        """return ghost index"""
        return self.ghost_index
 
    def get_dot_count(self):
        """return dot count"""
        return self.dot_count
 
    def get_dot_limit(self):
        """return dot limit"""
        return self.dot_limit
 
    def get_tile(self):
        """return target tile"""
        return self.tile
 
    def get_mode():
        """return mode"""
        return Ghost.mode
 
    def set_pos(self, pos):
        """set position"""
        self.pos = pos
     
    def set_direction(self, direction):
        """set direction"""
        self.direction = direction
     
    def set_dot_count(self, dot_count):
        """set dot count"""
        self.dot_count = dot_count
     
    def set_tile(self, tile):
        """set target tile"""
        self.tile = tile
     
    def set_sprite_index(self, index):
        """set sprite index"""
        self.sprite_index = index
 
    def set_mode(mode):
        """set ghost mode"""
        global previous_mode, frightened_timer, eaten_ghosts_count, flicker_timer
        # stop frightened_sound
        frightened_sound.rewind()
        # turn on mode change
        if not Ghost.mode == "frightened":
            for ghost in ghost_list:
                ghost.has_turned = False
                ghost.turn()
     
     
 
        # handle frightened mode #
        if mode == "frightened":
            # play frightened_sound
            frightened_sound.play()
            # reset eaten ghosts count
            eaten_ghosts_count = 0
            # reset flicker_timer
            flicker_timer = 0
            if Ghost.mode != "frightened":
                previous_mode = Ghost.mode
            for ghost in ghost_list:
                if ghost not in eaten_ghosts:
                    # 1. turn ghosts purple
                    ghost.sprite_index = 1
                    # 2. slow down the ghosts
                    Ghost.default_vel = 1
                    # 3. Reset frightened_timer
                    frightened_timer = 0
        else:
            for ghost in ghost_list:
                ghost.sprite_index = 0
            Ghost.default_vel = 2
             
        # change mode
        Ghost.mode = mode              
             
         
     
 
class Blinky(Ghost):
    def __init__(self):
        Ghost.__init__(self)
        self.ghost_index = 0
        self.start_pos = [14 * 16, 14.5 * 16]
        self.pos = self.start_pos
        self.scatter_tile = [2, 25]
        self.tile = [0, 0]
        self.in_house = False
        self.dot_limit = 0
     
    def tile_update(self):
        # update target tile
        self.tile = [pacman.get_pos()[1] // 16, pacman.get_pos()[0] // 16]
                 
class Pinky(Ghost):
    def __init__(self):
        Ghost.__init__(self)
        self.ghost_index = 1
        self.start_pos = [14 * 16, 17.5 * 16]
        self.pos = self.start_pos
        self.scatter_tile = [2, 2]
        self.tile = [0, 0]
        self.in_house = True
        self.dot_limit = 0
    def tile_update(self):
        # update target tile
        self.tile = [(pacman.get_pos()[1] // 16 + 4 * direction_to_vel[pacman.get_direction()][1][1]), (pacman.get_pos()[0] // 16 + 4 * direction_to_vel[pacman.get_direction()][1][0])]
     
class Inky(Ghost):
    def __init__(self):
        Ghost.__init__(self)
        self.ghost_index = 2
        self.start_pos = [12 * 16, 17.5 * 16]
        self.pos = self.start_pos
        self.scatter_tile = [34, 27]
        self.tile = [0, 0]
        self.in_house = True
        if level == 1:
            self.dot_limit = 30
        else:
            self.dot_limit = 0
    def tile_update(self):
        # update target tile
        intermediate_block = [pacman.get_pos()[1] // 16 + 2 * direction_to_vel[pacman.get_direction()][1][1], pacman.get_pos()[0] // 16 + 2 * direction_to_vel[pacman.get_direction()][1][0]]
        blinky_block = [blinky.get_pos()[1] // 16, blinky.get_pos()[0] // 16]
        difference = [intermediate_block[0] - blinky_block[0], intermediate_block[1] - blinky_block[1]]
        self.tile = [intermediate_block[0] + difference[0], intermediate_block[1] + difference[1]]
     
     
class Clyde(Ghost):
    def __init__(self):
        Ghost.__init__(self)
        self.ghost_index = 3
        self.start_pos = [16 * 16, 17.5 * 16]
        self.pos = self.start_pos
        self.scatter_tile = [34, 0]
        self.tile = [0, 0]
        self.in_house = True
        if level == 1:
            self.dot_limit = 60
        elif level == 2:
            self.dot_limit = 50
        else:
            self.dot_limit = 0
    def tile_update(self):
        # update target tile
        cur_block = [self.pos[1] // 16, self.pos[0] // 16]
        pacman_block = [pacman.get_pos()[1] // 16, pacman.get_pos()[0] // 16]
        distance = dist(cur_block, pacman_block)
        if distance > 8:
            self.tile = pacman_block
        else:
            self.tile = self.scatter_tile
         

class Popup:
    def __init__(self, pos, value):
        self.pos = pos
        self.value = value
        self.size = 12
        self.color = "White"
        self.init_pos = pos[1]
        self.vel = 1
     
    def draw(self, canvas):
        global popups
        canvas.draw_text(str(self.value), self.pos, self.size, self.color)
        self.pos[1] -= self.vel
        if self.pos[1] <= self.init_pos - 30:
            popups.remove(self)
         
class Edible:
    global level
    def __init__(self):
        self.pos = (14 * 16, 20.5 * 16)
        self.value = level * 100
        if level <= 7:
            self.sprite_index = (level - 1)
        else:
            self.sprite_index = 6
     
    def draw(self, canvas):
        """draw edible on the canvas"""
        canvas.draw_image(edible_image, (14 + (self.sprite_index * 28), 14), (28, 28), self.pos, (28, 28))
     
    def get_value(self):
        """return edible's value"""
        return self.value
 
    def get_sprite_index(self):
        """return sprite index"""
        return self.sprite_index

     
#########################
# define event handlers #
#########################
def draw(canvas):
    global started, pac_time, cur_direction, score, lives, frightened_timer, flicker_timer, global_dot_counter, release_ghost_timer, edible_exists, edible_timer, level, one_up, fruit_count
    # draw background
    canvas.draw_image(board_image, (224, 288), (448, 576), (WIDTH / 2, HEIGHT / 2), (WIDTH, HEIGHT))
 
    # blackout eaten pellets
    for i in range(4, 33):
        blackout_range = 0
        for j in range(1, 28):
            if board[i][j] == 1 :
                blackout_range += 1
            else:
                if blackout_range!=0:
                    canvas.draw_polygon([((j - blackout_range) * 16, i * 16), ((j - blackout_range) * 16, (i * 16) + 16),
                                         (j * 16, (i * 16) + 16), (j * 16, i * 16)], 1, "Black", "Black")
                    blackout_range = 0
                 
    if started:              
        # draw Pacman
        pacman.draw(canvas)
 
        # play kill animation
        if not pacman.is_alive():
            flicker_timer = (flicker_timer + 1) % 10
            if flicker_timer % 10 == 0:
                pacman.set_sprite_index(pacman.get_sprite_index() + 1)
 
        ## update pacman direction ##
        # check if pacman is in the center of a grid point
        if pacman.get_pos()[0] % 16 == 8 and pacman.get_pos()[1] % 16 == 8:
            # determine pacman's current block
            cur_block = [pacman.get_pos()[1] // 16, pacman.get_pos()[0] // 16]
            # determine if pacman can move in that direction
            if board[cur_block[0] + direction_to_vel[cur_direction][1][1]][(cur_block[1] + direction_to_vel[cur_direction][1][0]) % 28] not in [0, 4]:
                pacman.set_vel(direction_to_vel[cur_direction][1])
                pacman.set_direction(cur_direction)
                pacman.set_orientation(direction_to_vel[cur_direction][2])
        #############################
     
        # draw Ghosts
        process_group(ghost_list, canvas)
 
        # make ghosts flicker
        if Ghost.get_mode() == "frightened":
            if frightened_timer > 4:
                flicker_timer = (flicker_timer + 1) % 50
                if flicker_timer < 25:
                    for ghost in ghost_list:
                        if ghost not in eaten_ghosts:
                            ghost.set_sprite_index(2)
                else:
                    for ghost in ghost_list:
                        if ghost not in eaten_ghosts:
                            ghost.set_sprite_index(1)
 
        ## release ghosts from house ##
        # 1. determine preferred ghost
        min = 4
        for ghost in ghosts_in_house:
            if ghost.get_index() < min:
                min = ghost.get_index()
                preferred_ghost = ghost
         
        # 2. update counter whenever pacman eats a dot
        # 2.1 check if there is a ghost in house
        if ghosts_in_house:
            # 2.1. check if pacman is in the middle of a tile
            if pacman.get_pos()[0] % 16 == 8 and pacman.get_pos()[1] % 16 == 8:
                # 2.2. check if pacman's tile has a pellet in it
                cur_block = [pacman.get_pos()[1] // 16, pacman.get_pos()[0] // 16]
                if board[cur_block[0]][cur_block[1]] == 2:
                    # 3. Release ghost
                    # if global_dot_counter is enabled, use it.
                    if global_dot_counter:
                        global_dot_counter += 1
                        if global_dot_counter in [8, 18, 33]:
                            ghosts_in_house.remove(preferred_ghost)
                    # else, use individual dot counters
                    else:
                        preferred_ghost.set_dot_count(preferred_ghost.get_dot_count() + 1)
                        if preferred_ghost.get_dot_count() >= preferred_ghost.get_dot_limit():
                            ghosts_in_house.remove(preferred_ghost)
                            preferred_ghost.set_dot_count(0)
            # Alternate, timer based way of releasing ghosts
            if release_ghost_timer >= 4:
                ghosts_in_house.remove(preferred_ghost)
                release_ghost_timer = 0

    # else, display "click to start!"
    else:
        canvas.draw_text("Click to start!", (11*16, 21*16), 18, "Red")
     
    # remove eaten pellet
    for tile in eaten_pellets:
        board[tile[0]][tile[1]] = 1
     
    # draw popups
    process_group(popups, canvas)
 
    # draw edibles
    if len(eaten_pellets) in eatable_spawn_list:
        eatable_spawn_list.remove(len(eaten_pellets))
        edible_exists = True
    if edible_exists:
        edible.draw(canvas)
        if [pacman.get_pos()[1] // 16, pacman.get_pos()[0] // 16] in [[20,13], [20,14]]:
            score += edible.get_value()
            popup = Popup(list(pacman.get_pos()), edible.get_value())
            popups.add(popup)
            if edible.get_sprite_index() not in eaten_fruits:
                eaten_fruits[fruit_count] = edible.get_sprite_index()
                fruit_count += 1
            edible_sound.play()
            edible_exists = False
            edible_timer = 0
   
    # display score and level
    canvas.draw_text("Score: " + str(score), (304, 16), 16, "White")
    canvas.draw_text("Level: " + str(level), (304, 32), 16, "White")
 
    # display lives
    for i in range(lives):
        canvas.draw_image(pacman_image, (13, 13), (26, 26), (32 + (i * 30), 560), (26, 26))
     
    # update level
    if len(eaten_pellets) == 244:
        level += 1
        reset()
     

     
    # award one_up when player's score reaches 10000
    if score == 10000 and not one_up:
        lives += 1
        one_up = True
        one_up_sound.play()
     
    # draw eaten fruits
    for i in range(7):
        if eaten_fruits[i] != -1:
            canvas.draw_image(edible_image, (14 + (eaten_fruits[i] * 28), 14), (28, 28), (16 * 14 + (i * 33), 16 * 35), (28, 28))

def move(key):            
    global cur_direction
    if key == simplegui.KEY_MAP["left"]:
        cur_direction = "left"
    elif key == simplegui.KEY_MAP["right"]:
        cur_direction = "right"
    if key == simplegui.KEY_MAP["up"]:
        cur_direction = "up"
    if key == simplegui.KEY_MAP["down"]:
        cur_direction = "down"
     
def tick():
    global started, game_timer, global_timer, level, frightened_timer, previous_mode, pacman, temp, ghost_list, ghosts_in_house, cur_direction, blinky, pinky, inky, clyde, release_ghost_timer, edible_exists, edible_timer
 
    if started:
        # resurrect pacman and the ghosts
        if not pacman.is_alive():
            # wait for the kill animation to stop
            if global_timer - temp > 2:
                # check game over condition
                if lives == 0:
                    started = False
                # reset ghosts and pacman
                else:
                    blinky = Blinky()
                    pinky = Pinky()
                    inky = Inky()
                    clyde = Clyde()
                    pacman = Pacman()
                    cur_direction = "left"
                    ghost_list = [blinky, pinky, inky, clyde]
                    ghosts_in_house = [pinky, inky, clyde]
                    Ghost.set_mode("scatter")
                    global_timer = 0
                    release_ghost_timer = 0
     
        if not Ghost.get_mode() == "frightened":
            global_timer += 1
            if level == 1:
                wave_set = 0
            elif level in range(2, 5):
                wave_set = 1
            else:
                wave_set = 2
     
 
            if global_timer == levels[wave_set][0]:
                Ghost.set_mode("chase")
            elif global_timer == levels[wave_set][1]:
                Ghost.set_mode("scatter")
            elif global_timer == levels[wave_set][2]:
                Ghost.set_mode("chase")
            elif global_timer == levels[wave_set][3]:
                Ghost.set_mode("scatter")
            elif global_timer == levels[wave_set][4]:
                Ghost.set_mode("chase")
            elif global_timer == levels[wave_set][5]:
                Ghost.set_mode("scatter")
            elif global_timer == levels[wave_set][6]:
                Ghost.set_mode("chase")
 
        else:
            frightened_timer += 1
            if frightened_timer == 7:
                Ghost.set_mode(previous_mode)
         
        # update release_ghost_timer
        release_ghost_timer += 1
 
        # edible timer
        if edible_exists:
            edible_timer += 1
            if edible_timer >= 10:
                edible_exists = False
                edible_timer = 0
 
    # update game_timer
    if game_timer:
        game_timer += 1
        if game_timer == 5:
            reset()
            started = True
            game_timer = 0
     
def start_game(pos):
    global started, game_timer, lives, score, level, one_up, eaten_fruits, fruit_count
    # start the game if it hasn't already started
    if not started:
        # reset lives, score and level
        lives = 3
        score = 0
        level = 1
        one_up = False
        eaten_fruits = [-1, -1, -1, -1, -1, -1, -1]
        fruit_count = 0
        # play intro music
        intro_sound.play()
        # set game_timer
        game_timer = 1
     
################
# create frame #
################
frame = simplegui.create_frame("Pac-Man - Manuel Augusto Antao - 2013-07-09", WIDTH, HEIGHT)

###########################
# register event handlers #
###########################
frame.set_draw_handler(draw)
frame.set_keydown_handler(move)
frame.set_mouseclick_handler(start_game)
main_timer = simplegui.create_timer(1000, tick)

######################
# set things rolling #
######################
frame.start()
main_timer.start()

Sem comentários: