Compare commits

...

6 Commits

Author SHA1 Message Date
b69ff74c95 simple game statemachine 2021-03-21 14:23:30 +01:00
7437d683ad updated states 2021-03-14 14:21:33 +01:00
06acfdc1e2 gamelogic 2021-03-14 13:59:13 +01:00
4c1d8b7206 removed player states from main statemachine 2021-03-14 08:56:14 +01:00
0daa47f004 removed gameplayer state 2021-03-13 18:52:04 +01:00
e1cb096bd0 added late night 2021-03-13 06:31:37 +01:00
4 changed files with 225 additions and 99 deletions

View File

@@ -7,90 +7,35 @@ Trying to rebuild [https://github.com/foin137/werwolfonline.eu](https://github.c
```plantuml ```plantuml
@startuml(id=overview) @startuml(id=overview)
state WaitingForPlayers <<fork>> hide empty description
state WaitingForPlayers
[*] --> WaitingForPlayers: Created [*] --> WaitingForPlayers: Created
WaitingForPlayers --> GameMaster: Creator
WaitingForPlayers --> Player: Player
GameMaster : Show the game id/link WaitingForPlayers --> StartGame: Start && ready
WaitingForPlayers : Create game in Database
EditRules -> GameMaster: close Rules
GameMaster -> EditRules: edit rules
state WaitingForPlayersEnd <<join>>
Player --> WaitingForPlayersEnd: Ready
GameMaster --> GameMaster: start &&\nlen(Players)>=len(special roles) &&\nnum_werewolfes > 1
GameMaster --> WaitingForPlayersEnd: start &&\n(len(Players)<len(special roles) ||\n num_werewolfes < 1)
WaitingForPlayersEnd --> StartGame
StartGame : Assign Player Roles StartGame : Assign Player Roles
StartGame : Show Introduction StartGame : Show Introduction
StartGame --> MainPhase : all ready StartGame --> Night : All players ready
state MainPhase {
!include https://gitea.d1v3.de/matthias/pywerewolf/raw/branch/master/README.md!DayAndNight
}
@enduml
```
```plantuml
@startuml(id=DayAndNight)
state NightPhaseCupin <<fork>>
[*] --> NightPhaseCupin: Game has Cupin
[*] --> NightPhaseMain: no Cupin
state NightPhaseCupinEnd <<join>> Night --> SelectVictim
NightPhaseCupin --> WaitToContinueCupin: !Cupin
NightPhaseCupin --> SelectLovedOnes: Cupin
WaitToContinueCupin --> NightPhaseCupinEnd
SelectLovedOnes --> NightPhaseCupinEnd: selected and confirmed loved ones
state NightPhaseLovedOnes <<fork>> SelectVictim --> KillVictim
state NightPhaseLovedOnesEnd <<fork>> KillVictim --> ShowDead: Some Villagers left
NightPhaseCupinEnd --> NightPhaseLovedOnes KillVictim --> Award: No Villagers left
NightPhaseLovedOnes --> LovedOnesAwake: Selected Loved Ones
NightPhaseLovedOnes --> WaitForLovedOnes: other
LovedOnesAwake --> NightPhaseLovedOnesEnd: ready
WaitForLovedOnes --> NightPhaseLovedOnesEnd
NightPhaseLovedOnesEnd --> NightPhaseMain : all ready
state NightPhaseMain {
!include https://gitea.d1v3.de/matthias/pywerewolf/raw/branch/master/README.md!NightPhaseMain
}
NightPhaseMain --> NightPhaseLate : Witch/Leaderwolf && voted && actions done and all ready
NightPhaseLate: LeaderWolf selects victim to convert
NightPhaseLate: witch is allowed to select heal/murder
NightPhaseMain --> ShowDead: no Witch/Leaderwolf && voted && actions done and all ready
NightPhaseLate --> ShowDead: actions taken
@enduml
```
```plantuml
@startuml(id=NightPhaseMain)
state NightPhaseMainStart <<fork>>
[*] --> NightPhaseMainStart
NightPhaseMainStart --> SelectVictim: Werewolfes || Spy
NightPhaseMainStart --> SelectIdentity: Seer
NightPhaseMainStart --> SelectProtectee: Protector
NightPhaseMainStart --> SeeWerewolfes: ParanormalInvestigator && FirstRound*
NightPhaseMainStart --> Sleep: Villager
state NightPhaseMainEnd <<join>>
SelectVictim --> NightPhaseMainEnd: Werewolfes || Spy
SelectIdentity --> NightPhaseMainEnd: Done
SelectProtectee --> NightPhaseMainEnd: Done
SeeWerewolfes --> NightPhaseMainEnd: Done
Sleep --> NightPhaseMainEnd: Ready
NightPhaseMainEnd --> [*]
ShowDead: Show the Dead of the night
ShowDead --> Election: No Major
Election --> Discussion: Major elected
ShowDead --> Discussion: all ready || major ready
Discussion --> Accuse: time is up / major forwarded
Accuse --> Poll : Accuse ok
Poll --> ShootTheConvict: Poll successfull
Poll --> Voting: Poll unsuccessfull
Voting --> ShootTheConvict: Voting Completed
ShootTheConvict --> Award: No Werewolfes left
ShootTheConvict --> Night: Some Werewolfes left
@enduml @enduml
``` ```
@@ -99,3 +44,4 @@ NightPhaseMainEnd --> [*]

View File

@@ -1,48 +1,213 @@
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,
)
],
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: 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