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/
*.jsonl
dataset/
/dataset/
models/
+3 -3
View File
@@ -1,12 +1,12 @@
from quart_common.web.logger import build_logger, await_log
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 snakes import SnakeBuilder
from server.storage.StorageLoader import StorageLoader
from server.storage import StorageLoader
from server.metrics import (
MetricsStoreBuilder,
@@ -39,7 +39,7 @@ class Server:
self.store_game_state = False
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_store = GameBoardStoreBuilder.build(
self.game_state_store = GameStateStoreBuilder.build(
backend=game_state_backend,
redis_url=game_state_redis_url,
ttl_seconds=game_state_ttl_sec,
@@ -3,17 +3,17 @@ from server.GameBoard import GameBoard
class Dataset:
VALID_MOVES = {"up", "down", "left", "right"}
def __init__(self, game_board: GameBoard):
def __init__(self, game_board:GameBoard):
self.game_board = game_board
def _did_we_win(self):
winners = self.game_board.winner_snake_names or []
return "me" in winners
def _is_good_move(self, move: str):
def _is_good_move(self, move:str):
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()
did_win = self._did_we_win()
@@ -1,23 +1,8 @@
import argparse
import glob
import hashlib
import json
import shutil
import argparse, hashlib, shutil, glob, json
from pathlib import Path
class DatasetCurator:
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,
):
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 = input_files
self.output_file = Path(output_file)
self.min_turn = min_turn
@@ -66,42 +51,36 @@ class DatasetCurator:
return resolved
def _safe_options_count(self, row: dict):
def _safe_options_count(self, row:dict):
history = row.get("history", {})
for item in history.get("data", []):
if item.get("function") == "get_possible_moves":
return len(item.get("safe_positions", {}))
return None
def _state_hash(self, row: dict):
def _state_hash(self, row:dict):
board = row.get("game_board", {})
snakes = board.get("snakes", [])
snakes_key = []
for snake in snakes:
snakes_key.append(
(
snakes_key.append((
snake.get("id"),
snake.get("health"),
tuple(
(seg.get("x"), seg.get("y")) for seg in snake.get("body", [])
),
)
)
tuple((seg.get("x"), seg.get("y")) for seg in snake.get("body", [])),
))
key = {
"width": board.get("width"),
"height": board.get("height"),
"snakes": sorted(snakes_key),
"food": sorted((f.get("x"), f.get("y")) for f in board.get("food", [])),
"hazards": sorted(
(h.get("x"), h.get("y")) for h in board.get("hazards", [])
),
"hazards": sorted((h.get("x"), h.get("y")) for h in board.get("hazards", [])),
}
raw = json.dumps(key, sort_keys=True, separators=(",", ":"))
return hashlib.sha1(raw.encode("utf-8")).hexdigest()
def _score(self, row: dict):
def _score(self, row:dict):
score = 0
turn = int(row.get("turn", 0))
safe_options = self._safe_options_count(row)
@@ -197,7 +176,7 @@ class DatasetCurator:
"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)
archived = []
@@ -235,7 +214,6 @@ class DatasetCurator:
return archived
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Create curated best-moves dataset")
parser.add_argument(
@@ -1,7 +1,5 @@
import argparse
import json
from pathlib import Path
import argparse, json
class DatasetExporter:
def __init__(self, input_dir:str, output_file:str):
@@ -56,8 +54,12 @@ class DatasetExporter:
if __name__ == "__main__":
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("--output", default="data/dataset/good_moves.jsonl", help="Output JSONL file")
parser.add_argument(
"--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()
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
import argparse, glob, json, re
from datetime import datetime
from pathlib import Path
class DatasetStats:
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
def _resolve_input_files(self):
@@ -49,20 +45,20 @@ class DatasetStats:
return resolved
def _infer_day(self, file_path: Path):
def _infer_day(self, file_path:Path):
match = self.DAY_PATTERN.search(file_path.name)
if match:
return match.group(1)
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"]
rows = game["rows"]
avg_safe = game["avg_safe_options"]
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)
def _pressure_score(self, game: dict):
def _pressure_score(self, game:dict):
max_turn = game["max_turn"]
rows = max(1, game["rows"])
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)
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")
if isinstance(top_level, int):
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
import inspect
import pickle
import inspect, pickle
class RedisGameBoardStore:
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 server.game_board_stats.RedisGameBoardStore import RedisGameBoardStore
from .MemoryGameBoardStore import MemoryGameBoardStore
from .RedisGameBoardStore import RedisGameBoardStore
class GameBoardStoreBuilder:
class GameStateStoreBuilder:
@classmethod
def build(self, backend:str="memory", **kwargs) -> MemoryGameBoardStore|RedisGameBoardStore:
selected = (backend or "memory").strip().lower()
+1 -1
View File
@@ -1,5 +1,5 @@
from server.GameBoard import GameBoard
from server.Dataset import Dataset
from server.dataset.Dataset import Dataset
from datetime import datetime
import json, time
+2 -3
View File
@@ -1,11 +1,10 @@
from server.dataset.Dataset import Dataset
from server.GameBoard import GameBoard
from server.Dataset import Dataset
from server.Files import save_file
import aiofiles
import aiofiles.os
import gzip
import json, os
import gzip, json, os
class LocalStorage:
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
from typing import cast
from server.Dataset import Dataset
from server.dataset.Dataset import Dataset
from server.GameBoard import GameBoard
class DummySnake:
+1 -1
View File
@@ -3,7 +3,7 @@ import tempfile
import unittest
from pathlib import Path
from server.DatasetExporter import DatasetExporter
from server.dataset.DatasetExporter import DatasetExporter
class TestDatasetExporter(unittest.TestCase):
def test_export_jsonl(self):
+4 -6
View File
@@ -2,9 +2,7 @@ import unittest
from typing import Any, cast
from server.GameBoard import GameBoard
from server.game_board_stats import GameBoardStoreBuilder
from server.game_board_stats.MemoryGameBoardStore import MemoryGameBoardStore
from server.game_board_stats.RedisGameBoardStore import RedisGameBoardStore
from server.game_state_store import GameStateStoreBuilder, MemoryGameBoardStore, RedisGameBoardStore
from snakes.TemplateSnake import TemplateSnake
class _FakeRedis:
@@ -73,9 +71,9 @@ class TestGameStateStore(unittest.IsolatedAsyncioTestCase):
return board
def test_builder_selects_store_backend(self):
memory_store = GameBoardStoreBuilder.build(backend="memory")
redis_store = GameBoardStoreBuilder.build(backend="redis")
default_store = GameBoardStoreBuilder.build(backend="unknown")
memory_store = GameStateStoreBuilder.build(backend="memory")
redis_store = GameStateStoreBuilder.build(backend="redis")
default_store = GameStateStoreBuilder.build(backend="unknown")
self.assertIsInstance(memory_store, MemoryGameBoardStore)
self.assertIsInstance(redis_store, RedisGameBoardStore)