rework folder structure complitly
This commit is contained in:
+1
-1
@@ -12,5 +12,5 @@ data/
|
||||
dbschema/migrations/
|
||||
|
||||
*.jsonl
|
||||
dataset/
|
||||
/dataset/
|
||||
models/
|
||||
|
||||
+3
-3
@@ -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
|
||||
@@ -0,0 +1,4 @@
|
||||
from .Dataset import Dataset
|
||||
from .DatasetExporter import DatasetExporter
|
||||
from .DatasetCurator import DatasetCurator
|
||||
from .DatasetStats import DatasetStats
|
||||
+1
-2
@@ -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,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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
@@ -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,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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user