143 lines
4.9 KiB
Python
143 lines
4.9 KiB
Python
from datetime import datetime
|
|
from typing import Optional
|
|
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.models.orm import GameState
|
|
from app.models.schemas import GamePhase, GameStateResponse
|
|
|
|
|
|
class GameStateManager:
|
|
PHASE_ORDER = GamePhase.ordered_phases()
|
|
PHASE_COUNT = len(PHASE_ORDER)
|
|
|
|
async def get_state(self, session: AsyncSession) -> Optional[GameState]:
|
|
result = await session.execute(
|
|
select(GameState).order_by(GameState.created_at.desc()).limit(1)
|
|
)
|
|
return result.scalars().first()
|
|
|
|
async def get_or_create_state(self, session: AsyncSession) -> GameState:
|
|
state = await self.get_state(session)
|
|
if state is None:
|
|
state = GameState(
|
|
current_phase=GamePhase.INTRO.value,
|
|
is_running=False,
|
|
is_paused=False,
|
|
)
|
|
session.add(state)
|
|
await session.commit()
|
|
await session.refresh(state)
|
|
return state
|
|
|
|
async def next_phase(self, session: AsyncSession) -> GameState:
|
|
state = await self.get_or_create_state(session)
|
|
current = state.current_phase
|
|
try:
|
|
idx = self.PHASE_ORDER.index(current)
|
|
if idx < self.PHASE_COUNT - 1:
|
|
state.current_phase = self.PHASE_ORDER[idx + 1]
|
|
except ValueError:
|
|
pass
|
|
|
|
state.phase_started_at = datetime.utcnow()
|
|
state.updated_at = datetime.utcnow()
|
|
await session.commit()
|
|
await session.refresh(state)
|
|
return state
|
|
|
|
async def prev_phase(self, session: AsyncSession) -> GameState:
|
|
state = await self.get_or_create_state(session)
|
|
current = state.current_phase
|
|
try:
|
|
idx = self.PHASE_ORDER.index(current)
|
|
if idx > 0:
|
|
state.current_phase = self.PHASE_ORDER[idx - 1]
|
|
except ValueError:
|
|
pass
|
|
|
|
state.phase_started_at = datetime.utcnow()
|
|
state.updated_at = datetime.utcnow()
|
|
await session.commit()
|
|
await session.refresh(state)
|
|
return state
|
|
|
|
def get_current_phase(self, state: GameState) -> str:
|
|
return state.current_phase
|
|
|
|
def get_progress_percent(self, state: GameState) -> float:
|
|
try:
|
|
idx = self.PHASE_ORDER.index(state.current_phase)
|
|
return (idx / max(self.PHASE_COUNT - 1, 1)) * 100
|
|
except ValueError:
|
|
return 0.0
|
|
|
|
async def start_game(self, session: AsyncSession, script_id: str) -> GameState:
|
|
state = await self.get_or_create_state(session)
|
|
state.active_script_id = script_id
|
|
state.current_phase = GamePhase.INTRO.value
|
|
state.is_running = True
|
|
state.is_paused = False
|
|
state.phase_started_at = datetime.utcnow()
|
|
state.updated_at = datetime.utcnow()
|
|
await session.commit()
|
|
await session.refresh(state)
|
|
return state
|
|
|
|
async def pause_game(self, session: AsyncSession) -> GameState:
|
|
state = await self.get_or_create_state(session)
|
|
state.is_paused = True
|
|
state.updated_at = datetime.utcnow()
|
|
await session.commit()
|
|
await session.refresh(state)
|
|
return state
|
|
|
|
async def resume_game(self, session: AsyncSession) -> GameState:
|
|
state = await self.get_or_create_state(session)
|
|
state.is_paused = False
|
|
state.updated_at = datetime.utcnow()
|
|
await session.commit()
|
|
await session.refresh(state)
|
|
return state
|
|
|
|
async def reset(self, session: AsyncSession) -> GameState:
|
|
state = await self.get_or_create_state(session)
|
|
state.current_phase = GamePhase.INTRO.value
|
|
state.current_speaker_id = None
|
|
state.speaker_order = []
|
|
state.active_script_id = None
|
|
state.is_running = False
|
|
state.is_paused = False
|
|
state.phase_started_at = None
|
|
state.config = {}
|
|
state.updated_at = datetime.utcnow()
|
|
await session.commit()
|
|
await session.refresh(state)
|
|
return state
|
|
|
|
async def set_speaker(self, session: AsyncSession, character_id: str) -> GameState:
|
|
state = await self.get_or_create_state(session)
|
|
state.current_speaker_id = character_id
|
|
state.updated_at = datetime.utcnow()
|
|
await session.commit()
|
|
await session.refresh(state)
|
|
return state
|
|
|
|
def to_response(self, state: GameState) -> GameStateResponse:
|
|
return GameStateResponse(
|
|
id=state.id,
|
|
current_phase=GamePhase(state.current_phase),
|
|
current_speaker_id=state.current_speaker_id,
|
|
speaker_order=state.speaker_order or [],
|
|
active_script_id=state.active_script_id,
|
|
is_running=state.is_running,
|
|
is_paused=state.is_paused,
|
|
phase_started_at=state.phase_started_at,
|
|
config=state.config or {},
|
|
progress_percent=self.get_progress_percent(state),
|
|
)
|
|
|
|
|
|
game_state_manager = GameStateManager()
|