← Arcade ๐Ÿ† Players My player card

Retro Temple Run

โ˜… Retro Temple Run โ˜…
Retro Temple Run 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: Temple-Run runner (playable, pygame UI)
  • Category: retrogames
  • Kind: code ยท scored as score (higher is better)
  • Run: โ€”
  • Benchmark score: 96.0
  • Benchmark date: 2026-09-17
  • Published: 2026-09-25
Original prompt
Write a complete, runnable Python program using `pygame` that implements a Temple-Run-style runner. Open a window, show a player that runs forward automatically while the speed increases over time, can switch between three lanes (LEFT/RIGHT arrows) and jump (UP arrow) to avoid obstacles, and the run ends on collision. Display the distance/score. Include a game loop handling QUIT. Use only primitive shapes - no external assets. Design note (interpret freely): the pioneer 3-lane endless runner - fixed third-person camera, auto-running character, lane switches/jumps/slides, tilt-steered corners on twisting tracks, and coins/relics with a rising speed. The chase-camera feel matters. 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 json
import math
import os
import random
from pathlib import Path

import pygame


WIDTH, HEIGHT = 900, 700
HORIZON, GROUND = 170, 635
VIEW_DISTANCE = 180.0
PLAYER_Z = 7.0
HIGH_SCORE_FILE = Path.cwd() / "high_scores.json"
LATEST_RUN_FILE = Path("/tmp/alpaca_score.json")

pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Temple Trail")
clock = pygame.time.Clock()
FONT = pygame.font.Font(None, 30)
SMALL = pygame.font.Font(None, 23)
LARGE = pygame.font.Font(None, 62)
MEDIUM = pygame.font.Font(None, 40)


def clean_name(value):
    return "".join(
        c for c in str(value).upper() if c.isascii() and (c.isalnum() or c in " _-")
    ).strip()[:12] or "RUNNER"


def load_scores():
    try:
        data = json.loads(HIGH_SCORE_FILE.read_text(encoding="utf-8"))
        if not isinstance(data, list):
            return []
        scores = []
        for item in data:
            if not isinstance(item, dict):
                continue
            score = item.get("score")
            if isinstance(score, bool) or not isinstance(score, (int, float)):
                continue
            if not math.isfinite(score) or score < 0:
                continue
            scores.append({
                "initials": clean_name(item.get("initials", item.get("name", "RUNNER"))),
                "score": int(score),
            })
        return sorted(scores, key=lambda entry: entry["score"], reverse=True)[:5]
    except (OSError, ValueError, TypeError, OverflowError):
        return []


def write_json(path, value):
    temporary = path.with_name(path.name + ".tmp")
    try:
        path.parent.mkdir(parents=True, exist_ok=True)
        temporary.write_text(json.dumps(value, indent=2), encoding="utf-8")
        os.replace(temporary, path)
        return True
    except OSError:
        return False


def text(message, x, y, font=FONT, color=(245, 240, 217), center=False):
    image = font.render(str(message), True, color)
    rect = image.get_rect(center=(x, y)) if center else image.get_rect(topleft=(x, y))
    screen.blit(image, rect)


class Game:
    def __init__(self):
        self.high_scores = load_scores()
        self.state = "title"
        self.save_warning = ""
        self.initials = ""
        self.saved = False
        self.reset_world()

    def reset_world(self):
        self.distance = 0.0
        self.score = 0
        self.bonus = 0
        self.coins = 0
        self.speed = 27.0
        self.elapsed = 0.0
        self.lane = 1
        self.lane_position = 1.0
        self.jump_height = 0.0
        self.jump_velocity = 0.0
        self.objects = []
        self.spawn_timer = 0.45

    def start(self):
        self.reset_world()
        self.initials = ""
        self.saved = False
        self.save_warning = ""
        self.state = "playing"
        pygame.key.stop_text_input()

    def finish(self):
        if self.state != "playing":
            return
        self.state = "name"
        self.initials = ""
        self.saved = False
        pygame.key.start_text_input()

    def record_run(self):
        if self.saved:
            return
        entry = {"initials": clean_name(self.initials), "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]
        board_ok = write_json(HIGH_SCORE_FILE, self.high_scores)
        latest_ok = write_json(LATEST_RUN_FILE, entry)
        if not board_ok or not latest_ok:
            self.save_warning = "Could not save: " + ", ".join(
                label for ok, label in (
                    (board_ok, "high_scores.json"),
                    (latest_ok, "/tmp/alpaca_score.json"),
                ) if not ok
            )
        self.saved = True
        pygame.key.stop_text_input()
        self.state = "over"

    def project(self, lane, z):
        depth = max(0.0, min(1.0, 1.0 - z / VIEW_DISTANCE))
        perspective = depth ** 1.65
        scale = 0.11 + 0.89 * perspective
        bend = (
            math.sin(self.distance * 0.007 + z * 0.012)
            - math.sin(self.distance * 0.007)
        ) * 78 * (1.0 - perspective)
        center = WIDTH / 2 + bend
        return center + (lane - 1) * 147 * scale, HORIZON + (GROUND - HORIZON) * perspective, scale

    def spawn_row(self):
        blocked_count = 2 if random.random() < 0.42 else 1
        blocked = random.sample([0, 1, 2], blocked_count)
        for lane in blocked:
            kind = "pillar" if random.random() < 0.28 else "barrier"
            self.objects.append({
                "lane": lane, "z": VIEW_DISTANCE, "kind": kind, "resolved": False
            })
        safe_lane = random.choice([lane for lane in range(3) if lane not in blocked])
        for offset in (0, 8, 16):
            self.objects.append({
                "lane": safe_lane,
                "z": VIEW_DISTANCE + offset,
                "kind": "coin",
                "resolved": False,
            })

    def update(self, dt):
        if self.state != "playing":
            return

        self.elapsed += dt
        self.speed = min(76.0, 27.0 + self.elapsed * 0.65)
        travel = self.speed * dt
        self.distance += travel
        self.lane_position += (self.lane - self.lane_position) * min(1.0, dt * 15)

        if self.jump_height > 0 or self.jump_velocity > 0:
            self.jump_velocity -= 24.0 * dt
            self.jump_height += self.jump_velocity * dt
            if self.jump_height <= 0:
                self.jump_height = 0.0
                self.jump_velocity = 0.0

        self.spawn_timer -= dt
        if self.spawn_timer <= 0:
            self.spawn_row()
            self.spawn_timer = random.uniform(1.20, 1.65)

        for obj in self.objects:
            previous_z = obj["z"]
            obj["z"] -= travel
            if obj["resolved"] or not (obj["z"] <= PLAYER_Z < previous_z):
                continue

            obj["resolved"] = True
            same_lane = abs(self.lane_position - obj["lane"]) < 0.58
            if obj["kind"] == "coin":
                if same_lane:
                    self.bonus += 25
                    self.coins += 1
            else:
                clears = obj["kind"] == "barrier" and self.jump_height > 1.35
                if same_lane and not clears:
                    self.score = int(self.distance) + self.bonus
                    self.finish()
                    return
                self.bonus += 10

        self.objects = [obj for obj in self.objects if obj["z"] > -12]
        self.score = int(self.distance) + self.bonus

    def draw_background(self):
        screen.fill((43, 77, 91))
        pygame.draw.circle(screen, (250, 188, 96), (700, 91), 43)
        pygame.draw.polygon(screen, (40, 67, 70), [
            (0, 190), (0, 117), (120, 62), (230, 165), (345, 79),
            (470, 183), (600, 104), (790, 175), (900, 86), (900, 215)
        ])
        pygame.draw.rect(screen, (27, 65, 50), (0, HORIZON, WIDTH, HEIGHT))

        for segment in range(35, 0, -1):
            far_z = segment * VIEW_DISTANCE / 35
            near_z = (segment - 1) * VIEW_DISTANCE / 35
            left_far = self.project(-0.65, far_z)[:2]
            right_far = self.project(2.65, far_z)[:2]
            left_near = self.project(-0.65, near_z)[:2]
            right_near = self.project(2.65, near_z)[:2]
            band = int((near_z + self.distance) / 12) % 2
            color = (126, 107, 78) if band else (137, 116, 84)
            pygame.draw.polygon(screen, color, [left_far, right_far, right_near, left_near])
            for edge in (-0.65, 2.65):
                a = self.project(edge, far_z)
                b = self.project(edge, near_z)
                pygame.draw.line(screen, (220, 182, 107), a[:2], b[:2],
                                 max(1, int(9 * b[2])))

        for lane_edge in (0.5, 1.5):
            for index in range(19):
                z = index * 12 - (self.distance % 12)
                if z < 0 or z + 6 > VIEW_DISTANCE:
                    continue
                a = self.project(lane_edge, z)
                b = self.project(lane_edge, z + 6)
                pygame.draw.line(screen, (202, 174, 120), a[:2], b[:2],
                                 max(1, int(3 * a[2])))

        for index in range(12):
            z = index * 18 - (self.distance % 18)
            if z < 0:
                continue
            for side in (-1.25, 3.25):
                x, y, scale = self.project(side, z)
                w, h = 34 * scale, 128 * scale
                pygame.draw.rect(screen, (44, 49, 39),
                                 (int(x - w / 2), int(y - h), max(1, int(w)), int(h)))
                pygame.draw.rect(screen, (71, 80, 52),
                                 (int(x - w * 0.75), int(y - h), int(w * 1.5), max(2, int(13 * scale))))

    def draw_object(self, obj):
        if obj["z"] > VIEW_DISTANCE or (obj["kind"] == "coin" and obj["resolved"]):
            return
        x, y, scale = self.project(obj["lane"], obj["z"])
        if obj["kind"] == "coin":
            radius = max(3, int(13 * scale))
            center = (int(x), int(y - 36 * scale))
            pygame.draw.circle(screen, (255, 202, 38), center, radius)
            pygame.draw.circle(screen, (255, 244, 146), center, radius, max(1, int(3 * scale)))
            return

        width = 88 * scale
        height = (137 if obj["kind"] == "pillar" else 43) * scale
        depth = 17 * scale
        pygame.draw.ellipse(screen, (66, 62, 46),
                            (int(x - width * 0.7), int(y - 7 * scale),
                             int(width * 1.4), max(2, int(17 * scale))))
        rect = pygame.Rect(int(x - width / 2), int(y - height), int(width), int(height))
        color = (89, 103, 100) if obj["kind"] == "pillar" else (162, 64, 43)
        pygame.draw.rect(screen, color, rect)
        pygame.draw.polygon(screen, (191, 169, 116), [
            (rect.left, rect.top), (rect.left + depth, rect.top - depth),
            (rect.right + depth, rect.top - depth), (rect.right, rect.top)
        ])
        pygame.draw.polygon(screen, (60, 65, 57), [
            (rect.right, rect.top), (rect.right + depth, rect.top - depth),
            (rect.right + depth, rect.bottom - depth), (rect.right, rect.bottom)
        ])
        if obj["kind"] == "barrier":
            pygame.draw.rect(screen, (244, 186, 66),
                             (rect.left, rect.top + int(12 * scale), rect.width, max(2, int(8 * scale))))
        else:
            pygame.draw.line(screen, (144, 155, 136),
                             (int(x), rect.top + 15 * scale), (int(x), rect.bottom - 15 * scale),
                             max(2, int(5 * scale)))

    def draw_player(self):
        x, ground_y, scale = self.project(self.lane_position, PLAYER_Z)
        y = ground_y - self.jump_height * 43 * scale
        shadow_width = int(57 * scale / (1 + self.jump_height * 0.15))
        pygame.draw.ellipse(screen, (58, 54, 42),
                            (int(x - shadow_width / 2), int(ground_y - 6),
                             shadow_width, 13))
        stride = math.sin(self.elapsed * 18) * 13 if self.jump_height == 0 else 8
        thick = max(3, int(10 * scale))
        hip = (int(x), int(y - 34 * scale))
        for side in (-1, 1):
            foot = (int(x + side * 13 * scale), int(y - max(0, side * stride) * scale))
            pygame.draw.line(screen, (35, 43, 69), hip, foot, thick)
            pygame.draw.line(screen, (36, 30, 27), foot,
                             (foot[0] + int(side * 7 * scale), foot[1]), thick)
        pygame.draw.rect(screen, (37, 166, 183),
                         (int(x - 17 * scale), int(y - 76 * scale),
                          int(34 * scale), int(45 * scale)), border_radius=6)
        for side in (-1, 1):
            shoulder = (int(x + side * 18 * scale), int(y - 69 * scale))
            hand = (int(x + side * 26 * scale), int(y - (43 + side * stride * 0.6) * scale))
            pygame.draw.line(screen, (237, 178, 122), shoulder, hand, max(3, int(8 * scale)))
        pygame.draw.circle(screen, (234, 177, 119),
                           (int(x), int(y - 90 * scale)), max(4, int(15 * scale)))
        pygame.draw.rect(screen, (102,  sixty := 60,  thirty := 30),
                         (int(x - 21 * scale), int(y - 100 * scale),
                          int(42 * scale), max(3, int(8 * scale))))
        pygame.draw.rect(screen, (133, 86, 40),
                         (int(x - 12 * scale), int(y - 113 * scale),
                          int(24 * scale), int(17 * scale)))

    def draw_board(self, top):
        text("TOP 5 RUNNERS", WIDTH / 2, top, MEDIUM, (250, 208, 108), True)
        if not self.high_scores:
            text("No scores yet โ€” make your first run!", WIDTH / 2, top + 45, SMALL, center=True)
        for index, entry in enumerate(self.high_scores):
            y = top + 38 + index * 29
            text(f"{index + 1}.  {entry['initials']}", 275, y, SMALL)
            image = SMALL.render(f"{entry['score']:,}", True, (245, 240, 217))
            screen.blit(image, (625 - image.get_width(), y))

    def draw(self):
        self.draw_background()
        distant = [obj for obj in self.objects if obj["z"] >= PLAYER_Z]
        close = [obj for obj in self.objects if obj["z"] < PLAYER_Z]
        for obj in sorted(distant, key=lambda obj: obj["z"], reverse=True):
            self.draw_object(obj)
        self.draw_player()
        for obj in sorted(close, key=lambda obj: obj["z"], reverse=True):
            self.draw_object(obj)

        pygame.draw.rect(screen, (20, 32, 34), (0, 0, WIDTH, 68))
        text(f"SCORE  {self.score:,}", 22, 12)
        text(f"{int(self.distance)} m", 345, 12)
        text(f"COINS  {self.coins}", 545, 12)
        text(f"{self.speed:.0f} m/s", 770, 12)
        text("LEFT / RIGHT: switch lanes     UP: jump     Gold +25     Dodge +10     Distance +1/m",
             WIDTH / 2, 53, SMALL, (189, 206, 197), True)

        if self.state == "playing":
            return

        veil = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
        veil.fill((0, 0, 0, 145))
        screen.blit(veil, (0, 0))
        pygame.draw.rect(screen, (23, 38, 42), (170, 95, 560, 555), border_radius=18)
        pygame.draw.rect(screen, (181, 151, 88), (170, 95, 560, 555), 2, border_radius=18)

        if self.state == "title":
            text("TEMPLE TRAIL", WIDTH / 2, 145, LARGE, (255, 211, 108), True)
            text("Three lanes. One endless escape.", WIDTH / 2, 196, FONT, center=True)
            text("LEFT / RIGHT to move โ€ข UP to jump", WIDTH / 2, 233, SMALL, center=True)
            text("Jump red barriers; dodge tall stone pillars.", WIDTH / 2, 263, SMALL, center=True)
            text("Collect gold in any jump position.", WIDTH / 2, 291, SMALL, center=True)
            self.draw_board(345)
            text("ENTER or SPACE to run", WIDTH / 2, 588, MEDIUM, (116, 229, 194), True)
        else:
            text("RUN FINISHED", WIDTH / 2, 145, LARGE, (255, 211, 108), True)
            text(f"Score {self.score:,}  โ€ข  Distance {int(self.distance)} m",
                 WIDTH / 2, 196, FONT, center=True)
            if self.state == "name":
                text("Enter name / initials, then press ENTER:", WIDTH / 2, 240, SMALL, center=True)
                pygame.draw.rect(screen, (10, 24, 28), (275, 261, 350, 45), border_radius=6)
                cursor = "_" if pygame.time.get_ticks() % 1000 < 500 else ""
                text(self.initials + cursor, WIDTH / 2, 284, MEDIUM, (116, 229, 194), True)
                text("Blank name defaults to RUNNER.", WIDTH / 2, 326, SMALL, center=True)
            else:
                text(f"Recorded as {clean_name(self.initials)}", WIDTH / 2, 258, FONT,
                     (116, 229, 194), True)
            self.draw_board(370)
            if self.state == "over":
                text("ENTER / SPACE: new run    ESC: exit", WIDTH / 2, 586, SMALL, center=True)
            else:
                text("ENTER: save score    ESC: save as RUNNER", WIDTH / 2, 586, SMALL, center=True)
            if self.save_warning:
                text("Save failed; check console for details.", WIDTH / 2, 620, SMALL,
                     (255, 151, 125), True)


def main():
    game = Game()
    running = True
    while running:
        dt = min(clock.tick(60) / 1000.0, 0.04)
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                if game.state == "playing":
                    game.finish()
                if game.state == "name":
                    game.record_run()
                running = False
                break

            if event.type == pygame.TEXTINPUT and game.state == "name":
                for character in event.text.upper():
                    if character.isascii() and (character.isalnum() or character in " _-"):
                        game.initials = (game.initials + character)[:12]

            if event.type != pygame.KEYDOWN:
                continue

            if game.state == "playing":
                if event.key == pygame.K_LEFT:
                    game.lane = max(0, game.lane - 1)
                elif event.key == pygame.K_RIGHT:
                    game.lane = min(2, game.lane + 1)
                elif event.key == pygame.K_UP and game.jump_height == 0:
                    game.jump_velocity = 10.5
                elif event.key == pygame.K_ESCAPE:
                    game.finish()
            elif game.state == "name":
                if event.key == pygame.K_BACKSPACE:
                    game.initials = game.initials[:-1]
                elif event.key in (pygame.K_RETURN, pygame.K_KP_ENTER, pygame.K_ESCAPE):
                    if event.key == pygame.K_ESCAPE:
                        game.initials = ""
                    game.record_run()
                    if game.save_warning:
                        print(game.save_warning)
            elif event.key in (pygame.K_RETURN, pygame.K_KP_ENTER, pygame.K_SPACE):
                game.start()
            elif event.key == pygame.K_ESCAPE:
                running = False
                break

        if running:
            game.update(dt)
            game.draw()
            pygame.display.flip()

    if game.save_warning:
        print(game.save_warning)
    pygame.quit()


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