Skip to the content.

High-level Game Structure

Let’s take a crack at piecing together a complete pygame with a

  1. start screen,
  2. game play mode with scorekeeping and a lose condition, and
  3. an end screen (with the option for replay).
'''
Block catchy game with all the bells and whistles(?), maybe
'''

import sys
import random
import pygame

# CONSTANTS
SCREEN_WIDTH = 1200
SCREEN_HEIGHT = 800
FPS = 60
BTN_MARGIN = 5

CATCHER_WIDTH = 100
CATCHER_HEIGHT = 25
CATCHER_SPEED = 10
CATCHER_START_HEALTH = 2

OBJ_SPAWN_CHANCE = 0.2
CHANCE_SPAWN_IS_GOOD = 0.25
OBJ_SPEED = 5

BAD_OBJECT_WIDTH = 10
BAD_OBJECT_HEIGHT = 10
BAD_OBJECT_DMG = 1

GOOD_OBJECT_WIDTH = 10
GOOD_OBJECT_HEIGHT = 10
BAD_OBJECT_WIDTH = 10
BAD_OBJECT_HEIGHT = 10

RED = [255, 0, 0]
LIGHT_GREY = [230, 230, 230]
BLACK = [0, 0, 0]
BLUE = [0,0,230]
WHITE = [255, 255, 255]
YELLOW = [255, 229, 153]

# Initial setup
pygame.init()
screen = pygame.display.set_mode([SCREEN_WIDTH, SCREEN_HEIGHT])
pygame.display.set_caption("EXTREME BLOCK CATCHING")
clock = pygame.time.Clock()

game_screens = ['title', 'play', 'end']
cur_game_screen = 'title'

title_bg_color = BLACK
play_bg_color = LIGHT_GREY
end_bg_color = BLACK
button_color = YELLOW

title_font = pygame.font.SysFont('impact', 64)
button_font = pygame.font.SysFont('impact', 18)
game_info_font = pygame.font.SysFont('impact', 24)
################################################################################
# Title screen components
################################################################################
title_screen_bg_color = BLACK
# Title text
title_surface = title_font.render('2 FAST 2 CATCH BLOCKS', True, WHITE)
title_rect = title_surface.get_rect()
title_rect.x = (SCREEN_WIDTH / 2) - (title_rect.width / 2) # Put rect in middle of screen (perfectly in middle x)
title_rect.y = (SCREEN_HEIGHT / 2) - (title_rect.height)   # Put rect in middle of the screen (sitting on top of horizontal midline)

# Play button
play_btn_txt_surface = button_font.render('>>>> CATCH BLOCKS <<<<', True, BLACK)
# setup play button background
play_btn_bg_rect = play_btn_txt_surface.get_rect()
play_btn_bg_rect.width += 2 * BTN_MARGIN  # Add some margins to the button
play_btn_bg_rect.height += 2 * BTN_MARGIN # Add margin to the button
play_btn_bg_rect.x = title_rect.midbottom[0] - (play_btn_bg_rect.width / 2)
play_btn_bg_rect.y = title_rect.midbottom[1]
# setup play button text
play_btn_txt_rect = play_btn_txt_surface.get_rect()
play_btn_txt_rect.x = play_btn_bg_rect.x + BTN_MARGIN
play_btn_txt_rect.y = play_btn_bg_rect.y + BTN_MARGIN

################################################################################
# Play screen components
################################################################################

catcher_start_x = (SCREEN_WIDTH / 2) - (CATCHER_WIDTH / 2)
catcher_start_y = SCREEN_HEIGHT - CATCHER_HEIGHT - 1
catcher = pygame.Rect([catcher_start_x, catcher_start_y], [CATCHER_WIDTH, CATCHER_HEIGHT])
falling_objects = []

# bomb_img = pygame.image.load('bomb-small.png') # Challenge: use bomb img instead of red squares

health = CATCHER_START_HEALTH
score = 0

################################################################################
# End screen components
################################################################################

# game over
game_over_txt = title_font.render('GAME OVER', True, BLACK)
game_over_rect = game_over_txt.get_rect()
game_over_rect.x = (SCREEN_WIDTH / 2) - (game_over_rect.width / 2) # Put rect in middle of screen (perfectly in middle x)
game_over_rect.y = (SCREEN_HEIGHT / 2) - (game_over_rect.height)   # Put rect in middle of the screen (sitting on top of horizontal midline)

# Replay button
replay_btn_txt = game_info_font.render('PLAY AGAIN?', True, BLACK)
# setup play button background
replay_btn_bg_rect = replay_btn_txt.get_rect()
replay_btn_bg_rect.width += 2 * BTN_MARGIN  # Add some margins to the button
replay_btn_bg_rect.height += 2 * BTN_MARGIN # Add margin to the button
replay_btn_bg_rect.x = game_over_rect.midbottom[0] - (replay_btn_bg_rect.width / 2)
replay_btn_bg_rect.y = game_over_rect.midbottom[1]
# setup play button text
replay_btn_txt_rect = replay_btn_txt.get_rect()
replay_btn_txt_rect.x = replay_btn_bg_rect.x + BTN_MARGIN
replay_btn_txt_rect.y = replay_btn_bg_rect.y + BTN_MARGIN

while True:

    if cur_game_screen == 'title':
        screen.fill(title_bg_color)
        # Draw the title
        screen.blit(title_surface, title_rect)
        # Draw the play button
        pygame.draw.rect(screen, button_color, play_btn_bg_rect)
        screen.blit(play_btn_txt_surface, play_btn_txt_rect)

    elif cur_game_screen == 'play':
        screen.fill(play_bg_color)

        # Use arrow keys to control the catcher
        pressed_keys = pygame.key.get_pressed()
        if pressed_keys[pygame.K_RIGHT] and not pressed_keys[pygame.K_LEFT]:
            catcher.x = (catcher.x + CATCHER_SPEED) % SCREEN_WIDTH
        elif pressed_keys[pygame.K_LEFT] and not pressed_keys[pygame.K_RIGHT]:
            catcher.x = (catcher.x - CATCHER_SPEED) % SCREEN_WIDTH

        # Draw falling rocks
        spawn_new_falling_object = len(falling_objects) == 0 or (random.random() < OBJ_SPAWN_CHANCE)
        if spawn_new_falling_object:
            # Good or bad object?
            obj_good = random.random() < CHANCE_SPAWN_IS_GOOD
            if obj_good:
                # obj is friendly
                new_obj_rect = pygame.Rect([random.randint(GOOD_OBJECT_WIDTH, SCREEN_WIDTH - 2*GOOD_OBJECT_WIDTH), 0], [GOOD_OBJECT_WIDTH, GOOD_OBJECT_HEIGHT])
                falling_objects.append({"rect": new_obj_rect, "good": True})
            else:
                # obj is EVIL
                new_obj_rect = pygame.Rect([random.randint(BAD_OBJECT_WIDTH, SCREEN_WIDTH - 2*BAD_OBJECT_WIDTH), 0], [BAD_OBJECT_WIDTH, BAD_OBJECT_HEIGHT])
                falling_objects.append({"rect": new_obj_rect, "good": False})

        # Draw objects
        on_screen_objs = []
        for obj in falling_objects:
            obj["rect"].y += OBJ_SPEED
            color = BLUE if obj["good"] else RED

            pygame.draw.rect(screen, color, obj["rect"])

            if catcher.colliderect(obj["rect"]):
                # Collision!
                if obj["good"]:
                    score += 1
                else:
                    health -= BAD_OBJECT_DMG
                print("Health =", health, "Score =", score)
            elif obj["rect"].y < SCREEN_HEIGHT:
                on_screen_objs.append(obj)

        falling_objects = on_screen_objs


        # Draw the catcher rectangle
        pygame.draw.rect(screen, BLACK, catcher)

        # Draw game info
        health_surface = game_info_font.render('HEALTH: ' + str(health), True, BLACK)
        health_rect = health_surface.get_rect()
        health_rect.x = SCREEN_WIDTH - health_rect.width - 50
        health_rect.y = 50
        screen.blit(health_surface, health_rect)

        score_surface = game_info_font.render('SCORE: ' + str(score), True, BLACK)
        score_rect = score_surface.get_rect()
        score_rect.x = SCREEN_WIDTH - score_rect.width - 50
        score_rect.y = health_rect.bottom + 50
        screen.blit(score_surface, score_rect)

        # End game state
        if health == 0:
            cur_game_screen = 'end'

    elif cur_game_screen == 'end':
        # screen.fill(end_bg_color)
        screen.blit(game_over_txt, game_over_rect)

        pygame.draw.rect(screen, button_color, replay_btn_bg_rect)
        screen.blit(replay_btn_txt, replay_btn_txt_rect)


    # setup the event loop
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        if cur_game_screen == 'title':
            # Title screen events
            if event.type == pygame.MOUSEBUTTONDOWN:
                mouse_pos = pygame.mouse.get_pos()
                if play_btn_bg_rect.collidepoint(mouse_pos):
                    cur_game_screen = 'play'
        if cur_game_screen == 'end':
            # End game screen events
            if event.type == pygame.MOUSEBUTTONDOWN:
                mouse_pos = pygame.mouse.get_pos()
                if replay_btn_bg_rect.collidepoint(mouse_pos):
                    cur_game_screen = 'title'
                    score = 0
                    health = CATCHER_START_HEALTH
                    falling_objects = []
                    catcher.x = catcher_start_x
                    catcher.y = catcher_start_y

    pygame.display.update()
    clock.tick(FPS)

|>> download block_catchy.py