Flappy Square in Python
Pygame Online · 153 lines · Up or Space to flap
Flap through the gaps between the pipes for as long as you can.
How it works
Gravity in two lines
Every frame gravity adds a little to the downward speed, and the speed moves the square. A flap sets the speed to a fixed upward value. That is the whole physics of the game.
speed_y += GRAVITY bird_y += speed_y
Pipes are dictionaries
A pipe is just where it is (x), where its gap is (gap_y) and whether you scored it. Each new gap is near the last one, so the course stays possible.
def add_pipe(): """A new pipe at the right edge, its gap not too far from the last one.""" if pipes: gap_y = pipes[-1]["gap_y"] + random.randint(-90, 90) else: gap_y = HEIGHT // 2 gap_y = max(70, min(gap_y, GROUND - 70)) pipes.append({"x": WIDTH, "gap_y": gap_y, "scored": False})
From a gap to two rectangles
pipe_rects turns a pipe into the top and bottom rectangles, which are used both to draw it and to check for a crash.
def pipe_rects(pipe): """The top and bottom parts of a pipe.""" top = pygame.Rect(pipe["x"], 0, PIPE_WIDTH, pipe["gap_y"] - GAP // 2) bottom_y = pipe["gap_y"] + GAP // 2 bottom = pygame.Rect(pipe["x"], bottom_y, PIPE_WIDTH, GROUND - bottom_y) return top, bottom
Make it yours
Change the code above and press Run again. Some ideas:
- Make the gaps narrower as the score rises.
- Change
GRAVITYandFLAPto make the square floatier or heavier. - Add coins between the pipes that are worth an extra point.
- Draw a proper bird with circles and a triangle for the beak.
The whole program
Plain pygame: it runs the same on a computer with Python and pygame installed.
import random import pygame pygame.init() WIDTH, HEIGHT = 480, 360 GROUND = HEIGHT - 24 # where the ground starts screen = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption("Flappy Square") clock = pygame.time.Clock() font = pygame.font.Font(None, 30) score_font = pygame.font.Font(None, 64) big_font = pygame.font.Font(None, 56) BG = (16, 18, 22) GROUND_COLOR = (34, 38, 46) YELLOW = (255, 212, 59) BLUE = (48, 105, 152) LIGHT_BLUE = (90, 162, 224) RED = (235, 95, 85) TEXT = (230, 232, 235) GRAVITY = 0.35 FLAP = -6.2 # upward speed after a flap GAP = 110 # space between the top and the bottom pipe PIPE_WIDTH = 52 PIPE_SPEED = 3 PIPE_EVERY = 80 # frames between new pipes FLAP_KEYS = (pygame.K_UP, pygame.K_SPACE, pygame.K_RETURN) bird = pygame.Rect(100, 150, 24, 24) def new_game(): global bird_y, speed_y, pipes, pipe_timer, score, state bird_y = 150 speed_y = 0 bird.y = bird_y pipes = [] pipe_timer = 0 score = 0 state = "ready" # "ready", "playing" or "over" def add_pipe(): """A new pipe at the right edge, its gap not too far from the last one.""" if pipes: gap_y = pipes[-1]["gap_y"] + random.randint(-90, 90) else: gap_y = HEIGHT // 2 gap_y = max(70, min(gap_y, GROUND - 70)) pipes.append({"x": WIDTH, "gap_y": gap_y, "scored": False}) def pipe_rects(pipe): """The top and bottom parts of a pipe.""" top = pygame.Rect(pipe["x"], 0, PIPE_WIDTH, pipe["gap_y"] - GAP // 2) bottom_y = pipe["gap_y"] + GAP // 2 bottom = pygame.Rect(pipe["x"], bottom_y, PIPE_WIDTH, GROUND - bottom_y) return top, bottom def draw_text(text, text_font, color, center): image = text_font.render(text, True, color) screen.blit(image, image.get_rect(center=center)) new_game() best = 0 wait = 0 # frames until a new game may start 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 in FLAP_KEYS: if state in ("ready", "playing"): state = "playing" speed_y = FLAP elif wait == 0: new_game() if wait > 0: wait -= 1 if state == "playing": speed_y += GRAVITY bird_y += speed_y if bird_y < 0: # the top of the screen is a ceiling bird_y = 0 speed_y = 0 bird_y = min(bird_y, GROUND - bird.height) # and the ground is a floor bird.y = round(bird_y) pipe_timer -= 1 if pipe_timer <= 0: add_pipe() pipe_timer = PIPE_EVERY for pipe in pipes: pipe["x"] -= PIPE_SPEED if not pipe["scored"] and pipe["x"] + PIPE_WIDTH < bird.left: pipe["scored"] = True score += 1 # Forget pipes that have left the screen. pipes = [pipe for pipe in pipes if pipe["x"] > -PIPE_WIDTH] crashed = bird.bottom >= GROUND for pipe in pipes: top, bottom = pipe_rects(pipe) if bird.colliderect(top) or bird.colliderect(bottom): crashed = True if crashed: state = "over" best = max(best, score) wait = 40 elif state == "over" and bird.bottom < GROUND: # After a crash the square drops to the ground. speed_y = max(speed_y, 0) + GRAVITY bird_y = min(bird_y + speed_y, GROUND - bird.height) bird.y = round(bird_y) screen.fill(BG) for pipe in pipes: top, bottom = pipe_rects(pipe) pygame.draw.rect(screen, BLUE, top) pygame.draw.rect(screen, BLUE, bottom) # A lighter band at each end of the gap. pygame.draw.rect(screen, LIGHT_BLUE, (top.x, top.bottom - 12, PIPE_WIDTH, 12)) pygame.draw.rect(screen, LIGHT_BLUE, (bottom.x, bottom.y, PIPE_WIDTH, 12)) pygame.draw.rect(screen, GROUND_COLOR, (0, GROUND, WIDTH, HEIGHT - GROUND)) pygame.draw.line(screen, LIGHT_BLUE, (0, GROUND), (WIDTH, GROUND), 2) pygame.draw.rect(screen, RED if state == "over" else YELLOW, bird, border_radius=5) pygame.draw.rect(screen, BG, (bird.right - 9, bird.y + 6, 4, 4)) # an eye if state == "ready": draw_text("Flappy Square", big_font, YELLOW, (WIDTH // 2, 90)) draw_text("Press Up or Space to flap", font, TEXT, (WIDTH // 2, 250)) else: draw_text(str(score), score_font, TEXT, (WIDTH // 2, 40)) if state == "over": panel = pygame.Rect(0, 0, 340, 140) panel.center = (WIDTH // 2, HEIGHT // 2) pygame.draw.rect(screen, BG, panel, border_radius=12) pygame.draw.rect(screen, BLUE, panel, 2, border_radius=12) draw_text("Game over", big_font, YELLOW, (panel.centerx, panel.y + 38)) draw_text(f"Score {score} Best {best}", font, TEXT, (panel.centerx, panel.y + 80)) draw_text("Press Up or Space to try again", font, TEXT, (panel.centerx, panel.y + 112)) pygame.display.flip() clock.tick(60) pygame.quit()