Beam search 1010! game implementation.
This repo contains:
-
deterministic game engine in
beam1010/(state, rules, move generation) -
beam-search planner in
beam1010/beam_search.py -
An offline simulator in
simulate_beam.pythat repeatedly:- draws a 3-piece hand
- runs beam search to plan the best sequence for that hand
- applies the planned moves and records rich telemetry to JSON
-
An experimentation setup to run simulations automatically.
The simulator json output is meant for inspecting / tuning heuristic weights.
- Python 3.10+ (no third-party dependencies)
Run a single simulated game (beam search runs every hand):
python3 simulate_beam.py --games number_of_games --seed integer --beam-width integer --out directory/name.jsonlRun an experiment
python3 experiment.py --games nimber_of_games --beam-width integer --max-moves optional integer --out-dir runs/_exp_rand --sidecar falseThe moves-only sidecar file (*.moves.json) is optional.
To write it next to --out, pass --write-moves-sidecar:
python3 simulate_beam.py --games 1 --seed 0 --beam-width 10 --out runs/one.jsonl --write-moves-sidecarOr choose an explicit path with --moves-out:
python3 simulate_beam.py --games 1 --seed 0 --beam-width 10 --out runs/one.jsonl --moves-out runs/one.moves.jsonTip: avoid naming your main output *.moves.json.
The *.moves.json suffix is reserved for the sidecar file name.
create game with json output:
python3 simulate_beam.py --games 1 --seed 0 --beam-width 10 --format json --out runs/one.json
for sidecar too:
python3 simulate_beam.py --games 1 --seed 0 --beam-width 10 --format json --out runs/_tmp_two.json --write-moves-sidecar
Replay game
python3 game.py --replay runs/one.json
If you want one output file per game (instead of a single JSON/JSONL file containing many games), use experiment.py.
By default (when --seed is omitted) it generates a random seed per game and writes:
SeedXXXXX.jsonSeedXXXXX.moves.json(only if--sidecar true)
Example:
python3 experiment.py --games 10 --beam-width 25 --out-dir runs/exp --sidecar trueIf you want reproducible experiments, pass a base seed (game i uses seed+i):
python3 experiment.py --games 10 --seed 0 --beam-width 25 --out-dir runs/exp --sidecar trueIf you want a multi-line, human-readable file, write JSON instead of JSONL:
python3 simulate_beam.py --games 1 --seed 0 --beam-width 10 --format json --out runs/one.jsonLimit the run to a small number of placements (useful for debugging):
python3 simulate_beam.py --games 1 --seed 0 --beam-width 10 --max-moves 10 --format json --out runs/ten_moves.jsonThat command also produces the moves-only sidecar:
runs/ten_moves.moves.json
Beam search is executed inside simulate_beam.py once per hand:
- It calls
beam1010.beam_search.beam_search_best_sequence(state, beam_width=..., depth=len(hand)). - “Option A” behavior: the search depth is limited to the current hand only (no lookahead into future random hands).
Default output is JSON Lines: one JSON object per game per line (compact / one line). This is great for large runs and streaming.
With --format json, the simulator writes a single JSON array of game results (multi-line, indented).
Each game result includes:
final_score,moves,segments(one segment per hand)
Each segment includes:
hand: piece names dealt for that handplanned_moves: the planned sequence for the handmoves: the executed moves with scoring + heuristic values
Each move object contains both the hand index and the piece itself:
{
"piece_index": 0,
"piece_name": "plus",
"piece": {"name": "plus", "shape": [[0,1,0],[1,1,1],[0,1,0]]},
"row": 3,
"col": 5
}The sidecar file is meant to be easy to parse for downstream analysis.
- It is written only when you pass
--write-moves-sidecaror--moves-out .... - You can override the path with
--moves-out path/to/file.json.
Schema:
- If
--games 1: a JSON array of move objects:
{
"piece_index": 0,
"piece_name": "plus",
"row": 3,
"col": 5,
"score_after": 123
}- If
--games N(N>1): a JSON array of game objects:
{
"seed": 0,
"moves": [
{"piece_index": 0, "piece_name": "plus", "row": 3, "col": 5, "score_after": 123}
]
}Note: the sidecar intentionally omits full piece shapes; the main output includes piece (name + shape).
If you want a quick “turn-by-turn” log from a pretty JSON run:
python3 - <<'PY'
import json
game = json.load(open('runs/one.json','r',encoding='utf-8'))[0]
turn = 0
for seg in game['segments']:
for m in seg['moves']:
turn += 1
mv = m['move']
print(f"{turn:04d} {mv['piece_name']} @ ({mv['row']},{mv['col']}) score={m['score_after']}")
PYYou can override heuristic weights from the CLI:
python3 simulate_beam.py --games 10 --seed 0 --beam-width 25 \
--mobility 0.2 --near-full-lines 1.5 --roughness -0.1 \
--format json --out runs/tuned.jsonThe heuristic function is in beam1010/heuristics.py.
There is also a simple console version (manual play):
python3 game.pyUse the replay mode in game.py to apply recorded moves one-by-one from a simulator output file.
It supports both:
- the main simulator output (recommended; contains hands + piece shapes)
- the compact moves-only sidecar (
*.moves.json) (best-effort; may not include shapes)
Examples:
# Replay the full output (recommended)
python3 game.py --replay runs/ten_moves.json
# Replay the moves-only sidecar
python3 game.py --replay runs/ten_moves.moves.json
# If the file contains multiple games (JSON array), pick one
python3 game.py --replay runs/many.json --game-index 3Controls at the prompt:
- Press Enter (or type
next/n) to advance one move - Type
qto quit - Type
?for help
Run all tests:
pytest -qIf you don't have pytest installed, you can also run them with the standard library:
python3 -m unittest discover -s tests -p "test_*.py"beam1010/— engine + beam searchstate.py— immutableGameStaterules.py— placement, line clears, scoring,simulate_movemoves.py— legal move enumerationheuristics.py— evaluation function + weightsbeam_search.py— beam search implementation
simulate_beam.py— offline simulator + JSON/JSONL outputgame.py— interactive console game + piece catalog used by the simulatorexperiment.py- offline simulator automating simulation process