
Beginning Python - From Novice To Professional (2005)
.pdf540 |
C H A P T E R 2 9 ■ P R O J E C T 1 0 : D O - I T - Y O U R S E L F A R C A D E G A M E |
screen.fill(config.background_color)
# Remember to call flip, to make the changes visible: pygame.display.flip()
def display(self, screen):
"""
Used to display the State after it has already been displayed once. The default behavior is to do nothing.
"""
pass
class Level(State):
"""
A game level. Takes care of counting how many weights have been dropped, moving the sprites around, and other tasks relating to game logic.
"""
def __init__(self, number=1): self.number = number
# How many weights remain to dodge in this level? self.remaining = config.weights_per_level
speed = config.drop_speed
#One speed_increase added for each level above 1: speed += (self.number-1) * config.speed_increase
#Create the weight and banana:
self.weight = weight = objects.Weight(speed) self.banana = banana = objects.Banana()
both = self.weight, self.banana # This could contain more sprites...
self.sprites = pygame.sprite.RenderUpdates(both)
def update(self, game):
"Updates the game state from the previous frame."
#Update all sprites: self.sprites.update()
#If the banana touches the weight, tell the game to switch to
#a GameOver state:
if self.banana.touches(self.weight): game.nextState = GameOver()
#Otherwise, if the weight has landed, reset it. If all the
#weights of this level have been dodged, tell the game to
#switch to a LevelCleared state:
elif self.weight.landed: self.weight.reset()
C H A P T E R 2 9 ■ P R O J E C T 1 0 : D O - I T - Y O U R S E L F A R C A D E G A M E |
541 |
self.remaining -= 1
if self.remaining == 0:
game.nextState = LevelCleared(self.number)
def display(self, screen):
"""
Displays the state after the first display (which simply wipes the screen). As opposed to firstDisplay, this method uses pygame.display.update with a list of rectangles that need to be updated, supplied from self.sprites.draw.
"""
screen.fill(config.background_color) updates = self.sprites.draw(screen) pygame.display.update(updates)
class Paused(State):
"""
A simple, paused game state, which may be broken out of by pressing either a keyboard key or the mouse button.
"""
finished = 0 # Has the user ended the pause?
image = None # Set this to a file name if you want an image text = '' # Set this to some informative text
def handle(self, event):
"""
Handles events by delegating to State (which handles quitting in general) and by reacting to key presses and mouse
clicks. If a key is pressed or the mouse is clicked, self.finished is set to true.
"""
State.handle(self, event)
if event.type in [MOUSEBUTTONDOWN, KEYDOWN]: self.finished = 1
def update(self, game):
"""
Update the level. If a key has been pressed or the mouse has been clicked (i.e., self.finished is true), tell the game to move to the state represented by self.nextState() (should be implemented by subclasses).
"""
if self.finished:
game.nextState = self.nextState()
542 |
C H A P T E R 2 9 ■ P R O J E C T 1 0 : D O - I T - Y O U R S E L F A R C A D E G A M E |
def firstDisplay(self, screen):
"""
The first time the Paused state is displayed, draw the image (if any) and render the text.
"""
#First, clear the screen by filling it with the background color: screen.fill(config.background_color)
#Create a Font object with the default appearance, and specified size: font = pygame.font.Font(None, config.font_size)
#Get the lines of text in self.text, ignoring empty lines at
#the top or bottom:
lines = self.text.strip().splitlines()
#Calculate the height of the text (using font.get_linesize()
#to get the height of each line of text):
height = len(lines) * font.get_linesize()
#Calculate the placement of the text (centered on the screen): center, top = screen.get_rect().center
top -= height // 2
#If there is an image to display...
if self.image:
# load it:
image = pygame.image.load(self.image).convert()
# get its rect:
r = image.get_rect()
#move the text down by half the image height: top += r.height // 2
#place the image 20 pixels above the text: r.midbottom = center, top - 20
#blit the image to the screen: screen.blit(image, r)
antialias = 1 # Smooth the text black = 0, 0, 0 # Render it as black
#Render all the lines, starting at the calculated top, and
#move down font.get_linesize() pixels for each line:
for line in lines:
text = font.render(line.strip(), antialias, black) r = text.get_rect()
r.midtop = center, top screen.blit(text, r)
top += font.get_linesize()
C H A P T E R 2 9 ■ P R O J E C T 1 0 : D O - I T - Y O U R S E L F A R C A D E G A M E |
543 |
# Display all the changes: pygame.display.flip()
class Info(Paused):
"""
A simple paused state that displays some information about the game. It is followed by a Level state (the first level).
"""
nextState = Level text = '''
In this game you are a banana, trying to survive a course in
self-defense against fruit, where the participants will "defend" themselves against you with a 16 ton weight.'''
class StartUp(Paused):
"""
A paused state that displays a splash image and a welcome message. It is followed by an Info state.
"""
nextState = Info
image = config.splash_image text = '''
Welcome to Squish,
the game of Fruit Self-Defense'''
class LevelCleared(Paused):
"""
A paused state that informs the user that he or she has cleared a given level. It is followed by the next level state.
"""
def __init__(self, number): self.number = number
self.text = '''Level %i cleared
Click to start next level''' % self.number
544 C H A P T E R 2 9 ■ P R O J E C T 1 0 : D O - I T - Y O U R S E L F A R C A D E G A M E
def nextState(self):
return Level(self.number+1)
class GameOver(Paused):
"""
A state that informs the user that he or she has lost the game. It is followed by the first level.
"""
nextState = Level text = '''
Game Over
Click to Restart, Esc to Quit'''
class Game:
"""
A game object that takes care of the main event loop, including changing between the different game states.
"""
def __init__(self, *args):
#Get the directory where the game and the images are located: path = os.path.abspath(args[0])
dir = os.path.split(path)[0]
#Move to that directory (so that the images files may be
#opened later on):
os.chdir(dir)
#Start with no state: self.state = None
#Move to StartUp in the first event loop iteration: self.nextState = StartUp()
def run(self):
"""
This method sets things in motion. It performs some vital initialization tasks, and enters the main event loop.
"""
pygame.init() # This is needed to initialize all the pygame modules
#Decide whether to display the game in a window or to use the
#full screen:
flag = 0 |
# Default (window) mode |
546 |
C H A P T E R 2 9 ■ P R O J E C T 1 0 : D O - I T - Y O U R S E L F A R C A D E G A M E |
games at the Pygame Web site. If playing with Pygame gets you hooked on game development, you might want to check out these Web sites:
•http://www.gamedev.net
•http://www.flipcode.com
A Web search should give you plenty of other similar sites.
What Now?
Well, this is it. You have finished the last project. If you take stock of what you have accomplished (assuming that you have followed all the projects), you should be rightfully impressed with yourself. The breadth of the topics presented has given you a taste of the possibilities that await you in the world of Python programming. I hope you have enjoyed the trip this far, and I wish you good luck on your continued journey as a Python programmer.


548 |
A P P E N D I X A ■ T H E S H O R T V E R S I O N |
The first two examples are equivalent.
The index variable given in the for loop iterates through the elements of a list (written with brackets, as in the example). To make an “ordinary” for loop (that is, a counting loop), use the built-in function range:
# Print out the values from 0 to 99, inclusive for value in range(100):
print value
The line beginning with # is a comment and is ignored by the interpreter.
Now you know enough (in theory) to implement any algorithm in Python. Let’s add some basic user interaction. To get input from the user (from a text prompt), use the built-in function input:
x = input("Please enter a number: ") print "The square of that number is", x*x
The input function displays the (optional) prompt given and lets the user enter any valid Python value. In this case, we were expecting a number. If something else (such as a string) is entered, the program would halt with an error message. To avoid that, you would have to add some error checking. I won’t go into that here; suffice it to say that if you want the user input returned verbatim as a string (so that anything can be entered), use the function raw_input instead. If you wanted to convert an input string s to an integer, you could then use int(s).
■Note If you want to input a string with input, the user has to write the quotes explicitly. In Python, strings can be enclosed in either single or double quotes.
So, you have control structures, input, and output covered—now you need some snazzy data structures. The most important ones are lists and dictionaries. Lists are written with brackets, and can (naturally) be nested:
name = ["Cleese", "John"] x = [[1,2,3],[y,z],[[[]]]]
One of the nice things about lists is that you can access their elements separately or in groups, through indexing and slicing. Indexing is done (as in many other languages) by writing the index in brackets after the list. (Note that the first element has index 0.)
print name[1], name[0] # Prints "John Cleese" name[0] = "Smith"
Slicing is almost like indexing, except that you indicate both the start and stop index of the result, with a colon (:) separating them:
x = ["SPAM","SPAM","SPAM","SPAM","SPAM","eggs","and","SPAM"] print x[5:7] # Prints the list ["eggs","and"]

A P P E N D I X A ■ T H E S H O R T V E R S I O N |
549 |
Notice that the end is noninclusive. If one of the indices is dropped, it is assumed that you want everything in that direction. In other words, the slice x[:3] means “every element from the beginning of x up to element 3, noninclusive.” (It could be argued that it would actually mean element 4 because the counting starts at 0. Oh, well.) The slice x[3:] would, on the other hand, mean “every element from x, starting at element 3 (inclusive) up to, and including, the last one.” For really interesting results, you can use negative numbers, too: x[-3] is the third element from the end of the list.
■Tip While on the subject of indexing, you might be interested to know that the built-in function len gives you the length of a list.
Now, then—what about dictionaries? To put it simply, they are like lists, except that their contents aren’t ordered. How do you index them then? Well, every element has a key, or a name, which is used to look up the element just as in a real dictionary. The following example demonstrates the syntax used to create dictionaries:
phone = { "Alice" : 23452532, "Boris" : 252336, "Clarice" : 2352525, "Doris" : 23624643 }
person = { 'first name': "Robin", 'last name': "Hood", 'occupation': "Scoundrel" }
Now, to get person’s occupation, you use the expression person["occupation"]. If you wanted to change his or her last name, you could write
person['last name'] = "of Locksley"
Simple, isn’t it? Like lists, dictionaries can hold other dictionaries. Or lists, for that matter. And naturally lists can hold dictionaries, too. That way, you can easily make some quite advanced data structures.
Functions
Next step: abstraction. You want to give a name to a piece of code and call it with a couple of parameters. In other words, you want to define a function (also called a procedure). That’s easy. Use the keyword def as follows:
def square(x): return x*x
print square(2) # Prints out 4
The return statement is used to return a value from the function.
When you pass a parameter to a function, you bind the parameter to the value, thus creating a new reference. This means that you can modify the original value directly inside the function,