Files
snake-python/tests/test_GameplayDatabase.py
T
daniel156161 4f022d3d01
Build and Push Docker Container / build-and-push (push) Successful in 7m29s
feat(snake): optimize duels and persist customizations
- Add deadline-aware iterative duel search with bitboards and tuple bodies.

- Reuse transposition bounds and move-order hints across search depths.

- Persist snake colors, heads, and tails in SQLite and PostgreSQL.

- Restore customization metadata when hydrating dashboard replays.

- Cover duel deadlines, cache reuse, schema storage, and replay output.
2026-08-01 17:19:35 +02:00

152 lines
5.7 KiB
Python

import unittest
from pathlib import Path
import tempfile, sqlite3
from server.database import GameplayDatabase, GameplayBackendBuilder
class TestGameplayDatabase(unittest.IsolatedAsyncioTestCase):
def _build_state(self, turn:int, me_head:tuple[int, int], enemy_head:tuple[int, int], include_enemy:bool=True) -> dict:
snakes = [
{
"id": "me",
"name": "Me",
"health": 90,
"length": 3,
"customizations": {
"color": "#00ff00",
"head": "ferret",
"tail": "swirl",
},
"head": {"x": me_head[0], "y": me_head[1]},
"body": [
{"x": me_head[0], "y": me_head[1]},
{"x": me_head[0] - 1, "y": me_head[1]},
{"x": me_head[0] - 2, "y": me_head[1]},
],
}
]
if include_enemy:
snakes.append({
"id": "enemy",
"name": "Enemy",
"health": 90,
"length": 3,
"head": {"x": enemy_head[0], "y": enemy_head[1]},
"body": [
{"x": enemy_head[0], "y": enemy_head[1]},
{"x": enemy_head[0], "y": enemy_head[1] + 1},
{"x": enemy_head[0], "y": enemy_head[1] + 2},
],
})
return {
"turn": turn,
"game": {
"id": "game-abc",
"source": "league",
"map": "standard",
"ruleset": {"name": "standard", "version": "v1.0.0"},
},
"board": {
"width": 11,
"height": 11,
"food": [{"x": 2, "y": 2}],
"hazards": [],
"snakes": snakes,
},
"you": snakes[0],
}
async def test_records_gameplay_with_wal_and_inferred_moves(self):
with tempfile.TemporaryDirectory() as temp_dir:
db_path = Path(temp_dir) / "gameplay.sqlite3"
database = GameplayDatabase(GameplayBackendBuilder.build(db_path=str(db_path), busy_timeout_ms=4000))
await database.record_game_start(self._build_state(turn=0, me_head=(1, 1), enemy_head=(5, 5)))
await database.record_turn(
self._build_state(turn=1, me_head=(2, 1), enemy_head=(5, 4)),
my_move="right",
my_thinking={
"turn": 1,
"reason": "safe_space",
"scores": {"right": 1.0},
},
)
await database.record_turn(
self._build_state(turn=2, me_head=(2, 2), enemy_head=(4, 4)),
my_move="up",
my_thinking={"turn": 2, "reason": "food", "scores": {"up": 1.4}},
)
await database.record_game_end(self._build_state(turn=2, me_head=(2, 2), enemy_head=(4, 4), include_enemy=False))
connection = sqlite3.connect(str(db_path))
journal_mode = connection.execute("PRAGMA journal_mode").fetchone()[0]
self.assertEqual(str(journal_mode).lower(), "wal")
games = connection.execute("SELECT status, winner_you, final_turn FROM games WHERE game_id = ?", ("game-abc",)).fetchone()
self.assertEqual(games[0], "finished")
self.assertEqual(games[1], 1)
self.assertEqual(games[2], 2)
turns_count = connection.execute("SELECT COUNT(*) FROM turns WHERE game_id = ?", ("game-abc",)).fetchone()[0]
self.assertEqual(turns_count, 2)
compact_turn = connection.execute("""
SELECT snakes_json, you_json, food_json, hazards_json
FROM turns WHERE game_id = ? AND turn = ?
""", ("game-abc", 2)).fetchone()
self.assertEqual(compact_turn, ("[]", "{}", '[{"x":2,"y":2}]', "[]"))
stored_body = connection.execute("""
SELECT body_json FROM snake_turns
WHERE game_id = ? AND turn = ? AND snake_id = ?
""", ("game-abc", 2, "me")).fetchone()[0]
self.assertNotEqual(stored_body, "[]")
identities = connection.execute("""
SELECT snake_id, snake_name, is_you, customizations_json FROM game_snakes
WHERE game_id = ? ORDER BY snake_id
""", ("game-abc",)).fetchall()
self.assertEqual(identities, [
("enemy", "Enemy", 0, "{}"),
("me", "Me", 1, '{"color":"#00ff00","head":"ferret","tail":"swirl"}'),
])
repeated_identity = connection.execute("""
SELECT snake_name, is_you FROM snake_turns
WHERE game_id = ? AND turn = ? AND snake_id = ?
""", ("game-abc", 2, "me")).fetchone()
self.assertEqual(repeated_identity, (None, 0))
me_inferred = connection.execute("SELECT inferred_move FROM snake_turns WHERE game_id = ? AND turn = ? AND snake_id = ?", ("game-abc", 2, "me")).fetchone()[0]
enemy_inferred = connection.execute("SELECT inferred_move FROM snake_turns WHERE game_id = ? AND turn = ? AND snake_id = ?", ("game-abc", 2, "enemy")).fetchone()[0]
self.assertEqual(me_inferred, "up")
self.assertEqual(enemy_inferred, "left")
summary = await database.get_summary()
self.assertEqual(summary["finished_games"], 1)
self.assertEqual(summary["wins"], 1)
replay = await database.get_game_replay("game-abc")
self.assertIsNotNone(replay)
replay = replay or {}
self.assertEqual(replay["game"]["final_turn"], 2)
self.assertEqual(len(replay["turns"]), 2)
self.assertEqual(replay["turns"][1]["my_move"], "up")
self.assertEqual(replay["turns"][1]["my_thinking"]["reason"], "food")
self.assertEqual(replay["turns"][1]["food"], [{"x": 2, "y": 2}])
self.assertEqual(replay["turns"][1]["you"]["id"], "me")
me = next(snake for snake in replay["turns"][1]["snakes"] if snake["snake_id"] == "me")
self.assertEqual(me["customizations"], {
"color": "#00ff00",
"head": "ferret",
"tail": "swirl",
})
self.assertEqual(len(me["body"]), 3)
api_me = next(snake for snake in replay["turns"][1]["board"]["snakes"] if snake["id"] == "me")
self.assertEqual(api_me["customizations"], me["customizations"])
connection.close()
if __name__ == "__main__":
unittest.main()