Rotate the playfield to portrait like a real pinball machine: - canvas 480x360 landscape -> 360x480 portrait; playfield 308px wide, ~390px tall, with HUD, title/game-over screens and HUD re-laid out for the narrow format (score/best top row, balls/stars/fever below) - flippers reduced to a regular ~quarter-width size (82px) pivoting at the lower corners, tips meeting over a wide centre drain - table elements re-placed for the taller field: 3 star lanes up top, candy bumpers mid-field, slingshots and floor-level outlane guard posts at the bottom; data-checked with the autoplay bot (~350 bumper hits / 30k frames, centre+side drains, no stuck balls) - probes & autoplay policy updated for the portrait geometry; all 13 pixel-probes pass
3.3 KiB
3.3 KiB
🍬 Cosmic Candy Pinball
A cute, 8-bit, pixel-art pinball game, written from scratch in Python with pygame-ce. 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
portrait 360×480 virtual canvas — like a proper pinball machine —
upscaled with crisp nearest-neighbour scaling for an authentic retro look.
Running it
Fastest (uv, already used in this project):
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+):
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
.venv/bin/python main.py
Note: the dependency is
pygame-ce, notpygame(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):
.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.pybot 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.
