โ
Retro Flappy Bird โ
๐ฆ Game code, download & prompt info
๐ฅ๏ธ Desktop app (python) โ play it live above, right in your browser, or download it below
Original prompt
Write a complete, runnable Python program using `pygame` that implements a Flappy Bird game. Open a window, a bird with vertical velocity affected by gravity each tick, SPACE makes it flap upward, pipes scroll in from the right with a gap to fly through, and the game ends on collision with a pipe or the ground. Display the score. Include a game loop handling QUIT. Use only primitive shapes - no external assets. Design note (interpret freely): the brutally punishing one-button game - a single precise tap flaps the bird against constant gravity while you thread gaps between scrolling pipes, and any collision is instant death. The sharp, unforgiving difficulty curve is the defining trait. SCORING & PERSISTENT HIGH-SCORE BOARD (REQUIRED): The game MUST award points for gameplay actions and maintain a persistent high-score table. When a run finishes, prompt the player to enter their name/initials and record it with the score. Save the top 5 scores (name + score) to a local JSON file in the working directory (e.g. high_scores.json), and also write the latest finished run as {"initials": name, "score": number} to /tmp/alpaca_score.json (overwrite it every run) so the arcade scoreboard can pick it up automatically, load and display them on startup and on the game-over screen, and write a new entry whenever a run finishes. Scores must survive restarting the program. Every new game MUST start with the score reset to 0 - never carry over the previous game's score into the next session. The program must run top-to-bottom and open a visible window. OUTPUT REQUIREMENT: Respond with ONLY the raw, runnable source code in a single fenced code block, with the first line starting at column 0 (no leading indentation). Do not include any thinking, explanation, summary, or prose before or after the code, and keep comments inside the code minimal.import json
import math
import random
from pathlib import Path
import pygame
WIDTH, HEIGHT = 540, 760
GROUND_Y = HEIGHT - 80
FPS = 60
BIRD_X = 145
BIRD_RADIUS = 15
GRAVITY = 1500.0
FLAP_VELOCITY = -435.0
PIPE_WIDTH = 78
HIGH_SCORE_FILE = Path.cwd() / "high_scores.json"
LATEST_SCORE_FILE = Path("/tmp/alpaca_score.json")
SKY = (18, 25, 43)
WHITE = (239, 244, 250)
MUTED = (157, 177, 199)
YELLOW = (255, 208, 65)
GREEN = (48, 191, 121)
DARK_GREEN = (24, 113, 76)
RED = (255, 95, 105)
def load_high_scores():
try:
data = json.loads(HIGH_SCORE_FILE.read_text(encoding="utf-8"))
if not isinstance(data, list):
return []
valid = []
for entry in data:
if not isinstance(entry, dict):
continue
name = entry.get("initials")
score = entry.get("score")
if (
isinstance(name, str)
and name.strip()
and type(score) is int
and score >= 0
):
valid.append({"initials": name.strip()[:12], "score": score})
return sorted(valid, key=lambda entry: entry["score"], reverse=True)[:5]
except (OSError, ValueError):
return []
def write_json(path, data):
temporary = path.with_name(path.name + ".tmp")
temporary.write_text(json.dumps(data, indent=2), encoding="utf-8")
temporary.replace(path)
def circle_hits_rect(cx, cy, radius, rect):
closest_x = max(rect.left, min(cx, rect.right))
closest_y = max(rect.top, min(cy, rect.bottom))
return (cx - closest_x) ** 2 + (cy - closest_y) ** 2 <= radius ** 2
class FlappyBird:
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Flappy Bird โ One Tap, No Mercy")
self.clock = pygame.time.Clock()
self.font_small = pygame.font.Font(None, 24)
self.font_medium = pygame.font.Font(None, 32)
self.font_large = pygame.font.Font(None, 52)
self.font_score = pygame.font.Font(None, 72)
self.high_scores = load_high_scores()
self.state = "menu"
self.running = True
self.score = 0
self.bird_y = HEIGHT * 0.40
self.velocity = 0.0
self.pipes = []
self.spawn_timer = 0.0
self.initials = ""
self.last_name = ""
self.saved = False
self.save_error = ""
self.ground_offset = 0.0
def text(self, message, y, font=None, color=WHITE, x=None):
font = font or self.font_medium
surface = font.render(message, True, color)
rect = surface.get_rect()
if x is None:
rect.midtop = (WIDTH // 2, y)
else:
rect.topleft = (x, y)
self.screen.blit(surface, rect)
def start_game(self):
self.score = 0
self.bird_y = HEIGHT * 0.40
self.velocity = FLAP_VELOCITY
self.pipes = []
self.spawn_timer = 0.65
self.saved = False
self.save_error = ""
self.initials = ""
self.state = "playing"
def finish_game(self):
if self.state != "playing":
return
self.state = "name"
self.initials = ""
pygame.key.start_text_input()
pygame.key.set_text_input_rect(pygame.Rect(120, 216, 300, 44))
def save_run(self):
if self.saved:
return
name = self.initials.strip() or self.last_name or "ANON"
name = name[:12]
self.last_name = name
entry = {"initials": name, "score": int(self.score)}
self.high_scores.append(entry)
self.high_scores.sort(key=lambda item: item["score"], reverse=True)
self.high_scores = self.high_scores[:5]
errors = []
for path, payload in (
(HIGH_SCORE_FILE, self.high_scores),
(LATEST_SCORE_FILE, entry),
):
try:
write_json(path, payload)
except OSError as error:
errors.append(path.name)
print(f"Could not save {path}: {error}")
self.save_error = "Save failed: " + ", ".join(errors) if errors else ""
self.saved = True
pygame.key.stop_text_input()
self.state = "gameover"
def spawn_pipe(self):
gap = max(132, 178 - self.score * 2)
center = random.randint(125 + gap // 2, GROUND_Y - 100 - gap // 2)
self.pipes.append(
{
"x": float(WIDTH + 20),
"top": center - gap // 2,
"bottom": center + gap // 2,
"scored": False,
}
)
def pipe_rectangles(self, pipe):
x = int(pipe["x"])
return (
pygame.Rect(x, 0, PIPE_WIDTH, pipe["top"]),
pygame.Rect(x, pipe["bottom"], PIPE_WIDTH, GROUND_Y - pipe["bottom"]),
pygame.Rect(x - 5, pipe["top"] - 22, PIPE_WIDTH + 10, 22),
pygame.Rect(x - 5, pipe["bottom"], PIPE_WIDTH + 10, 22),
)
def handle_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
if self.state == "name":
self.save_run()
self.running = False
continue
if event.type == pygame.TEXTINPUT and self.state == "name":
for character in event.text:
if (
len(self.initials) < 12
and character.isascii()
and (character.isalnum() or character in " _-")
):
self.initials += character.upper()
if event.type != pygame.KEYDOWN:
continue
if self.state == "menu":
if event.key in (pygame.K_SPACE, pygame.K_RETURN):
self.start_game()
elif event.key == pygame.K_ESCAPE:
self.running = False
elif self.state == "playing":
if event.key == pygame.K_SPACE:
self.velocity = FLAP_VELOCITY
elif event.key == pygame.K_ESCAPE:
self.finish_game()
elif self.state == "name":
if event.key == pygame.K_BACKSPACE:
self.initials = self.initials[:-1]
elif event.key == pygame.K_RETURN:
self.save_run()
elif self.state == "gameover":
if event.key in (pygame.K_SPACE, pygame.K_RETURN):
self.start_game()
elif event.key == pygame.K_ESCAPE:
self.running = False
def update(self, dt):
if self.state != "playing":
return
speed = min(335.0, 225.0 + self.score * 5.0)
self.ground_offset = (self.ground_offset + speed * dt) % 32
self.velocity += GRAVITY * dt
self.bird_y += self.velocity * dt
self.spawn_timer -= dt
if self.spawn_timer <= 0:
self.spawn_pipe()
self.spawn_timer += max(1.08, 1.48 - self.score * 0.014)
for pipe in self.pipes:
pipe["x"] -= speed * dt
if self.bird_y + BIRD_RADIUS >= GROUND_Y:
self.bird_y = GROUND_Y - BIRD_RADIUS
self.finish_game()
return
if self.bird_y - BIRD_RADIUS <= 0:
self.bird_y = BIRD_RADIUS
self.finish_game()
return
for pipe in self.pipes:
for rect in self.pipe_rectangles(pipe):
if circle_hits_rect(BIRD_X, self.bird_y, BIRD_RADIUS, rect):
self.finish_game()
return
for pipe in self.pipes:
if not pipe["scored"] and pipe["x"] + PIPE_WIDTH + 5 < BIRD_X - BIRD_RADIUS:
pipe["scored"] = True
self.score += 1
self.pipes = [pipe for pipe in self.pipes if pipe["x"] > -PIPE_WIDTH - 10]
def draw_world(self):
self.screen.fill(SKY)
for i in range(30):
x = (i * 137 + 25) % WIDTH
y = (i * 89 + 30) % (GROUND_Y - 30)
pygame.draw.circle(self.screen, (43, 57, 77), (x, y), 2)
for pipe in self.pipes:
rectangles = self.pipe_rectangles(pipe)
for rect in rectangles:
pygame.draw.rect(self.screen, GREEN, rect)
pygame.draw.rect(self.screen, DARK_GREEN, rect, 3)
x = int(pipe["x"])
pygame.draw.rect(
self.screen, (99, 222, 148), (x + 10, 0, 8, max(0, pipe["top"] - 24))
)
pygame.draw.rect(
self.screen,
(99, 222, 148),
(x + 10, pipe["bottom"] + 24, 8, GROUND_Y - pipe["bottom"] - 24),
)
pygame.draw.rect(self.screen, (73, 65, 60), (0, GROUND_Y, WIDTH, HEIGHT))
pygame.draw.rect(self.screen, GREEN, (0, GROUND_Y, WIDTH, 7))
for x in range(-32, WIDTH + 32, 32):
offset = int(self.ground_offset)
pygame.draw.line(
self.screen,
(106, 92, 75),
(x - offset, GROUND_Y + 18),
(x + 18 - offset, GROUND_Y + 36),
4,
)
y = int(self.bird_y)
if self.state == "menu":
y = 180 + int(math.sin(pygame.time.get_ticks() / 220) * 7)
pygame.draw.circle(self.screen, (113, 74, 27), (BIRD_X, y), BIRD_RADIUS + 2)
pygame.draw.circle(self.screen, YELLOW, (BIRD_X, y), BIRD_RADIUS)
pygame.draw.ellipse(self.screen, (241, 155, 43), (BIRD_X - 15, y, 18, 10))
pygame.draw.polygon(
self.screen,
(255, 125, 47),
[(BIRD_X + 11, y + 1), (BIRD_X + 23, y + 5), (BIRD_X + 11, y + 8)],
)
pygame.draw.circle(self.screen, WHITE, (BIRD_X + 7, y - 5), 6)
pygame.draw.circle(self.screen, SKY, (BIRD_X + 9, y - 5), 3)
def draw_board(self, y):
self.text("TOP 5 โ LOCAL HIGH SCORES", y, self.font_medium, YELLOW)
for index in range(5):
row_y = y + 43 + index * 36
if index < len(self.high_scores):
entry = self.high_scores[index]
name, score = entry["initials"], str(entry["score"])
else:
name, score = "---", "-"
self.text(f"{index + 1}. {name}", row_y, self.font_small, x=115)
surface = self.font_small.render(score, True, WHITE)
self.screen.blit(surface, surface.get_rect(topright=(425, row_y)))
def draw(self):
self.draw_world()
if self.state == "playing":
self.text(str(self.score), 38, self.font_score)
self.text("SPACE: flap +1 per pipe", HEIGHT - forty(), self.font_small)
elif self.state == "menu":
self.text("ONE TAP. NO MERCY.", sixty(), self.font_large, YELLOW)
self.text("FLAPPY BIRD", 118, self.font_medium)
self.text("SPACE / ENTER to start", 230)
self.text("SPACE flaps. Every pipe earns 1 point.", 270, self.font_small, MUTED)
self.text("Avoid pipes, ceiling, and ground.", 297, self.font_small, MUTED)
self.draw_board(365)
self.text("Gaps tighten. Pipes accelerate.", 622, self.font_small, RED)
else:
veil = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
veil.fill((8, 12, 23, 220))
self.screen.blit(veil, (0, 0))
self.text("RUN OVER", 65, self.font_large, RED)
self.text(f"SCORE {self.score}", 127, self.font_large, YELLOW)
if self.state == "name":
self.text("Enter name / initials (12 characters)", 190, self.font_small)
box = pygame.Rect(110, 221, 320, 47)
pygame.draw.rect(self.screen, (30, 43, 64), box, border_radius=6)
pygame.draw.rect(self.screen, YELLOW, box, 2, border_radius=6)
cursor = "_" if pygame.time.get_ticks() % 1000 < 500 else ""
self.text(self.initials + cursor, 232)
self.text("ENTER saves your run (blank = ANON)", 284, self.font_small, MUTED)
else:
self.text(f"Recorded for {self.last_name}", 211)
self.text("SPACE / ENTER to try again", 265, self.font_medium, YELLOW)
self.text("ESC to quit", 304, self.font_small, MUTED)
self.draw_board(367)
if self.save_error:
self.text("Storage error โ see terminal.", 622, self.font_small, RED)
elif self.state == "gameover":
self.text("Scores saved. Every new run starts at 0.", 622, self.font_small, MUTED)
pygame.display.flip()
def run(self):
try:
while self.running:
dt = min(self.clock.tick(FPS) / 1000.0, 0.05)
self.handle_events()
if not self.running:
break
remaining = dt
while remaining > 0:
step = min(remaining, 1 / 120)
self.update(step)
remaining -= step
self.draw()
finally:
pygame.quit()
def forty():
return 40
def sixty():
return 60
if __name__ == "__main__":
FlappyBird().run()
๐ฎ Press โถ Play above to run it live in your browser, or download it below and run locally (pip install pygame), then enter your score by hand