โ
Retro Crossy Road โ
๐ฆ 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 Crossy-Road-style hopper. Open a window, a player that hops forward one row at a time (UP arrow) across scrolling rows of obstacles (cars/water) moving horizontally, with LEFT/RIGHT to change lanes. The run ends on collision with a moving obstacle. Display the score (rows crossed). Include a game loop handling QUIT. Use only primitive shapes - no external assets. Design note (interpret freely): the Frogger-like hopper where each tap hops exactly one tile across lanes of speeding traffic and floating logs, standing still risks getting run over, and difficulty ramps procedurally as your score rises. The 'one more hop' loop is the soul of it. 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 pygame
import json
import os
import sys
pygame.init()
TILE = 40
COLS = 15
ROWS = 15
WIDTH = TILE * COLS
HEIGHT = TILE * ROWS
FPS = 60
SCREEN = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Crossy Hopper")
CLOCK = pygame.time.Clock()
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GREEN = (60, 180, 75)
GRAY = (90, 90, 90)
BLUE = (40, 90, 200)
YELLOW = (240, 220, 60)
RED = (220, 60, 60)
DARK = (30, 30, 40)
LIGHT = (200, 200, 210)
HIGH_SCORE_FILE = "high_scores.json"
TMP_SCORE_FILE = "/tmp/alpaca_score.json"
FONT = pygame.font.SysFont("consolas", 20)
BIG_FONT = pygame.font.SysFont("consolas", 32, bold=True)
SMALL_FONT = pygame.font.SysFont("consolas", 16)
def load_high_scores():
if os.path.exists(HIGH_SCORE_FILE):
try:
with open(HIGH_SCORE_FILE, "r") as f:
data = json.load(f)
if isinstance(data, list):
return data[:5]
except Exception:
pass
return []
def save_high_scores(scores):
try:
with open(HIGH_SCORE_FILE, "w") as f:
json.dump(scores[:5], f)
except Exception:
pass
def write_tmp_score(initials, score):
try:
with open(TMP_SCORE_FILE, "w") as f:
json.dump({"initials": initials, "score": score}, f)
except Exception:
pass
def add_high_score(scores, initials, score):
scores.append({"name": initials, "score": score})
scores.sort(key=lambda x: x["score"], reverse=True)
return scores[:5]
class Lane:
def __init__(self, row, kind, speed, direction, difficulty):
self.row = row
self.kind = kind # 'road' or 'water'
self.speed = speed
self.direction = direction
self.obstacles = []
spacing = TILE * 4
count = (WIDTH // spacing) + 3
for i in range(count):
x = i * spacing + (row * 17) % spacing
w = TILE * 2 if kind == "road" else TILE * 3
self.obstacles.append([x, w])
def update(self, dt):
for ob in self.obstacles:
ob[0] += self.speed * self.direction * dt
for ob in self.obstacles:
if self.direction > 0 and ob[0] > WIDTH + TILE:
ob[0] = -ob[1] - TILE * 2
elif self.direction < 0 and ob[0] + ob[1] < -TILE:
ob[0] = WIDTH + TILE * 2
def draw(self, surface, y):
if self.kind == "road":
pygame.draw.rect(surface, GRAY, (0, y, WIDTH, TILE))
for ob in self.obstacles:
pygame.draw.rect(surface, RED, (ob[0], y + 6, ob[1], TILE - 12), border_radius=4)
else:
pygame.draw.rect(surface, BLUE, (0, y, WIDTH, TILE))
for ob in self.obstacles:
pygame.draw.rect(surface, (140, 90, 40), (ob[0], y + 8, ob[1], TILE - 16), border_radius=6)
class Game:
def __init__(self):
self.reset()
def reset(self):
self.score = 0
self.player_col = COLS // 2
self.player_row = ROWS - 2
self.lanes = {}
self.next_row = ROWS - 3
self.generate_lanes(ROWS - 3)
self.state = "play"
self.initials = ""
self.high_scores = load_high_scores()
self.death_timer = 0
def generate_lanes(self, up_to_row):
while self.next_row >= 0:
r = self.next_row
difficulty = 1.0 + self.score * 0.05
if r % 2 == 0:
kind = "road"
speed = (60 + self.score * 4) * (1 if r % 4 == 0 else -1)
else:
kind = "water"
speed = (40 + self.score * 3) * (1 if r % 4 == 1 else -1)
self.lanes[r] = Lane(r, kind, speed, 1 if speed > 0 else -1, difficulty)
self.next_row -= 1
def hop(self, dc, dr):
if self.state != "play":
return
new_col = self.player_col + dc
new_row = self.player_row + dr
if 0 <= new_col < COLS and 0 <= new_row < ROWS:
self.player_col = new_col
self.player_row = new_row
if dr < 0:
self.score += 1
if self.player_row < 3:
self.shift_world()
def shift_world(self):
new_lanes = {}
for r, lane in self.lanes.items():
new_lanes[r + 1] = lane
lane.row = r + 1
self.lanes = new_lanes
self.player_row += 1
self.next_row = 0
difficulty = 1.0 + self.score * 0.05
r = 0
if r % 2 == 0:
kind = "road"
speed = (60 + self.score * 4) * (1 if r % 4 == 0 else -1)
else:
kind = "water"
speed = (40 + self.score * 3) * (1 if r % 4 == 1 else -1)
self.lanes[r] = Lane(r, kind, speed, 1 if speed > 0 else -1, difficulty)
def update(self, dt):
if self.state == "play":
for lane in self.lanes.values():
lane.update(dt)
self.check_collision()
elif self.state == "dead":
self.death_timer += dt
if self.death_timer > 0.5:
self.state = "entry"
def check_collision(self):
lane = self.lanes.get(self.player_row)
if lane is None:
return
px = self.player_col * TILE + TILE * 0.15
pw = TILE * 0.7
for ob in lane.obstacles:
ox, ow = ob
if px < ox + ow and px + pw > ox:
if lane.kind == "road":
self.state = "dead"
self.death_timer = 0
return
else:
return
if lane.kind == "water":
self.state = "dead"
self.death_timer = 0
def draw(self, surface):
surface.fill(DARK)
for r in range(ROWS):
y = r * TILE
lane = self.lanes.get(r)
if lane:
lane.draw(surface, y)
else:
pygame.draw.rect(surface, GREEN, (0, y, WIDTH, TILE))
px = self.player_col * TILE + TILE * 0.15
py = self.player_row * TILE + TILE * 0.15
color = YELLOW if self.state != "dead" else RED
pygame.draw.rect(surface, color, (px, py, TILE * 0.7, TILE * 0.7), border_radius=6)
score_surf = FONT.render(f"Score: {self.score}", True, WHITE)
surface.blit(score_surf, (10, 10))
hs = self.high_scores[0]["score"] if self.high_scores else 0
hs_surf = FONT.render(f"Best: {hs}", True, WHITE)
surface.blit(hs_surf, (WIDTH - hs_surf.get_width() - 10, 10))
def draw_game_over(self, surface):
surface.fill(DARK)
title = BIG_FONT.render("GAME OVER", True, RED)
surface.blit(title, (WIDTH // 2 - title.get_width() // 2, 40))
sc = FONT.render(f"Score: {self.score}", True, WHITE)
surface.blit(sc, (WIDTH // 2 - sc.get_width() // 2, 90))
if self.state == "entry":
prompt = FONT.render("Enter initials (A-Z), ENTER to save:", True, WHITE)
surface.blit(prompt, (WIDTH // 2 - prompt.get_width() // 2, 130))
box = pygame.Rect(WIDTH // 2 - 60, 160, 120, 40)
pygame.draw.rect(surface, WHITE, box, 2)
txt = BIG_FONT.render(self.initials + "_", True, YELLOW)
surface.blit(txt, (WIDTH // 2 - txt.get_width() // 2, 165))
board_title = FONT.render("HIGH SCORES", True, YELLOW)
surface.blit(board_title, (WIDTH // 2 - board_title.get_width() // 2, 220))
for i, entry in enumerate(self.high_scores):
line = SMALL_FONT.render(f"{i+1}. {entry['name']} {entry['score']}", True, WHITE)
surface.blit(line, (WIDTH // 2 - line.get_width() // 2, 250 + i * 22))
hint = SMALL_FONT.render("Press R to restart, ESC to quit", True, LIGHT)
surface.blit(hint, (WIDTH // 2 - hint.get_width() // 2, HEIGHT - 30))
def finish_run(self):
name = self.initials if self.initials else "AAA"
self.high_scores = add_high_score(self.high_scores, name, self.score)
save_high_scores(self.high_scores)
write_tmp_score(name, self.score)
def main():
game = Game()
running = True
while running:
dt = CLOCK.tick(FPS) / 1000.0
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if game.state == "play":
if event.key == pygame.K_UP:
game.hop(0, -1)
elif event.key == pygame.K_DOWN:
game.hop(0, 1)
elif event.key == pygame.K_LEFT:
game.hop(-1, 0)
elif event.key == pygame.K_RIGHT:
game.hop(1, 0)
elif event.key == pygame.K_ESCAPE:
running = False
elif game.state == "entry":
if event.key == pygame.K_RETURN:
game.finish_run()
game.state = "gameover"
elif event.key == pygame.K_BACKSPACE:
game.initials = game.initials[:-1]
elif event.key == pygame.K_ESCAPE:
running = False
else:
ch = event.unicode.upper()
if ch.isalpha() and len(game.initials) < 3:
game.initials += ch
elif game.state == "gameover":
if event.key == pygame.K_r:
game.reset()
elif event.key == pygame.K_ESCAPE:
running = False
game.update(dt)
if game.state in ("play", "dead"):
game.draw(SCREEN)
else:
game.draw_game_over(SCREEN)
pygame.display.flip()
pygame.quit()
sys.exit()
if __name__ == "__main__":
main()
๐ฎ 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