c646392b84
- Preserve snake customizations across database migrations and merges. - Lazily load optional storage backends for SQLite maintenance scripts. - Match Apex territory and nearest-food tie-breaking semantics. - Resolve duel occupancy after simultaneous movement and food growth. - Recompute simulated head-to-head danger after body growth. - Add regression coverage and declare the aiofiles dependency.
227 lines
8.0 KiB
Markdown
227 lines
8.0 KiB
Markdown
# Battlesnake Python Starter Project
|
|
An official Battlesnake template written in Python. Get started at [play.battlesnake.com](https://play.battlesnake.com).
|
|
|
|

|
|
|
|
This project is a great starting point for anyone wanting to program their first Battlesnake in Python. It can be run locally or easily deployed to a cloud provider of your choosing. See the [Battlesnake API Docs](https://docs.battlesnake.com/api) for more detail.
|
|
|
|
## Technologies Used
|
|
This project uses [Python 3](https://www.python.org/) and [Flask](https://flask.palletsprojects.com/). It also comes with an optional [Dockerfile](https://docs.docker.com/engine/reference/builder/) to help with deployment.
|
|
|
|
## Run Your Battlesnake
|
|
Install dependencies using pip
|
|
|
|
```sh
|
|
pip install -r requirements.txt
|
|
```
|
|
|
|
Start your Battlesnake
|
|
```sh
|
|
python main.py
|
|
```
|
|
|
|
You should see the following output once it is running
|
|
|
|
```sh
|
|
Running your Battlesnake at http://0.0.0.0:8000
|
|
* Serving Flask app 'My Battlesnake'
|
|
* Debug mode: off
|
|
```
|
|
|
|
Open [localhost:8000](http://localhost:8000) in your browser and you should see
|
|
|
|
```json
|
|
{"apiversion":"1","author":"","color":"#888888","head":"default","tail":"default"}
|
|
```
|
|
|
|
## Play a Game Locally
|
|
Install the [Battlesnake CLI](https://github.com/BattlesnakeOfficial/rules/tree/main/cli)
|
|
* You can [download compiled binaries here](https://github.com/BattlesnakeOfficial/rules/releases)
|
|
* or [install as a go package](https://github.com/BattlesnakeOfficial/rules/tree/main/cli#installation) (requires Go 1.18 or higher)
|
|
|
|
Command to run a local game
|
|
```sh
|
|
battlesnake play -W 11 -H 11 --name 'Python Starter Project' --url http://localhost:8000 -g solo --browser
|
|
```
|
|
|
|
## Next Steps
|
|
Continue with the [Battlesnake Quickstart Guide](https://docs.battlesnake.com/quickstart) to customize and improve your Battlesnake's behavior.
|
|
|
|
## Included Competitive Snake
|
|
This repo now includes `snakes/BestBattleSnake.py`, a stronger default snake that combines:
|
|
- collision and head-to-head risk checks
|
|
- flood-fill space evaluation to avoid traps
|
|
- food routing that gets more aggressive as health drops
|
|
- tail access checks for better long-term survival
|
|
|
|
Run it explicitly with:
|
|
```sh
|
|
SNAKE=BestBattleSnake python main.py
|
|
```
|
|
|
|
Optional duel tuning (when only 2 snakes are alive):
|
|
```sh
|
|
BATTLE_SNAKE_DUEL_STYLE=balanced python main.py
|
|
```
|
|
|
|
Allowed values: `safe`, `balanced`, `aggressive`.
|
|
|
|
## PrismBattleSnake_GPT_5_6_Sol
|
|
`PrismBattleSnake_GPT_5_6_Sol` is a separate snake that keeps Apex's strategy while
|
|
accelerating hot spatial operations with a Python-integer bitboard engine. Its
|
|
filename, class, and registry key include the model name, while its public
|
|
Battlesnake API name remains `PrismBattleSnake`.
|
|
|
|
Run it with:
|
|
```sh
|
|
SNAKE=PrismBattleSnake_GPT_5_6_Sol python main.py
|
|
```
|
|
|
|
Benchmark Apex and Prism against sampled positions from a gameplay database:
|
|
```sh
|
|
python scripts/benchmark_snakes_from_db.py \
|
|
--database /path/to/gameplay.sqlite3 \
|
|
--samples 100
|
|
```
|
|
|
|
The benchmark opens SQLite read-only and reports mean, median, p95, and maximum
|
|
move latency. Increase `--samples` for a broader but slower comparison.
|
|
|
|
## Compact gameplay database
|
|
New gameplay turns use normalized storage: the turn row stores food, hazards,
|
|
move, and thinking data once; snake identity is stored once per game in
|
|
`game_snakes`; and changing snake state/body data lives in `snake_turns`. Replay
|
|
loading rebuilds the normal Battlesnake board payload.
|
|
|
|
Create and verify a separate compact copy of an existing SQLite database. By
|
|
default, replay-heavy rows are retained for games rated `medium` or `high` by
|
|
structural completeness, valid moves, thinking coverage, game length, move
|
|
diversity, opponent data, and terminal outcome. All game-result rows remain
|
|
stored, so historical win/loss rates stay persistent when low-quality replay
|
|
data is removed.
|
|
```sh
|
|
python scripts/migrate_gameplay_database.py \
|
|
--source /path/to/gameplay.sqlite3 \
|
|
--destination /path/to/gameplay.compact.sqlite3 \
|
|
--minimum-quality medium
|
|
```
|
|
|
|
After reviewing the compact copy, `--replace` renames the original to a
|
|
timestamped backup and puts the verified compact database at the original path.
|
|
Stop all writers before using it:
|
|
```sh
|
|
python scripts/migrate_gameplay_database.py \
|
|
--source /path/to/gameplay.sqlite3 \
|
|
--replace
|
|
```
|
|
|
|
The migration never modifies the source in place. It verifies row counts and
|
|
runs SQLite's `integrity_check` before any replacement.
|
|
|
|
### Record new games while cleanup runs
|
|
Point the running server at a temporary delta database while the old database
|
|
is being compacted. After stopping the writer and flushing the delta database,
|
|
merge it into the cleaned copy:
|
|
```sh
|
|
python scripts/merge_gameplay_databases.py \
|
|
--base /path/to/gameplay.compact.sqlite3 \
|
|
--delta /path/to/gameplay.delta.sqlite3 \
|
|
--destination /path/to/gameplay.merged.sqlite3 \
|
|
--minimum-quality medium
|
|
```
|
|
|
|
The merger keeps all game results, quality-rates delta games, regenerates
|
|
numeric turn IDs, and verifies row counts, foreign keys, and database integrity.
|
|
Identical game IDs are skipped; conflicting duplicates abort the merge. After
|
|
reviewing the result, `--replace-base` backs up and replaces the cleaned base.
|
|
Stop the delta writer before the final merge and file swap.
|
|
|
|
## Export Training Dataset
|
|
Game saves now include a `dataset` section with labeled move samples.
|
|
|
|
Export all stored samples to JSONL:
|
|
|
|
```sh
|
|
python -m server.DatasetExporter --input data --output data/dataset/good_moves.jsonl
|
|
```
|
|
|
|
Or with `just`:
|
|
|
|
```sh
|
|
just export-dataset
|
|
```
|
|
|
|
Curate a high-quality training subset (single file):
|
|
|
|
```sh
|
|
python -m server.DatasetCurator --input good_moves-2026-04-03.jsonl --output data/dataset/best_moves.jsonl
|
|
```
|
|
|
|
Curate from multiple JSONL sources (repeat `--input`):
|
|
|
|
```sh
|
|
python -m server.DatasetCurator \
|
|
--input good_moves-2026-04-03.jsonl \
|
|
--input good_moves-2026-04-04.jsonl \
|
|
--output data/dataset/best_moves.jsonl
|
|
```
|
|
|
|
Curate from folder or glob:
|
|
|
|
```sh
|
|
python -m server.DatasetCurator --input data/dataset --output data/dataset/best_moves.jsonl
|
|
python -m server.DatasetCurator --input "good_moves-*.jsonl" --output data/dataset/best_moves.jsonl
|
|
```
|
|
|
|
Append mode (keeps existing curated rows and deduplicates against them):
|
|
|
|
```sh
|
|
python -m server.DatasetCurator --input "good_moves-*.jsonl" --output data/dataset/best_moves.jsonl --append
|
|
```
|
|
|
|
Archive processed input files after curation:
|
|
|
|
```sh
|
|
python -m server.DatasetCurator --input "good_moves-*.jsonl" --output data/dataset/best_moves.jsonl --append --archive-input
|
|
python -m server.DatasetCurator --input "good_moves-*.jsonl" --output data/dataset/best_moves.jsonl --append --archive-input --archive-dir data/dataset/archive
|
|
```
|
|
|
|
Or with `just`:
|
|
|
|
```sh
|
|
just curate-dataset
|
|
just curate-dataset append=true
|
|
just curate-dataset append=true archive=true archive_dir=data/dataset/archive
|
|
```
|
|
|
|
Analyze dataset quality overall and by day (best game overall/day included):
|
|
|
|
```sh
|
|
python -m server.DatasetStats --input "good_moves-*.jsonl"
|
|
python -m server.DatasetStats --input data/dataset --output data/dataset/stats-report.json
|
|
```
|
|
|
|
The stats report now includes both:
|
|
- `best_game` (survival/length focused)
|
|
- `best_pressure_game` (high-pressure quality focused: fewer safe options + strong survival)
|
|
|
|
Or with `just`:
|
|
|
|
```sh
|
|
just analyze-dataset
|
|
just analyze-dataset input=data/dataset output=data/dataset/stats-report.json
|
|
```
|
|
|
|
To store compact dataset-only records (JSONL) and skip full per-game JSON files:
|
|
|
|
```sh
|
|
STORE_DATASET_ONLY=true DATASET_JSONL_PATH=data/dataset/good_moves.jsonl python main.py
|
|
```
|
|
|
|
Optional compact storage tuning:
|
|
- `DATASET_ROTATE_DAILY=true` creates one JSONL file per day (default: `true`)
|
|
- `DATASET_JSONL_MAX_MB=50` rotates when file reaches max size in MB (default: `50`)
|
|
- `DATASET_COMPRESS_ROTATED=true` gzip-compresses rotated/old JSONL files (default: `true`)
|
|
|
|
**Note:** To play games on [play.battlesnake.com](https://play.battlesnake.com) you'll need to deploy your Battlesnake to a live web server OR use a port forwarding tool like [ngrok](https://ngrok.com/) to access your server locally.
|