rework folder structure complitly

This commit is contained in:
2026-04-04 23:01:34 +02:00
parent 2b8f0396e3
commit eb290dd634
17 changed files with 57 additions and 82 deletions
+1 -1
View File
@@ -12,5 +12,5 @@ data/
dbschema/migrations/ dbschema/migrations/
*.jsonl *.jsonl
dataset/ /dataset/
models/ models/
+3 -3
View File
@@ -1,12 +1,12 @@
from quart_common.web.logger import build_logger, await_log from quart_common.web.logger import build_logger, await_log
from server.Files import read_file from server.Files import read_file
from server.game_board_stats import GameBoardStoreBuilder from server.game_state_store import GameStateStoreBuilder
from server.GameBoard import GameBoard from server.GameBoard import GameBoard
from snakes import SnakeBuilder from snakes import SnakeBuilder
from server.storage.StorageLoader import StorageLoader from server.storage import StorageLoader
from server.metrics import ( from server.metrics import (
MetricsStoreBuilder, MetricsStoreBuilder,
@@ -39,7 +39,7 @@ class Server:
self.store_game_state = False self.store_game_state = False
normalized_backend = (game_state_backend or 'memory').strip().lower() normalized_backend = (game_state_backend or 'memory').strip().lower()
self.game_state_local_cache = (game_state_local_cache and normalized_backend != 'memory') self.game_state_local_cache = (game_state_local_cache and normalized_backend != 'memory')
self.game_state_store = GameBoardStoreBuilder.build( self.game_state_store = GameStateStoreBuilder.build(
backend=game_state_backend, backend=game_state_backend,
redis_url=game_state_redis_url, redis_url=game_state_redis_url,
ttl_seconds=game_state_ttl_sec, ttl_seconds=game_state_ttl_sec,
@@ -3,17 +3,17 @@ from server.GameBoard import GameBoard
class Dataset: class Dataset:
VALID_MOVES = {"up", "down", "left", "right"} VALID_MOVES = {"up", "down", "left", "right"}
def __init__(self, game_board: GameBoard): def __init__(self, game_board:GameBoard):
self.game_board = game_board self.game_board = game_board
def _did_we_win(self): def _did_we_win(self):
winners = self.game_board.winner_snake_names or [] winners = self.game_board.winner_snake_names or []
return "me" in winners return "me" in winners
def _is_good_move(self, move: str): def _is_good_move(self, move:str):
return move in self.VALID_MOVES return move in self.VALID_MOVES
def build(self, only_good_moves: bool = True): def build(self, only_good_moves:bool=True):
game_type = self.game_board.get_type_of_game() game_type = self.game_board.get_type_of_game()
did_win = self._did_we_win() did_win = self._did_we_win()
@@ -1,23 +1,8 @@
import argparse import argparse, hashlib, shutil, glob, json
import glob
import hashlib
import json
import shutil
from pathlib import Path from pathlib import Path
class DatasetCurator: class DatasetCurator:
def __init__( def __init__(self, input_files:list[str], output_file:str, min_turn:int=6, late_turn:int=20, max_safe_options:int=2, min_score:int=3, append:bool=False, archive_input:bool=False, archive_dir:str|None=None):
self,
input_files: list[str],
output_file: str,
min_turn: int = 6,
late_turn: int = 20,
max_safe_options: int = 2,
min_score: int = 3,
append: bool = False,
archive_input: bool = False,
archive_dir: str | None = None,
):
self.input_files = input_files self.input_files = input_files
self.output_file = Path(output_file) self.output_file = Path(output_file)
self.min_turn = min_turn self.min_turn = min_turn
@@ -66,42 +51,36 @@ class DatasetCurator:
return resolved return resolved
def _safe_options_count(self, row: dict): def _safe_options_count(self, row:dict):
history = row.get("history", {}) history = row.get("history", {})
for item in history.get("data", []): for item in history.get("data", []):
if item.get("function") == "get_possible_moves": if item.get("function") == "get_possible_moves":
return len(item.get("safe_positions", {})) return len(item.get("safe_positions", {}))
return None return None
def _state_hash(self, row: dict): def _state_hash(self, row:dict):
board = row.get("game_board", {}) board = row.get("game_board", {})
snakes = board.get("snakes", []) snakes = board.get("snakes", [])
snakes_key = [] snakes_key = []
for snake in snakes: for snake in snakes:
snakes_key.append( snakes_key.append((
(
snake.get("id"), snake.get("id"),
snake.get("health"), snake.get("health"),
tuple( tuple((seg.get("x"), seg.get("y")) for seg in snake.get("body", [])),
(seg.get("x"), seg.get("y")) for seg in snake.get("body", []) ))
),
)
)
key = { key = {
"width": board.get("width"), "width": board.get("width"),
"height": board.get("height"), "height": board.get("height"),
"snakes": sorted(snakes_key), "snakes": sorted(snakes_key),
"food": sorted((f.get("x"), f.get("y")) for f in board.get("food", [])), "food": sorted((f.get("x"), f.get("y")) for f in board.get("food", [])),
"hazards": sorted( "hazards": sorted((h.get("x"), h.get("y")) for h in board.get("hazards", [])),
(h.get("x"), h.get("y")) for h in board.get("hazards", [])
),
} }
raw = json.dumps(key, sort_keys=True, separators=(",", ":")) raw = json.dumps(key, sort_keys=True, separators=(",", ":"))
return hashlib.sha1(raw.encode("utf-8")).hexdigest() return hashlib.sha1(raw.encode("utf-8")).hexdigest()
def _score(self, row: dict): def _score(self, row:dict):
score = 0 score = 0
turn = int(row.get("turn", 0)) turn = int(row.get("turn", 0))
safe_options = self._safe_options_count(row) safe_options = self._safe_options_count(row)
@@ -197,7 +176,7 @@ class DatasetCurator:
"output_file": str(self.output_file), "output_file": str(self.output_file),
} }
def _archive_processed_files(self, input_paths: list[Path]): def _archive_processed_files(self, input_paths:list[Path]):
self.archive_dir.mkdir(parents=True, exist_ok=True) self.archive_dir.mkdir(parents=True, exist_ok=True)
archived = [] archived = []
@@ -235,7 +214,6 @@ class DatasetCurator:
return archived return archived
if __name__ == "__main__": if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Create curated best-moves dataset") parser = argparse.ArgumentParser(description="Create curated best-moves dataset")
parser.add_argument( parser.add_argument(
@@ -1,7 +1,5 @@
import argparse
import json
from pathlib import Path from pathlib import Path
import argparse, json
class DatasetExporter: class DatasetExporter:
def __init__(self, input_dir:str, output_file:str): def __init__(self, input_dir:str, output_file:str):
@@ -56,8 +54,12 @@ class DatasetExporter:
if __name__ == "__main__": if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Export Battlesnake dataset to JSONL") parser = argparse.ArgumentParser(description="Export Battlesnake dataset to JSONL")
parser.add_argument("--input", default="data", help="Input directory with stored game JSON files") parser.add_argument(
parser.add_argument("--output", default="data/dataset/good_moves.jsonl", help="Output JSONL file") "--input", default="data", help="Input directory with stored game JSON files"
)
parser.add_argument(
"--output", default="data/dataset/good_moves.jsonl", help="Output JSONL file"
)
args = parser.parse_args() args = parser.parse_args()
report = DatasetExporter(args.input, args.output).export_jsonl() report = DatasetExporter(args.input, args.output).export_jsonl()
@@ -1,16 +1,12 @@
import argparse
import glob
import json
import re
from collections import Counter, defaultdict from collections import Counter, defaultdict
import argparse, glob, json, re
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
class DatasetStats: class DatasetStats:
DAY_PATTERN = re.compile(r"(\d{4}-\d{2}-\d{2})") DAY_PATTERN = re.compile(r"(\d{4}-\d{2}-\d{2})")
def __init__(self, input_files: list[str]): def __init__(self, input_files:list[str]):
self.input_files = input_files self.input_files = input_files
def _resolve_input_files(self): def _resolve_input_files(self):
@@ -49,20 +45,20 @@ class DatasetStats:
return resolved return resolved
def _infer_day(self, file_path: Path): def _infer_day(self, file_path:Path):
match = self.DAY_PATTERN.search(file_path.name) match = self.DAY_PATTERN.search(file_path.name)
if match: if match:
return match.group(1) return match.group(1)
return datetime.fromtimestamp(file_path.stat().st_mtime).strftime("%Y-%m-%d") return datetime.fromtimestamp(file_path.stat().st_mtime).strftime("%Y-%m-%d")
def _game_score(self, game: dict): def _game_score(self, game:dict):
max_turn = game["max_turn"] max_turn = game["max_turn"]
rows = game["rows"] rows = game["rows"]
avg_safe = game["avg_safe_options"] avg_safe = game["avg_safe_options"]
pressure_bonus = 0 if avg_safe is None else max(0.0, 4.0 - avg_safe) pressure_bonus = 0 if avg_safe is None else max(0.0, 4.0 - avg_safe)
return round(max_turn * 2.0 + rows + pressure_bonus, 3) return round(max_turn * 2.0 + rows + pressure_bonus, 3)
def _pressure_score(self, game: dict): def _pressure_score(self, game:dict):
max_turn = game["max_turn"] max_turn = game["max_turn"]
rows = max(1, game["rows"]) rows = max(1, game["rows"])
pressure_turns = game["pressure_turns"] pressure_turns = game["pressure_turns"]
@@ -72,7 +68,7 @@ class DatasetStats:
safe_tightness = 0.0 if avg_safe is None else max(0.0, 3.0 - avg_safe) safe_tightness = 0.0 if avg_safe is None else max(0.0, 3.0 - avg_safe)
return round(max_turn * 1.2 + pressure_ratio * 120.0 + safe_tightness * 20.0, 3) return round(max_turn * 1.2 + pressure_ratio * 120.0 + safe_tightness * 20.0, 3)
def _extract_safe_options(self, row: dict): def _extract_safe_options(self, row:dict):
top_level = row.get("safe_options") top_level = row.get("safe_options")
if isinstance(top_level, int): if isinstance(top_level, int):
return top_level return top_level
+4
View File
@@ -0,0 +1,4 @@
from .Dataset import Dataset
from .DatasetExporter import DatasetExporter
from .DatasetCurator import DatasetCurator
from .DatasetStats import DatasetStats
@@ -1,6 +1,5 @@
from server.GameBoard import GameBoard from server.GameBoard import GameBoard
import inspect import inspect, pickle
import pickle
class RedisGameBoardStore: class RedisGameBoardStore:
def __init__(self, redis_url:str="redis://localhost:6379/0", key_prefix:str="snake:gameboard", ttl_seconds:int=900, **kwargs): def __init__(self, redis_url:str="redis://localhost:6379/0", key_prefix:str="snake:gameboard", ttl_seconds:int=900, **kwargs):
@@ -1,7 +1,7 @@
from server.game_board_stats.MemoryGameBoardStore import MemoryGameBoardStore from .MemoryGameBoardStore import MemoryGameBoardStore
from server.game_board_stats.RedisGameBoardStore import RedisGameBoardStore from .RedisGameBoardStore import RedisGameBoardStore
class GameBoardStoreBuilder: class GameStateStoreBuilder:
@classmethod @classmethod
def build(self, backend:str="memory", **kwargs) -> MemoryGameBoardStore|RedisGameBoardStore: def build(self, backend:str="memory", **kwargs) -> MemoryGameBoardStore|RedisGameBoardStore:
selected = (backend or "memory").strip().lower() selected = (backend or "memory").strip().lower()
+1 -1
View File
@@ -1,5 +1,5 @@
from server.GameBoard import GameBoard from server.GameBoard import GameBoard
from server.Dataset import Dataset from server.dataset.Dataset import Dataset
from datetime import datetime from datetime import datetime
import json, time import json, time
+2 -3
View File
@@ -1,11 +1,10 @@
from server.dataset.Dataset import Dataset
from server.GameBoard import GameBoard from server.GameBoard import GameBoard
from server.Dataset import Dataset
from server.Files import save_file from server.Files import save_file
import aiofiles import aiofiles
import aiofiles.os import aiofiles.os
import gzip import gzip, json, os
import json, os
class LocalStorage: class LocalStorage:
def __init__(self, file_path:str, **kwargs): def __init__(self, file_path:str, **kwargs):
-7
View File
@@ -1,7 +0,0 @@
class StorageLoader:
@classmethod
def build(self, selected_storage:str):
storage_module = __import__(f'server.storage.{selected_storage}', fromlist=[selected_storage])
storage_class = getattr(storage_module, selected_storage)
return storage_class
+6
View File
@@ -0,0 +1,6 @@
class StorageLoader:
@classmethod
def build(self, selected_storage: str):
storage_module = __import__(f"server.storage.{selected_storage}", fromlist=[selected_storage])
storage_class = getattr(storage_module, selected_storage)
return storage_class
+1 -1
View File
@@ -1,7 +1,7 @@
import unittest import unittest
from typing import cast from typing import cast
from server.Dataset import Dataset from server.dataset.Dataset import Dataset
from server.GameBoard import GameBoard from server.GameBoard import GameBoard
class DummySnake: class DummySnake:
+1 -1
View File
@@ -3,7 +3,7 @@ import tempfile
import unittest import unittest
from pathlib import Path from pathlib import Path
from server.DatasetExporter import DatasetExporter from server.dataset.DatasetExporter import DatasetExporter
class TestDatasetExporter(unittest.TestCase): class TestDatasetExporter(unittest.TestCase):
def test_export_jsonl(self): def test_export_jsonl(self):
+4 -6
View File
@@ -2,9 +2,7 @@ import unittest
from typing import Any, cast from typing import Any, cast
from server.GameBoard import GameBoard from server.GameBoard import GameBoard
from server.game_board_stats import GameBoardStoreBuilder from server.game_state_store import GameStateStoreBuilder, MemoryGameBoardStore, RedisGameBoardStore
from server.game_board_stats.MemoryGameBoardStore import MemoryGameBoardStore
from server.game_board_stats.RedisGameBoardStore import RedisGameBoardStore
from snakes.TemplateSnake import TemplateSnake from snakes.TemplateSnake import TemplateSnake
class _FakeRedis: class _FakeRedis:
@@ -73,9 +71,9 @@ class TestGameStateStore(unittest.IsolatedAsyncioTestCase):
return board return board
def test_builder_selects_store_backend(self): def test_builder_selects_store_backend(self):
memory_store = GameBoardStoreBuilder.build(backend="memory") memory_store = GameStateStoreBuilder.build(backend="memory")
redis_store = GameBoardStoreBuilder.build(backend="redis") redis_store = GameStateStoreBuilder.build(backend="redis")
default_store = GameBoardStoreBuilder.build(backend="unknown") default_store = GameStateStoreBuilder.build(backend="unknown")
self.assertIsInstance(memory_store, MemoryGameBoardStore) self.assertIsInstance(memory_store, MemoryGameBoardStore)
self.assertIsInstance(redis_store, RedisGameBoardStore) self.assertIsInstance(redis_store, RedisGameBoardStore)