Step 5: Start and restart the game¶
Up to now, the game has started when you run it and closed when you either win or lose. In this step, we’ll add a new context: waiting for the game to start for the first time, or to restart after a game has completed.
This involves introducing the idea of a status for the game: either “Starting”: waiting for the game to begin; or “Running”: playing the game. Depending on which status is current, we will show different things on the screen and react differently to keypresses, mouse movements etc.
Step 5a: Add and display a Running status¶

The Code¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | WIDTH = 640 HEIGHT = 480 class Game(object): pass game = Game() game.score = 0 game.status = "Running" # # Create a status display, as wide as the screen and 60 pixels high. # It's placed at the bottom of the screen # STATUS_DISPLAY = ZRect(0, HEIGHT - 60, WIDTH, 60) # # Create a game window which is as wide as the screen but allows # a status display underneath # GAME_WINDOW = ZRect(0, 0, WIDTH, HEIGHT - STATUS_DISPLAY.height - 1) GAME_WINDOW.background_colour = "darkblue" GAME_WINDOW.frame_colour = "white" class Ball(ZRect): pass # # The ball is a red square halfway across the game window # ball = Ball((0, 0), (30, 30)) ball.center = GAME_WINDOW.center ball.colour = "red" # # The ball moves one step right and one step down each tick # ball.direction = 1, 1 # # The ball moves at a speed of 3 steps each tick # ball.speed = 3 class Bat(ZRect): pass # # The bat is a green oblong which starts just along the bottom # of the game window and halfway across. # BAT_W = 150 BAT_H = 15 bat = Bat(GAME_WINDOW.centerx, GAME_WINDOW.bottom - BAT_H, BAT_W, BAT_H) bat.colour = "green" class Brick(ZRect): pass # # The brick is a rectangle one eight the width of the game window # and one quarter high as it is wide. # N_BRICKS = 8 BRICK_W = GAME_WINDOW.width / N_BRICKS BRICK_H = BRICK_W / 4 BRICK_COLOURS = ["purple", "lightgreen", "lightblue", "orange"] # # Create <N_BRICKS> blocks, filling the full width of the game window. # Each brick is as high as a quarter of its width, so they remain # proportional as the number of blocks or the screen size changes. # # The brick colours cycle through <BRICK_COLOURS> # bricks = [] for n_brick in range(N_BRICKS): brick = Brick( GAME_WINDOW.left + (n_brick * BRICK_W), GAME_WINDOW.top, BRICK_W, BRICK_H ) brick.colour = BRICK_COLOURS[n_brick % len(BRICK_COLOURS)] bricks.append(brick) def draw(): # # Clear the screen, draw the game window and place the ball at its current position # screen.clear() # # Draw the game window and a frame around it # screen.draw.filled_rect(GAME_WINDOW, GAME_WINDOW.background_colour) screen.draw.rect(GAME_WINDOW.inflate(+2, +2), GAME_WINDOW.frame_colour) # # Show the current status, centred inside the status area # ## DELETE --> screen.draw.text("Score: %d" % game.score, center=STATUS_DISPLAY.center) screen.draw.text( "Score: %d" % game.score, left=STATUS_DISPLAY.left + 4, centery=STATUS_DISPLAY.centery ) screen.draw.text( "Status: %s" % game.status, right=STATUS_DISPLAY.right - 4, centery=STATUS_DISPLAY.centery ) screen.draw.filled_rect(ball, ball.colour) screen.draw.filled_rect(bat, bat.colour) for brick in bricks: screen.draw.filled_rect(brick, brick.colour) def on_mouse_move(pos): # # Make the bat follow the horizontal movement of the mouse. # Ensure that the bat does not move outside the game window. # x, y = pos bat.centerx = x bat.clamp_ip(GAME_WINDOW) def update(): # # Move the ball along its current direction at its current speed # dx, dy = ball.direction ball.move_ip(ball.speed * dx, ball.speed * dy) # # Bounce the ball off the bat # if ball.colliderect(bat): ball.direction = dx, -dy # # If the ball hits a brick, kill that brick and # bounce the ball. # to_kill = ball.collidelist(bricks) if to_kill >= 0: bricks.pop(to_kill) game.score += 1 ball.direction = dx, -dy # # Bounce the ball off the left or right walls # if ball.right >= GAME_WINDOW.right or ball.left <= GAME_WINDOW.left: ball.direction = -dx, dy # # If the ball hits the bottom wall, you lose # if ball.bottom >= GAME_WINDOW.bottom: exit() # # Bounce the ball off the top wall # if ball.top <= GAME_WINDOW.top: ball.direction = dx, -dy # # If there are no bricks left, you win # if not bricks: exit() |
What’s happening?¶
- We add a
status
attribute to the existinggame
object is initially set to “Running”. - We add the game status to the status display below the game, pushing the score to the left to make room.
Step 5b: Have the game pause before starting¶

The Code¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | WIDTH = 640 HEIGHT = 480 class Game(object): pass game = Game() game.score = 0 ## DELETE --> game.status = "Running" game.status = "Starting" # # Create a status display, as wide as the screen and 60 pixels high. # It's placed at the bottom of the screen # STATUS_DISPLAY = ZRect(0, HEIGHT - 60, WIDTH, 60) # # Create a game window which is as wide as the screen but allows # a status display underneath # GAME_WINDOW = ZRect(0, 0, WIDTH, HEIGHT - STATUS_DISPLAY.height - 1) GAME_WINDOW.background_colour = "darkblue" GAME_WINDOW.frame_colour = "white" class Ball(ZRect): pass # # The ball is a red square halfway across the game window # ball = Ball((0, 0), (30, 30)) ball.center = GAME_WINDOW.center ball.colour = "red" # # The ball moves one step right and one step down each tick # ball.direction = 1, 1 # # The ball moves at a speed of 3 steps each tick # ball.speed = 3 class Bat(ZRect): pass # # The bat is a green oblong which starts just along the bottom # of the game window and halfway across. # BAT_W = 150 BAT_H = 15 bat = Bat(GAME_WINDOW.centerx, GAME_WINDOW.bottom - BAT_H, BAT_W, BAT_H) bat.colour = "green" class Brick(ZRect): pass # # The brick is a rectangle one eight the width of the game window # and one quarter high as it is wide. # N_BRICKS = 8 BRICK_W = GAME_WINDOW.width / N_BRICKS BRICK_H = BRICK_W / 4 BRICK_COLOURS = ["purple", "lightgreen", "lightblue", "orange"] # # Create <N_BRICKS> blocks, filling the full width of the game window. # Each brick is as high as a quarter of its width, so they remain # proportional as the number of blocks or the screen size changes. # # The brick colours cycle through <BRICK_COLOURS> # bricks = [] for n_brick in range(N_BRICKS): brick = Brick( GAME_WINDOW.left + (n_brick * BRICK_W), GAME_WINDOW.top, BRICK_W, BRICK_H ) brick.colour = BRICK_COLOURS[n_brick % len(BRICK_COLOURS)] bricks.append(brick) def draw(): # # Clear the screen, draw the game window and place the ball at its current position # screen.clear() # # Draw the game window and a frame around it # screen.draw.filled_rect(GAME_WINDOW, GAME_WINDOW.background_colour) screen.draw.rect(GAME_WINDOW.inflate(+2, +2), GAME_WINDOW.frame_colour) # # Show the current status, centred inside the status area # screen.draw.text( "Score: %d" % game.score, left=STATUS_DISPLAY.left + 4, centery=STATUS_DISPLAY.centery ) screen.draw.text( "Status: %s" % game.status, right=STATUS_DISPLAY.right - 4, centery=STATUS_DISPLAY.centery ) if game.status == "Running": ► screen.draw.filled_rect(ball, ball.colour) ► screen.draw.filled_rect(bat, bat.colour) ► for brick in bricks: ► screen.draw.filled_rect(brick, brick.colour) def on_mouse_move(pos): # # Make the bat follow the horizontal movement of the mouse. # Ensure that the bat does not move outside the game window. # if game.status == "Running": ► x, y = pos ► bat.centerx = x ► bat.clamp_ip(GAME_WINDOW) def on_key_down(key): if game.status == "Starting": if key == keys.SPACE: game.status = "Running" def update(): if game.status == "Running": # # Move the ball along its current direction at its current speed # ► dx, dy = ball.direction ► ball.move_ip(ball.speed * dx, ball.speed * dy) # # Bounce the ball off the bat # ► if ball.colliderect(bat): ► ball.direction = dx, -dy # # If the ball hits a brick, kill that brick and # bounce the ball. # ► to_kill = ball.collidelist(bricks) ► if to_kill >= 0: ► bricks.pop(to_kill) ► game.score += 1 ► ball.direction = dx, -dy # # Bounce the ball off the left or right walls # ► if ball.right >= GAME_WINDOW.right or ball.left <= GAME_WINDOW.left: ► ball.direction = -dx, dy # # If the ball hits the bottom wall, you lose # ► if ball.bottom >= GAME_WINDOW.bottom: ► exit() # # Bounce the ball off the top wall # ► if ball.top <= GAME_WINDOW.top: ► ball.direction = dx, -dy # # If there are no bricks left, you win # ► if not bricks: ► exit() |
What’s happening?¶
- We make sure that certain pieces of code only run when the game is Running, not when it’s waiting to start.
- We add one new piece of functionality: if the game is Starting (ie waiting to start) we wait until the Space key is pressed and then change the status to Running, causing the other parts of the program to kick in.
Change it around¶
- Choose a different key to make the game start
- Display a message in the status window to indicate which key should be pressed.
Step 5c: Allow the game to restart once it’s complete¶

The Code¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | WIDTH = 640 HEIGHT = 480 class Game(object): pass game = Game() game.score = 0 game.status = "Starting" # # Create a status display, as wide as the screen and 60 pixels high. # It's placed at the bottom of the screen # STATUS_DISPLAY = ZRect(0, HEIGHT - 60, WIDTH, 60) # # Create a game window which is as wide as the screen but allows # a status display underneath # GAME_WINDOW = ZRect(0, 0, WIDTH, HEIGHT - STATUS_DISPLAY.height - 1) GAME_WINDOW.background_colour = "darkblue" GAME_WINDOW.frame_colour = "white" class Ball(ZRect): pass # # The ball is a red square halfway across the game window # ball = Ball((0, 0), (30, 30)) ball.center = GAME_WINDOW.center ball.colour = "red" # # The ball moves one step right and one step down each tick # ball.direction = 1, 1 # # The ball moves at a speed of 3 steps each tick # ball.speed = 3 class Bat(ZRect): pass # # The bat is a green oblong which starts just along the bottom # of the game window and halfway across. # BAT_W = 150 BAT_H = 15 bat = Bat(GAME_WINDOW.centerx, GAME_WINDOW.bottom - BAT_H, BAT_W, BAT_H) bat.colour = "green" class Brick(ZRect): pass # # The brick is a rectangle one eight the width of the game window # and one quarter high as it is wide. # N_BRICKS = 8 BRICK_W = GAME_WINDOW.width / N_BRICKS BRICK_H = BRICK_W / 4 BRICK_COLOURS = ["purple", "lightgreen", "lightblue", "orange"] # # The brick colours cycle through <BRICK_COLOURS> # bricks = [] def reset_game(): # # At the beginning of the game, centre the ball on the game window # and position the bat halfway across the game window and and sliding # along its bottom edge. # ball.center = GAME_WINDOW.center bat.center = (GAME_WINDOW.centerx, GAME_WINDOW.bottom - BAT_H) # # Create <N_BRICKS> blocks, filling the full width of the game window. # Each brick is as high as a quarter of its width, so they remain # proportional as the number of blocks or the screen size changes. # bricks.clear() ► for n_brick in range(N_BRICKS): ► brick = Brick( ► GAME_WINDOW.left + (n_brick * BRICK_W), GAME_WINDOW.top, ► BRICK_W, BRICK_H ► ) ► brick.colour = BRICK_COLOURS[n_brick % len(BRICK_COLOURS)] ► bricks.append(brick) def draw(): # # Clear the screen, draw the game window and place the ball at its current position # screen.clear() # # Draw the game window and a frame around it # screen.draw.filled_rect(GAME_WINDOW, GAME_WINDOW.background_colour) screen.draw.rect(GAME_WINDOW.inflate(+2, +2), GAME_WINDOW.frame_colour) # # Fill in the status window # if game.status == "Starting": # # If the game is waiting to start indicate how to start # screen.draw.text("Press SPACE to start", center=STATUS_DISPLAY.center) elif game.status == "Running": # # If the game is running show the current status, centred inside the status area # ► screen.draw.text( ► "Score: %d" % game.score, ► left=STATUS_DISPLAY.left + 4, ► centery=STATUS_DISPLAY.centery ► ) ► screen.draw.text( ► "Status: %s" % game.status, ► right=STATUS_DISPLAY.right - 4, ► centery=STATUS_DISPLAY.centery ► ) # # Fill in the gameplay window # if game.status == "Running": screen.draw.filled_rect(ball, ball.colour) screen.draw.filled_rect(bat, bat.colour) for brick in bricks: screen.draw.filled_rect(brick, brick.colour) def on_mouse_move(pos): # # Make the bat follow the horizontal movement of the mouse. # Ensure that the bat does not move outside the game window. # if game.status == "Running": x, y = pos bat.centerx = x bat.clamp_ip(GAME_WINDOW) def on_key_down(key): if game.status == "Starting": if key == keys.SPACE: reset_game() game.status = "Running" def update(): if game.status == "Running": # # Move the ball along its current direction at its current speed # dx, dy = ball.direction ball.move_ip(ball.speed * dx, ball.speed * dy) # # Bounce the ball off the bat # if ball.colliderect(bat): ball.direction = dx, -dy # # If the ball hits a brick, kill that brick and # bounce the ball. # to_kill = ball.collidelist(bricks) if to_kill >= 0: bricks.pop(to_kill) game.score += 1 ball.direction = dx, -dy # # Bounce the ball off the left or right walls # if ball.right >= GAME_WINDOW.right or ball.left <= GAME_WINDOW.left: ball.direction = -dx, dy # # If the ball hits the bottom wall, you lose # if ball.bottom >= GAME_WINDOW.bottom: ## DELETE --> exit() game.status = "Starting" # # Bounce the ball off the top wall # if ball.top <= GAME_WINDOW.top: ball.direction = dx, -dy # # If there are no bricks left, you win # if not bricks: ## DELETE --> exit() game.status = "Starting" |
What’s happening?¶
- We move into its own function all the changes needed to restart the game.
- When Space is pressed, we reset the game before switching to Running mode
- When the game completes (win or lose) we switch back to Starting mode rather than dropping straight out.
Change it around¶
- Add a “Kill” key which, when pressed, aborts the game and waits to restart.
- In the status line, display the most recent result (win or lose) before restarting.