custom-cluedo / backend /models.py
clementpep's picture
fix: game logic and ai comments
496e0a2
"""
Data models for Cluedo Custom game.
Defines the structure of players, cards, and game state.
"""
from typing import List, Optional, Dict
from pydantic import BaseModel, Field
from enum import Enum
import random
import string
class NarrativeTone(str, Enum):
"""Narrative tone options for the game."""
SERIOUS = "🕵️ Sérieuse"
PARODY = "😂 Parodique"
FANTASY = "🧙‍♂️ Fantastique"
THRILLER = "🎬 Thriller"
HORROR = "👻 Horreur"
class CardType(str, Enum):
"""Types of cards in the game."""
CHARACTER = "character"
WEAPON = "weapon"
ROOM = "room"
class Card(BaseModel):
"""Represents a single card in the game."""
name: str
card_type: CardType
class Player(BaseModel):
"""Represents a player in the game."""
id: str
name: str
cards: List[Card] = Field(default_factory=list)
is_active: bool = True
current_room_index: int = 0 # Position on the board
has_rolled: bool = False # Track if player rolled dice this turn
class GameStatus(str, Enum):
"""Status of a game."""
WAITING = "waiting" # Waiting for players to join
IN_PROGRESS = "in_progress" # Game is running
FINISHED = "finished" # Game has ended
class Turn(BaseModel):
"""Represents a turn action in the game."""
player_id: str
player_name: str
action: str # "move", "suggest", "accuse", "pass"
details: Optional[str] = None
ai_comment: Optional[str] = None # Desland's sarcastic comment
timestamp: str
class Solution(BaseModel):
"""The secret solution to the mystery."""
character: Card
weapon: Card
room: Card
class InvestigationNote(BaseModel):
"""Player's notes on the investigation."""
player_id: str
element_name: str # Name of suspect/weapon/room
element_type: str # "suspect", "weapon", "room"
status: str # "unknown", "eliminated", "maybe"
class RoomPosition(BaseModel):
"""Position of a room on the board."""
name: str
x: int # Grid X position
y: int # Grid Y position
class BoardLayout(BaseModel):
"""Board layout configuration."""
rooms: List[RoomPosition] = Field(default_factory=list)
grid_width: int = 8
grid_height: int = 8
class Game(BaseModel):
"""Represents a complete game instance."""
game_id: str
name: str
status: GameStatus = GameStatus.WAITING
# Theme and narrative
narrative_tone: str = NarrativeTone.SERIOUS.value
custom_prompt: Optional[str] = None
# Game elements (customizable)
rooms: List[str]
custom_weapons: List[str] = Field(default_factory=list)
custom_suspects: List[str] = Field(default_factory=list)
use_ai: bool = False
# Players
players: List[Player] = Field(default_factory=list)
max_players: int = 8
current_player_index: int = 0
# Cards
characters: List[Card] = Field(default_factory=list)
weapons: List[Card] = Field(default_factory=list)
room_cards: List[Card] = Field(default_factory=list)
# Solution
solution: Optional[Solution] = None
# Game state
turns: List[Turn] = Field(default_factory=list)
winner: Optional[str] = None
# Investigation notes (for UI)
investigation_notes: List[InvestigationNote] = Field(default_factory=list)
# Board layout
board_layout: Optional[BoardLayout] = None
# AI-generated content
scenario: Optional[str] = None
@staticmethod
def generate_game_id() -> str:
"""Generate a unique 4-character game ID (like AB7F)."""
chars = string.ascii_uppercase + string.digits
return ''.join(random.choices(chars, k=4))
def add_player(self, player_name: str) -> Player:
"""Add a new player to the game."""
player_id = ''.join(random.choices(string.ascii_lowercase + string.digits, k=8))
# All players start in the first room
player = Player(id=player_id, name=player_name, current_room_index=0)
self.players.append(player)
return player
def get_current_player(self) -> Optional[Player]:
"""Get the player whose turn it is."""
if not self.players:
return None
return self.players[self.current_player_index]
def next_turn(self):
"""Move to the next active player's turn."""
if not self.players:
return
# Reset has_rolled for current player
if self.current_player_index < len(self.players):
self.players[self.current_player_index].has_rolled = False
# Skip eliminated players
attempts = 0
while attempts < len(self.players):
self.current_player_index = (self.current_player_index + 1) % len(self.players)
if self.players[self.current_player_index].is_active:
break
attempts += 1
def is_full(self) -> bool:
"""Check if the game has reached maximum players."""
return len(self.players) >= self.max_players
class CreateGameRequest(BaseModel):
"""Request to create a new game."""
game_name: str
narrative_tone: str = NarrativeTone.SERIOUS.value
custom_prompt: Optional[str] = None
rooms: List[str]
custom_weapons: List[str]
custom_suspects: List[str]
use_ai: bool = False
board_layout: Optional[BoardLayout] = None
class JoinGameRequest(BaseModel):
"""Request to join an existing game."""
game_id: str
player_name: str
class GameAction(BaseModel):
"""Request to perform a game action."""
game_id: str
player_id: str
action_type: str # "move", "suggest", "accuse", "pass"
# For movement
dice_roll: Optional[int] = None
target_room_index: Optional[int] = None
# For suggestions/accusations
character: Optional[str] = None
weapon: Optional[str] = None
room: Optional[str] = None