gamelogic

This commit is contained in:
2021-03-14 13:59:13 +01:00
parent 4c1d8b7206
commit 06acfdc1e2
4 changed files with 192 additions and 28 deletions

View File

@@ -33,11 +33,11 @@ MainPhaseDay --> MainPhaseNight: Some Werewolfes left
```plantuml ```plantuml
@startuml(id=TheNight) @startuml(id=TheNight)
hide empty description hide empty description
[*] --> NightPhaseCupin: Game has Cupin [*] --> NightPhaseCupin:
[*] --> NightPhaseMain: no Cupin
NightPhaseCupin --> InformLovedOnes NightPhaseCupin --> InformLovedOnes: Cupin selected
InformLovedOnes --> NightPhaseMain InformLovedOnes --> NightPhaseMain: ack
NightPhaseCupin --> NightPhaseMain : Cupin already selected or No Cupin
' state NightPhaseCupinEnd <<join>> ' state NightPhaseCupinEnd <<join>>
' NightPhaseCupin --> WaitToContinueCupin: !Cupin ' NightPhaseCupin --> WaitToContinueCupin: !Cupin
' NightPhaseCupin --> SelectLovedOnes: Cupin ' NightPhaseCupin --> SelectLovedOnes: Cupin

View File

@@ -1,48 +1,197 @@
from pywerwolf import models from pywerwolf import models
import types
import typing as t import typing as t
import uuid 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): class Game(object):
state: models.GameState statemachine: StateMachine
players: t.List[Player]
def __init__(self, game_id: t.Union[str, uuid.UUID] = None): 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,
)
],
}
if game_id is None: if game_id is None:
self.create_new_game_state() self.create_new_game_state()
else: else:
self.load_game_state(game_id) self.load_game_state(game_id)
def create_new_game_state(self): 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 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]): def load_game_state(self, game_id: t.Union[str, uuid.UUID]):
_logger.info("Loading game")
pass pass
def valid_game(self): def valid_game(self):
# check if game has been created or loaded # check if game has been created or loaded
return True return True
def check_victory(self):
"""check for a winner of the current game"""
if not self.valid_game():
return None
if self.state.currentPhase == models.GamePhase.Award:
return True
if sum(1 for _ in self.werewolfes) > 0:
if sum(1 for _ in self.villagers) <= 0:
return models.RoleGroup.Werewolfs
pass
else:
return models.RoleGroup.Villagers
if sum(1 for _ in self.player_alive) == 2:
living = list(self.player_alive)
if living[0].lovedOne == living[1]:
return models.RoleGroup.LovedOnes
return None
@property @property
def player_alive(self): def player_alive(self):

View File

@@ -46,11 +46,11 @@ class Roles(enum.Enum):
Leaderwolf = enum.auto() Leaderwolf = enum.auto()
@classmethod @classmethod
def isWerwolf(cls, role: Roles): def isWerwolf(cls, role: "Roles"):
return role in [cls.Werewolf, cls.Leaderwolf] return role in [cls.Werewolf, cls.Leaderwolf]
@classmethod @classmethod
def getGroup(cls, role: Roles): def getGroup(cls, role: "Roles"):
return RoleGroup.Werewolfs if role in [cls.Werewolf, cls.Leaderwolf] else RoleGroup.Villagers return RoleGroup.Werewolfs if role in [cls.Werewolf, cls.Leaderwolf] else RoleGroup.Villagers
class Rules(object): class Rules(object):
@@ -98,6 +98,7 @@ class Player(object):
diedInRound: int diedInRound: int
accusedBy: "Player" accusedBy: "Player"
@property @property
def isWerwolf(self): def isWerwolf(self):
return Roles.isWerwolf(self.role) return Roles.isWerwolf(self.role)

View File

@@ -5,4 +5,18 @@ import pywerwolf.gamelogic as gl
def test_game_init(): def test_game_init():
g = gl.Game() g = gl.Game()
assert g.game_id is not None
def test_game_start_next_transition():
g = gl.Game()
g.start_game()
assert g.game_id is not None
assert g.statemachine.state == gl.GameState.start_game is not None
def test_game_start_with_player_not_ready():
g = gl.Game()
g.add_player()
g.start_game()
assert g.game_id is not None
assert g.statemachine.state == gl.GameState.waiting_for_players is not None