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:
@@ -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())
|
||||
Reference in New Issue
Block a user