Step 6: Make the game harder and add a scoreboard

At this point, the “game” isn’t really much of a game: as long as you don’t lose control of the mouse, you’re pretty much guaranteed to knock all the blocks out. This also means that the score doesn’t really have much meaning since everyone who finishes will score the same!

In this step, we’re going to add several gameplay elements:

  • If right mouse key is pressed, the bat and ball will both grow smaller and the ball will become faster. They can right-click several times up to a limit. The smaller/faster the bat/ball are, the higher the score when a brick is knocked out.
  • If the left mouse key is pressed, the speed/sizes will move back towards what they were at the start of the game.
  • We will indicate the score on each brick as well as keeping track of the running score at the bottom of the screen.
  • At the end of the round, we’ll have a separate screen showing a high score list. (For the moment, without names: just the scores).

Step 6a: Show the scores on each brick

../_images/step-s6a.gif

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
193
194
195
WIDTH = 640
HEIGHT = 480

class Game(object): pass
game = Game()
game.score = 0
game.status = "Starting"
game.score_per_brick = 1

#
# 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)

    game.score = 0

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)
            screen.draw.textbox("%s" % game.score_per_brick, brick)

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)
            ## DELETE --> game.score += 1
            game.score += game.score_per_brick
            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:
            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:
            game.status = "Starting"

What’s happening?

This is the easiest of the parts of step 6: we’re simply using the game object to keep track of how much each brick will currently score when it’s knocked out, and we’re showing that number on each brick. When a brick is knocked out, the game score increases by that brick’s score. (Which is always 1!)

Change it around

  • Give each brick colour a different score.

    Hint: Use a dictionary to map brick colours to scores

Step 6b: Make the game harder / easier

../_images/step-s6b.gif

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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
WIDTH = 640
HEIGHT = 480

#
# Create a series of difficulty levels, specifying
# the speed & size of the ball and the width of the bat.
#
levels = [
    {"speed" : 3, "ball_size" : (30, 30), "bat_width" : 150},
    {"speed" : 4, "ball_size" : (24, 24), "bat_width" : 100},
    {"speed" : 5, "ball_size" : (12, 12), "bat_width" : 72},
]

class Game(object): pass
game = Game()
game.score = 0
game.status = "Starting"
game.score_per_brick = 1
game.current_level = 0

#
# 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
#
## DELETE --> ball = Ball((0, 0), (30, 30))
## DELETE --> ball.center = GAME_WINDOW.center
ball_size = levels[game.current_level]['ball_size']
ball = Ball(GAME_WINDOW.center, ball_size)
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
#
## DELETE --> ball.speed = 3
ball.speed = levels[game.current_level]['speed']

class Bat(ZRect): pass
#
# The bat is a green oblong which starts just along the bottom
# of the game window and halfway across.
#
## DELETE --> BAT_W = 150
## DELETE --> BAT_H = 15
BAT_W = levels[game.current_level]['bat_width']
BAT_H = BAT_W / 10
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)

    game.score = 0
    game.current_level = 0
    set_up_level()

def set_up_level():
    level = levels[game.current_level]
    ball.speed = level['speed']
    ball.size = level['ball_size']
    bat.width = level['bat_width']
    bat.height = bat.width / 10
    game.score_per_brick = 1 + game.current_level

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)
            screen.draw.textbox("%s" % game.score_per_brick, brick)

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_mouse_down(button):
    #
    # If the right button is pressed make the game more difficult
    # by shrinking the bat and the ball and increasing the speed.
    # If the left button is pressed, make it easier again.
    # The score on each brick goes up or down corresponding to
    # how much harder / easier the game is.
    #
    if button == mouse.RIGHT:
        if game.current_level < len(levels):
            game.current_level += 1
    elif button == mouse.LEFT:
        if game.current_level > 0:
            game.current_level -= 1
    set_up_level ()

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 += game.score_per_brick
            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:
            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:
            game.status = "Starting"

What’s happening?

  • We create three levels of difficulty, specifying for each one how fast the ball moves and how big it is, and how big the bat is. For now, we’re going to the use the level counter as the brick score.

    NB In Python, the first item in a list is Number 0, the second item is Number 1 and so on. There are good reasons for this, but it takes some getting used to.

  • When the mouse buttons are pressed, we go down or up through the levels, not allowing the level to go below zero nor to go beyond the levels we’ve specified. The check if game.current_level < len(levels) checks that we’re not going to go beyond the number of levels we have whatever that number is. We could just have checked for three levels (because that’s how many we know we have). But this way, we can add some more levels of difficulty, or remove some, and the code will work without any other changes.

  • Since we’ll be writing the same code at the beginning of the game and every times a user moves through the levels, we factor it out into its own function, called set_up_level.

Change it around

  • Add more levels and play with the ones which are there
  • Show the current level in the status window along with the score
  • Allow the brick score to vary with the levels
  • Allow use of the up- and down-arrow keys on the keyboard to change levels. Likewise allow the left- and right-arrow keys to control the bat.

Step 6c: Show the top 10 scores

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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
WIDTH = 640
HEIGHT = 480

#
# Create a series of difficulty levels, specifying
# the speed & size of the ball and the width of the bat.
#
levels = [
    {"speed" : 3, "ball_size" : (30, 30), "bat_width" : 150},
    {"speed" : 4, "ball_size" : (24, 24), "bat_width" : 100},
    {"speed" : 5, "ball_size" : (12, 12), "bat_width" : 72},
]

class Game(object): pass
game = Game()
game.score = 0
game.status = "Starting"
game.score_per_brick = 1
game.current_level = 0
game.scoreboard = []

#
# 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_size = levels[game.current_level]['ball_size']
ball = Ball(GAME_WINDOW.center, ball_size)
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 = levels[game.current_level]['speed']

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 = levels[game.current_level]['bat_width']
BAT_H = BAT_W / 10
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)

    game.score = 0
    game.current_level = 0
    set_up_level()

def set_up_level():
    level = levels[game.current_level]
    ball.speed = level['speed']
    ball.size = level['ball_size']
    bat.width = level['bat_width']
    bat.height = bat.width / 10
    game.score_per_brick = 1 + game.current_level

def draw_scoreboard():
    top_10_scores = sorted(game.scoreboard, reverse=True)[:10]
    scoreline_height = GAME_WINDOW.height / 12

    scoreline_box = ZRect(
        GAME_WINDOW.left, GAME_WINDOW.top,
        GAME_WINDOW.width, scoreline_height
    )
    screen.draw.textbox("Top 10 Scores", scoreline_box)

    for n, score in enumerate(top_10_scores):
        scoreline_y_offset = (2 + n) * scoreline_height;
        scoreline_box = ZRect(
            GAME_WINDOW.left, GAME_WINDOW.top + scoreline_y_offset,
            GAME_WINDOW.width, scoreline_height
        )
        screen.draw.textbox("%1d - %s" % (1 + n, score), scoreline_box)

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 == "Starting":
        #
        # If the game is waiting to start, show the current high scoreboard
        #
        draw_scoreboard()

    ## DELETE --> if game.status == "Running":
    elif 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)
            screen.draw.textbox("%s" % game.score_per_brick, brick)

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_mouse_down(button):
    #
    # If the right button is pressed make the game more difficult
    # by shrinking the bat and the ball and increasing the speed.
    # If the left button is pressed, make it easier again.
    # The score on each brick goes up or down corresponding to
    # how much harder / easier the game is.
    #
    if button == mouse.RIGHT:
        if game.current_level < len(levels):
            game.current_level += 1
    elif button == mouse.LEFT:
        if game.current_level > 0:
            game.current_level -= 1
    set_up_level ()

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 += game.score_per_brick
            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:
            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:
            game.scoreboard.append(game.score)
            game.status = "Starting"

What’s happening?

  • As part of the overall game object we set up a scoreboard, initially empty. The scoreboard object is, for the moment, a Python list to which we will add the score as each game completes.
  • If the game is waiting to start (ie the status is “Starting”) then show the scoreboard in place of the gameplay window. We’re keeping this simple: since we know there will be no more than 10 entries, we’re dividing the window into 12 slices, the bottom 10 of which will contain the scores.
  • Finally, when each game completes – when no bricks remain – then take the score at that point and add it to the list of scores.

Change it around

  • Do a Top 5 or a Top 20 rather than a Top 10

    Hint: Within the draw_scoreboard function, create a variable which specifies how many scores you want to show; use that, suitably adjusted, whenever an exact number appears through the rest of the function.

  • Keep track of the time as well as the score to provide a tie-breaker

    Hint: Instead of holding the score as a number in the scoreboard object, hold a 2-tuple instead containing the score and the number of seconds.