From c6e8cc7d29717dc7285cab5845f75d7b376bedca Mon Sep 17 00:00:00 2001 From: Matthias Bilger Date: Fri, 12 Mar 2021 00:59:38 +0100 Subject: [PATCH] updated roles, init tests, added states --- README.md | 50 +++++++++++++++++++++++++ pywerwolf/gamelogic.py | 59 ++++++++++++++++++++++++++++- pywerwolf/models.py | 84 +++++++++++++++++++++++------------------- pywerwolf/texts.py | 8 +++- setup.py | 23 ++++++++++++ tests/test_game.py | 8 ++++ 6 files changed, 193 insertions(+), 39 deletions(-) create mode 100644 setup.py create mode 100644 tests/test_game.py diff --git a/README.md b/README.md index 8edb314..e1ea344 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,53 @@ # PyWerwolf Trying to rebuild [https://github.com/foin137/werwolfonline.eu](https://github.com/foin137/werwolfonline.eu) in python + + +## Phases of the game + +```plantuml +@startuml +[*] --> WaitingForPlayers: Created +WaitingForPlayers : Show the game id/link +WaitingForPlayers : Create game in Database +WaitingForPlayers : Allow setting of rules + +WaitingForPlayers --> StartGame: len(Players)>=len(special roles) && start && num_werewolfes > 1 +WaitingForPlayers --> WaitingForPlayers: start && (len(Players)> +state NightPhaseCupin <> +StartGame --> GameHasCupin : all ready +GameHasCupin --> NightPhaseCupin: Game has Cupin +GameHasCupin --> NightPhaseMain: no Cupin +NightPhaseMain : Werewolfes awake & select (vote) their victim +NightPhaseMain : Seer/Spy/Protector/ParanormalInvestigator awaken & access feat +NightPhaseMain : sleep & confirm + + +state NightPhaseCupinEnd <> +NightPhaseCupin --> WaitToContinueCupin: !Cupin +NightPhaseCupin --> SelectLovedOnes: Cupin +WaitToContinueCupin --> NightPhaseCupinEnd +SelectLovedOnes --> NightPhaseCupinEnd: selected and confirmed loved ones + +state NightPhaseLovedOnes <> +state NightPhaseLovedOnesEnd <> +NightPhaseCupinEnd --> NightPhaseLovedOnes +NightPhaseLovedOnes --> LovedOnesAwake: Selected Loved Ones +NightPhaseLovedOnes --> WaitForLovedOnes: other +LovedOnesAwake --> NightPhaseLovedOnesEnd: ready +WaitForLovedOnes --> NightPhaseLovedOnesEnd + +NightPhaseLovedOnesEnd --> NightPhaseMain : all ready +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 +``` diff --git a/pywerwolf/gamelogic.py b/pywerwolf/gamelogic.py index 7d34687..2e69ff2 100644 --- a/pywerwolf/gamelogic.py +++ b/pywerwolf/gamelogic.py @@ -1,3 +1,60 @@ -import models +from pywerwolf import models +import typing as t +import uuid +class Game(object): + state: models.GameState + + def __init__(self, game_id: t.Union[str, uuid.UUID] = None): + if game_id is None: + self.create_new_game_state() + else: + self.load_game_state(game_id) + + def create_new_game_state(self): + pass + + def load_game_state(self, game_id: t.Union[str, uuid.UUID]): + 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): + """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) diff --git a/pywerwolf/models.py b/pywerwolf/models.py index d149dbc..123f5e8 100644 --- a/pywerwolf/models.py +++ b/pywerwolf/models.py @@ -21,6 +21,13 @@ class GamePhase(enum.Enum): PollResult = enum.auto() Award = enum.auto() +class RoleGroup(enum.Enum): + NoGroup = enum.auto() + Villagers = enum.auto() + Werewolfs = enum.auto() + LovedOnes = enum.auto() + + class Roles(enum.Enum): NoRole = enum.auto() Villager = enum.auto() @@ -31,35 +38,52 @@ class Roles(enum.Enum): Cupid = enum.auto() Protector = enum.auto() ParanormalInvestigator = enum.auto() - Lycantrop = enum.auto() + Lycantroph = enum.auto() Spy = enum.auto() - Murder = enum.auto() Pacifist = enum.auto() OldMan = enum.auto() Murder = enum.auto() Leaderwolf = enum.auto() + @classmethod + def isWerwolf(cls, role: Roles): + return role in [cls.Werewolf, cls.Leaderwolf] + + @classmethod + def getGroup(cls, role: Roles): + return RoleGroup.Werewolfs if role in [cls.Werewolf, cls.Leaderwolf] else RoleGroup.Villagers + class Rules(object): showRoles: bool #`charaktereAufdecken` INT ( 2 ) DEFAULT 0, passMajor: bool #`buergermeisterWeitergeben` INT ( 2 ) DEFAULT 0, seerSeesIdentity: bool #`seherSiehtIdentitaet` INT ( 2 ) DEFAULT 1, - roleCount: t.Dict[Roles, int] - #`werwolfzahl` INT ( 5 ) DEFAULT 0 , - #`hexenzahl` INT ( 5 ) DEFAULT 0 , - #`seherzahl` INT ( 5 ) DEFAULT 0 , - #`jaegerzahl` INT ( 5 ) DEFAULT 0 , - #`amorzahl` INT ( 2 ) DEFAULT 0 , - #`beschuetzerzahl` INT ( 5 ) DEFAULT 0 , - #`parErmZahl` INT (5) DEFAULT 0 , - #`lykantrophenzahl` INT ( 5 ) DEFAULT 0 , - #`spionezahl` INT ( 5 ) DEFAULT 0 , - #`idiotenzahl` INT ( 5 ) DEFAULT 0 , - #`pazifistenzahl` INT ( 5 ) DEFAULT 0 , - #`altenzahl` INT ( 5 ) DEFAULT 0 , - #`urwolfzahl` INT ( 5 ) DEFAULT 0 , + werewolfes: int + witches: int + seers: int + hunters: int + cupids: int + protectors: int + paranormals: int + lycantrophs: int + spys: int + murders: int + pacifists: int + oldmans: int + leaderwolfs: int randomSelect: bool #`zufaelligeAuswahl` INT ( 2 ) DEFAULT 0 , randomBonus: int #`zufaelligeAuswahlBonus` INT ( 5 ) DEFAULT 0 , unanimously: bool #`werwolfeinstimmig` INT ( 2 ) DEFAULT 1 , + timer_unanimously: int + timer_unanimously_per_wolf: int + timer_unsuccessfull: int + timer_unsuccessfull_per_wolf: int + timer_accusation: int + timer_accusation_per_player: int + timer_votation: int + timer_votation_per_player: int + timer_inactivity: int + timer_inactivity_per_player: int + @@ -74,6 +98,10 @@ class Player(object): diedInRound: int accusedBy: "Player" + @property + def isWerwolf(self): + return Roles.isWerwolf(self.role) + # `wahlAuf` INT ( 5 ) DEFAULT -1 , # `angeklagtVon` INT ( 5 ) DEFAULT -1 , @@ -92,29 +120,11 @@ class Player(object): # `countdownBis` INT (10) DEFAULT 0 , # `countdownAb` INT (10) DEFAULT 0 , - -class Game(object): +class GameState(object): currentPhase: GamePhase #`spielphase` INT( 5 ) DEFAULT 0, gameRound: int rules: Rules log: t.List[str] + players: t.List[Player] #`werwolfopfer` INT ( 5 ) DEFAULT -1 , - #`werwolftimer1` INT ( 10 ) DEFAULT 60 , - #`werwolfzusatz1` INT ( 10 ) DEFAULT 4 , - #`werwolftimer2` INT ( 10 ) DEFAULT 50 , - #`werwolfzusatz2` INT ( 10 ) DEFAULT 3 , - #`dorftimer` INT ( 10 ) DEFAULT 550 , - #`dorfzusatz` INT ( 10 ) DEFAULT 10 , - #`dorfstichwahltimer` INT ( 10 ) DEFAULT 200 , - #`dorfstichwahlzusatz` INT ( 10 ) DEFAULT 5 , - #`inaktivzeit` INT ( 10 ) DEFAULT 40 , - #`inaktivzeitzusatz` INT ( 10 ) DEFAULT 0 , - #`tagestext` TEXT , - #`nacht` INT ( 5 ) DEFAULT 1 , - #`log` LONGTEXT , - #`list_lebe` LONGTEXT, - #`list_lebe_aktualisiert` BIGINT DEFAULT 0, - #`list_tot` LONGTEXT, - #`list_tot_aktualisiert` BIGINT DEFAULT 0, - #`waiting_for_others_time` BIGINT, - #`letzterAufruf` BIGINT + diff --git a/pywerwolf/texts.py b/pywerwolf/texts.py index ac9f49e..d9dc26f 100644 --- a/pywerwolf/texts.py +++ b/pywerwolf/texts.py @@ -54,12 +54,18 @@ texts = { models.Roles.Cupid: "Armor", models.Roles.Protector: "Beschützer/in", models.Roles.ParanormalInvestigator: "Paranormaler Ermittler", - models.Roles.Lycantrop: "Lykantroph/in", + models.Roles.Lycantroph: "Lykantroph/in", models.Roles.Spy: "Spion/in", models.Roles.Murder: "Mordlustige(r)", models.Roles.Pacifist: "Pazifist/in", models.Roles.OldMan: "Der/Die Alte", models.Roles.Leaderwolf: "Urwolf", } + }, +"role_group":{ + "de": { + True : "Werwölfe", + False: "Dorfbewohner" } +}, } diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..2ba7c91 --- /dev/null +++ b/setup.py @@ -0,0 +1,23 @@ +import setuptools + +with open("README.md", "r") as fh: + long_description = fh.read() + +setuptools.setup( + name="pywerwolf", + version="0.0.1", + author="Matthias Bilger", + author_email="matthias@bilger.info", + description="A Python implementation of Werewolf/Mafia game", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://github.com/m42e/pywerwolf", + license="GPL", + packages=setuptools.find_packages(), # Required + classifiers=[ + "Programming Language :: Python :: 3", + "Operating System :: OS Independent", + 'License :: Other/Proprietary License', + ], + python_requires='>=3.6', +) diff --git a/tests/test_game.py b/tests/test_game.py new file mode 100644 index 0000000..d94e4e9 --- /dev/null +++ b/tests/test_game.py @@ -0,0 +1,8 @@ +import pytest + +import pywerwolf +import pywerwolf.gamelogic as gl + +def test_game_init(): + g = gl.Game() +