Editor

Score, lives and game states

Pygame fundamentals · Lesson 5 of 7

A program becomes a game when it keeps score, can be lost and can start again.

One variable says what the game is doing

Most games are in one of a few states: waiting to start, playing, or over. Keep the current one in a variable such as state, and let each part of the loop check it: the stars move only while state == "playing", and the "Game over" panel is drawn only when it is over.

Without states, a game starts the moment the program does and ends by closing the window.

Starting again

Everything that must be fresh for a new game goes in one function, new_game(). It sets the score, the lives and the stars back to the start. global lets the function change those variables, which live outside it.

The best score stays outside new_game(), so it survives from game to game.

import random
import pygame

pygame.init()
screen = pygame.display.set_mode((480, 320))
clock = pygame.time.Clock()
font = pygame.font.Font(None, 30)
big_font = pygame.font.Font(None, 64)
basket = pygame.Rect(210, 290, 70, 14)


def new_game():
    global score, lives, stars
    score = 0
    lives = 3
    stars = [pygame.Rect(random.randint(0, 460), -20, 18, 18)]


new_game()
best = 0
state = "ready"  # "ready", "playing" or "over"

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
            if state != "playing":
                new_game()
                state = "playing"

    if state == "playing":
        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT]:
            basket.x -= 7
        if keys[pygame.K_RIGHT]:
            basket.x += 7
        basket.clamp_ip(screen.get_rect())
        for star in stars:
            star.y += 3 + score // 5
            if star.colliderect(basket):
                score += 1
                star.topleft = (random.randint(0, 460), -20)
            elif star.top > 320:
                lives -= 1
                star.topleft = (random.randint(0, 460), -20)
        if score >= 5 * len(stars):  # another star every 5 points
            stars.append(pygame.Rect(random.randint(0, 460), -60, 18, 18))
        if lives == 0:
            state = "over"
            best = max(best, score)

    screen.fill((16, 18, 22))
    for star in stars:
        pygame.draw.ellipse(screen, (255, 212, 59), star)
    pygame.draw.rect(screen, (90, 162, 224), basket, border_radius=5)
    screen.blit(font.render(f"Score {score}   Lives {lives}   Best {best}", True, (230, 232, 235)), (12, 10))
    if state != "playing":
        message = "Catch the stars" if state == "ready" else "Game over"
        title = big_font.render(message, True, (255, 212, 59))
        screen.blit(title, title.get_rect(center=(240, 140)))
        hint = font.render("Press Space to play", True, (230, 232, 235))
        screen.blit(hint, hint.get_rect(center=(240, 190)))
    pygame.display.flip()
    clock.tick(60)

pygame.quit()

Make it harder as it goes

Two lines make this game get harder: the stars fall faster as the score grows (3 + score // 5), and a new star joins every five points. Small rules like these keep a game interesting for longer than a fixed speed does.

Your turn

Change the programs above in the game panel, then press Run.

  • Add a red "bomb" that falls like a star: catching it costs a life.
  • Show "Level 2", "Level 3" and so on, going up every 10 points.
  • Pause the game when P is pressed, with a "Paused" message.

Flappy Square has the same three states, a best score and a one-key restart. Open Flappy Square.