2025-10-04 20:40:44 +02:00
|
|
|
import sqlite3
|
|
|
|
|
import json
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
from typing import Optional, Dict, List
|
2025-10-04 23:26:27 +02:00
|
|
|
from server.logger import logger
|
|
|
|
|
import time
|
2025-10-04 20:40:44 +02:00
|
|
|
|
|
|
|
|
class Database:
|
|
|
|
|
"""Handles all database operations"""
|
|
|
|
|
|
|
|
|
|
def __init__(self, db_path: str = "data/game.db"):
|
|
|
|
|
self.db_path = db_path
|
|
|
|
|
Path("data").mkdir(exist_ok=True)
|
|
|
|
|
|
|
|
|
|
def _get_connection(self):
|
|
|
|
|
"""Get database connection"""
|
|
|
|
|
conn = sqlite3.connect(self.db_path)
|
|
|
|
|
conn.row_factory = sqlite3.Row
|
|
|
|
|
return conn
|
|
|
|
|
|
|
|
|
|
def init_db(self):
|
|
|
|
|
"""Initialize database tables"""
|
2025-10-04 23:26:27 +02:00
|
|
|
logger.info(f"Initializing database at {self.db_path}...")
|
2025-10-04 20:40:44 +02:00
|
|
|
with self._get_connection() as conn:
|
|
|
|
|
conn.execute('''
|
|
|
|
|
CREATE TABLE IF NOT EXISTS players (
|
|
|
|
|
player_id TEXT PRIMARY KEY,
|
|
|
|
|
nickname TEXT UNIQUE NOT NULL,
|
|
|
|
|
money INTEGER NOT NULL,
|
|
|
|
|
population INTEGER NOT NULL,
|
|
|
|
|
color TEXT NOT NULL,
|
|
|
|
|
last_online REAL NOT NULL
|
|
|
|
|
)
|
|
|
|
|
''')
|
|
|
|
|
conn.execute('''
|
|
|
|
|
CREATE TABLE IF NOT EXISTS buildings (
|
|
|
|
|
x INTEGER NOT NULL,
|
|
|
|
|
y INTEGER NOT NULL,
|
|
|
|
|
type TEXT NOT NULL,
|
|
|
|
|
owner_id TEXT NOT NULL,
|
|
|
|
|
name TEXT,
|
|
|
|
|
placed_at REAL NOT NULL,
|
|
|
|
|
PRIMARY KEY (x, y),
|
|
|
|
|
FOREIGN KEY (owner_id) REFERENCES players (player_id)
|
|
|
|
|
)
|
|
|
|
|
''')
|
|
|
|
|
conn.commit()
|
2025-10-04 23:26:27 +02:00
|
|
|
logger.info("Database initialized successfully.")
|
2025-10-04 20:40:44 +02:00
|
|
|
|
|
|
|
|
def save_game_state(self, game_state):
|
|
|
|
|
"""Save complete game state to database"""
|
2025-10-04 23:26:27 +02:00
|
|
|
logger.debug(f"Saving game state to database... ({len(game_state.players)} players, {len(game_state.buildings)} buildings)")
|
|
|
|
|
start_time = time.perf_counter()
|
2025-10-04 20:40:44 +02:00
|
|
|
with self._get_connection() as conn:
|
2025-10-04 23:26:27 +02:00
|
|
|
player_data = [
|
|
|
|
|
(p.player_id, p.nickname, p.money, p.population, p.color, p.last_online)
|
|
|
|
|
for p in game_state.players.values()
|
|
|
|
|
]
|
|
|
|
|
if player_data:
|
|
|
|
|
conn.executemany('''
|
2025-10-04 21:15:02 +02:00
|
|
|
INSERT OR REPLACE INTO players (player_id, nickname, money, population, color, last_online)
|
2025-10-04 20:40:44 +02:00
|
|
|
VALUES (?, ?, ?, ?, ?, ?)
|
2025-10-04 23:26:27 +02:00
|
|
|
''', player_data)
|
|
|
|
|
|
|
|
|
|
cursor = conn.execute('SELECT x, y FROM buildings')
|
|
|
|
|
db_coords = {(row['x'], row['y']) for row in cursor}
|
|
|
|
|
gs_coords = set(game_state.buildings.keys())
|
|
|
|
|
|
|
|
|
|
coords_to_delete = db_coords - gs_coords
|
|
|
|
|
if coords_to_delete:
|
|
|
|
|
conn.executemany('DELETE FROM buildings WHERE x = ? AND y = ?', list(coords_to_delete))
|
|
|
|
|
|
|
|
|
|
building_data = [
|
|
|
|
|
(b.x, b.y, b.building_type.value, b.owner_id, b.name, b.placed_at)
|
|
|
|
|
for b in game_state.buildings.values()
|
|
|
|
|
]
|
|
|
|
|
if building_data:
|
|
|
|
|
conn.executemany('''
|
|
|
|
|
INSERT OR REPLACE INTO buildings (x, y, type, owner_id, name, placed_at)
|
2025-10-04 20:40:44 +02:00
|
|
|
VALUES (?, ?, ?, ?, ?, ?)
|
2025-10-04 23:26:27 +02:00
|
|
|
''', building_data)
|
2025-10-04 20:40:44 +02:00
|
|
|
|
|
|
|
|
conn.commit()
|
2025-10-04 23:26:27 +02:00
|
|
|
duration = time.perf_counter() - start_time
|
|
|
|
|
logger.debug(f"Game state saved to database in {duration:.4f} seconds.")
|
2025-10-04 20:40:44 +02:00
|
|
|
|
|
|
|
|
def load_game_state(self) -> dict:
|
|
|
|
|
"""Load complete game state from database"""
|
2025-10-04 23:26:27 +02:00
|
|
|
logger.info("Loading game state from database...")
|
|
|
|
|
start_time = time.perf_counter()
|
2025-10-04 20:40:44 +02:00
|
|
|
try:
|
|
|
|
|
with self._get_connection() as conn:
|
2025-10-04 23:26:27 +02:00
|
|
|
players, buildings = [], []
|
|
|
|
|
|
2025-10-04 20:40:44 +02:00
|
|
|
cursor = conn.execute('SELECT * FROM players')
|
|
|
|
|
for row in cursor:
|
2025-10-04 23:26:27 +02:00
|
|
|
players.append(dict(row))
|
2025-10-04 20:40:44 +02:00
|
|
|
|
|
|
|
|
cursor = conn.execute('SELECT * FROM buildings')
|
|
|
|
|
for row in cursor:
|
2025-10-04 23:26:27 +02:00
|
|
|
buildings.append(dict(row))
|
2025-10-04 20:40:44 +02:00
|
|
|
|
2025-10-04 23:26:27 +02:00
|
|
|
duration = time.perf_counter() - start_time
|
|
|
|
|
logger.info(f"Loaded {len(players)} players and {len(buildings)} buildings in {duration:.4f} seconds.")
|
|
|
|
|
return {"players": players, "buildings": buildings}
|
2025-10-04 20:40:44 +02:00
|
|
|
except Exception as e:
|
2025-10-04 23:26:27 +02:00
|
|
|
logger.error(f"Error loading game state: {e}", exc_info=True)
|
2025-10-04 20:40:44 +02:00
|
|
|
return {"players": [], "buildings": []}
|