Files
daniel156161 f14d780f29
Build and Push Docker Container / build-and-push (push) Successful in 7m53s
feat: add live replays and PostgreSQL maintenance tools
- Stream compact live replay updates across local and clustered dashboards.
- Render responsive snake bodies as SVG paths with aligned custom icons.
- Add cache-busted assets, replay fallback routes, and live-follow playback.
- Support PostgreSQL benchmark sampling and idempotent SQLite migration.
- Add dry-run cleanup for old low-quality PostgreSQL replay payloads.
- Reward safe perimeter lanes and bump Prism to version 1.5.0.
- Add backend, migration, dashboard, and perimeter regression coverage.
2026-08-02 00:50:46 +02:00

228 lines
8.9 KiB
Makefile

# Justfile for Migrate Database Changes Workflow
# Docs: https://just.systems/man/en/
# ------------------------------------------------------------------------------
# Global settings
# ------------------------------------------------------------------------------
# Load Env
set dotenv-load
set dotenv-required := true
# Use zsh
set shell := ["bash", "-cu"]
BATTLESNAKE_CLI_DIR := ".testing/tools/battlesnake-cli"
BATTLESNAKE_CLI_BIN := ".testing/tools/battlesnake-cli/battlesnake"
# ------------------------------------------------------------------------------
# Default
# ------------------------------------------------------------------------------
# List all Available recipes
[private]
default:
@just --list --unsorted
# ------------------------------------------------------------------------------
# Snake Script helpers
# ------------------------------------------------------------------------------
run:
"{{justfile_directory()}}/main.py"
run-snake port="8000" snake="BestBattleSnake":
HOST="127.0.0.1" PORT="{{port}}" SNAKE="{{snake}}" DEBUG="false" DEBUG_SERVER="false" "{{justfile_directory()}}/main.py"
run-4-snakes base_port="9101" snake="BestBattleSnake":
#!/usr/bin/env bash
set -euo pipefail
pids=()
for i in 0 1 2 3; do
port="$(({{base_port}} + i))"
echo "Starting snake on :$port"
HOST="127.0.0.1" PORT="$port" SNAKE="{{snake}}" DEBUG="false" DEBUG_SERVER="false" "{{justfile_directory()}}/main.py" &
pids[$i]="$!"
done
cleanup() {
for pid in "${pids[@]}"; do
kill "$pid" 2>/dev/null || true
done
wait || true
}
trap cleanup EXIT INT TERM
wait
bench-best-snake iterations="1000":
#!/usr/bin/env bash
set -euo pipefail
PYTHONPATH="{{justfile_directory()}}" python "{{justfile_directory()}}/tests/bench_best_battle_snake.py" --iterations "{{iterations}}"
bench-snake-arena positions="100" output="" database="":
#!/usr/bin/env bash
set -euo pipefail
args=(--positions "{{positions}}")
if [ -n "{{database}}" ]; then args+=(--database "{{database}}"); fi
if [ -n "{{output}}" ]; then args+=(--json-output "{{output}}"); fi
PYTHONPATH="{{justfile_directory()}}" uv run python "{{justfile_directory()}}/scripts/benchmark_snake_arena.py" "${args[@]}"
bench-snake-arena-postgres positions="100" output="":
#!/usr/bin/env bash
set -euo pipefail
: "${GAMEPLAY_DB_PG_DSN:?Set GAMEPLAY_DB_PG_DSN in .env or the environment}"
args=(--positions "{{positions}}" --database "$GAMEPLAY_DB_PG_DSN")
if [ -n "{{output}}" ]; then args+=(--json-output "{{output}}"); fi
PYTHONPATH="{{justfile_directory()}}" uv run python "{{justfile_directory()}}/scripts/benchmark_snake_arena.py" "${args[@]}"
cleanup-gameplay-postgres older_than_days="30" execute="false" vacuum="false":
#!/usr/bin/env bash
set -euo pipefail
: "${GAMEPLAY_DB_PG_DSN:?Set GAMEPLAY_DB_PG_DSN in .env or the environment}"
args=(--dsn "$GAMEPLAY_DB_PG_DSN" --older-than-days "{{older_than_days}}")
if [ "{{execute}}" = "true" ]; then args+=(--execute); fi
if [ "{{vacuum}}" = "true" ]; then args+=(--vacuum); fi
PYTHONPATH="{{justfile_directory()}}" uv run python "{{justfile_directory()}}/scripts/cleanup_postgresql_gameplay.py" "${args[@]}"
bench-snake-tournament games="20" gametype="standard" map="standard" output="":
#!/usr/bin/env bash
set -euo pipefail
args=(--games "{{games}}" --gametype "{{gametype}}" --map "{{map}}")
if [ -n "{{output}}" ]; then args+=(--json-output "{{output}}"); fi
PYTHONPATH="{{justfile_directory()}}" python "{{justfile_directory()}}/scripts/run_seeded_snake_tournament.py" "${args[@]}"
build-battlesnake-cli:
#!/usr/bin/env bash
set -euo pipefail
install_dir="{{justfile_directory()}}/{{BATTLESNAKE_CLI_DIR}}"
bin_path="{{justfile_directory()}}/{{BATTLESNAKE_CLI_BIN}}"
mkdir -p "$install_dir"
if [ ! -f "{{justfile_directory()}}/local-client/go.mod" ]; then
echo "Missing local-client source. Run: git submodule update --init --recursive"
exit 1
fi
(
cd "{{justfile_directory()}}/local-client"
go build -o "$bin_path" ./cli/battlesnake/main.go
)
"$bin_path" --help > /dev/null
echo "Built Battlesnake CLI at $bin_path"
battlesnake-cli-version:
#!/usr/bin/env bash
set -euo pipefail
"{{justfile_directory()}}/{{BATTLESNAKE_CLI_BIN}}" --help
# ------------------------------------------------------------------------------
# Testing helpers
# ------------------------------------------------------------------------------
test-unit:
#!/usr/bin/env bash
set -euo pipefail
PYTHONPATH="{{justfile_directory()}}" python -m unittest discover -s "{{justfile_directory()}}/tests" -p "test_*.py"
# Dashboard front-end logic (head/tail orientation, board geometry).
test-js:
#!/usr/bin/env bash
set -euo pipefail
node --test "{{justfile_directory()}}"/tests/js/*.test.mjs
test-constrictor: build-battlesnake-cli
#!/usr/bin/env bash
set -euo pipefail
BATTLESNAKE_CLI="{{justfile_directory()}}/{{BATTLESNAKE_CLI_BIN}}"
"$BATTLESNAKE_CLI" play -W 11 -H 11 --name 'Python Starter Project' --url http://localhost:8000 -g constrictor --browser --minimumFood 0
test-seed: build-battlesnake-cli
#!/usr/bin/env bash
set -euo pipefail
BATTLESNAKE_CLI="{{justfile_directory()}}/{{BATTLESNAKE_CLI_BIN}}"
"$BATTLESNAKE_CLI" play -W 11 -H 11 --name 'Python Starter Project' --url http://localhost:8000 -g solo --browser --seed 1713099635738952360
test-local-4 mode="standard" map="standard" base_port="9101" snake="BestBattleSnake" seed="1713099635738952360" browser="true": build-battlesnake-cli
#!/usr/bin/env bash
set -euo pipefail
BATTLESNAKE_CLI="{{justfile_directory()}}/{{BATTLESNAKE_CLI_BIN}}"
LOG_DIR="{{justfile_directory()}}/.testing/tools/snake-logs"
mkdir -p "$LOG_DIR"
pids=()
for i in 0 1 2 3; do
port="$(({{base_port}} + i))"
log_file="$LOG_DIR/snake-$((i+1)).log"
echo "Starting snake-$((i+1)) on :$port (log: $log_file)"
HOST="127.0.0.1" PORT="$port" SNAKE="{{snake}}" DEBUG="false" DEBUG_SERVER="false" "{{justfile_directory()}}/main.py" > >(tee "$log_file") 2>&1 &
pids[$i]="$!"
done
cleanup() {
for pid in "${pids[@]}"; do
kill "$pid" 2>/dev/null || true
done
wait || true
}
trap cleanup EXIT INT TERM
for i in 0 1 2 3; do
port="$(({{base_port}} + i))"
for _ in $(seq 1 30); do
if curl -fsS "http://127.0.0.1:$port" >/dev/null 2>&1; then
break
fi
sleep 0.2
done
if ! curl -fsS "http://127.0.0.1:$port" >/dev/null 2>&1; then
echo "Snake on :$port did not start correctly. Check logs in $LOG_DIR"
exit 1
fi
done
BROWSER_FLAG=""
if [ "{{browser}}" = "true" ]; then
BROWSER_FLAG="--browser"
fi
"$BATTLESNAKE_CLI" play -W 11 -H 11 \
--name "Snake 1" --url "http://127.0.0.1:{{base_port}}" \
--name "Snake 2" --url "http://127.0.0.1:$(({{base_port}} + 1))" \
--name "Snake 3" --url "http://127.0.0.1:$(({{base_port}} + 2))" \
--name "Snake 4" --url "http://127.0.0.1:$(({{base_port}} + 3))" \
-g "{{mode}}" --map "{{map}}" --seed "{{seed}}" $BROWSER_FLAG
# ------------------------------------------------------------------------------
# Dataset helpers
# ------------------------------------------------------------------------------
export-dataset input=".testing/data" output=".testing/data/dataset/good_moves.jsonl":
python -m server.DatasetExporter --input "{{input}}" --output "{{output}}"
curate-dataset input="good_moves-*.jsonl" output=".testing/data/dataset/best_moves.jsonl" min_turn="6" late_turn="20" max_safe_options="2" min_score="3" append="false" archive="false" archive_dir="":
FLAGS=""; if [ "{{append}}" = "true" ]; then FLAGS="$FLAGS --append"; fi; if [ "{{archive}}" = "true" ]; then FLAGS="$FLAGS --archive-input"; fi; if [ -n "{{archive_dir}}" ]; then FLAGS="$FLAGS --archive-dir {{archive_dir}}"; fi; python -m server.DatasetCurator --input "{{input}}" --output "{{output}}" --min-turn "{{min_turn}}" --late-turn "{{late_turn}}" --max-safe-options "{{max_safe_options}}" --min-score "{{min_score}}" $FLAGS
analyze-dataset input="good_moves-*.jsonl" output="":
if [ -n "{{output}}" ]; then python -m server.DatasetStats --input "{{input}}" --output "{{output}}"; else python -m server.DatasetStats --input "{{input}}"; fi
train-ai input=".testing/data/dataset/best_moves.jsonl" rl_input=".testing/data/dataset/rl_bootstrap.jsonl" output=".testing/models/battlesnake_softmax_v2.json" eval_split="0.2" seed="42" epochs="14" lr="0.08":
if [ -f "{{rl_input}}" ]; then python -m server.TrainBattleSnakeAI --input "{{input}}" --input "{{rl_input}}" --output "{{output}}" --eval-split "{{eval_split}}" --seed "{{seed}}" --epochs "{{epochs}}" --lr "{{lr}}"; else python -m server.TrainBattleSnakeAI --input "{{input}}" --output "{{output}}" --eval-split "{{eval_split}}" --seed "{{seed}}" --epochs "{{epochs}}" --lr "{{lr}}"; fi
run-trained model=".testing/models/battlesnake_softmax_v2.json" port="8000":
TRAINED_SNAKE_MODEL="{{model}}" SNAKE="TrainedBattleSnake" PORT="{{port}}" "{{justfile_directory()}}/main.py"