rework folder structure complitly
This commit is contained in:
+1
-1
@@ -12,5 +12,5 @@ data/
|
|||||||
dbschema/migrations/
|
dbschema/migrations/
|
||||||
|
|
||||||
*.jsonl
|
*.jsonl
|
||||||
dataset/
|
/dataset/
|
||||||
models/
|
models/
|
||||||
|
|||||||
+3
-3
@@ -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((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 = {
|
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
|
||||||
@@ -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
|
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,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
|
||||||
@@ -55,8 +55,8 @@ class EdgeDB:
|
|||||||
calculations = snake_calulations[i] if i < len(snake_calulations) else []
|
calculations = snake_calulations[i] if i < len(snake_calulations) else []
|
||||||
calculations.append({
|
calculations.append({
|
||||||
"dataset": {
|
"dataset": {
|
||||||
"is_good_move": labels_by_turn.get(moves[i]["turn"], False)
|
"is_good_move": labels_by_turn.get(moves[i]["turn"], False)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
data.append({
|
data.append({
|
||||||
"turn": moves[i]["turn"],
|
"turn": moves[i]["turn"],
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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
|
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:
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user