Compare commits
6 Commits
fa6b2d31d6
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| b69ff74c95 | |||
| 7437d683ad | |||
| 06acfdc1e2 | |||
| 4c1d8b7206 | |||
| 0daa47f004 | |||
| e1cb096bd0 |
126
README.md
126
README.md
@@ -7,132 +7,35 @@ Trying to rebuild [https://github.com/foin137/werwolfonline.eu](https://github.c
|
||||
|
||||
```plantuml
|
||||
@startuml(id=overview)
|
||||
state WaitingForPlayers <<fork>>
|
||||
hide empty description
|
||||
|
||||
state WaitingForPlayers
|
||||
[*] --> WaitingForPlayers: Created
|
||||
WaitingForPlayers --> GameMaster: Creator
|
||||
WaitingForPlayers --> Player: Player
|
||||
|
||||
GameMaster : Show the game id/link
|
||||
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
|
||||
WaitingForPlayers --> StartGame: Start && ready
|
||||
StartGame : Assign Player Roles
|
||||
StartGame : Show Introduction
|
||||
|
||||
StartGame --> MainPhaseNight : all ready
|
||||
state MainPhaseNight {
|
||||
!include https://gitea.d1v3.de/matthias/pywerewolf/raw/branch/master/README.md!TheNight
|
||||
}
|
||||
NightPhaseMain --> Award: No Villagers left
|
||||
MainPhaseNight --> MainPhaseDay
|
||||
state MainPhaseDay {
|
||||
!include https://gitea.d1v3.de/matthias/pywerewolf/raw/branch/master/README.md!TheDay
|
||||
}
|
||||
NightPhaseMain --> Award: No Werewolfes left
|
||||
NightPhaseMain --> MainPhaseNight
|
||||
@enduml
|
||||
```
|
||||
|
||||
```plantuml
|
||||
@startuml(id=TheNight)
|
||||
state NightPhaseCupin <<fork>>
|
||||
[*] --> NightPhaseCupin: Game has Cupin
|
||||
[*] --> NightPhaseMain: no Cupin
|
||||
StartGame --> Night : All players ready
|
||||
|
||||
|
||||
state NightPhaseCupinEnd <<join>>
|
||||
NightPhaseCupin --> WaitToContinueCupin: !Cupin
|
||||
NightPhaseCupin --> SelectLovedOnes: Cupin
|
||||
WaitToContinueCupin --> NightPhaseCupinEnd
|
||||
SelectLovedOnes --> NightPhaseCupinEnd: selected and confirmed loved ones
|
||||
Night --> SelectVictim
|
||||
|
||||
state NightPhaseLovedOnes <<fork>>
|
||||
state NightPhaseLovedOnesEnd <<fork>>
|
||||
NightPhaseCupinEnd --> NightPhaseLovedOnes
|
||||
NightPhaseLovedOnes --> LovedOnesAwake: Selected Loved Ones
|
||||
NightPhaseLovedOnes --> WaitForLovedOnes: other
|
||||
LovedOnesAwake --> NightPhaseLovedOnesEnd: ready
|
||||
WaitForLovedOnes --> NightPhaseLovedOnesEnd
|
||||
SelectVictim --> KillVictim
|
||||
KillVictim --> ShowDead: Some Villagers left
|
||||
KillVictim --> Award: No Villagers left
|
||||
|
||||
NightPhaseLovedOnesEnd --> NightPhaseMain : all ready
|
||||
|
||||
state NightPhaseMain {
|
||||
!include https://gitea.d1v3.de/matthias/pywerewolf/raw/branch/master/README.md!NightPhaseMain
|
||||
}
|
||||
NightPhaseMain --> NightPhaseLate : Witch/Leaderwolf &&\nvoted &&\nactions done and all ready
|
||||
state NightPhaseLate {
|
||||
!include https://gitea.d1v3.de/matthias/pywerewolf/raw/branch/master/README.md!NightPhaseLate
|
||||
}
|
||||
NightPhaseMain --> [*]: no Witch/Leaderwolf &&\nvoted &&\nactions done and all ready
|
||||
NightPhaseLate --> [*]: actions taken
|
||||
@enduml
|
||||
```
|
||||
|
||||
```plantuml
|
||||
@startuml(id=TheDay)
|
||||
[*] --> ShowDead: Done
|
||||
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:
|
||||
Poll --> Shoot: Poll successfull
|
||||
Accuse --> Poll : Accuse ok
|
||||
Poll --> ShootTheConvict: Poll successfull
|
||||
Poll --> Voting: Poll unsuccessfull
|
||||
Voting --> ShootTheWolf
|
||||
ShootTheWolf --> [*]: Done
|
||||
@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 --> [*]
|
||||
|
||||
@enduml
|
||||
```
|
||||
|
||||
```plantuml
|
||||
@startuml(id=NightPhaseLate)
|
||||
state NightPhaseLateStart <<fork>>
|
||||
[*] --> NightPhaseLateStart
|
||||
NightPhaseLateStart --> SelectHealOrPoison: Witch
|
||||
NightPhaseLateStart --> SelectLeaderVictim: Leaderwolf
|
||||
NightPhaseLateStart --> SleepAgain: Villager
|
||||
|
||||
|
||||
state NightPhaseLateEnd <<join>>
|
||||
SelectHealOrPoison --> NightPhaseLateEnd: Selection finished
|
||||
SelectHealOrPoison -left-> SelectHealOrPoison: Next selection
|
||||
SelectLeaderVictim --> NightPhaseLateEnd: Done
|
||||
SleepAgain --> NightPhaseLateEnd
|
||||
|
||||
NightPhaseLateEnd --> [*]
|
||||
|
||||
Voting --> ShootTheConvict: Voting Completed
|
||||
ShootTheConvict --> Award: No Werewolfes left
|
||||
ShootTheConvict --> Night: Some Werewolfes left
|
||||
@enduml
|
||||
```
|
||||
|
||||
@@ -141,3 +44,4 @@ NightPhaseLateEnd --> [*]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,48 +1,213 @@
|
||||
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):
|
||||
state: models.GameState
|
||||
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
|
||||
|
||||
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
|
||||
def player_alive(self):
|
||||
|
||||
@@ -46,11 +46,11 @@ class Roles(enum.Enum):
|
||||
Leaderwolf = enum.auto()
|
||||
|
||||
@classmethod
|
||||
def isWerwolf(cls, role: Roles):
|
||||
def isWerwolf(cls, role: "Roles"):
|
||||
return role in [cls.Werewolf, cls.Leaderwolf]
|
||||
|
||||
@classmethod
|
||||
def getGroup(cls, role: Roles):
|
||||
def getGroup(cls, role: "Roles"):
|
||||
return RoleGroup.Werewolfs if role in [cls.Werewolf, cls.Leaderwolf] else RoleGroup.Villagers
|
||||
|
||||
class Rules(object):
|
||||
@@ -98,6 +98,7 @@ class Player(object):
|
||||
diedInRound: int
|
||||
accusedBy: "Player"
|
||||
|
||||
|
||||
@property
|
||||
def isWerwolf(self):
|
||||
return Roles.isWerwolf(self.role)
|
||||
|
||||
@@ -5,4 +5,18 @@ import pywerwolf.gamelogic as gl
|
||||
|
||||
def test_game_init():
|
||||
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