Files
ds4.1-flipper/autotest.py
T
georgios cf2c0e4558 Portrait 360x480 table with regular-sized flippers
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
2026-09-09 19:02:11 +02:00

152 lines
5.0 KiB
Python

"""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 > 200:
g.keys["L"] = b.x < 196
g.keys["R"] = b.x > 164
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()