Compare commits
6 Commits
1741a1ab9f
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| b69ff74c95 | |||
| 7437d683ad | |||
| 06acfdc1e2 | |||
| 4c1d8b7206 | |||
| 0daa47f004 | |||
| e1cb096bd0 |
96
README.md
96
README.md
@@ -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 --> [*]
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user