add better dashboard with full snake game board
This commit is contained in:
@@ -3,7 +3,7 @@ import asyncio, sqlite3, json
|
||||
from pathlib import Path
|
||||
|
||||
class GameplayDatabase:
|
||||
def __init__(self, db_path:str, busy_timeout_ms:int = 5000):
|
||||
def __init__(self, db_path:str, busy_timeout_ms:int=5000):
|
||||
self.db_path = db_path
|
||||
self.busy_timeout_ms = max(1000, int(busy_timeout_ms))
|
||||
self._initialize_database()
|
||||
@@ -18,12 +18,15 @@ class GameplayDatabase:
|
||||
connection.execute("PRAGMA foreign_keys = ON")
|
||||
connection.execute("PRAGMA journal_mode = WAL")
|
||||
connection.execute("PRAGMA synchronous = NORMAL")
|
||||
connection.execute("PRAGMA temp_store = MEMORY")
|
||||
connection.execute("PRAGMA journal_size_limit = 1048576")
|
||||
connection.execute(f"PRAGMA busy_timeout = {self.busy_timeout_ms}")
|
||||
return connection
|
||||
|
||||
def _initialize_database(self) -> None:
|
||||
Path(self.db_path).parent.mkdir(parents=True, exist_ok=True)
|
||||
with self._connect() as connection:
|
||||
connection.execute("PRAGMA auto_vacuum = INCREMENTAL")
|
||||
connection.executescript("""
|
||||
CREATE TABLE IF NOT EXISTS games (
|
||||
game_id TEXT PRIMARY KEY,
|
||||
@@ -37,6 +40,8 @@ class GameplayDatabase:
|
||||
ruleset_version TEXT,
|
||||
your_snake_id TEXT,
|
||||
your_snake_name TEXT,
|
||||
your_snake_type TEXT,
|
||||
your_snake_version TEXT,
|
||||
winner_names_json TEXT,
|
||||
winner_you INTEGER NOT NULL DEFAULT 0,
|
||||
final_turn INTEGER NOT NULL DEFAULT 0,
|
||||
@@ -79,8 +84,11 @@ class GameplayDatabase:
|
||||
CREATE INDEX IF NOT EXISTS idx_turns_game_turn ON turns(game_id, turn);
|
||||
CREATE INDEX IF NOT EXISTS idx_games_status ON games(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_snake_turns_game_turn ON snake_turns(game_id, turn);
|
||||
""")
|
||||
""")
|
||||
self._ensure_column_exists(connection, "turns", "my_thinking_json", "TEXT")
|
||||
self._ensure_column_exists(connection, "games", "your_snake_type", "TEXT")
|
||||
self._ensure_column_exists(connection, "games", "your_snake_version", "TEXT")
|
||||
connection.execute("PRAGMA optimize")
|
||||
|
||||
def _ensure_column_exists(self, connection:sqlite3.Connection, table_name:str, column_name:str, column_type:str) -> None:
|
||||
existing = connection.execute(f"PRAGMA table_info({table_name})").fetchall()
|
||||
@@ -125,7 +133,7 @@ class GameplayDatabase:
|
||||
return "down"
|
||||
return None
|
||||
|
||||
def _record_game_start_sync(self, game_state:dict) -> None:
|
||||
def _record_game_start_sync(self, game_state:dict, snake_type:str|None=None, snake_version:str|None=None) -> None:
|
||||
game = game_state.get("game", {})
|
||||
board = game_state.get("board", {})
|
||||
you = self._extract_you(game_state)
|
||||
@@ -135,8 +143,9 @@ class GameplayDatabase:
|
||||
connection.execute("""
|
||||
INSERT INTO games (
|
||||
game_id, started_at, width, height, source, map_name,
|
||||
ruleset_name, ruleset_version, your_snake_id, your_snake_name, status
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'running')
|
||||
ruleset_name, ruleset_version, your_snake_id, your_snake_name,
|
||||
your_snake_type, your_snake_version, status
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'running')
|
||||
ON CONFLICT(game_id) DO UPDATE SET
|
||||
width = excluded.width,
|
||||
height = excluded.height,
|
||||
@@ -146,6 +155,8 @@ class GameplayDatabase:
|
||||
ruleset_version = excluded.ruleset_version,
|
||||
your_snake_id = excluded.your_snake_id,
|
||||
your_snake_name = excluded.your_snake_name,
|
||||
your_snake_type = excluded.your_snake_type,
|
||||
your_snake_version = excluded.your_snake_version,
|
||||
status = 'running'
|
||||
""",
|
||||
(
|
||||
@@ -159,8 +170,13 @@ class GameplayDatabase:
|
||||
ruleset.get("version"),
|
||||
you.get("id"),
|
||||
you.get("name"),
|
||||
)
|
||||
snake_type,
|
||||
snake_version,
|
||||
),
|
||||
)
|
||||
connection.execute("PRAGMA wal_checkpoint(PASSIVE)")
|
||||
connection.execute("PRAGMA incremental_vacuum(200)")
|
||||
connection.execute("PRAGMA optimize")
|
||||
|
||||
def _record_turn_sync(self, game_state:dict, my_move:str|None, my_thinking:dict|None) -> None:
|
||||
game = game_state.get("game", {})
|
||||
@@ -317,7 +333,7 @@ class GameplayDatabase:
|
||||
).fetchone()
|
||||
|
||||
recent = connection.execute("""
|
||||
SELECT game_id, started_at, ended_at, map_name, your_snake_name, winner_you, final_turn, status
|
||||
SELECT game_id, started_at, ended_at, map_name, your_snake_name, your_snake_type, your_snake_version, winner_you, final_turn, status
|
||||
FROM games
|
||||
ORDER BY started_at DESC
|
||||
LIMIT ?
|
||||
@@ -339,6 +355,8 @@ class GameplayDatabase:
|
||||
"ended_at": row["ended_at"],
|
||||
"map": row["map_name"],
|
||||
"snake": row["your_snake_name"],
|
||||
"snake_type": row["your_snake_type"],
|
||||
"snake_version": row["your_snake_version"],
|
||||
"winner_you": bool(row["winner_you"]),
|
||||
"final_turn": int(row["final_turn"] or 0),
|
||||
"status": row["status"],
|
||||
@@ -349,7 +367,8 @@ class GameplayDatabase:
|
||||
with self._connect() as connection:
|
||||
rows = connection.execute("""
|
||||
SELECT game_id, started_at, ended_at, map_name, source, ruleset_name,
|
||||
your_snake_name, winner_you, winner_names_json, final_turn, status
|
||||
your_snake_name, your_snake_type, your_snake_version,
|
||||
winner_you, winner_names_json, final_turn, status
|
||||
FROM games
|
||||
ORDER BY started_at DESC
|
||||
LIMIT ?
|
||||
@@ -365,6 +384,8 @@ class GameplayDatabase:
|
||||
"source": row["source"],
|
||||
"ruleset": row["ruleset_name"],
|
||||
"snake": row["your_snake_name"],
|
||||
"snake_type": row["your_snake_type"],
|
||||
"snake_version": row["your_snake_version"],
|
||||
"winner_you": bool(row["winner_you"]),
|
||||
"winner_names": self._from_json(row["winner_names_json"]) or [],
|
||||
"final_turn": int(row["final_turn"] or 0),
|
||||
@@ -376,6 +397,7 @@ class GameplayDatabase:
|
||||
game_row = connection.execute("""
|
||||
SELECT game_id, started_at, ended_at, width, height, source, map_name,
|
||||
ruleset_name, ruleset_version, your_snake_id, your_snake_name,
|
||||
your_snake_type, your_snake_version,
|
||||
winner_names_json, winner_you, final_turn, status
|
||||
FROM games
|
||||
WHERE game_id = ?
|
||||
@@ -448,6 +470,8 @@ class GameplayDatabase:
|
||||
"ruleset_version": game_row["ruleset_version"],
|
||||
"your_snake_id": game_row["your_snake_id"],
|
||||
"your_snake_name": game_row["your_snake_name"],
|
||||
"your_snake_type": game_row["your_snake_type"],
|
||||
"your_snake_version": game_row["your_snake_version"],
|
||||
"winner_names": self._from_json(game_row["winner_names_json"]) or [],
|
||||
"winner_you": bool(game_row["winner_you"]),
|
||||
"final_turn": int(game_row["final_turn"] or 0),
|
||||
@@ -456,8 +480,8 @@ class GameplayDatabase:
|
||||
"turns": replay_turns,
|
||||
}
|
||||
|
||||
async def record_game_start(self, game_state:dict) -> None:
|
||||
await asyncio.to_thread(self._record_game_start_sync, game_state)
|
||||
async def record_game_start(self, game_state: dict, snake_type:str|None=None, snake_version:str|None=None) -> None:
|
||||
await asyncio.to_thread(self._record_game_start_sync, game_state, snake_type, snake_version)
|
||||
|
||||
async def record_turn(self, game_state:dict, my_move:str|None, my_thinking:dict|None=None) -> None:
|
||||
await asyncio.to_thread(self._record_turn_sync, game_state, my_move, my_thinking)
|
||||
|
||||
Reference in New Issue
Block a user