โ
Retro Temple Run โ
๐ฆ 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 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.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