from pywerwolf import models import types import typing as t import uuid import collections from dataclasses import dataclass import logging _logger = logging.getLogger(__name__) logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG) class GameState(object): def __init__(self, name): self.name = name def __str__(self): return self.name def __repr__(self): return self.name GameState.initial = GameState("Initial") GameState.waiting_for_players = GameState("Waiting for Players") GameState.start_game = GameState("Start Game") GameState.night_phase = GameState("Night Phase") GameState.day_phase = GameState("Day Phase") GameState.award = GameState("Award") class Input: pass class TimeOut(Input): pass class GameMasterStartGame(Input): pass class Condition: def condition(self, input) -> bool: raise RuntimeError("not implemented") class Transition: def transition(self, input): raise RuntimeError("transition not implemented") @dataclass class TransitionObject: name: str inputType: type transition: Transition condition: t.List[Condition] next: GameState class StateMachine: """Generic Statemachine Executes actions on transitions """ transitionTable: t.Dict[GameState, TransitionObject] state: GameState data: object def __init__( self, initialState: GameState, tranTable: t.Dict[GameState, TransitionObject], data: object, ): self.state = initialState self.transitionTable = tranTable self.data = data _logger.info("Created Statemachine") def nextState(self, input): """Transfer into the next state Depending on the - input - the current state - the condition """ for transition in self.transitionTable[self.state]: _logger.debug("Checking Transition: %s", transition.name) if transition.inputType is not None and not isinstance( input, transition.inputType ): _logger.debug("Input type is required and not given. Expected: %s got %s", transition.inputType, type(input)) continue if transition.condition is not None: def check_condition(x): _logger.debug("Check condition %s", x.__doc__) if not x(input): _logger.debug("Condition %s not met", x.__doc__) return True _logger.debug("Condition %s met", x.__doc__) return False if any(map(check_condition, transition.condition)): continue if transition.transition is not None: t = transition.transition t(input) self.state = transition.next _logger.info(f"Transition done to: {self.state}") break else: _logger.warning("No transition performed for input") class Player(object): def __init__(self, name="Someone"): self.ready = False self.name = name class Game(object): statemachine: StateMachine players: t.List[Player] def __init__(self, game_id: t.Union[str, uuid.UUID] = None): self.players = [] self.states = { GameState.initial: [ TransitionObject( name = "Initial 2 Waiting", inputType=None, condition=None, transition=self.create_game_id, next=GameState.waiting_for_players, ) ], GameState.waiting_for_players: [ TransitionObject( name = "Waiting 2 MainPhase", inputType=GameMasterStartGame, condition=[self.players_ready], transition=self.assign_roles, next=GameState.start_game, ) ], GameState.start_game: [ TransitionObject( name = "StartGame 2 MainPhaseNight", inputType=GameMasterStartGame, condition=[self.players_ready, self.no_cupin], transition=self.assign_roles, next=GameState.night_phase, ), TransitionObject( name = "StartGame 2 CupinSelect", inputType=GameMasterStartGame, condition=[self.has_cupin], transition=self.assign_roles, next=GameState.night_phase, ) ], } if game_id is None: self.create_new_game_state() else: self.load_game_state(game_id) def create_new_game_state(self): _logger.info("Creating a new game") self.statemachine = StateMachine(GameState.initial, self.states, self) self.statemachine.nextState(None) def players_ready(self, input) -> bool: """Players Ready?""" _logger.debug("[%s] check if players ready", self.game_id) return all(map(lambda x: x.ready, self.players)) def remove_players_ready_flag(self): _logger.debug("[%s] remove players ready flag", self.game_id) for p in self.players: p.ready = False def assign_roles(self, _): pass def start_game(self): self.statemachine.nextState(GameMasterStartGame()) def add_player(self): self.players.append(Player()) def create_game_id(self, *args): self.game_id = uuid.uuid4() _logger.info("[%s] created game", self.game_id) def load_game_state(self, game_id: t.Union[str, uuid.UUID]): _logger.info("Loading game") pass def valid_game(self): # check if game has been created or loaded return True @property def player_alive(self): """Generator for players still alive""" return filter(lambda x: x.alive, self.state.players) @property def werewolfes(self): """Generator for players that are werewolfes""" return filter(models.Player.isWerwolf, self.player_alive) @property def villagers(self): """Generator for players that are villagers""" return filter(not (models.Player.isWerwolf), self.player_alive)