feat(snake): optimize duels and persist customizations
Build and Push Docker Container / build-and-push (push) Successful in 7m29s
Build and Push Docker Container / build-and-push (push) Successful in 7m29s
- 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.
This commit is contained in:
@@ -76,6 +76,7 @@ CREATE TABLE IF NOT EXISTS game_snakes (
|
||||
snake_id TEXT NOT NULL,
|
||||
snake_name TEXT,
|
||||
is_you BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
customizations JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
PRIMARY KEY (game_id, snake_id)
|
||||
);
|
||||
|
||||
@@ -114,6 +115,7 @@ ALTER TABLE games ADD COLUMN IF NOT EXISTS quality_tier TEXT;
|
||||
ALTER TABLE games ADD COLUMN IF NOT EXISTS quality_reasons JSONB;
|
||||
ALTER TABLE turns ADD COLUMN IF NOT EXISTS my_thinking JSONB;
|
||||
ALTER TABLE snake_turns ADD COLUMN IF NOT EXISTS latency TEXT;
|
||||
ALTER TABLE game_snakes ADD COLUMN IF NOT EXISTS customizations JSONB NOT NULL DEFAULT '{}'::jsonb;
|
||||
"""
|
||||
|
||||
# Force TOAST compression on the large JSONB columns so that even
|
||||
@@ -458,14 +460,22 @@ class PostgresqlGameplayBackend(GameplayBackendTemplate):
|
||||
async with pool.acquire() as conn:
|
||||
async with conn.transaction():
|
||||
await conn.executemany("""
|
||||
INSERT INTO game_snakes (game_id, snake_id, snake_name, is_you)
|
||||
VALUES ($1,$2,$3,$4)
|
||||
INSERT INTO game_snakes (
|
||||
game_id, snake_id, snake_name, is_you, customizations
|
||||
) VALUES ($1,$2,$3,$4,$5)
|
||||
ON CONFLICT (game_id, snake_id) DO UPDATE SET
|
||||
snake_name = EXCLUDED.snake_name,
|
||||
is_you = EXCLUDED.is_you
|
||||
is_you = EXCLUDED.is_you,
|
||||
customizations = EXCLUDED.customizations
|
||||
""",
|
||||
[
|
||||
(game_id, snake.get("id"), snake.get("name"), snake.get("id") == you.get("id"))
|
||||
(
|
||||
game_id,
|
||||
snake.get("id"),
|
||||
snake.get("name"),
|
||||
snake.get("id") == you.get("id"),
|
||||
snake.get("customizations") or {},
|
||||
)
|
||||
for snake in snakes if snake.get("id") is not None
|
||||
],
|
||||
)
|
||||
@@ -739,6 +749,7 @@ class PostgresqlGameplayBackend(GameplayBackendTemplate):
|
||||
COALESCE(gs.snake_name, st.snake_name) AS snake_name,
|
||||
st.health, st.length, st.head_x, st.head_y, st.body AS body_json,
|
||||
COALESCE(gs.is_you, st.is_you) AS is_you,
|
||||
COALESCE(gs.customizations, '{}'::jsonb) AS customizations_json,
|
||||
st.inferred_move, st.latency
|
||||
FROM snake_turns AS st
|
||||
LEFT JOIN game_snakes AS gs
|
||||
|
||||
@@ -112,6 +112,7 @@ class SqliteGameplayBackend(GameplayBackendTemplate):
|
||||
snake_id TEXT NOT NULL,
|
||||
snake_name TEXT,
|
||||
is_you INTEGER NOT NULL DEFAULT 0,
|
||||
customizations_json TEXT NOT NULL DEFAULT '{}',
|
||||
PRIMARY KEY (game_id, snake_id),
|
||||
FOREIGN KEY (game_id) REFERENCES games(game_id) ON DELETE CASCADE
|
||||
);
|
||||
@@ -140,6 +141,7 @@ class SqliteGameplayBackend(GameplayBackendTemplate):
|
||||
self._ensure_column_exists(connection, "games", "your_snake_version", "TEXT")
|
||||
self._ensure_column_exists(connection, "games", "game_type", "TEXT")
|
||||
self._ensure_column_exists(connection, "snake_turns", "latency", "TEXT")
|
||||
self._ensure_column_exists(connection, "game_snakes", "customizations_json", "TEXT NOT NULL DEFAULT '{}'")
|
||||
self._ensure_column_exists(connection, "games", "winner_name", "TEXT")
|
||||
self._ensure_column_exists(connection, "games", "has_replay", "INTEGER NOT NULL DEFAULT 1")
|
||||
self._ensure_column_exists(connection, "games", "quality_status", "TEXT NOT NULL DEFAULT 'retained'")
|
||||
@@ -266,14 +268,22 @@ class SqliteGameplayBackend(GameplayBackendTemplate):
|
||||
|
||||
with self._connect() as connection:
|
||||
connection.executemany("""
|
||||
INSERT INTO game_snakes (game_id, snake_id, snake_name, is_you)
|
||||
VALUES (?, ?, ?, ?)
|
||||
INSERT INTO game_snakes (
|
||||
game_id, snake_id, snake_name, is_you, customizations_json
|
||||
) VALUES (?, ?, ?, ?, ?)
|
||||
ON CONFLICT(game_id, snake_id) DO UPDATE SET
|
||||
snake_name = excluded.snake_name,
|
||||
is_you = excluded.is_you
|
||||
is_you = excluded.is_you,
|
||||
customizations_json = excluded.customizations_json
|
||||
""",
|
||||
[
|
||||
(game_id, snake.get("id"), snake.get("name"), 1 if snake.get("id") == you.get("id") else 0)
|
||||
(
|
||||
game_id,
|
||||
snake.get("id"),
|
||||
snake.get("name"),
|
||||
1 if snake.get("id") == you.get("id") else 0,
|
||||
self._to_json(snake.get("customizations") or {}),
|
||||
)
|
||||
for snake in snakes if snake.get("id") is not None
|
||||
],
|
||||
)
|
||||
@@ -545,6 +555,7 @@ class SqliteGameplayBackend(GameplayBackendTemplate):
|
||||
COALESCE(gs.snake_name, st.snake_name) AS snake_name,
|
||||
st.health, st.length, st.head_x, st.head_y, st.body_json,
|
||||
COALESCE(gs.is_you, st.is_you) AS is_you,
|
||||
COALESCE(gs.customizations_json, '{}') AS customizations_json,
|
||||
st.inferred_move, st.latency
|
||||
FROM snake_turns AS st
|
||||
LEFT JOIN game_snakes AS gs
|
||||
|
||||
Reference in New Issue
Block a user