import asyncpg import json from typing import Optional, Dict, List from datetime import datetime class GameDatabase: def __init__(self, host: str = "localhost", port: int = 5432, database: str = "czechdb", user: str = "czech", password: str = "password"): self.host = host self.port = port self.database = database self.user = user self.password = password self.pool = None async def connect(self): """Создание пула соединений""" self.pool = await asyncpg.create_pool( host=self.host, port=self.port, database=self.database, user=self.user, password=self.password, min_size=2, max_size=10 ) await self.init_db() async def close(self): """Закрытие пула соединений""" if self.pool: await self.pool.close() async def init_db(self): """Инициализация базы данных""" async with self.pool.acquire() as conn: # Таблица комнат await conn.execute(''' CREATE TABLE IF NOT EXISTS rooms ( id TEXT PRIMARY KEY, game_started BOOLEAN DEFAULT FALSE, current_player_index INTEGER DEFAULT 0, dealer_index INTEGER DEFAULT 0, chosen_suit TEXT, waiting_for_eight BOOLEAN DEFAULT FALSE, card_drawn_this_turn BOOLEAN DEFAULT FALSE, deck_size INTEGER DEFAULT 52, creator_id TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ''') # Таблица игроков await conn.execute(''' CREATE TABLE IF NOT EXISTS players ( id TEXT PRIMARY KEY, room_id TEXT, nickname TEXT NOT NULL, ready BOOLEAN DEFAULT FALSE, score INTEGER DEFAULT 0, player_order INTEGER DEFAULT 0, FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE ) ''') # Таблица карт в руке await conn.execute(''' CREATE TABLE IF NOT EXISTS player_cards ( id SERIAL PRIMARY KEY, player_id TEXT, card_suit TEXT NOT NULL, card_rank TEXT NOT NULL, card_id TEXT NOT NULL, FOREIGN KEY (player_id) REFERENCES players(id) ON DELETE CASCADE ) ''') # Таблица колоды await conn.execute(''' CREATE TABLE IF NOT EXISTS deck_cards ( id SERIAL PRIMARY KEY, room_id TEXT, card_suit TEXT NOT NULL, card_rank TEXT NOT NULL, card_id TEXT NOT NULL, card_order INTEGER, FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE ) ''') # Таблица сброса await conn.execute(''' CREATE TABLE IF NOT EXISTS discard_pile ( id SERIAL PRIMARY KEY, room_id TEXT, card_suit TEXT NOT NULL, card_rank TEXT NOT NULL, card_id TEXT NOT NULL, card_order INTEGER, FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE ) ''') async def save_room(self, room_data: Dict): """Сохранение состояния комнаты""" async with self.pool.acquire() as conn: await conn.execute(''' INSERT INTO rooms (id, game_started, current_player_index, dealer_index, chosen_suit, waiting_for_eight, card_drawn_this_turn, deck_size, creator_id, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, CURRENT_TIMESTAMP) ON CONFLICT (id) DO UPDATE SET game_started = $2, current_player_index = $3, dealer_index = $4, chosen_suit = $5, waiting_for_eight = $6, card_drawn_this_turn = $7, deck_size = $8, creator_id = $9, updated_at = CURRENT_TIMESTAMP ''', room_data['id'], room_data['game_started'], room_data['current_player_index'], room_data['dealer_index'], room_data.get('chosen_suit'), room_data.get('waiting_for_eight', False), room_data.get('card_drawn_this_turn', False), room_data.get('deck_size', 52), room_data.get('creator_id') ) async def save_player(self, player_data: Dict, room_id: str, order: int): """Сохранение игрока""" async with self.pool.acquire() as conn: await conn.execute(''' INSERT INTO players (id, room_id, nickname, ready, score, player_order) VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (id) DO UPDATE SET room_id = $2, nickname = $3, ready = $4, score = $5, player_order = $6 ''', player_data['id'], room_id, player_data['nickname'], player_data['ready'], player_data['score'], order ) async def save_player_cards(self, player_id: str, cards: List[Dict]): """Сохранение карт игрока""" async with self.pool.acquire() as conn: # Удаляем старые карты await conn.execute('DELETE FROM player_cards WHERE player_id = $1', player_id) # Добавляем новые if cards: await conn.executemany(''' INSERT INTO player_cards (player_id, card_suit, card_rank, card_id) VALUES ($1, $2, $3, $4) ''', [(player_id, card['suit'], card['rank'], card['id']) for card in cards]) async def save_deck(self, room_id: str, cards: List[Dict]): """Сохранение колоды""" async with self.pool.acquire() as conn: await conn.execute('DELETE FROM deck_cards WHERE room_id = $1', room_id) if cards: await conn.executemany(''' INSERT INTO deck_cards (room_id, card_suit, card_rank, card_id, card_order) VALUES ($1, $2, $3, $4, $5) ''', [(room_id, card['suit'], card['rank'], card['id'], idx) for idx, card in enumerate(cards)]) async def save_discard_pile(self, room_id: str, cards: List[Dict]): """Сохранение сброса""" async with self.pool.acquire() as conn: await conn.execute('DELETE FROM discard_pile WHERE room_id = $1', room_id) if cards: await conn.executemany(''' INSERT INTO discard_pile (room_id, card_suit, card_rank, card_id, card_order) VALUES ($1, $2, $3, $4, $5) ''', [(room_id, card['suit'], card['rank'], card['id'], idx) for idx, card in enumerate(cards)]) async def load_room(self, room_id: str) -> Optional[Dict]: """Загрузка комнаты""" async with self.pool.acquire() as conn: row = await conn.fetchrow('SELECT * FROM rooms WHERE id = $1', room_id) if not row: return None return { 'id': row['id'], 'game_started': row['game_started'], 'current_player_index': row['current_player_index'], 'dealer_index': row['dealer_index'], 'chosen_suit': row['chosen_suit'], 'waiting_for_eight': row['waiting_for_eight'], 'card_drawn_this_turn': row['card_drawn_this_turn'], 'deck_size': row.get('deck_size', 52), 'creator_id': row.get('creator_id') } async def load_players(self, room_id: str) -> List[Dict]: """Загрузка игроков комнаты""" async with self.pool.acquire() as conn: rows = await conn.fetch(''' SELECT id, nickname, ready, score, player_order FROM players WHERE room_id = $1 ORDER BY player_order ''', room_id) return [{ 'id': row['id'], 'nickname': row['nickname'], 'ready': row['ready'], 'score': row['score'], 'order': row['player_order'] } for row in rows] async def load_player_cards(self, player_id: str) -> List[Dict]: """Загрузка карт игрока""" async with self.pool.acquire() as conn: rows = await conn.fetch(''' SELECT card_suit, card_rank, card_id FROM player_cards WHERE player_id = $1 ''', player_id) return [{ 'suit': row['card_suit'], 'rank': row['card_rank'], 'id': row['card_id'] } for row in rows] async def load_deck(self, room_id: str) -> List[Dict]: """Загрузка колоды""" async with self.pool.acquire() as conn: rows = await conn.fetch(''' SELECT card_suit, card_rank, card_id FROM deck_cards WHERE room_id = $1 ORDER BY card_order ''', room_id) return [{ 'suit': row['card_suit'], 'rank': row['card_rank'], 'id': row['card_id'] } for row in rows] async def load_discard_pile(self, room_id: str) -> List[Dict]: """Загрузка сброса""" async with self.pool.acquire() as conn: rows = await conn.fetch(''' SELECT card_suit, card_rank, card_id FROM discard_pile WHERE room_id = $1 ORDER BY card_order ''', room_id) return [{ 'suit': row['card_suit'], 'rank': row['card_rank'], 'id': row['card_id'] } for row in rows] async def delete_room(self, room_id: str): """Удаление комнаты и всех связанных данных""" async with self.pool.acquire() as conn: # Получаем всех игроков комнаты player_ids = await conn.fetch('SELECT id FROM players WHERE room_id = $1', room_id) # Удаляем карты игроков for row in player_ids: await conn.execute('DELETE FROM player_cards WHERE player_id = $1', row['id']) # Удаляем игроков await conn.execute('DELETE FROM players WHERE room_id = $1', room_id) # Удаляем колоду и сброс await conn.execute('DELETE FROM deck_cards WHERE room_id = $1', room_id) await conn.execute('DELETE FROM discard_pile WHERE room_id = $1', room_id) # Удаляем комнату await conn.execute('DELETE FROM rooms WHERE id = $1', room_id) async def room_exists(self, room_id: str) -> bool: """Проверка существования комнаты""" async with self.pool.acquire() as conn: result = await conn.fetchval('SELECT 1 FROM rooms WHERE id = $1 LIMIT 1', room_id) return result is not None async def get_all_rooms(self) -> List[str]: """Получение всех комнат""" async with self.pool.acquire() as conn: rows = await conn.fetch('SELECT id FROM rooms') return [row['id'] for row in rows] async def cleanup_old_rooms(self, hours: int = 24) -> int: """Удаление комнат, неактивных более N часов""" async with self.pool.acquire() as conn: # Находим старые комнаты rows = await conn.fetch(''' SELECT id FROM rooms WHERE updated_at < NOW() - INTERVAL '%s hours' ''' % hours) old_rooms = [row['id'] for row in rows] # Удаляем каждую старую комнату for room_id in old_rooms: await self.delete_room(room_id) return len(old_rooms) async def get_room_stats(self) -> Dict: """Получение статистики по комнатам""" async with self.pool.acquire() as conn: total_rooms = await conn.fetchval('SELECT COUNT(*) FROM rooms') active_games = await conn.fetchval('SELECT COUNT(*) FROM rooms WHERE game_started = TRUE') inactive_1h = await conn.fetchval(''' SELECT COUNT(*) FROM rooms WHERE updated_at < NOW() - INTERVAL '1 hour' ''') inactive_24h = await conn.fetchval(''' SELECT COUNT(*) FROM rooms WHERE updated_at < NOW() - INTERVAL '24 hours' ''') return { 'total_rooms': total_rooms, 'active_games': active_games, 'inactive_1h': inactive_1h, 'inactive_24h': inactive_24h }