Step 3: Add bricks for the ball to knock out

In this step we’re going to put a row of “bricks” along the top of the game screen, each one a different colour. When the ball hits a brick, the brick disappears. When all the bricks have been knocked out, the player has won.

Step 3a: Put one brick on the screen

At the end of Step 3a you will see: one blue brick attached to the ceiling. The ball will ignore it.

../_images/step-s3a.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
WIDTH = 640
HEIGHT = 480

class Ball(ZRect): pass
#
# The ball is a red square halfway across the game screen
#
ball = Ball(0, 0, 30, 30)
ball.center = WIDTH / 2, HEIGHT / 2
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 screen and halfway across.
#
BAT_W = 150
BAT_H = 15
bat = Bat(WIDTH / 2, HEIGHT - 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 screen
# and one quarter high as it is wide.
#
N_BRICKS = 8
BRICK_W = WIDTH / N_BRICKS
BRICK_H = BRICK_W / 4
#
# Create just one brick for now, at the top left of othe screen
#
brick = Brick(0, 0, BRICK_W, BRICK_H)
brick.colour = "blue"

def draw():
    #
    # Clear the screen and place the ball at its current position
    #
    screen.clear()
    screen.draw.filled_rect(ball, ball.colour)
    screen.draw.filled_rect(bat, bat.colour)
    screen.draw.filled_rect(brick, brick.colour)

def on_mouse_move(pos):
    #
    # Make the bat follow the horizontal movement of the mouse.
    #
    x, y = pos
    bat.centerx = x

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

    #
    # Bounce the ball off the left or right walls
    #
    if ball.right >= WIDTH or ball.left <= 0:
        ball.direction = -dx, dy

    #
    # If the ball hits the bottom of the screen, you lose
    #
    if ball.bottom >= HEIGHT:
        exit()

    #
    # Bounce the ball off the top wall
    #
    if ball.top <= 0:
        ball.direction = dx, -dy

What’s happening?

  • The brick is another PyGame rectangle. At first, we’re only going to create one of it. Later, we’ll use it like a potato print to generate several bricks in a row.
  • Since we want the bricks to fit evenly across the screen, we decide how many we want (N_BRICKS) and then divide the screen width (WIDTH) by that many to get the width of each brick (BRICK_W). We could have done it the other way round: decide how wide our brick should be and then seen how many could fit across the screen.
  • To keep the brick proportions similar if we change the screen size, we make the height of each brick (BRICK_H) a quarter of its width (BRICK_W).
  • To keep things simple in this step, we just create one brick at the top-left corner of the screen. In the next step we’ll get rid of this one brick and create a row of them.

Change it around

  • Change the colour of the brick

  • Make the brick appear at the right of the screen

    Hint: You can position the left-hand edge of the brick one brick-width away (BRICK_W) from the right-hand edge of the screen (WIDTH).

Step 3b: Put eight bricks across the screen

At the end of Step 3a you will see: eight bricks attached to the ceiling, each in a different colour. The ball will still ignore them.

../_images/step-s3b.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
WIDTH = 640
HEIGHT = 480

class Ball(ZRect): pass
#
# The ball is a red square halfway across the game screen
#
ball = Ball(0, 0, 30, 30)
ball.center = WIDTH / 2, HEIGHT / 2
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 screen and halfway across.
#
BAT_W = 150
BAT_H = 15
bat = Bat(WIDTH / 2, HEIGHT - 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 screen
# and one quarter high as it is wide.
#
N_BRICKS = 8
BRICK_W = WIDTH / N_BRICKS
BRICK_H = BRICK_W / 4
BRICK_COLOURS = ["purple", "lightgreen", "lightblue", "orange"]
#
# Create <N_BRICKS> blocks, filling the full width of the screen.
# 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):
## DELETE --> brick = Brick(0, 0, BRICK_W, BRICK_H)
    brick = Brick(n_brick * BRICK_W, 0, BRICK_W, BRICK_H)
## DELETE --> brick.colour = "blue"
    brick.colour = BRICK_COLOURS[n_brick % len(BRICK_COLOURS)]
    bricks.append(brick)

def draw():
    #
    # Clear the screen and place the ball at its current position
    #
    screen.clear()
    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.
    #
    x, y = pos
    bat.centerx = x

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

    #
    # Bounce the ball off the left or right walls
    #
    if ball.right >= WIDTH or ball.left <= 0:
        ball.direction = -dx, dy

    #
    # If the ball hits the bottom of the screen, you lose
    #
    if ball.bottom >= HEIGHT:
        exit()

    #
    # Bounce the ball off the top wall
    #
    if ball.top <= 0:
        ball.direction = dx, -dy

What’s happening?

There’s quite a bit going on in this step: we’re multiplying our one brick by eight and looping round a list of possible colours.

  • BRICK_COLOURS is a sequence of colour names which is called a “list” in Python. You can add as many colour names to it as you like. The list of possible colours is quite long (cf https://en.wikipedia.org/wiki/X11_color_names) but you can guess some obvious ones, and there’s a shorter list in the PyGame Zero section at the end of these worksheets.

  • From lines 46 to 52 we are creating a list of bricks. Each brick is made from the Brick class we created previously, but each one has a different position and a different colour.

  • Some of the lines start with “## DELETE”; these are the lines we used to create one brick on the previous step which we’re now going to delete.

  • A “for loop” in Python starts with a “for … in …:” and then every line which should happen as part of the same loop is intended (pushed in to the right) by the same amount. As soon as Python sees a line which is back out to the left again, it treats it as separate from the loop.

  • Line 51 selects the brick colour by picking the next colour from the list of colours and then looping back to the beginning when it reaches the end. That’s what the “% len(…)” part is doing. This means that you can have as many or as few colours as you like with as many or as few bricks as you like without having to match the number of colours with the number of bricks.

    The “%” thing is called a modulo operator. It gives you what’s left over when you divide something by something else.

  • Lines 61 to 63 are using the list of bricks and drawing them, each at its own position and with its own colour.

Change it around

  • Change the number of bricks

  • Change how high each brick is

  • Add or remove some colours

  • Make each brick a randomly-chosen colour

    Hint: You will need to import the random module and use the random.choice function.

Step 3c: Have the ball knock out bricks until none is left

At the end of Step 3c you will see: the ball knock out the bricks it hits

../_images/step-s3c.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
WIDTH = 640
HEIGHT = 480

class Ball(ZRect): pass
#
# The ball is a red square halfway across the game screen
#
ball = Ball(0, 0, 30, 30)
ball.center = WIDTH / 2, HEIGHT / 2
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 screen and halfway across.
#
BAT_W = 150
BAT_H = 15
bat = Bat(WIDTH / 2, HEIGHT - 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 screen
# and one quarter high as it is wide.
#
N_BRICKS = 8
BRICK_W = WIDTH / N_BRICKS
BRICK_H = BRICK_W / 4
BRICK_COLOURS = ["purple", "lightgreen", "lightblue", "orange"]
#
# Create <N_BRICKS> blocks, filling the full width of the screen.
# 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(n_brick * BRICK_W, 0, BRICK_W, BRICK_H)
    brick.colour = BRICK_COLOURS[n_brick % len(BRICK_COLOURS)]
    bricks.append(brick)

def draw():
    #
    # Clear the screen and place the ball at its current position
    #
    screen.clear()
    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.
    #
    x, y = pos
    bat.centerx = x

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)
        ball.direction = dx, -dy

    #
    # Bounce the ball off the left or right walls
    #
    if ball.right >= WIDTH or ball.left <= 0:
        ball.direction = -dx, dy

    #
    # If the ball hits the bottom of the screen, you lose
    #
    if ball.bottom >= HEIGHT:
        exit()

    #
    # Bounce the ball off the top wall
    #
    if ball.top <= 0:
        ball.direction = dx, -dy

    #
    # If there are no bricks left, you win
    #
    if not bricks:
        exit()

What’s happening?

  • collidelist checks whether one rectangle has collided with any of a list of rectangles. It returns the position in the list of the rectangle which was the point of collision. If there was no collision, it returns -1.

    Since our ball is a rectangle and our bricks are a list of rectangles, this gives us an easy way to work out which brick was hit by the ball (if any). If one is hit (to_kill >= 0) we drop that brick from our list (bricks.pop) and bounce the ball.

    Now that the brick is not in the list, it will not be drawn the next time we redraw the screen and it will appear to have knocked out.

  • The games ends in success when there are no bricks left. The line if not bricks: is Python shorthand for saying: if the list of bricks is empty – ie if there are no bricks left.

Change it around

  • Have the ball change colour according to which brick it’s knocked out

    Hint: When you detect the collision between the ball and one of the bricks, before popping the brick from the list of bricks, copy its colour to the ball’s colour.