From db49b466c2f5c33464ba8389415e5cc1ab0f3c19 Mon Sep 17 00:00:00 2001 From: georgios Date: Wed, 9 Sep 2026 16:18:39 +0200 Subject: [PATCH] Cosmic Candy Pinball: procedural 8-bit pinball game Native pygame-ce game with fully procedural pixel art and chiptune audio (no asset files). Pure-logic physics module with flipper angular momentum, plus headless autoplay/screenshot/probe dev tools. --- .gitignore | 10 + README.md | 90 ++++ asciipic.py | 49 +++ autotest.py | 151 +++++++ font.py | 116 +++++ game.py | 937 ++++++++++++++++++++++++++++++++++++++++ main.py | 34 ++ physics.py | 448 +++++++++++++++++++ probe.py | 86 ++++ requirements.txt | 4 + runtest.py | 68 +++ sfx.py | 246 +++++++++++ shots.py | 91 ++++ shots/1_title.png | Bin 0 -> 5196 bytes shots/1_title_2x.png | Bin 0 -> 8100 bytes shots/2_serve.png | Bin 0 -> 8517 bytes shots/2_serve_2x.png | Bin 0 -> 12167 bytes shots/3_play.png | Bin 0 -> 8190 bytes shots/3_play_2x.png | Bin 0 -> 11869 bytes shots/4_fever.png | Bin 0 -> 9263 bytes shots/4_fever_2x.png | Bin 0 -> 12979 bytes shots/5_drain.png | Bin 0 -> 9537 bytes shots/5_drain_2x.png | Bin 0 -> 13245 bytes shots/6_gameover.png | Bin 0 -> 9112 bytes shots/6_gameover_2x.png | Bin 0 -> 12790 bytes 25 files changed, 2330 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 asciipic.py create mode 100644 autotest.py create mode 100644 font.py create mode 100644 game.py create mode 100644 main.py create mode 100644 physics.py create mode 100644 probe.py create mode 100644 requirements.txt create mode 100644 runtest.py create mode 100644 sfx.py create mode 100644 shots.py create mode 100644 shots/1_title.png create mode 100644 shots/1_title_2x.png create mode 100644 shots/2_serve.png create mode 100644 shots/2_serve_2x.png create mode 100644 shots/3_play.png create mode 100644 shots/3_play_2x.png create mode 100644 shots/4_fever.png create mode 100644 shots/4_fever_2x.png create mode 100644 shots/5_drain.png create mode 100644 shots/5_drain_2x.png create mode 100644 shots/6_gameover.png create mode 100644 shots/6_gameover_2x.png diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ec8aab7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# Python +__pycache__/ +*.pyc + +# project venv +.venv/ + +# generated +highscore.json +probe_play.png diff --git a/README.md b/README.md new file mode 100644 index 0000000..f05b403 --- /dev/null +++ b/README.md @@ -0,0 +1,90 @@ +# 🍬 Cosmic Candy Pinball + +A cute, 8-bit, pixel-art **pinball** game, written from scratch in Python with +[pygame-ce](https://pygame.org). 100% procedural β€” there are **no image or +audio asset files**: every sprite, texture, particle, chiptune and sound +effect is generated by code at runtime. + +Not web-based: it opens its own native window and renders on a low-res +`480Γ—360` virtual canvas, upscaled with crisp nearest-neighbour scaling for an +authentic retro look. + +![demo](shots/3_play.png) + +## Running it + +Fastest (uv, already used in this project): + +```bash +uv venv .venv --python 3.12 # once +uv pip install --python .venv/bin/python -r requirements.txt # once +.venv/bin/python main.py +``` + +Or with plain venv + pip (Python 3.10+): + +```bash +python3 -m venv .venv +.venv/bin/pip install -r requirements.txt +.venv/bin/python main.py +``` + +> Note: the dependency is **`pygame-ce`**, not `pygame` (the community edition +> supports newer Python versions and is what this was built against). + +## Controls + +| Key | Action | +| --- | --- | +| `←` `β†’` (or `Z` / `/`) | Flippers | +| hold `Space` / `↑` | Charge the **Candy Cannon** β€” release to fire | +| `M` | Mute | +| `P` | Pause | +| `R` | Restart game | +| `Esc` | Quit | +| `Enter` | Start / play again | + +## How to play + +- Light the **3 star lanes** up top β†’ **CANDY FEVER!** (double points for + 20s β€” keep hitting stars to extend it). +- Bounce off the three kawaii candy bumpers and the two slingshots. Every hit + inside 2.5s builds your **combo multiplier** (up to Γ—9). +- Watch the bottom-centre drain and the two outlanes… or lose the ball. +- Every **50,000 points** earns an extra ball. +- Squirrel says encouraging things. Blush at the cat in the corner. + +## Project layout + +| file | purpose | +| --- | --- | +| `main.py` | entry point / main loop | +| `game.py` | game states, scoring, juice, all rendering (pixel art) | +| `physics.py` | pure pinball physics (flippers, bumpers, slingshots, drains) β€” no rendering | +| `font.py` | hand-made 5Γ—5 pixel bitmap font | +| `sfx.py` | procedural chiptune + music synthesis (pure code, no samples) | +| `highscore.json` | best score (written at game over) | + +Dev / test tools (run headless, no window needed): + +```bash +.venv/bin/python autotest.py 40000 # auto-playing bot, prints game-health stats +.venv/bin/python runtest.py 900 # simulated session incl. rendering +.venv/bin/python shots.py shots # dumps screenshots of every state +.venv/bin/python probe.py # pixel-probes the rendered layout +``` + +## Design notes + +- **Physics**: fixed 60 Hz logic with up to 6 collision substeps so the ball + never tunnels; kinematic flippers whose angular velocity is transferred to + the ball (real "scoop" feel); inward-tilting floor funnels; centre drain + + guarded outlanes; a softlock rescue nudge if a ball ever parks too long. +- **Fun tuning was data-driven**: an `autotest.py` bot plays thousands of + frames and reports hit/drain rates per table element, so bumper, slingshot + and flipper geometry was tuned against measured behaviour, not guesswork. +- **Audio**: square/triangle/noise oscillators -> PCM at startup; distinct + pitched bump sounds that climb with your combo, plus two generative music + loops (menu + play). + +Licence: MIT β€” go wild. diff --git a/asciipic.py b/asciipic.py new file mode 100644 index 0000000..cb4388d --- /dev/null +++ b/asciipic.py @@ -0,0 +1,49 @@ +"""Downsample a screenshot to coarse ASCII so art can be sanity-checked in a +terminal. Usage: python asciipic.py shots/3_play_2x.png""" + +import sys +import pygame + +NAMES = { + "P": (255, 130, 195), "G": (255, 214, 92), "M": (110, 240, 200), + "W": (240, 240, 250), "B": (120, 205, 255), "f": (64, 50, 140), + "b": (22, 15, 56), "d": (14, 9, 42), "V": (200, 120, 255), + "K": (255, 230, 225), "r": (255, 90, 130), "c": (90, 70, 160), +} + + +def main(): + path = sys.argv[1] + surf = pygame.image.load(path) + w, h = surf.get_size() + cw, ch = 6, 7 + print("image", w, h) + keys = list(NAMES) + for row in range(0, h, ch): + line = [] + for col in range(0, w, cw): + # average color in cell + acc = [0, 0, 0] + n = 0 + for y in range(row, min(row + ch, h), 2): + for x in range(col, min(col + cw, w), 2): + px = surf.get_at((x, y)) + acc[0] += px[0]; acc[1] += px[1]; acc[2] += px[2] + n += 1 + if n == 0: + line.append("?") + continue + avg = tuple(a / n for a in acc) + # nearest named color, weighted + best, bd = "?", 1e9 + for k, c in NAMES.items(): + d = (avg[0] - c[0]) ** 2 * 1.2 + (avg[1] - c[1]) ** 2 + (avg[2] - c[2]) ** 2 + if d < bd: + bd, best = d, k + # dark cells just show a dot of their tone + line.append(best) + print("".join(line)) + + +if __name__ == "__main__": + main() diff --git a/autotest.py b/autotest.py new file mode 100644 index 0000000..1755931 --- /dev/null +++ b/autotest.py @@ -0,0 +1,151 @@ +"""Headless autoplay test: drive the game for N frames, report health stats. + +Usage: python autotest.py [frames] [seed] +""" + +import os +import sys + +os.environ.setdefault("SDL_VIDEODRIVER", "dummy") +os.environ.setdefault("SDL_AUDIODRIVER", "dummy") + +import pygame # noqa: E402 +from game import Game # noqa: E402 + + +class AutoPlayer: + def __init__(self, g, seed=3): + self.g = g + self.rng = __import__("random").Random(seed) + self.charge_until = 0 + self.prev_events = None + self.stats = dict(launches=0, drains=0, bumpers=0, slings=0, + star_lit=0, fever=0, games=0, extra=0, + d_center=0, d_side=0) + self.prev = dict(hits=0, slingflash=0, lit=0, lives=3, next_extra=30000) + self.stuck = 0 + self.prev_pos = None + + def tick(self): + g = self.g + t = g.table + + if g.state == "over": + g.state = "play" + g.new_game() + self.stats["games"] += 1 + self.prev["lives"] = 3 + self.prev["next_extra"] = 30000 + + # charge then release on a schedule + if g.pending == "serve" and not t.ball_live: + if self.charge_until <= 0: + self.charge_until = self.rng.randint(24, 70) + g.keys["fire"] = True + self.charge_until -= 1 + if self.charge_until <= 0: + g.keys["fire"] = False + else: + g.keys["fire"] = False + self.charge_until = 0 + + was_live = t.ball_live + self.prev_frame = getattr(self, "prev_frame", None) + + # flipper policy: catch anything heading down the middle/bottom + b = t.ball + if t.ball_live and b.vy > 0 and b.y > 150: + g.keys["L"] = b.x < 258 + g.keys["R"] = b.x > 222 + else: + if t.ball_live and self.rng.random() < 0.04: + g.keys["L"] = self.rng.random() < 0.5 + g.keys["R"] = not g.keys["L"] + else: + g.keys["L"] = g.keys["R"] = False + + g.update() + + b = t.ball + if not was_live and t.ball_live: + self.stats["launches"] += 1 + + # count events that fired this frame (only when physics actually ran) + if t.events is not self.prev_events: + self.prev_events = t.events + for e in t.events: + if e.kind == "bumper": + self.stats["bumpers"] += 1 + elif e.kind == "sling": + self.stats["slings"] += 1 + elif e.kind == "star_lit": + self.stats["star_lit"] += 1 + if sum(s["lit"] for s in t.stars) == len(t.stars): + self.stats["fever"] += 1 + elif e.kind == "drain": + if 180 < e.x < 300: + self.stats["d_center"] += 1 + else: + self.stats["d_side"] += 1 + + if b.speed > 0.8: + self.stuck = 0 + elif t.ball_live: + self.stuck += 1 + if self.stuck > 120: + print(" !! ball appears stuck at (%.0f, %.0f)" % (b.x, b.y)) + self.stuck = 0 + b.vx = 2 + b.vy = -2 + else: + self.stuck = 0 + if g.lives < self.prev["lives"]: + self.stats["drains"] += self.prev["lives"] - g.lives + self.prev["lives"] = g.lives + if g.lives > self.prev["lives"]: + self.stats["extra"] += g.lives - self.prev["lives"] + self.prev["lives"] = g.lives + + def report(self): + s = self.stats + print("=== AUTOPLAY REPORT ===") + print("games played :", s["games"]) + print("launches :", s["launches"]) + print("drains :", s["drains"]) + print("bumper hits :", s["bumpers"]) + print("sling hits :", s["slings"]) + print("stars lit :", s["star_lit"]) + print("fevers :", s["fever"]) + print("extra lives :", s["extra"]) + print("final score :", self.g.score) + print("hi :", self.g.hi) + print("avg drain/ball ok" if s["drains"] > 0 else "!! NO DRAINS SEEN") + print("drains: center=%d side=%d" % (s["d_center"], s["d_side"])) + if s["bumpers"] == 0: + print("!! BUMPERS NEVER HIT") + if s["slings"] == 0: + print("!! SLINGS NEVER HIT") + if s["star_lit"] == 0: + print("!! STARS NEVER LIT") + return (s["bumpers"], s["slings"], s["star_lit"], s["drains"]) + + +def main(): + frames = int(sys.argv[1]) if len(sys.argv) > 1 else 40000 + seed = int(sys.argv[2]) if len(sys.argv) > 2 else 3 + g = Game() + g.state = "play" + g.new_game() + ap = AutoPlayer(g, seed) + clock = pygame.time.Clock() + for i in range(frames): + ap.tick() + if i % 2000 == 0: + print("frame %d score=%d lives=%d ball_live=%s pending=%s" % + (i, g.score, g.lives, g.table.ball_live, g.pending)) + ap.report() + pygame.quit() + + +if __name__ == "__main__": + main() diff --git a/font.py b/font.py new file mode 100644 index 0000000..6082737 --- /dev/null +++ b/font.py @@ -0,0 +1,116 @@ +"""Tiny hand-rolled 5x5 pixel bitmap font, rendered chunky for an 8-bit look.""" + +import pygame + + +GLYPHS = { + "A": [".###.", "#...#", "#####", "#...#", "#...#"], + "B": ["####.", "#...#", "####.", "#...#", "####."], + "C": [".###.", "#....", "#....", "#....", ".###."], + "D": ["####.", "#...#", "#...#", "#...#", "####."], + "E": ["#####", "#....", "####.", "#....", "#####"], + "F": ["#####", "#....", "####.", "#....", "#...."], + "G": [".###.", "#....", "#.###", "#...#", ".###."], + "H": ["#...#", "#...#", "#####", "#...#", "#...#"], + "I": ["#####", "..#..", "..#..", "..#..", "#####"], + "J": ["..###", "...#.", "...#.", "#..#.", ".##.."], + "K": ["#...#", "#..#.", "###..", "#..#.", "#...#"], + "L": ["#....", "#....", "#....", "#....", "#####"], + "M": ["#...#", "##.##", "#.#.#", "#...#", "#...#"], + "N": ["#...#", "##..#", "#.#.#", "#..##", "#...#"], + "O": [".###.", "#...#", "#...#", "#...#", ".###."], + "P": ["####.", "#...#", "####.", "#....", "#...."], + "Q": [".###.", "#...#", "#...#", "#..#.", ".##.#"], + "R": ["####.", "#...#", "####.", "#..#.", "#...#"], + "S": [".###.", "#....", ".###.", "....#", "####."], + "T": ["#####", "..#..", "..#..", "..#..", "..#.."], + "U": ["#...#", "#...#", "#...#", "#...#", ".###."], + "V": ["#...#", "#...#", "#...#", ".#.#.", "..#.."], + "W": ["#...#", "#...#", "#.#.#", "##.##", "#...#"], + "X": ["#...#", ".#.#.", "..#..", ".#.#.", "#...#"], + "Y": ["#...#", ".#.#.", "..#..", "..#..", "..#.."], + "Z": ["#####", "...#.", "..#..", ".#...", "#####"], + "0": [".###.", "#..##", "#.#.#", "##..#", ".###."], + "1": ["..#..", ".##..", "..#..", "..#..", ".###."], + "2": ["####.", "....#", ".###.", "#....", "#####"], + "3": ["####.", "....#", ".###.", "....#", "####."], + "4": ["#...#", "#...#", "#####", "....#", "....#"], + "5": ["#####", "#....", "####.", "....#", "####."], + "6": [".###.", "#....", "####.", "#...#", ".###."], + "7": ["#####", "....#", "...#.", "..#..", ".#..."], + "8": [".###.", "#...#", ".###.", "#...#", ".###."], + "9": [".###.", "#...#", ".####", "....#", ".###."], + " ": [".....", ".....", ".....", ".....", "....."], + ".": [".....", ".....", ".....", "..##.", "..##."], + ",": [".....", ".....", "..#..", "..#..", ".#..."], + "!": ["..#..", "..#..", "..#..", ".....", "..#.."], + "?": [".###.", "#...#", "..#..", ".....", "..#.."], + "'": ["..#..", "..#..", ".....", ".....", "....."], + '"': [".#.#.", ".#.#.", ".....", ".....", "....."], + ":": [".....", "..#..", ".....", "..#..", "....."], + ";": [".....", "..#..", ".....", "..#..", ".#..."], + "-": [".....", ".....", "#####", ".....", "....."], + "+": ["..#..", "..#..", "#####", "..#..", "..#.."], + "*": ["#...#", ".#.#.", "..#..", ".#.#.", "#...#"], + "/": ["....#", "...#.", "..#..", ".#...", "#...."], + "=": [".....", "#####", ".....", "#####", "....."], + "<": ["...#.", "..#..", ".#...", "..#..", "...#."], + ">": [".#...", "..#..", "...#.", "..#..", ".#..."], + "(": ["...#.", "..#..", ".#...", "..#..", "...#."], + ")": [".#...", "..#..", "...#.", "..#..", ".#..."], + "~": [".....", ".#.#.", ".#.#.", ".....", "....."], + "|": ["..#..", "..#..", "..#..", "..#..", "..#.."], + "%": ["##..#", "##.#.", "..#..", ".#.##", "#..##"], + "&": [".##..", "#..#.", ".#...", "#.#.#", ".##.#"], + "@": [".###.", "#.#.#", "#.###", "#....", ".###."], + "#": [".#.#.", "#####", ".#.#.", "#####", ".#.#."], + "^": ["..#..", ".#.#.", "#...#", ".....", "....."], + "_": [".....", ".....", ".....", ".....", "#####"], +} + +_text_cache = {} + + +def text_width(text, scale=1, spacing=1): + if not text: + return 0 + return (len(text) * 5 + (len(text) - 1) * spacing) * scale + + +def render_text(text, color, scale=1, spacing=1, shadow=None): + """Return a Surface with the pixel text drawn.""" + key = (text, color, scale, spacing, shadow) + if key in _text_cache: + return _text_cache[key] + w = text_width(text, scale, spacing) + h = 5 * scale + surf = pygame.Surface((w, h), pygame.SRCALPHA) + if shadow is not None: + _draw_into(surf, text, shadow, scale, spacing, (scale, scale)) + _draw_into(surf, text, color, scale, spacing, (0, 0)) + _text_cache[key] = surf + return surf + + +def _draw_into(surf, text, color, scale, spacing, off): + x0 = off[0] + for ch in text.upper(): + g = GLYPHS.get(ch, GLYPHS[" "]) + for row in range(5): + for col in range(5): + if g[row][col] == "#": + surf.fill(color, (x0 + col * scale, off[1] + row * scale, scale, scale)) + x0 += (5 + spacing) * scale + + +def draw_text(surf, x, y, text, color, scale=1, spacing=1, align="left", shadow=(20, 12, 40, 180)): + """Draw text. align in {'left','center','right'}. Shadow color or None.""" + t = render_text(text, color, scale, spacing, shadow) + if align == "center": + x -= t.get_width() // 2 + elif align == "right": + x -= t.get_width() + surf.blit(t, (round(x), round(y))) + return t.get_width(), t.get_height() + + diff --git a/game.py b/game.py new file mode 100644 index 0000000..516a68e --- /dev/null +++ b/game.py @@ -0,0 +1,937 @@ +"""Cosmic Candy Pinball - game logic + procedural pixel-art rendering.""" + +import math +import os +import random + +import pygame + +from physics import (CV_W, CV_H, PLAY_L, PLAY_R, PLAY_T, PLAY_B, BALL_R, + CENTER_HOLE, OUTLANE_L, OUTLANE_R, DRAIN_Y, LAUNCH_DIR, + LAUNCH_MIN, LAUNCH_MAX, Table, FLIP_PIVOT_L, FLIP_PIVOT_R, + FLIP_RADIUS, FLIP_LEN) +import font as pix + +# --------------------------------------------------------------------------- +# palette +# --------------------------------------------------------------------------- +C = dict( + bg_top=(42, 26, 96), bg_bot=(14, 9, 42), + hud=(22, 15, 56), hud_line=(255, 120, 190), + field=(64, 50, 140), field2=(70, 54, 150), + rail_dark=(22, 14, 52), rail=(255, 150, 200), rail_hi=(255, 210, 235), + floor=(176, 140, 255), floor_hi=(226, 205, 255), floor_dark=(70, 50, 150), + pit=(8, 5, 26), pit_glint=(60, 40, 120), + white=(255, 255, 255), ink=(26, 18, 60), + pink=(255, 138, 196), pink_d=(200, 82, 150), pink_l=(255, 200, 230), + mint=(110, 240, 200), mint_d=(40, 170, 140), mint_l=(200, 255, 240), + lemon=(255, 214, 92), lemon_d=(214, 150, 40), lemon_l=(255, 244, 190), + candy=(200, 120, 255), candy_d=(140, 70, 210), + flipper=(120, 205, 255), flipper_d=(50, 120, 210), flipper_l=(215, 245, 255), + gold=(255, 214, 92), gold_d=(200, 130, 40), + heart=(255, 90, 130), dim=(180, 170, 220), + fur=(255, 230, 225), fur_d=(150, 110, 130), ear=(255, 130, 180), + blush=(255, 140, 170), eye=(40, 30, 70), + bubble=(30, 22, 78), bubble_b=(255, 150, 200), +) + +PASTELS = [(255, 158, 205), (255, 226, 140), (140, 245, 215), + (165, 190, 255), (240, 165, 255), (255, 255, 255)] + + +def star_pts(cx, cy, ro, ri, n=5, rot=-90.0): + pts = [] + for i in range(n * 2): + r = ro if i % 2 == 0 else ri + a = math.radians(rot + i * 180.0 / n) + pts.append((cx + r * math.cos(a), cy + r * math.sin(a))) + return pts + + +def draw_star(surf, cx, cy, ro, color, ri=None, outline=None): + ri = ri if ri is not None else ro * 0.45 + if outline: + pygame.draw.polygon(surf, outline, star_pts(cx, cy, ro + 1, ri + 1)) + pygame.draw.polygon(surf, color, star_pts(cx, cy, ro, ri)) + pygame.draw.polygon(surf, (255, 255, 255), star_pts(cx, cy, ro * 0.4, ri * 0.5)) + + +def draw_heart(surf, x, y, s, color): + m = [" .##. ", "##### ", "######", "##### ", " .##. "] + for row, line in enumerate(m): + for col, ch in enumerate(line): + if ch == "#": + surf.fill(color, (x + col * s, y + row * s, s, s)) + + +CAT_HEAD = [ + "..##....##..", + ".##.#..#.##.", + "##..####..##", + "##....##..##", + "##..####..##", + "###########.", + ".#########..", + "..#######...", + "..###.###...", + "..##...##...", +] + +BODY = [ + "..#####...", + ".#######..", + ".#######..", + "##########", + ".#######..", + "...##.##..", +] + + +def draw_cat(surf, x, y, s, expr="happy", t=0.0): + """Pixel cat: x,y = head center (top-ish). s = pixel scale.""" + head_w = 12 * s + hx = int(x - head_w // 2) + hy = int(y - head_w // 2) + for row, line in enumerate(CAT_HEAD): + for col, ch in enumerate(line): + if ch == "#": + surf.fill(C["fur"], (hx + col * s, hy + row * s, s, s)) + # eyes (blink on a cycle), mouth, blush + blink = (int(t * 2.2) % 70) > 63 + eye_y = hy + 4 * s + ex_l = hx + 3 * s + ex_r = hx + 8 * s + if expr == "happy": + pygame.draw.rect(surf, C["eye"], (ex_l, eye_y + s, s, s)) + pygame.draw.rect(surf, C["eye"], (ex_r, eye_y + s, s, s)) + elif expr == "sad": + pygame.draw.rect(surf, C["eye"], (ex_l, eye_y + 2 * s, s, s)) + pygame.draw.rect(surf, C["eye"], (ex_r, eye_y + 2 * s, s, s)) + elif expr == "star": # wide amazed eyes + pygame.draw.rect(surf, C["eye"], (ex_l - s, eye_y, 2 * s, 2 * s)) + pygame.draw.rect(surf, C["eye"], (ex_r, eye_y, 2 * s, 2 * s)) + else: + if blink: + pygame.draw.rect(surf, C["eye"], (ex_l, eye_y + s, 2 * s, s)) + pygame.draw.rect(surf, C["eye"], (ex_r, eye_y + s, 2 * s, s)) + else: + pygame.draw.rect(surf, C["eye"], (ex_l, eye_y + s, s, s)) + pygame.draw.rect(surf, C["eye"], (ex_r, eye_y + s, s, s)) + # mouth + my = hy + 6 * s + pygame.draw.rect(surf, C["eye"], (hx + 5 * s, my, s, s)) + # blush + surf.fill(C["blush"], (hx + s, hy + 5 * s, 2 * s, s)) + surf.fill(C["blush"], (hx + 9 * s, hy + 5 * s, 2 * s, s)) + + +def draw_body(surf, x, y, s): + w = 10 * s + bx = int(x - w // 2) + by = int(y) + for row, line in enumerate(BODY): + for col, ch in enumerate(line): + if ch == "#": + surf.fill(C["fur"], (bx + col * s, by + row * s, s, s)) + pygame.draw.rect(surf, C["fur_d"], (bx + 2 * s, by + 2 * s, w - 4 * s, s)) + # little tail curl + tx, ty = bx + w + 1, by + 3 * s + for i in range(4): + pygame.draw.rect(surf, C["fur"], (tx + i, ty - i, s, s)) + pygame.draw.rect(surf, C["ear"], (tx + 4, ty - 4, s, s)) + + +def draw_bumper(surf, b, face_t, blink_t): + cx, cy, r = b["x"], b["y"], b["r"] + color_map = {"pink": (C["pink"], C["pink_d"], C["pink_l"]), + "mint": (C["mint"], C["mint_d"], C["mint_l"]), + "lemon": (C["lemon"], C["lemon_d"], C["lemon_l"])} + base, dark, light = color_map[b["color"]] + squash = max(0, b["squash"]) + rr = int(r - (squash * 0.6)) + pygame.draw.circle(surf, (30, 20, 66), (int(cx) + 2, int(cy) + 3), rr) # shadow + pygame.draw.circle(surf, dark, (int(cx), int(cy)), rr) + pygame.draw.circle(surf, base, (int(cx), int(cy)), rr - 2) + pygame.draw.circle(surf, light, (int(cx), int(cy)), rr - 6) + # gloss pixels + for dx in (-3, -2, -1): + for dy in (-4, -3): + surf.fill((255, 255, 255), (int(cx) + dx, int(cy) + dy, 1, 1)) + # candy dots around rim + for i in range(8): + a = i * math.pi / 4 + 0.35 + surf.fill(dark, (int(cx + (rr - 3) * math.cos(a)), int(cy + (rr - 3) * math.sin(a)), 2, 2)) + # face + surprised = face_t < 9 + blink = blink_t > 0 + ex = 6 + ey = -2 + pygame.draw.circle(surf, C["eye"], (int(cx - ex), int(cy + ey)), 2 if surprised else 1) + pygame.draw.circle(surf, C["eye"], (int(cx + ex), int(cy + ey)), 2 if surprised else 1) + if blink and not surprised: + surf.fill(C["eye"], (int(cx - ex) - 1, int(cy + ey), 3, 1)) + surf.fill(C["eye"], (int(cx + ex) - 1, int(cy + ey), 3, 1)) + # mouth + if surprised: + pygame.draw.circle(surf, C["eye"], (int(cx), int(cy + 6)), 2) + else: + surf.fill(C["eye"], (int(cx) - 2, int(cy + 5), 5, 1)) + # blush + surf.fill(C["blush"], (int(cx - ex) - 4, int(cy + 3), 2, 1)) + surf.fill(C["blush"], (int(cx + ex) + 2, int(cy + 3), 2, 1)) + + +def draw_sling(surf, s): + x, y = int(s["x"]), int(s["y"]) + flash = s["flash"] > 0 + pygame.draw.circle(surf, (40, 30, 80), (x + 2, y + 3), 11) + pygame.draw.circle(surf, C["candy_d"], (x, y), 11) + body = (255, 255, 255) if flash else C["candy"] + pygame.draw.circle(surf, body, (x, y), 8) + pygame.draw.circle(surf, (255, 255, 255), (x, y), 3) + # swirl stripe + pygame.draw.arc(surf, C["candy_d"], (x - 8, y - 8, 16, 16), 0.4, 2.0, 2) + + +def draw_flipper(surf, f, side): + px, py = f["pivot"] + a = f["angle"] + tipx = px + math.cos(a) * f["length"] + tipy = py + math.sin(a) * f["length"] + p0 = (int(px), int(py)) + p1 = (int(tipx), int(tipy)) + # shadow + pygame.draw.line(surf, (24, 16, 50), (int(px), int(py) + 4), (int(tipx), int(tipy) + 4), 12) + # outline, body, highlight + pygame.draw.line(surf, C["flipper_d"], p0, p1, 12) + pygame.draw.line(surf, C["flipper"], p0, p1, 8) + # rounded ends + for (ex, ey) in (p0, p1): + pygame.draw.circle(surf, C["flipper_d"], (ex, ey), 6) + pygame.draw.circle(surf, C["flipper"], (ex, ey), 4) + # cute stripe near the top edge + nx, ny = -math.sin(a), math.cos(a) + hx, hy = int(px + nx * 3), int(py + ny * 3) + tx, ty = int(tipx + nx * 3), int(tipy + ny * 3) + pygame.draw.line(surf, C["flipper_l"], (hx, hy), (tx, ty), 2) + + +def draw_cannon(surf, cx, cy, charge, charging, firing_flash): + x, y = int(cx), int(cy) + pygame.draw.circle(surf, (26, 18, 58), (x + 2, y + 2), 20) # shadow + pygame.draw.circle(surf, (90, 60, 160), (x, y), 19) # rim + pygame.draw.circle(surf, (150, 100, 220), (x, y), 15) # body + pygame.draw.circle(surf, (210, 170, 255), (x, y), 15, 2) + # barrel mouth pointing up-left along LAUNCH_DIR + mx, my = LAUNCH_DIR + px, py = x + mx * 8, y + my * 8 + pygame.draw.line(surf, (70, 45, 130), (x + mx * 2, y + my * 2), (px, py), 12) + pygame.draw.line(surf, (190, 150, 250), (x + mx * 2, y + my * 2), (px, py), 6) + pygame.draw.circle(surf, (60, 38, 110), (int(px), int(py)), 7) + pygame.draw.circle(surf, (30, 18, 60), (int(px), int(py)), 4) + # charge glow / sparkle + if charging: + glow = 1.0 + elif firing_flash > 0: + glow = min(1.0, firing_flash / 5.0) + else: + glow = 0.0 + if glow > 0: + rr = int(4 + (1 - charge) * 3 + (1 - glow) * 0) + for i in range(3): + a = 0.9 + i * 2.2 + charge * 0.5 + sx = px + math.cos(a) * (7 + charge * 9) + sy = py + math.sin(a) * (7 + charge * 9) + surf.fill((255, 240, 170), (int(sx), int(sy), 2, 2)) + + +def draw_ball(surf, bx, by, vx, vy, waiting): + x, y = int(bx), int(by) + pygame.draw.circle(surf, (120, 130, 170), (x, y), BALL_R) # base shade + pygame.draw.circle(surf, (245, 250, 255), (x, y), BALL_R - 1) + # look direction + sp = math.hypot(vx, vy) + if sp > 0.4 and not waiting: + lx, ly = vx / sp, vy / sp + else: + lx, ly = -0.4, -0.92 # looking up + px, py = -ly, lx # perpendicular + ex = x + lx * 3 + ey = y + ly * 3 + s1 = 4 + pygame.draw.circle(surf, C["eye"], (int(ex - px * s1), int(ey - py * s1)), 1) + pygame.draw.circle(surf, C["eye"], (int(ex + px * s1), int(ey + py * s1)), 1) + pygame.draw.circle(surf, C["blush"], (int(x + px * (s1 + 1) - lx), int(y + py * (s1 + 1) - ly + 2)), 1) + pygame.draw.circle(surf, C["blush"], (int(x - px * (s1 + 1) - lx), int(y - py * (s1 + 1) - ly + 2)), 1) + pygame.draw.circle(surf, (255, 255, 255), (x - 2, y - 3), 2) # shine + + +def draw_ball_shadow(surf, bx, by, on_floor_y=PLAY_B): + x, y = int(bx), int(by) + k = min(1.0, max(0.15, 1.0 - (y - (on_floor_y - 40)) / 60.0)) + pygame.draw.circle(surf, (20, 14, 48), (x + 2, int(on_floor_y) + 4), int(6 * k)) + + +class Game: + def __init__(self, screen_size=None): + pygame.init() + pygame.display.set_caption("Cosmic Candy Pinball") + icon = pygame.Surface((16, 16)) + icon.fill(C["bg_top"]) + for row, line in enumerate(CAT_HEAD): + for col, ch in enumerate(line): + if ch == "#": + icon.fill(C["fur"], (int(col * 16 / 12), int(row * 16 / 10), 2, 2)) + pygame.display.set_icon(icon) + if screen_size is None: + info = pygame.display.Info() + scale = max(1, min(4, info.current_w // CV_W, info.current_h // CV_H)) + screen_size = (CV_W * scale, CV_H * scale) + self.scale = screen_size[0] // CV_W + self.win = pygame.display.set_mode(screen_size) + self.canvas = pygame.Surface((CV_W, CV_H)) + self.overlay = pygame.Surface((CV_W, CV_H), pygame.SRCALPHA) + + from sfx import Synth + self.sfx = Synth() + self.muted = False + + self.rng = random.Random() + self.table = Table(seed=self.rng.randrange(1 << 30)) + self.keys = {"L": False, "R": False, "fire": False} + self.was_charging = False + self.state = "title" # title | play | over + self.frame = 0 + self.stars_bg = [] + for _ in range(120): + self.stars_bg.append([self.rng.randrange(CV_W), self.rng.randrange(CV_H), + self.rng.random(), self.rng.randrange(40)]) + self._build_static() + + # run state + self.score = 0 + self.hi = self._load_hi() + self.lives = 3 + self.ball_no = 1 + self.new_hi = False + self.combo = 0 + self.combo_t = 0 + self.fever = 0.0 + self.next_extra = 50000 + self.messages = [] # (text,color,scale,x,y,life) + self.particles = [] + self.bubble = None # (text, life) + self.shake = 0.0 + self.charge = 0.0 + self.charging = False + self.pending = "serve" # serve | drain_pause | ball_banner | over_banner + self.pending_t = 0 + self.over_t = 0 + self.confetti_t = 0 + self.wall_cool = 0 + self.enter_was_down = False + self.paused = False + self.firing_flash = 0 + self.sfx.start_menu_music() + + # ------------------------------------------------------------------ files + def _hi_path(self): + d = os.path.dirname(os.path.abspath(__file__)) + return os.path.join(d, "highscore.json") + + def _load_hi(self): + try: + import json + with open(self._hi_path()) as fh: + return int(json.load(fh).get("hi", 0)) + except Exception: + return 0 + + def _save_hi(self): + try: + import json + with open(self._hi_path(), "w") as fh: + json.dump({"hi": self.hi}, fh) + except Exception: + pass + + # ------------------------------------------------------------------ static + def _build_static(self): + bg = pygame.Surface((CV_W, CV_H)) + for y in range(CV_H): + t = y / CV_H + col = tuple(int(a + (b - a) * t) for a, b in zip(C["bg_top"], C["bg_bot"])) + pygame.draw.line(bg, col, (0, y), (CV_W, y)) + # twinkle stars & planets for the background + for sx, sy, tw, ph in self.stars_bg: + if 0 < sx < 20 or 460 < sx < CV_W or sy < 46: + pygame.draw.rect(bg, (230, 225, 255), (sx, sy, 1, 1)) + self.bg = bg + + field = pygame.Surface((CV_W, CV_H), pygame.SRCALPHA) + # wall frame (thick rounded frame forming the rails) + field.fill((0, 0, 0, 0)) + rect = (PLAY_L - 3, PLAY_T - 3, PLAY_R - PLAY_L + 6, PLAY_B - PLAY_T + 6) + pygame.draw.rect(field, C["rail_dark"], rect, border_radius=10) + pygame.draw.rect(field, C["rail"], rect, width=3, border_radius=10) + pygame.draw.rect(field, C["rail_hi"], rect, width=1, border_radius=10) + # playfield interior + inner = (PLAY_L, PLAY_T, PLAY_R - PLAY_L, PLAY_B - PLAY_T) + pygame.draw.rect(field, C["field"], inner, border_radius=7) + pygame.draw.rect(field, C["field2"], (PLAY_L + 4, PLAY_T + 4, + PLAY_R - PLAY_L - 8, PLAY_B - PLAY_T - 8), border_radius=5) + # candy dot sparkle inside the field + r = self.rng + for _ in range(46): + dx = r.randrange(int(PLAY_L) + 12, int(PLAY_R) - 12) + dy = r.randrange(int(PLAY_T) + 14, int(PLAY_B) - 12) + if any((dx - b["pos"][0]) ** 2 + (dy - b["pos"][1]) ** 2 < (b["r"] + 4) ** 2 + for b in ({"pos": (150, 180), "r": 23}, {"pos": (330, 180), "r": 23}, + {"pos": (240, 240), "r": 23})): + continue + col = PASTELS[r.randrange(len(PASTELS))] + field.fill(tuple(c // 3 for c in col), (dx, dy, 2, 2)) + # floor ledges + for x0, x1 in ((OUTLANE_L, CENTER_HOLE[0]), (CENTER_HOLE[1], OUTLANE_R)): + pygame.draw.line(field, C["floor_dark"], (x0, PLAY_B + 1), (x1, PLAY_B + 1), 5) + pygame.draw.line(field, C["floor"], (x0, PLAY_B), (x1, PLAY_B), 3) + pygame.draw.line(field, C["floor_hi"], (x0, PLAY_B - 1), (x1, PLAY_B - 1), 1) + # pits (drains) + pits = [(PLAY_L, OUTLANE_L), (CENTER_HOLE[0], CENTER_HOLE[1]), (OUTLANE_R, PLAY_R)] + for x0, x1 in pits: + pygame.draw.rect(field, C["pit"], (x0, PLAY_B, x1 - x0, DRAIN_Y - PLAY_B + 6), border_radius=4) + pygame.draw.line(field, C["pit_glint"], (x0 + 3, PLAY_B + 3), (x0 + 3, PLAY_B + 9)) + # little post feet under the flipper pivots + for (px, py) in (FLIP_PIVOT_L, FLIP_PIVOT_R): + pygame.draw.circle(field, C["floor_dark"], (int(px), int(py) - 2), 7) + pygame.draw.circle(field, (150, 120, 255), (int(px), int(py) - 2), 5) + pygame.draw.circle(field, (220, 205, 255), (int(px), int(py) - 3), 2) + self.field = field + + hud = pygame.Surface((CV_W, 52)) + hud.fill(C["hud"]) + pygame.draw.line(hud, C["rail_dark"], (0, 50), (CV_W, 50), 2) + pygame.draw.line(hud, C["hud_line"], (0, 47), (CV_W, 47), 1) + pygame.draw.rect(hud, (255, 255, 255), (0, 0, 8, 52)) + pygame.draw.rect(hud, (255, 255, 255), (CV_W - 8, 0, 8, 52)) + self.hud = hud + + # ------------------------------------------------------------------ game + def new_game(self): + self.table = Table(seed=self.rng.randrange(1 << 30)) + self.score = 0 + self.lives = 3 + self.ball_no = 1 + self.new_hi = False + self.combo = 0 + self.combo_t = 0 + self.fever = 0.0 + self.next_extra = 50000 + self.messages = [] + self.particles = [] + self.bubble = None + self.shake = 0.0 + self.charge = 0.0 + self.charging = False + self.firing_flash = 0 + self.pending = "ball_banner" + self.pending_t = 90 + self.sfx.start_play_music() + self.sfx.play("start") + + def _ball_lost(self, x, y): + self.sfx.play("drain") + self.particles += [dict(x=x, y=y, vx=r.uniform(-1.5, 1.5), vy=r.uniform(-2, 0.5), + life=30, kind="spark", color=(255, 255, 255)) + for r in (self.rng,) for _ in range(8)] + self.shake = 4 + self.combo = 0 + self.cat_say("OOF!", 60, "sad") + self.lives -= 1 + if self.lives > 0: + self.pending = "drain_pause" + self.pending_t = 55 + else: + self.pending = "over_banner" + self.pending_t = 10 + if self.score > self.hi: + self.hi = self.score + self.new_hi = True + self._save_hi() + self.sfx.play("hi") + + def _serve_next(self): + self.ball_no += 1 + self.table.reset_ball_in_cannon() + self.fever = 0.0 + self.pending = "serve" + + def _start_fever(self): + self.fever = 60 * 20 + self.sfx.play("fever") + self.cat_say("CANDY FEVER!", 90, "star") + self.shake = 3 + self.add_msg("CANDY FEVER!", CV_W // 2, 150, C["gold"], scale=2) + self.add_msg("DOUBLE SCORE x2", CV_W // 2, 166, C["pink"], scale=1) + for i in range(30): + self.particles.append(dict( + x=self.rng.uniform(PLAY_L + 10, PLAY_R - 10), y=self.rng.uniform(60, 120), + vx=self.rng.uniform(-0.4, 0.4), vy=self.rng.uniform(0.4, 1.2), + life=80, kind="confetti", + color=PASTELS[self.rng.randrange(len(PASTELS))])) + + # ------------------------------------------------------------------ cat + def cat_say(self, text, frames, expr): + self.bubble = [text, frames, expr] + + # ------------------------------------------------------------------ update + def update(self): + self.frame += 1 + if self.paused: + return + if self.state == "title": + self._title_update() + return + if self.state == "over": + self._update_particles() + self._update_messages() + self.pending_t -= 1 + return + + # --- PLAY --- + for f in self.table.flippers: + self.table.press("L", self.keys["L"]) + self.table.press("R", self.keys["R"]) + + if self.pending == "ball_banner": + self.pending_t -= 1 + if self.pending_t <= 30: + self.pending = "serve" + elif self.pending == "drain_pause": + self.pending_t -= 1 + if self.pending_t <= 0: + self._serve_next() + elif self.pending == "over_banner": + self.pending_t -= 1 + if self.pending_t <= 0: + self.state = "over" + self.sfx.start_menu_music() + return + elif self.pending == "serve": + if self.table.ball_live: + self.pending = "live" + else: + self.charging = self.keys["fire"] + if self.charging: + self.was_charging = True + self.charge = min(1.0, self.charge + 0.022) + if self.frame % 5 == 0: + self.sfx.play("tick", vol=0.3 + self.charge * 0.5) + if self.charge >= 1.0: + self._fire() + else: + if self.was_charging and self.charge >= 0.06: + self._fire() + self.charge = 0.0 + self.was_charging = False + elif self.pending == "live": + events = self.table.update() + self._handle_events(events) + if self.pending != "live": + return # a drain just happened + + # timers + if self.combo > 0: + self.combo_t -= 1 + if self.combo_t <= 0: + self.combo = 0 + if self.fever > 0: + self.fever -= 1 + if self.confetti_t <= 0: + self.confetti_t = 4 + self.particles.append(dict( + x=self.rng.uniform(PLAY_L + 20, PLAY_R - 20), y=PLAY_T + 2, + vx=self.rng.uniform(-0.5, 0.5), vy=self.rng.uniform(0.3, 1.0), + life=70, kind="confetti", + color=PASTELS[self.rng.randrange(len(PASTELS))])) + if self.confetti_t > 0: + self.confetti_t -= 1 + if self.wall_cool > 0: + self.wall_cool -= 1 + if self.shake > 0: + self.shake = max(0.0, self.shake - 0.25) + if self.bubble: + self.bubble[1] -= 1 + if self.bubble[1] <= 0: + self.bubble = None + if self.firing_flash > 0: + self.firing_flash -= 1 + + self._update_particles() + self._update_messages() + + def _fire(self): + power = LAUNCH_MIN + (LAUNCH_MAX - LAUNCH_MIN) * self.charge + self.sfx.play("launch", vol=0.6 + 0.4 * self.charge) + ev = self.table.launch(power) + self.firing_flash = 6 + self.pending = "live" + self.was_charging = False + self.charge = 0.0 + self.add_msg("FIRE!", ev.x, ev.y, C["gold"], scale=1) + + # ------------------------------------------------------------------ events + def _handle_events(self, events): + mult = 2 if self.fever > 0 else 1 + for e in events: + if e.kind == "bumper": + self.combo = min(self.combo + 1, 9) + self.combo_t = 150 + pts = 250 * self.combo * mult + self._add_score(pts, e.x, e.y) + self.sfx.play("bump", pitch_var=min(9, self.combo), vol=0.8) + b = e.data + self.particles += [ + dict(x=e.x, y=e.y, vx=self.rng.uniform(-1.2, 1.2), + vy=self.rng.uniform(-1.8, -0.2), life=22, kind="spark", + color=(255, 255, 255)), + dict(x=e.x, y=e.y, life=16, kind="ring", color=(255, 255, 255)), + ] + self.shake = max(self.shake, 1.6) + if self.combo == 5: + self.sfx.play("combo") + self.cat_say("COMBO x5!", 60, "happy") + elif e.kind == "sling": + self.combo = min(self.combo + 1, 9) + self.combo_t = 150 + pts = 150 * self.combo * mult + self._add_score(pts, e.x, e.y) + self.sfx.play("sling", vol=0.8) + self.particles.append(dict(x=e.x, y=e.y, life=12, kind="ring", color=(255, 255, 255))) + self.shake = max(self.shake, 1.0) + elif e.kind == "star_lit": + lit = sum(1 for s in self.table.stars if s["lit"]) + pts = 100 * mult + self._add_score(pts, e.x, e.y) + self.sfx.play("starLit" if lit == len(self.table.stars) else "star") + if lit == len(self.table.stars): + self._start_fever() + else: + self.cat_say(["MEOW!", "NYA!", "PURR!"][lit - 1], 45, "happy") + elif e.kind == "star_again": + pts = (100 if self.fever > 0 else 25) * mult + self._add_score(pts, e.x, e.y, small=True) + if self.fever > 0: + self.fever = min(60 * 40, self.fever + 60 * 3) + elif e.kind == "drain": + self._ball_lost(e.x, e.y) + elif e.kind == "nudge": + self.sfx.play("tick", vol=0.2) + elif e.kind == "wall_hit": + if self.wall_cool <= 0 and e.data > 9.5: + self.sfx.play("wall", vol=min(0.5, (e.data - 9) / 5)) + self.wall_cool = 5 + elif e.kind == "flipper_hit": + if self.rng.random() < 0.3: + self.sfx.play("tick", vol=0.25) + elif e.kind == "launch": + pass + + def _add_score(self, pts, x, y, small=False): + self.score += pts + self.add_msg("+%d" % pts, x, y, C["gold"] if pts >= 500 else C["white"], scale=1) + # milestone extra lives + while self.score >= self.next_extra: + self.lives += 1 + self.sfx.play("extra") + self.cat_say("EXTRA BALL!", 90, "star") + self.add_msg("EXTRA BALL!", CV_W // 2, 120, C["pink"], scale=2) + self.next_extra += 30000 + + def add_msg(self, text, x, y, color, scale=1): + if len(self.messages) < 26: + self.messages.append([text, color, scale, float(x), float(y), 60]) + + # ------------------------------------------------------------------ fx + def _update_messages(self): + for m in self.messages[:]: + m[5] -= 1 + m[3] += self.rng.uniform(-0.05, 0.05) + m[4] -= 0.5 + if m[5] <= 0: + self.messages.remove(m) + + def _update_particles(self): + for p in self.particles[:]: + p["life"] -= 1 + if p["life"] <= 0: + self.particles.remove(p) + continue + if p["kind"] == "confetti": + p["vy"] += 0.02 + p["x"] += p["vx"] + p["y"] += p["vy"] + elif p["kind"] == "spark": + p["vy"] += 0.06 + p["x"] += p["vx"] + p["y"] += p["vy"] + elif p["kind"] == "ring": + pass + + # ------------------------------------------------------------------ render + def render(self): + c = self.canvas + if self.state == "title": + self._render_title(c) + elif self.state == "over": + self._render_over(c) + else: + self._render_play(c) + if self.shake > 0.5: + tmp = c.copy() + c.fill((0, 0, 0)) + sx = self.rng.randint(-2, 2) + sy = self.rng.randint(-2, 2) + c.blit(tmp, (int(sx * self.shake * 0.5), int(sy * self.shake * 0.5))) + if self.paused and self.state == "play": + self.overlay.fill((10, 8, 40, 170)) + c.blit(self.overlay, (0, 0)) + pix.draw_text(c, CV_W // 2, CV_H // 2 - 20, "PAUSED", C["white"], 3, align="center") + pix.draw_text(c, CV_W // 2, CV_H // 2 + 16, "PRESS P TO RESUME", C["dim"], 1, align="center") + pygame.transform.scale(c, self.win.get_size(), self.win) + pygame.display.flip() + + # ------------------------------------------------------------------ play render + def _render_play(self, c): + c.blit(self.bg, (0, 0)) + c.blit(self.field, (0, 0)) + t = self.table + + # slings + for s in t.slings: + draw_sling(c, s) + # flippers + for f in t.flippers: + draw_flipper(c, f, f["side"]) + # star lanes + for i, st in enumerate(t.stars): + lit = st["lit"] + pulse = st["pulse"] > 0 + ro = 8 + (2 if pulse else 0) + color = C["gold"] if lit else (110, 95, 175) + o = (200, 130, 40) if lit else (70, 60, 120) + draw_star(c, st["x"], st["y"], ro, color, outline=o) + # cannon + draw_cannon(c, t.cannon["x"], t.cannon["y"], self.charge, self.charging, self.firing_flash) + # bumpers + for b in t.bumpers: + draw_bumper(c, b, b["face_t"], b["blink"]) + # ball + ball = t.ball + if not t.ball_live: + draw_ball(c, ball.x, ball.y - 4, 0, 0, True) + else: + if ball.speed > 7: + for i, (px, py) in enumerate(t.trail[:-1]): + r = max(1, 4 - i) + c.fill((220, 210, 255), (int(px) - r // 2, int(py) - r // 2, r, r)) + draw_ball_shadow(c, ball.x, ball.y) + draw_ball(c, ball.x, ball.y, ball.vx, ball.vy, False) + + # combo indicator + if self.combo >= 2 and self.pending == "live": + col = (255, 140, 90) if self.combo >= 5 else C["gold"] + pix.draw_text(c, CV_W // 2, 56, "COMBO x%d!" % self.combo, col, 1, align="center") + + # particles + for p in self.particles: + x, y = int(p["x"]), int(p["y"]) + if p["kind"] == "confetti": + c.fill(p["color"], (x, y, 2, 2)) + elif p["kind"] == "spark": + c.fill(p["color"], (x, y, 2, 2)) + elif p["kind"] == "ring": + age = 16 - p["life"] + pygame.draw.circle(c, p["color"], (x, y), min(14, 2 + age), 1) + + # fever edge glow + if self.fever > 0: + col = C["pink"] if self.fever < 120 else C["gold"] + for i in range(0, 8): + pygame.draw.rect(c, col, (PLAY_L - 3 + i * 3, PLAY_T - 6, 1, 3)) + + # floating score text + for m in self.messages: + if m[5] > 10: + pix.draw_text(c, m[3], m[4], m[0], m[1], m[2], align="center") + + # cat bubble + if self.bubble: + self._draw_bubble(c) + + # pending banners + if self.pending == "ball_banner" and self.pending_t > 40: + self._dim_center(c) + pix.draw_text(c, CV_W // 2, 140, "BALL %d" % self.ball_no, C["white"], 3, align="center") + pix.draw_text(c, CV_W // 2, 170, "GET READY...", C["pink"], 1, align="center") + if self.pending == "drain_pause": + self._dim_center(c) + pix.draw_text(c, CV_W // 2, 150, "LOST THE BALL!", C["pink"], 2, align="center") + if self.pending == "serve" and not t.ball_live: + pix.draw_text(c, CV_W // 2, 300, "HOLD SPACE / UP TO CHARGE", C["dim"], 1, align="center") + + # HUD + self._render_hud(c) + + def _dim_center(self, c): + self.overlay.fill((0, 0, 0, 0)) + pygame.draw.rect(self.overlay, (12, 8, 40, 160), + (CV_W // 2 - 100, 110, 200, 90), border_radius=8) + c.blit(self.overlay, (0, 0)) + + def _draw_bubble(self, c): + text, life, expr = self.bubble + x0, y0 = 30, 70 + w = pix.text_width(text, 1) + 14 + pygame.draw.rect(c, C["bubble"], (x0, y0, w, 14), border_radius=4) + pygame.draw.rect(c, C["bubble_b"], (x0, y0, w, 14), 1, border_radius=4) + pix.draw_text(c, x0 + 7, y0 + 5, text, C["white"], 1) + # little cat reacting inside a corner + draw_cat(c, x0 + w + 14, y0 + 2, 1, expr, self.frame) + + def _render_hud(self, c): + c.blit(self.hud, (0, 0)) + pix.draw_text(c, 14, 4, "SCORE", C["dim"], 1) + pix.draw_text(c, 14, 13, "{:,}".format(self.score), C["white"], 2) + pix.draw_text(c, 170, 4, "BEST", C["dim"], 1) + best = max(self.hi, self.score) + pix.draw_text(c, 170, 13, "{:,}".format(best), C["gold"], 2) + # lives hearts + for i in range(self.lives): + draw_heart(c, 336 + i * 14, 14, 2, C["heart"]) + pix.draw_text(c, 336, 30, "LIVES", C["dim"], 1) + # stars progress + pix.draw_text(c, 14, 32, "STARS", C["dim"], 1) + for i, st in enumerate(self.table.stars): + x = 52 + i * 18 + draw_star(c, x, 37, 5 if st["lit"] else 4, + C["gold"] if st["lit"] else (90, 78, 150)) + # fever indicator + bar + if self.fever > 0: + frac = max(0.0, self.fever / (60 * 20)) + pygame.draw.rect(c, (60, 46, 110), (260, 30, 70, 6)) + pygame.draw.rect(c, (255, 150, 200), (260, 30, int(70 * frac), 6)) + pix.draw_text(c, 260, 38, "FEVER x2", C["gold"], 1) + if self.paused: + pix.draw_text(c, CV_W - 40, 40, "PAUSED", C["dim"], 1, align="right") + + # ------------------------------------------------------------------ title + def _title_update(self): + self._update_particles() + if self.frame % 30 == 0 and self.state == "title": + self.particles.append(dict( + x=self.rng.uniform(30, CV_W - 30), y=CV_H + 4, + vx=self.rng.uniform(-0.3, 0.3), vy=self.rng.uniform(-0.7, -0.3), + life=300, kind="spark", color=(255, 240, 200))) + self.sfx if False else None + + def _render_title(self, c): + c.blit(self.bg, (0, 0)) + # ringed planet + pygame.draw.circle(c, (150, 110, 220), (90, 70), 24) + pygame.draw.circle(c, (190, 160, 245), (90, 70), 24, 2) + pygame.draw.ellipse(c, (255, 200, 220), (58, 64, 64, 18), 2) + pygame.draw.circle(c, (110, 90, 200), (300, 320), 10) + pygame.draw.circle(c, (110, 90, 200), (300, 320), 10, 1) + # twinkling stars + for sx, sy, tw, ph in self.stars_bg: + if (self.frame + ph) % 70 < 6: + pygame.draw.rect(c, (255, 255, 255), (sx, sy, 1, 1)) + # particles + for p in self.particles: + c.fill(p["color"], (int(p["x"]), int(p["y"]), 2, 2)) + + # logo + words = [("COSMIC", 3, 36, C["pink"]), ("CANDY", 3, 62, C["gold"]), + ("PINBALL", 2, 108, C["mint"])] + y = 40 + for txt, sc, _, col in words: + tw = pix.text_width(txt, sc) + x = CV_W // 2 - tw // 2 + for i, ch in enumerate(txt): + col2 = col + pix.draw_text(c, x, y, ch, col2, sc, shadow=(20, 12, 50)) + x += (5 + 1) * sc + y += (5 + 2) * sc + # little shooting star occasionally + if self.frame % 90 < 24: + t0 = self.frame % 90 + sx = 330 + t0 * 3 + sy = 40 + t0 * 1.5 + pygame.draw.line(c, (255, 255, 255), (int(sx), int(sy)), (int(sx - 8), int(sy - 4)), 1) + + # cat + draw_body(c, CV_W // 2 - 26, 180, 2) + draw_cat(c, CV_W // 2 - 26, 168, 2, "happy", self.frame) + # instructions panel + pygame.draw.rect(c, (22, 15, 56), (70, 240, CV_W - 140, 74), border_radius=8) + pygame.draw.rect(c, C["rail"], (70, 240, CV_W - 140, 74), 2, border_radius=8) + lines = [ + ("LEFT / RIGHT ARROWS = FLIPPERS", C["flipper"]), + ("HOLD SPACE OR UP = CHARGE CANNON", C["gold"]), + ("M MUTE R RESTART P PAUSE", C["dim"]), + ] + for i, (txt, col) in enumerate(lines): + pix.draw_text(c, CV_W // 2, 250 + i * 20, txt, col, 1, align="center") + + if (self.frame // 30) % 2 == 0: + pix.draw_text(c, CV_W // 2, 325, "PRESS ENTER TO START", C["white"], 2, align="center") + + # ------------------------------------------------------------------ game over + def _render_over(self, c): + self._render_play(c) + self.overlay.fill((14, 9, 40, 215)) + c.blit(self.overlay, (0, 0)) + pix.draw_text(c, CV_W // 2, 60, "GAME OVER", C["pink"], 3, align="center") + pix.draw_text(c, CV_W // 2, 105, "SCORE", C["dim"], 1, align="center") + pix.draw_text(c, CV_W // 2, 115, "{:,}".format(self.score), C["white"], 3, align="center") + pix.draw_text(c, CV_W // 2, 160, "BEST", C["dim"], 1, align="center") + pix.draw_text(c, CV_W // 2, 170, "{:,}".format(self.hi), C["gold"], 2, align="center") + if self.new_hi and (self.frame // 20) % 2 == 0: + pix.draw_text(c, CV_W // 2, 205, "NEW HI-SCORE!", C["gold"], 1, align="center") + draw_body(c, CV_W // 2 - 20, 240, 2) + draw_cat(c, CV_W // 2 - 20, 226, 2, "sad", self.frame) + if (self.frame // 30) % 2 == 0: + pix.draw_text(c, CV_W // 2, 320, "PRESS ENTER TO PLAY AGAIN", C["white"], 1, align="center") + + # ------------------------------------------------------------------ input + def event(self, ev): + if ev.type == pygame.KEYDOWN: + if ev.key == pygame.K_ESCAPE: + return False + if ev.key == pygame.K_m: + self.muted = not self.muted + self.sfx.set_muted(self.muted) + self.sfx.set_sfx_muted(self.muted) + if ev.key == pygame.K_p and self.state == "play": + self.paused = not self.paused + if ev.key == pygame.K_r and self.state == "play": + self.new_game() + if ev.key in (pygame.K_RETURN, pygame.K_KP_ENTER, pygame.K_SPACE): + if ev.key in (pygame.K_RETURN, pygame.K_KP_ENTER): + self.enter_was_down = True + if self.state == "title": + self.state = "play" + self.new_game() + elif self.state == "over": + self.state = "play" + self.new_game() + if ev.type == pygame.QUIT: + return False + return True + + def set_keys(self): + k = pygame.key.get_pressed() + self.keys["L"] = k[pygame.K_LEFT] or k[pygame.K_z] or k[pygame.K_j] + self.keys["R"] = k[pygame.K_RIGHT] or k[pygame.K_SLASH] or k[pygame.K_LCTRL] + self.keys["fire"] = k[pygame.K_SPACE] or k[pygame.K_UP] or k[pygame.K_DOWN] diff --git a/main.py b/main.py new file mode 100644 index 0000000..b492e27 --- /dev/null +++ b/main.py @@ -0,0 +1,34 @@ +"""Cosmic Candy Pinball - entry point. + +Controls: + Left/Right arrows (or Z//) .... flippers + Hold Space / Up / Down ........ charge the candy cannon, release to fire + M ................................ mute + P ................................ pause + R ................................ restart the game + Esc .............................. quit +""" + +import pygame + +from game import Game + + +def main(): + g = Game() + clock = pygame.time.Clock() + running = True + while running: + for ev in pygame.event.get(): + if not g.event(ev): + running = False + if running: + g.set_keys() + g.update() + g.render() + clock.tick(60) + pygame.quit() + + +if __name__ == "__main__": + main() diff --git a/physics.py b/physics.py new file mode 100644 index 0000000..8ea44a2 --- /dev/null +++ b/physics.py @@ -0,0 +1,448 @@ +"""Pinball physics: pure logic, no rendering. Testable headless. + +Coordinate system: y grows downward. 480x360 virtual canvas. +""" + +import math +import random + +# --------------------------------------------------------------------------- +# Playfield geometry +# --------------------------------------------------------------------------- +CV_W, CV_H = 480, 360 +PLAY_L, PLAY_R, PLAY_T, PLAY_B = 20.0, 460.0, 52.0, 346.0 + +GRAVITY = 0.24 # px/frame^2 +MAX_SPEED = 13.0 # speed cap (prevents tunneling; ~300px of rise) +RESTITUTION = 0.42 +BALL_R = 7.0 + +FLIP_LEN = 170.0 +FLIP_REST = math.radians(6.0) # tips slightly lowered toward center drain +FLIP_UP = -math.radians(52.0) # fired position +FLIP_PIVOT_L = (62.0, 338.0) +FLIP_PIVOT_R = (418.0, 338.0) +FLIP_RADIUS = 5.5 +FLIP_SNAP_UP = 0.40 # rad/frame while firing +FLIP_SNAP_DOWN = 0.05 # gentle return speed +FLIP_BOUNCE = 0.12 + +# elements +BUMPERS = [ + dict(pos=(150.0, 180.0), r=23.0, color="pink"), + dict(pos=(330.0, 180.0), r=23.0, color="mint"), + dict(pos=(240.0, 240.0), r=23.0, color="lemon"), +] +SLING_KICK_L = (0.60, -0.80) +SLING_KICK_R = (-0.60, -0.80) +SLINGS = [ + # upper slingshots by the lower bumpers - kick toward table center + dict(pos=(150.0, 282.0), r=11.0, kick=SLING_KICK_L), + dict(pos=(330.0, 282.0), r=11.0, kick=SLING_KICK_R), + # guard posts at the outlane mouths - protect the corners + dict(pos=(44.0, 314.0), r=12.0, kick=(0.80, -0.60)), + dict(pos=(436.0, 314.0), r=12.0, kick=(-0.80, -0.60)), +] +STARS = [(168.0, 92.0), (240.0, 92.0), (312.0, 92.0)] + +CANNON = (440.0, 152.0) +CANNON_R = 17.0 +LAUNCH_DIR = (-0.94, -0.34) +LAUNCH_MIN, LAUNCH_MAX = 7.0, 13.5 + +DRAIN_Y = 362.0 +CENTER_HOLE = (200.0, 280.0) +OUTLANE_L = 46.0 +OUTLANE_R = 434.0 + + +def clamp(v, lo, hi): + return lo if v < lo else hi if v > hi else v + + +def seg_info(px, py, ax, ay, bx, by): + """Closest point on segment ab to p; returns (t, cx, cy, dist2).""" + abx, aby = bx - ax, by - ay + l2 = abx * abx + aby * aby + if l2 == 0.0: + return 0.0, ax, ay, (px - ax) ** 2 + (py - ay) ** 2 + t = clamp(((px - ax) * abx + (py - ay) * aby) / l2, 0.0, 1.0) + cx, cy = ax + abx * t, ay + aby * t + dx, dy = px - cx, py - cy + return t, cx, cy, dx * dx + dy * dy + + +class Ball: + __slots__ = ("x", "y", "vx", "vy", "rad", "prev_x", "prev_y") + + def __init__(self, x, y): + self.x, self.y = float(x), float(y) + self.vx = self.vy = 0.0 + self.rad = BALL_R + self.prev_x, self.prev_y = x, y + + @property + def speed(self): + return math.hypot(self.vx, self.vy) + + +class Event: + __slots__ = ("kind", "x", "y", "data") + + def __init__(self, kind, x=0.0, y=0.0, data=None): + self.kind, self.x, self.y, self.data = kind, x, y, data + + def __repr__(self): + return "Event(%s)" % self.kind + + +class Table: + def __init__(self, seed=1): + self.rng = random.Random(seed) + self.events = [] + self.frame = 0 + + # static walls / floors: list of (a, b, kind) where kind in + # ("rail", "floor") - rails keep balls in, floors hold them up + self.walls = [ + ((PLAY_L, PLAY_T), (PLAY_L, PLAY_B), "rail"), + ((PLAY_R, PLAY_T), (PLAY_R, PLAY_B), "rail"), + ((PLAY_L, PLAY_T), (PLAY_R, PLAY_T), "rail"), + # floors tilt gently down toward the center so balls funnel inward + ((OUTLANE_L, PLAY_B - 8), (CENTER_HOLE[0], PLAY_B), "floor"), + ((CENTER_HOLE[1], PLAY_B), (OUTLANE_R, PLAY_B - 8), "floor"), + ((CENTER_HOLE[0], PLAY_B), (CENTER_HOLE[0], DRAIN_Y), "hole"), + ((CENTER_HOLE[1], PLAY_B), (CENTER_HOLE[1], DRAIN_Y), "hole"), + ((OUTLANE_L, PLAY_B), (OUTLANE_L, DRAIN_Y), "hole"), + ((OUTLANE_R, PLAY_B), (OUTLANE_R, DRAIN_Y), "hole"), + ] + + self.bumpers = [ + dict(x=b["pos"][0], y=b["pos"][1], r=b["r"], color=b["color"], + cool=0, hit=0, squash=0, face_t=0.0, blink=0.0) + for b in BUMPERS] + self.slings = [ + dict(x=s["pos"][0], y=s["pos"][1], r=s["r"], cool=0, flash=0, + kick=s["kick"], guard=(i >= 2)) + for i, s in enumerate(SLINGS)] + self.stars = [dict(x=x, y=y, r=13.0, lit=False, cool=0, pulse=0) + for x, y in STARS] + self.flippers = [ + dict(side="L", pivot=FLIP_PIVOT_L, angle=FLIP_REST, rest=FLIP_REST, + up=FLIP_UP, length=FLIP_LEN, pressed=False, w=0.0, contacting=False), + dict(side="R", pivot=FLIP_PIVOT_R, angle=FLIP_REST, rest=FLIP_REST, + up=FLIP_UP, length=FLIP_LEN, pressed=False, w=0.0, contacting=False), + ] + self.cannon = dict(x=CANNON[0], y=CANNON[1], r=CANNON_R) + + self.ball = Ball(CANNON[0], CANNON[1]) + self.ball_live = False + self.charge = 0.0 + self.stuck_frames = 0 + self.trail = [] + + # ------------------------------------------------------------- input + def press(self, side, down): + f = self.flippers[0] if side == "L" else self.flippers[1] + f["pressed"] = down + + # ------------------------------------------------------------- setup + def reset_ball_in_cannon(self): + self.charge = 0.0 + self.ball_live = False + self.ball.x, self.ball.y = CANNON[0] + 6, CANNON[1] + self.ball.vx = self.ball.vy = 0.0 + self.ball.prev_x, self.ball.prev_y = self.ball.x, self.ball.y + self.stuck_frames = 0 + self.trail = [] + + def launch(self, power): + self.ball_live = True + mx, my = CANNON + off = CANNON_R + self.ball.rad + 2.0 + self.ball.x = mx + LAUNCH_DIR[0] * off + self.ball.y = my + LAUNCH_DIR[1] * off + self.ball.vx = LAUNCH_DIR[0] * power + self.ball.vy = LAUNCH_DIR[1] * power + self.ball.prev_x, self.ball.prev_y = self.ball.x, self.ball.y + self.trail = [(self.ball.x, self.ball.y)] + return Event("launch", self.ball.x, self.ball.y) + + # ------------------------------------------------------------- frame + def update(self): + self.events = [] + self.frame += 1 + + # decay timers + for b in self.bumpers: + if b["cool"] > 0: + b["cool"] -= 1 + if b["squash"] > 0: + b["squash"] -= 1 + b["face_t"] += 1 + if b["blink"] > 0: + b["blink"] -= 1 + elif self.rng.random() < 0.0025: + b["blink"] = 16 + for s in self.slings: + if s["cool"] > 0: + s["cool"] -= 1 + if s["flash"] > 0: + s["flash"] -= 1 + for st in self.stars: + if st["cool"] > 0: + st["cool"] -= 1 + if st["pulse"] > 0: + st["pulse"] -= 1 + + if not self.ball_live: + return self.events + if self.ball.y > DRAIN_Y + 30 and not self._in_drain(self.ball.x, self.ball.y): + # lost below the table somehow - force a drain + self.events.append(Event("drain", self.ball.x, self.ball.y)) + return self.events + + ball = self.ball + + # flipper angle update (kinematic; per-frame, then substepped) + for f in self.flippers: + target = f["up"] if f["pressed"] else f["rest"] + rate = FLIP_SNAP_UP if f["pressed"] else FLIP_SNAP_DOWN + d = target - f["angle"] + f["angle"] = target if abs(d) < rate else f["angle"] + (rate if d > 0 else -rate) + f["w"] = f["angle"] - (f.get("prev_angle", f["angle"])) + f["prev_angle"] = f["angle"] + f["contacting"] = False + + speed = ball.speed + n_sub = 1 if speed <= 0.0 else min(6, max(1, int(math.ceil(speed / 4.0)))) + for _ in range(n_sub): + self._substep(ball, 1.0 / n_sub) + + self.trail.append((ball.x, ball.y)) + if len(self.trail) > 9: + self.trail.pop(0) + + if ball.speed < 0.55: + self.stuck_frames += 1 + if self.stuck_frames > 60 and self.stuck_frames % 14 == 0: + ang = self.rng.uniform(0.0, math.tau) + ball.vx += math.cos(ang) * 1.3 + ball.vy += math.sin(ang) * 1.3 - 1.1 + self.events.append(Event("nudge", ball.x, ball.y)) + else: + self.stuck_frames = 0 + + if self._in_drain(ball.x, ball.y): + self.events.append(Event("drain", ball.x, ball.y)) + return self.events + + # ------------------------------------------------------------- helpers + def _in_drain(self, x, y): + if y < DRAIN_Y - 2: + return False + if CENTER_HOLE[0] - 6 < x < CENTER_HOLE[1] + 6: + return True + if x < OUTLANE_L - 4 or x > OUTLANE_R + 4: + return True + if y > DRAIN_Y + 8: + return x < OUTLANE_L + 10 or x > OUTLANE_R - 10 + return False + + # ------------------------------------------------------------- substep + def _substep(self, ball, dt): + ball.vy += GRAVITY * dt + ball.vx *= (1.0 - 0.014 * dt) + ball.vy *= (1.0 - 0.005 * dt) + ball.prev_x, ball.prev_y = ball.x, ball.y + ball.x += ball.vx * dt + ball.y += ball.vy * dt + + # static walls / floors + for (ax, ay), (bx, by), kind in self.walls: + t, cx, cy, d2 = seg_info(ball.x, ball.y, ax, ay, bx, by) + if d2 >= ball.rad * ball.rad: + continue + if t <= 0.0 or t >= 1.0: + continue + dist = math.sqrt(d2) + nx = (ball.x - cx) / dist + ny = (ball.y - cy) / dist + # one-way-ish: never fling a ball that has dropped past a floor plane + if kind == "floor" and ball.y > PLAY_B + ball.rad * 0.5 and ny < -0.3: + continue + ball.x = cx + nx * ball.rad + ball.y = cy + ny * ball.rad + vn = ball.vx * nx + ball.vy * ny + if vn < 0.0: + e = RESTITUTION if kind == "floor" else 0.5 + ball.vx -= (1.0 + e) * vn * nx + ball.vy -= (1.0 + e) * vn * ny + ball.vx *= 0.985 + if kind == "rail" and ball.speed > 8.0: + self.events.append(Event("wall_hit", ball.x, ball.y, ball.speed)) + + # bumpers + for b in self.bumpers: + self._bumper_collide(ball, b) + + # slingshots + for s in self.slings: + self._sling_collide(ball, s) + + # flippers + for f in self.flippers: + self._flipper_collide(ball, f) + + # cannon deflector + self._circle_push(ball, self.cannon["x"], self.cannon["y"], + self.cannon["r"], 0.5) + + # star lanes + for st in self.stars: + dx = ball.x - st["x"] + dy = ball.y - st["y"] + rr = st["r"] + ball.rad + if dx * dx + dy * dy < rr * rr and st["cool"] <= 0: + st["cool"] = 26 + st["pulse"] = 14 + if not st["lit"]: + st["lit"] = True + self.events.append(Event("star_lit", st["x"], st["y"], st)) + else: + self.events.append(Event("star_again", st["x"], st["y"], st)) + + self._cap(ball) + + @staticmethod + def _cap(ball): + sp = ball.speed + if sp > MAX_SPEED: + k = MAX_SPEED / sp + ball.vx *= k + ball.vy *= k + + def _circle_push(self, ball, cx, cy, cr, e, impulse=None, cool_key=None): + """Generic circle collider. impulse=(ix,iy,strength) overrides bounce.""" + dx = ball.x - cx + dy = ball.y - cy + rr = cr + ball.rad + d2 = dx * dx + dy * dy + if d2 >= rr * rr or d2 < 0.0001: + return False + d = math.sqrt(d2) + nx, ny = dx / d, dy / d + ball.x = cx + nx * rr + ball.y = cy + ny * rr + if impulse is not None: + kx, ky, sp = impulse + ball.vx = kx * sp + ball.vy = ky * sp + return True + vn = ball.vx * nx + ball.vy * ny + if vn < 0.0: + ball.vx -= (1.0 + e) * vn * nx + ball.vy -= (1.0 + e) * vn * ny + return True + + def _bumper_collide(self, ball, b): + dx = ball.x - b["x"] + dy = ball.y - b["y"] + rr = b["r"] + ball.rad + d2 = dx * dx + dy * dy + if d2 >= rr * rr or d2 < 0.0001: + return + d = math.sqrt(d2) + nx, ny = dx / d, dy / d + ball.x = b["x"] + nx * rr + ball.y = b["y"] + ny * rr + if b["cool"] > 0: + return + b["cool"] = 6 + b["hit"] += 1 + b["squash"] = 5 + b["face_t"] = 0.0 + kick = max(6.0, ball.speed * 0.72 + 4.2) + ball.vx = nx * kick + ball.vy = ny * kick + self.events.append(Event("bumper", b["x"] + nx * b["r"], b["y"] + ny * b["r"], b)) + + def _sling_collide(self, ball, s): + dx = ball.x - s["x"] + dy = ball.y - s["y"] + rr = s["r"] + ball.rad + d2 = dx * dx + dy * dy + if d2 >= rr * rr or d2 < 0.0001: + return + d = math.sqrt(d2) + nx, ny = dx / d, dy / d + ball.x = s["x"] + nx * rr + ball.y = s["y"] + ny * rr + if s["cool"] > 0: + return + s["cool"] = 4 + s["flash"] = 7 + kx, ky = s["kick"] + ball.vx = kx * 9.6 + ball.vy = ky * 9.6 + self.events.append(Event("sling", s["x"] + kx * 22, s["y"] + ky * 22, s)) + + def _flipper_collide(self, ball, f): + px, py = f["pivot"] + a = f["angle"] + tipx = px + math.cos(a) * f["length"] + tipy = py + math.sin(a) * f["length"] + t, cx, cy, d2 = seg_info(ball.x, ball.y, px, py, tipx, tipy) + rad = FLIP_RADIUS + ball.rad + if d2 >= rad * rad: + return + # endpoint (pivot) -> small circle collider; tip end -> still a segment + if t <= 0.0 or t >= 1.0: + ex, ey = (px, py) if t <= 0.0 else (tipx, tipy) + exx, eyy = ball.x - ex, ball.y - ey + ed = math.hypot(exx, eyy) + if ed < rad: + nx, ny = exx / ed, eyy / ed + ball.x = ex + nx * rad + ball.y = ey + ny * rad + self._flipper_impulse(ball, f, nx, ny) + return + dist = math.sqrt(d2) + nx = (ball.x - cx) / dist + ny = (ball.y - cy) / dist + ball.x = cx + nx * rad + ball.y = cy + ny * rad + self._flipper_impulse(ball, f, nx, ny) + + def _flipper_impulse(self, ball, f, nx, ny): + """Reflect relative velocity across the moving flipper surface.""" + w = f["w"] + px, py = f["pivot"] + # surface velocity of the flipper at the contact point, in px/frame + # rotating by angle w (screen-clockwise positive) => v = w * (-ry, rx) + rx, ry = ball.x - px, ball.y - py + svx = -ry * w + svy = rx * w + rvx = ball.vx - svx + rvy = ball.vy - svy + vn = rvx * nx + rvy * ny + + fresh = not f["contacting"] + f["contacting"] = True + if vn < -0.9: + if fresh: + j = -(1.0 + FLIP_BOUNCE) * vn + else: + j = -vn # inelastic follow-through while pressing + ball.vx += j * nx + ball.vy += j * ny + elif fresh and vn > 0.0: + # tiny push to keep the ball from sinking into a resting flipper + pass + # tangential traction: transfer some of the flipper's swipe to the ball + tx, ty = -ny, nx + vt = rvx * tx + rvy * ty + traction = 0.35 if abs(w) > 0.01 else 0.0 + ball.vx += vt * tx * -traction + svx * 0.0 + ball.vy += vt * ty * -traction + self._cap(ball) diff --git a/probe.py b/probe.py new file mode 100644 index 0000000..16d713f --- /dev/null +++ b/probe.py @@ -0,0 +1,86 @@ +"""Pixel-probe the rendered frames for layout sanity (since art can't be +viewed directly in this session).""" + +import os + +os.environ.setdefault("SDL_VIDEODRIVER", "dummy") +os.environ.setdefault("SDL_AUDIODRIVER", "dummy") + +import pygame # noqa: E402 +from game import Game, C # noqa: E402 +from physics import CV_W, CV_H # noqa: E402 + + +def count_px(surf, rect, pred): + n = 0 + for y in range(rect[1], rect[1] + rect[3]): + for x in range(rect[0], rect[0] + rect[2]): + px = surf.get_at((x, y)) + if pred(px): + n += 1 + return n + + +def near(c, t, tol=60): + return abs(c[0] - t[0]) <= tol and abs(c[1] - t[1]) <= tol and abs(c[2] - t[2]) <= tol + + +def main(): + g = Game() + g.state = "play" + g.new_game() + g.score = 123456 + g.lives = 3 + for st in g.table.stars: + st["lit"] = True + g.table.ball_live = True + g.pending = "live" + g.table.ball.x, g.table.ball.y = 240, 300 + g.table.ball.vx, g.table.ball.vy = 0, 3 + for _ in range(3): + g.update() + g.render() + s = g.canvas + checks = [] + # HUD white score digits around (14..100, 13..26) + checks.append(("HUD score digits", count_px(s, (14, 13, 110, 15), + lambda p: near(p, (255, 255, 255), 40)) > 25)) + # HUD gold BEST digits near (170..300, 13..26) + checks.append(("HUD gold best", count_px(s, (170, 13, 150, 15), + lambda p: near(p, C["gold"], 55)) > 10)) + # hearts red at (336..390, 14..30) + checks.append(("hearts", count_px(s, (336, 12, 60, 22), + lambda p: near(p, C["heart"], 50)) > 20)) + # three gold stars in HUD at (52..112, 30..44) + checks.append(("HUD stars lit", count_px(s, (44, 28, 90, 18), + lambda p: near(p, C["gold"], 55)) > 15)) + # playfield interior present (not black) + checks.append(("field fill", near(s.get_at((240, 100)), C["field"], 70))) + # pink rail present on left edge inside play area + rail_ok = count_px(s, (14, 70, 12, 20), lambda p: near(p, C["rail"], 60)) > 10 + checks.append(("left rail pink", rail_ok)) + # bumper pink at (150,180) + checks.append(("bumper pink", near(s.get_at((150, 178)), C["pink"], 55))) + # bumper lemon at (240,240) + checks.append(("bumper lemon", near(s.get_at((240, 238)), C["lemon"], 55))) + # pit dark center at (240, 352) + checks.append(("pit center", near(s.get_at((240, 353)), C["pit"], 30))) + # flipper blue present around (240, 320) + checks.append(("flipper blue", count_px(s, (100, 300, 280, 45), + lambda p: near(p, C["flipper"], 55)) > 30)) + # cannon purple body at (440,152) + checks.append(("cannon", near(s.get_at((440, 152)), (150, 100, 220), 70))) + # ball near (240,300) is white-ish + checks.append(("ball", count_px(s, (230, 290, 20, 20), + lambda p: near(p, (245, 250, 255), 40)) > 5)) + ok = True + for name, passed in checks: + print(("%s %s" % ("PASS" if passed else "FAIL", name))) + ok = ok and passed + pygame.image.save(s, "probe_play.png") + pygame.quit() + return 0 if ok else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..48f0979 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +# Runtime dependency (the only one!). Developed and tested on: +# CPython 3.12, pygame-ce 2.5.8 (pygame-ce, not pygame). +# Should also work on Python 3.10+. +pygame-ce==2.5.8 diff --git a/runtest.py b/runtest.py new file mode 100644 index 0000000..8b6a1ae --- /dev/null +++ b/runtest.py @@ -0,0 +1,68 @@ +"""Simulate a real play session: posts real pygame events through Game.event, +drives keys, renders every frame. Headless. Usage: python runtest.py [frames]""" + +import os +import sys +import random + +os.environ.setdefault("SDL_VIDEODRIVER", "dummy") +os.environ.setdefault("SDL_AUDIODRIVER", "dummy") + +import pygame # noqa: E402 +from game import Game # noqa: E402 + +pygame.init() +pygame.display.init() +KEYS = [pygame.K_LEFT, pygame.K_RIGHT, pygame.K_SPACE, + pygame.K_UP, pygame.K_z, pygame.K_j, pygame.K_SLASH] + + +def main(): + frames = int(sys.argv[1]) if len(sys.argv) > 1 else 900 + rng = random.Random(11) + g = Game() + # press Enter on the title + pygame.event.post(pygame.event.Event(pygame.KEYDOWN, key=pygame.K_RETURN)) + clock = pygame.time.Clock() + held = set() + start = pygame.time.get_ticks() + for i in range(frames): + # occasionally press/release flipper & launch keys for real + if rng.random() < 0.2: + k = rng.choice(KEYS) + if k in held: + held.discard(k) + pygame.event.post(pygame.event.Event(pygame.KEYUP, key=k)) + else: + held.add(k) + pygame.event.post(pygame.event.Event(pygame.KEYDOWN, key=k)) + if i % 700 == 350: + pygame.event.post(pygame.event.Event(pygame.KEYDOWN, key=pygame.K_p)) # pause test + if i % 700 == 520: + pygame.event.post(pygame.event.Event(pygame.KEYDOWN, key=pygame.K_p)) + for ev in pygame.event.get(): + if not g.event(ev): + print("quit requested") + pygame.quit() + return 0 + g.set_keys() + g.update() + g.render() + clock.tick(240) + dt = (pygame.time.get_ticks() - start) / 1000.0 + print("ran %d frames in %.1fs state=%s score=%d lives=%d" % + (frames, dt, g.state, g.score, g.lives)) + # throw a couple of state transitions: force game over to check the screen + g.lives = 1 + g.pending = "drain_pause" + for _ in range(200): + g.set_keys() + g.update() + g.render() + print("after forced drain state=%s over_t=%s" % (g.state, g.pending)) + pygame.quit() + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/sfx.py b/sfx.py new file mode 100644 index 0000000..22e15d1 --- /dev/null +++ b/sfx.py @@ -0,0 +1,246 @@ +"""Procedural chiptune SFX & music synthesis. No audio files - everything is +generated into PCM buffers at startup.""" + +import array +import math +import random + +import pygame +SR = 22050 # sample rate + +# note -> frequency +NOTE = { + "C": 261.63, "C#": 277.18, "D": 293.66, "D#": 311.13, "E": 329.63, + "F": 349.23, "F#": 369.99, "G": 392.00, "G#": 415.30, "A": 440.00, + "A#": 466.16, "B": 493.88, +} + + +def freq(note, octave): + return NOTE[note] * (2 ** (octave - 4)) + + +class Synth: + """Builds Sound objects through pygame.mixer (falls back silently if no audio).""" + + def __init__(self): + self.ok = False + self.sounds = {} + try: + if not pygame.mixer.get_init(): + pygame.mixer.init(SR, -16, 1, 512) + self.ok = True + except Exception: + return + self._rng = random.Random(7) + self._build_all() + self.music_channel = pygame.mixer.Channel(0) + self.sfx_channels = [pygame.mixer.Channel(i) for i in range(1, 6)] + self._ch_i = 0 + + # ---- sample builders ------------------------------------------------- + @staticmethod + def _env(n, attack=0.005, release=0.25): + """Apply simple AD envelope to n samples.""" + a = max(1, int(attack * SR)) + r = max(1, int(release * SR)) + out = [] + for i in range(n): + if i < a: + v = i / a + elif i > n - r: + v = max(0.0, (n - i) / r) + else: + v = 1.0 + out.append(v) + return out + + @staticmethod + def _wave(shape, phase, duty=0.5): + p = phase % 1.0 + if shape == "square": + return 1.0 if p < duty else -1.0 + if shape == "saw": + return 2.0 * p - 1.0 + if shape == "tri": + return 4.0 * abs(p - 0.5) - 1.0 + if shape == "noise": + return 1.0 + return math.sin(2 * math.pi * p) + + def tone(self, f, dur, vol=0.5, shape="square", slide=0.0, duty=0.5): + """slide: freq multiplier change over duration (e.g. 0.7 sweeps down).""" + n = max(1, int(SR * dur)) + env = self._env(n) + out = array.array("h") + phase = 0.0 + for i in range(n): + t = i / n + ff = f * (1.0 + (slide - 1.0) * t) + phase += ff / SR + v = self._wave(shape, phase, duty) * env[i] * vol + out.append(int(max(-1.0, min(1.0, v)) * 32000)) + return out + + def noise(self, dur, vol=0.35, lp=0.3, slide=1.0): + """Filtered-ish noise burst. lp = lowpass smoothing (0..1).""" + n = max(1, int(SR * dur)) + env = self._env(n) + out = array.array("h") + acc = 0.0 + for i in range(n): + acc += (self._rng.uniform(-1, 1) - acc) * lp + v = acc * env[i] * vol + out.append(int(max(-1.0, min(1.0, v)) * 32000)) + return out + + @staticmethod + def _mix(*arrays): + ln = max(len(a) for a in arrays) + acc = [0] * ln + for a in arrays: + for i, v in enumerate(a): + acc[i] += v + peak = max(1, max(abs(v) for v in acc)) + scale = 32000.0 / peak if peak > 32000 else 1.0 + return array.array("h", (int(v * scale) for v in acc)) + + def _snd(self, name, samples): + try: + self.sounds[name] = pygame.mixer.Sound(buffer=samples.tobytes()) + except Exception: + self.sounds[name] = None + + # ---- all effects ----------------------------------------------------- + def _build_all(self): + self._snd("flipL", self.tone(180, 0.07, 0.5, "square", slide=0.7)) + self._snd("flipR", self.tone(210, 0.07, 0.5, "square", slide=0.7)) + self._snd("wall", self.tone(90, 0.045, 0.18, "square", duty=0.3)) + self._snd("launch", self._mix( + self.noise(0.28, 0.5, lp=0.25), + self.tone(160, 0.3, 0.35, "saw", slide=1.9))) + self._snd("drain", self.tone(500, 0.55, 0.5, "saw", slide=0.18)) + self._snd("tick", self.tone(2400, 0.015, 0.12, "square")) + + # bumpers: pitched by combo (choose at play time via 'bump' variants) + for i in range(12): + f = 300 * (1.08 ** i) + self._snd("bump%d" % i, self.tone(f, 0.16, 0.5, "square", slide=0.85)) + + self._snd("sling", self._mix( + self.tone(700, 0.05, 0.4, "square", slide=0.5), + self.tone(350, 0.09, 0.4, "saw", slide=0.8))) + self._snd("star", self.tone(880, 0.12, 0.42, "tri", slide=1.5)) + self._snd("starLit", self._mix( + self.tone(660, 0.09, 0.4, "tri"), self.tone(990, 0.16, 0.4, "tri"))) + + # arpeggio helpers + def arp(notes, step=0.11, vol=0.42): + parts = [] + for i, (nm, oc) in enumerate(notes): + parts.append(self.tone(freq(nm, oc), step * 1.6, vol, "square", slide=1.1)) + if i < len(notes) - 1: + parts.append(array.array("h", [0]) * int(SR * step * 0.55)) + return self._mix(*parts) if parts else array.array("h", [0]) + + self._snd("fever", arp([("C", 5), ("E", 5), ("G", 5), ("C", 6), ("E", 6), ("G", 6)], 0.09, 0.45)) + self._snd("extra", arp([("C", 4), ("C", 5), ("G", 5), ("E", 5), ("C", 6)], 0.13, 0.5)) + self._snd("milestone", arp([("C", 5), ("E", 5), ("A", 5), ("E", 6)], 0.1, 0.45)) + self._snd("combo", arp([("A", 4), ("C", 5), ("E", 5)], 0.06, 0.35)) + + def seq(note_spec, step=0.13, vol=0.4, shape="square"): + parts = [] + for nm, oc in note_spec: + parts.append(self.tone(freq(nm, oc), step * 1.35, vol, shape)) + return self._mix(*parts) + + self._snd("gameover", seq( + [("E", 5), ("C", 5), ("A", 4), ("F", 4), ("C", 4)], 0.24, 0.4, "saw")) + self._snd("start", seq( + [("C", 4), ("E", 4), ("G", 4), ("C", 5), ("E", 5), ("G", 5)], 0.09, 0.45)) + self._snd("hi", seq([("C", 6), ("E", 6), ("G", 6), ("C", 7)], 0.08, 0.3, "tri")) + + self._build_music() + + def _build_music(self): + """Two short generative loops: a dreamy menu loop and a bouncy play loop.""" + # menu loop: soft arpeggio C - Am - F - G + chords = [["C", "E", "G"], ["A", "C", "E"], ["F", "A", "C"], ["G", "B", "D"]] + bass = [("C", 2), ("A", 2), ("F", 2), ("G", 2)] + beat = 0.26 + parts = [] + for ci in range(4): + n = int(SR * beat * 2) + parts.append(self.tone(freq(bass[ci][0], bass[ci][1]), beat * 2.1, 0.22, "tri")) + # melodic ripple over the chord + for k in range(4): + note = chords[ci][k % 3] + f0 = freq(note, 4 if k < 2 else 5) + parts.append(self.tone(f0, beat * 0.8, 0.14, "square", duty=0.4)) + parts.append(array.array("h", [0]) * int(SR * beat * 0.2)) + self._menu_music = self._mix(*parts) + + # play loop: bouncy 8th-note line C-C-G-G-A-A-G + line = [("C", 4), ("C", 4), ("G", 4), ("G", 4), ("A", 4), ("A", 4), ("G", 4), ("E", 4)] + bassp = [("C", 2), ("C", 2), ("G", 2), ("G", 2), ("A", 2), ("A", 2), ("E", 2), ("E", 2)] + step = 0.16 + parts = [] + for i in range(16): + nm, oc = line[i % 8] + bn, bo = bassp[i % 8] + parts.append(self.tone(freq(bn, bo), step * 1.8, 0.28, "tri")) + parts.append(self.tone(freq(nm, oc), step * 1.3, 0.18, "square", duty=0.4)) + if i % 2 == 1: + parts.append(self.noise(0.03, 0.10, lp=0.15)) + else: + parts.append(array.array("h", [0]) * int(SR * step * 0.3)) + self._play_music = self._mix(*parts) + self._menu = pygame.mixer.Sound(buffer=self._menu_music.tobytes()) + self._play = pygame.mixer.Sound(buffer=self._play_music.tobytes()) + self._menu.set_volume(0.5) + self._play.set_volume(0.45) + + # ---- playback -------------------------------------------------------- + def play(self, name, vol=1.0, pitch_var=0): + if not self.ok: + return + key = name + if pitch_var: + key = "%s%d" % (name, pitch_var) + s = self.sounds.get(key) + if s is None: + s = self.sounds.get(name) + if s is None: + return + ch = self.sfx_channels[self._ch_i] + self._ch_i = (self._ch_i + 1) % len(self.sfx_channels) + ch.set_volume(min(1.0, vol)) + ch.play(s) + + def start_menu_music(self): + if not self.ok: + return + self.music_channel.set_volume(0.8) + self.music_channel.play(self._menu, loops=-1) + + def start_play_music(self): + if not self.ok: + return + self.music_channel.set_volume(0.8) + self.music_channel.play(self._play, loops=-1) + + def stop_music(self): + if self.ok: + self.music_channel.stop() + + def set_muted(self, m): + self.muted = m + if self.ok: + self.music_channel.set_volume(0.0 if m else 0.8) + + def set_sfx_muted(self, m): + self.sfx_muted = m + if self.ok: + for ch in self.sfx_channels: + ch.set_volume(0.0 if m else 1.0) + diff --git a/shots.py b/shots.py new file mode 100644 index 0000000..d026e86 --- /dev/null +++ b/shots.py @@ -0,0 +1,91 @@ +"""Render screenshots of key game states (headless) so we can inspect art. + +Usage: python shots.py [outdir] +""" + +import os +import sys + +os.environ.setdefault("SDL_VIDEODRIVER", "dummy") +os.environ.setdefault("SDL_AUDIODRIVER", "dummy") + +import pygame # noqa: E402 +from game import Game # noqa: E402 + + +def save(g, name, out): + g.render() + path = os.path.join(out, name + ".png") + pygame.image.save(g.canvas, path) + # also save an upscaled preview + big = pygame.transform.scale(g.canvas, (CV_W * 2, CV_H * 2)) + pygame.image.save(big, os.path.join(out, name + "_2x.png")) + print("saved", path) + + +from physics import CV_W, CV_H # noqa: E402 + + +def main(): + out = sys.argv[1] if len(sys.argv) > 1 else "shots" + os.makedirs(out, exist_ok=True) + g = Game() + + # ---- title ---- + for _ in range(50): + g.update() + save(g, "1_title", out) + + # ---- serve hint ---- + g.state = "play" + g.new_game() + for _ in range(140): # past the ball banner + g.update() + save(g, "2_serve", out) + + # ---- mid play, staged action ---- + t = g.table + # force a live ball drifting toward the bumpers + t.reset_ball_in_cannon() + g.pending = "live" + t.ball_live = True + t.ball.x, t.ball.y = 415, 60 + t.ball.vx, t.ball.vy = -2.2, 3.1 + for _ in range(70): + g.keys["fire"] = False + g.update() + save(g, "3_play", out) + + # manually trigger some juice: bumper hit + combo + fever + g.combo = 4 + g.combo_t = 150 + g.fever = 60 * 12 + g.add_msg("+500", 150, 150, (255, 214, 92)) + for st in t.stars: + st["lit"] = True + b = t.bumpers[0] + b["squash"] = 4 + b["face_t"] = 0.0 + g.particles.append(dict(x=150, y=150, life=14, kind="ring", color=(255, 255, 255))) + g.bubble = ["CANDY FEVER!", 90, "star"] + save(g, "4_fever", out) + + # ---- drain overlay ---- + g.pending = "drain_pause" + g.pending_t = 40 + save(g, "5_drain", out) + + # ---- game over ---- + g.pending = "over_banner" + g.pending_t = 1 + g.lives = 0 + g.update() + for _ in range(80): + g.update() + save(g, "6_gameover", out) + + pygame.quit() + + +if __name__ == "__main__": + main() diff --git a/shots/1_title.png b/shots/1_title.png new file mode 100644 index 0000000000000000000000000000000000000000..a8b96cbede6e4eef49636d7b8edb731ab3c244a9 GIT binary patch literal 5196 zcmZ`-cU03^w+(_IBA|$LL`8IvP$D3`jRlbAfYL)X6hUc`5FoTzMnD-ENvOgoMWln& zKu{4-N`MfFv>;&UBvL{PNxlS~Z_S(c-XB@NwQ}z{=bn4^IeV}B=$f^u;6bT_002PH z-0YGq0KoN?{qsApkNpqeMk@>e5It&s$>e%O_VRc@sSFv;_uVOyaL{aDxL5Mgi&=5+ zJ{%sneyV}^_05eEvwLm?d&PPwX*1i)jn7<+)-M|I1YSLQ+5A9yx=X_A82(yO@CR@dqx3sQKT>yX&fT@9N>=w6_ek2#LNPT*i0}W^D=6n){@bmJRcHo?}2a>Vx-X zXS~Bq#6WnB^({=F$joDS0a7wV7#Bd}Xg_fJFH`C~hR5+5GHeJu022gg^qX+oeLGv3 z#{TluK7*`9igjL0A<^Rn5HPM(O>N(CGJ*&&<__xznDG0>#AH1uZPk~*>B z1N5Hus$gh;Pdwawwj~8oMinvZVp|)H{k#Akg*>i=(5lmMd8i$gIGv3a8wB)A5suld zN^7tOHFWbPfs`>4{zYKki{jQb-d8*5O_^KH;0xMF+m>#LhdS(g8Ph;LMNkLjy5+JF zqvC6Ps@T;QPozU`MEGW{5Nmi=$-85eC|wZqx+${iot6wjfB^fAP&3nKgiW3OYei#&a*bIQ9QASQ&Q^>;$<3eLd^&V1EA!Rk zVx__}&Sz{EoDT+U6CzBxoq=;sGVY|6G$C)&0ZKqn!>z7ky%;wfHl6VG5jEh*gq>yE z`@8)#KN2ek`B+{H3j;%xL4rN_efyz0nGf%WI>9ZE4|z42`c7=z-+h-

MZ+-G66F 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 a6Kj3Y&cIK8C6Dt;XnxuHQu(hQ3I72Yko2bj literal 0 HcmV?d00001 diff --git a/shots/1_title_2x.png b/shots/1_title_2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0bef325683ef9eda6fcff7fc2b71fcae91239728 GIT binary patch literal 8100 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 m$0Gxeu=Te9 literal 0 HcmV?d00001 diff --git a/shots/3_play_2x.png b/shots/3_play_2x.png new file mode 100644 index 0000000000000000000000000000000000000000..b41b23ec7fae6c61b55791584052200e7dd3a706 GIT binary patch literal 11869 zcmbVycU)6hxAvik<0vpMjAa19Q4tX-N=E}3QIQ%20i|0hA}C1&66&bk`~Nr^b#Iay{#cuTTSv7F` zzmfsqm;3|;AOTom0s!*c^JRcd0iDzWn;91F#aKqc2>n^7F8{bR zxJahZTx3AwcnE3DygffPRvubfEI&=2=#b|i$Sd0)f?VjSghA5qxU$n){b%zQ+3Ab* zl=$Q`B*5ve{L*%)^ zA!XpTjVkb7d+B=#9jZYoMEkgT`VhUio>qM7(pPv!+ZBc72+3cm21MO85L_>baN7R6*bpu%^y1%0&Ri^4 zlo+?4rf5F>IkWAMfu+lEeC@l>G@nWifl;BIn=)cDN?^?9&DR1vTiGQKpz>t`l5z!? zxdZRCg|M&LWK)UtuuxWb(#7`=C{@9JQ`?%~nQk<;5=N(u^5)<&M*1pCezT~ou3j%l z*#8@lU>ThID`mg`U(J;6!w?LZ!Y4c;NpadLX%SKLBzfJ>C{mZ z%*E-SPuvq{$0pDT1E_`Yg&jqR0rMDcJ;#F){+tx6h2CC-7~B)KYGVniu8TL|fSjMd zNhn_X(n8{-CV3PR=jJVPb#4x3_IcGQ1EF~+L5-iL&zUyD{78i}wQIq*?N`aeY4TQ! ztwY7uC8_mg#c`$c(=AL==3^bE-NIK`ae5rzX7P<04ru@s-xpK|b~m`gogw78hCs&! zjMAu%*dMlNG`c|92l<-yGk+%s{%Gbg+lMRc>5c8`U6@WOoA;UXSFG-hbU#x`U{Ad# zU{@#LGe}W>budx3_F|I0P1fg}T~W*&($$%)rHFxETZJfkKnh06QhP@3>5%8F=k&^S zJTiH{z3_n+9UCq_y=!ooSC&;YC;ubm@oOO%3Z%+!@U(qmr-6jAyIMFlNJQF|KKdoED5p~A*^8a*AT{1!& zunOTevlFQRk

  • epSnK_ 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 literal 0 HcmV?d00001 diff --git a/shots/4_fever.png b/shots/4_fever.png new file mode 100644 index 0000000000000000000000000000000000000000..5c595b2720321ad0f094c97158bd6fedc92ec0c7 GIT binary patch literal 9263 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 literal 0 HcmV?d00001 diff --git a/shots/4_fever_2x.png b/shots/4_fever_2x.png new file mode 100644 index 0000000000000000000000000000000000000000..94d0fb7c0389eff05f8d9066e2308b490451a5dc GIT binary patch literal 12979 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 aNY|b!HvZI*2l&q@fW=vxGx*bPcmD@nN3Q|^ literal 0 HcmV?d00001 diff --git a/shots/5_drain.png b/shots/5_drain.png new file mode 100644 index 0000000000000000000000000000000000000000..4f0080576848ca7480ad74d2dcf89aa272cd4f33 GIT binary patch literal 9537 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 KKJT34qyGabs#2=} literal 0 HcmV?d00001 diff --git a/shots/6_gameover.png b/shots/6_gameover.png new file mode 100644 index 0000000000000000000000000000000000000000..5cc14e387027a1ee2a194b42f9d182fe7ffd9f17 GIT binary patch literal 9112 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 qIN8f}zE&mvZj}448x3>p_@;sY#~Jq3AqjB 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