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.
This commit is contained in:
georgios
2026-09-09 16:18:39 +02:00
commit db49b466c2
25 changed files with 2330 additions and 0 deletions
+68
View File
@@ -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())