beam-boot / scripts /gym_baselines.py
StarAtNyte1's picture
Publish Beam Boot / Super Beam source
324ecd2 verified
Raw History Blame Contribute Delete
5.16 kB
"""Baselines for the SUPERBEAM gym env (beamboot/superbeam_gym.py).
python scripts/gym_baselines.py [--seeds 20] [--frame-skip 4]
Agents
random uniform over the 6 actions
scripted hold right; jump (right+jump) when the frame shows a pit in the
ground row or a walker within `lead` px ahead. Perception uses
only the 64x32 frame plus the player's screen x/y.
scripted+eps scripted with 10% uniformly random actions (seeded)
Writes results/gym_baselines.json.
"""
import argparse
import json
import os
import sys
import numpy as np
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, ROOT)
from beamboot.superbeam_gym import GROUND_Y, SuperBeamEnv # noqa: E402
OUT = os.path.join(ROOT, "results", "gym_baselines.json")
def obstacle_ahead(obs, lead):
"""(pit_dist, enemy_dist) in px from the player's right edge, or None."""
f, x, y = obs["frame"], obs["x"], obs["y"]
right = x + 8
pit = enemy = None
for d in range(0, lead + 1):
c = right + d
if c >= 64:
break
if pit is None and not f[24, c]:
pit = d
# walker body row (sprite row 3 = 0xFF) sits at y = 21; flag pole is 1 px wide
if enemy is None and f[GROUND_Y + 3, c] and f[GROUND_Y + 3, min(c + 1, 63)] \
and f[GROUND_Y + 3, min(c + 2, 63)]:
enemy = d
return pit, enemy
class ScriptedAgent:
def __init__(self, pit_lead=4, enemy_lead=14, eps=0.0, seed=0):
self.pit_lead, self.enemy_lead, self.eps = pit_lead, enemy_lead, eps
self.rng = np.random.default_rng(seed)
def act(self, obs):
if self.eps and self.rng.random() < self.eps:
return int(self.rng.integers(6))
if obs["y"] != GROUND_Y:
return 2
pit, enemy = obstacle_ahead(obs, max(self.pit_lead, self.enemy_lead))
if (pit is not None and pit <= self.pit_lead) or \
(enemy is not None and enemy <= self.enemy_lead):
return 4
return 2
class RandomAgent:
def __init__(self, seed=0):
self.rng = np.random.default_rng(seed)
def act(self, obs):
return int(self.rng.integers(6))
def run_episode(env, agent):
obs, info = env.reset()
steps = 0
while True:
obs, r, term, trunc, info = env.step(agent.act(obs))
steps += 1
if term or trunc:
return {"won": info["won"], "dead": info["dead"], "progress": info["best_progress"],
"frames": info["frames"], "steps": steps, "reward": env.total_reward}
def evaluate(name, make_agent, seeds, frame_skip, max_frames):
env = SuperBeamEnv(frame_skip=frame_skip, max_frames=max_frames)
eps = [run_episode(env, make_agent(s)) for s in seeds]
return {"agent": name, "episodes": len(eps),
"win_rate": sum(e["won"] for e in eps) / len(eps),
"death_rate": sum(e["dead"] for e in eps) / len(eps),
"mean_progress": float(np.mean([e["progress"] for e in eps])),
"max_progress": float(max(e["progress"] for e in eps)),
"mean_steps": float(np.mean([e["steps"] for e in eps])),
"episodes_detail": eps}
def tune(frame_skip, max_frames):
"""Grid over leads for the deterministic scripted agent; returns winning (pit, enemy) leads."""
env = SuperBeamEnv(frame_skip=frame_skip, max_frames=max_frames)
wins = []
for pl in range(0, 24, 2):
for el in range(0, 40, 2):
if run_episode(env, ScriptedAgent(pl, el))["won"]:
wins.append((pl, el))
return wins
def main(argv=None):
ap = argparse.ArgumentParser()
ap.add_argument("--seeds", type=int, default=20)
ap.add_argument("--frame-skip", type=int, default=4)
ap.add_argument("--max-frames", type=int, default=600)
args = ap.parse_args(argv)
seeds = list(range(args.seeds))
fs, mf = args.frame_skip, args.max_frames
wins = tune(fs, mf)
pl, el = wins[len(wins) // 2] if wins else (4, 14)
print("frame_skip=%d: scripted lead grid wins %d/%d cells; using pit_lead=%d enemy_lead=%d"
% (fs, len(wins), 12 * 20, pl, el))
rows = [
evaluate("random", lambda s: RandomAgent(s), seeds, fs, mf),
evaluate("scripted", lambda s: ScriptedAgent(pl, el), seeds[:1], fs, mf),
evaluate("scripted+eps0.1", lambda s: ScriptedAgent(pl, el, eps=0.1, seed=s), seeds, fs, mf),
]
print("%-16s %4s %8s %8s %10s %10s" % ("agent", "eps", "win", "death", "mean_prog", "max_prog"))
for r in rows:
print("%-16s %4d %8.2f %8.2f %10.3f %10.3f" % (r["agent"], r["episodes"], r["win_rate"],
r["death_rate"], r["mean_progress"], r["max_progress"]))
os.makedirs(os.path.dirname(OUT), exist_ok=True)
with open(OUT, "w") as f:
json.dump({"frame_skip": fs, "max_frames": mf, "scripted_leads": [pl, el],
"lead_grid_wins": wins, "results": rows}, f, indent=1)
print("wrote", os.path.relpath(OUT, ROOT))
return 0
if __name__ == "__main__":
raise SystemExit(main())