← Arcade ๐Ÿ† Players My player card

Retro Crossy Road

โ˜… Retro Crossy Road โ˜…
Retro Crossy Road gameplay screenshot
๐Ÿ“ฆ Game code, download & prompt info
๐Ÿ–ฅ๏ธ Desktop app (python) โ€” play it live above, right in your browser, or download it below
  • Model: openrouter:stealth/union-alpha
  • Test: Retro: Crossy Road hopper (playable, pygame UI)
  • Category: retrogames
  • Kind: code ยท scored as score (higher is better)
  • Run: โ€”
  • Benchmark score: 98.0
  • Benchmark date: 2026-09-17
  • Published: 2026-09-25
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.
โฌ‡ Download game.py
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