"""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()