orochi-discord/orochi/models.py

197 lines
5.3 KiB
Python
Raw Permalink Normal View History

2021-11-08 14:44:03 +00:00
import pickle
from dataclasses import dataclass, field
from enum import Enum
2021-11-12 13:32:02 +00:00
from pathlib import Path
2021-11-08 20:18:48 +00:00
from typing import ClassVar, Iterable, Generator
2021-11-08 14:44:03 +00:00
class Room(Enum):
A = 'A'
B = 'B'
C = 'C'
2021-11-27 09:27:52 +00:00
@property
def next(self) -> 'Room':
match self:
case Room.A:
return Room.B
case Room.B:
return Room.C
case Room.C:
return Room.A
2021-11-08 14:44:03 +00:00
2021-11-08 15:29:50 +00:00
class GameState(Enum):
PREPARING = 0
VOTING = 1
RESULTS = 2
2021-11-08 14:44:03 +00:00
class Vote(Enum):
ALLY = 'A'
BETRAY = 'B'
@dataclass(frozen=True)
class Player:
name: str
private_channel_id: int = field(hash=False)
@property
2021-11-08 20:18:48 +00:00
def round_votes(self) -> Generator["RoundVote", None, None]:
2021-11-08 14:44:03 +00:00
for r in Game.INSTANCE.rounds:
for room in r.rooms:
for vote in room.votes:
if self in vote.players:
yield vote
@property
2021-11-12 13:26:01 +00:00
def calculated_score(self) -> int:
2021-11-08 14:44:03 +00:00
s = 3
for vote in self.round_votes:
2021-11-08 21:10:08 +00:00
if vote.room.round.round == len(Game.INSTANCE.rounds) and Game.INSTANCE.state != GameState.RESULTS:
# Don't compute temporary scores
break
2021-11-08 14:44:03 +00:00
room = vote.room
other_vote = room.vote1 if room.vote1 is not vote else room.vote2
match vote.vote, other_vote.vote:
case Vote.ALLY, Vote.ALLY:
s += 2
case Vote.ALLY, Vote.BETRAY:
s -= 2
case Vote.BETRAY, Vote.ALLY:
s += 3
case Vote.BETRAY, Vote.BETRAY:
pass
2021-11-08 21:10:08 +00:00
if s <= 0:
# Player died
return s
2021-11-08 14:44:03 +00:00
return s
2021-11-12 13:26:01 +00:00
@property
def score(self) -> int:
if self in Game.INSTANCE.score_overrides:
return Game.INSTANCE.score_overrides[self]
return self.calculated_score
2021-11-08 23:29:07 +00:00
def __str__(self):
return self.name
2021-11-08 14:44:03 +00:00
@dataclass
class RoundVote:
2021-11-08 23:29:07 +00:00
player1: Player | None = None
2021-11-08 14:44:03 +00:00
player2: Player | None = None
vote: Vote | None = None
2021-11-12 13:15:39 +00:00
swapped: bool = field(default=False, init=False)
2021-11-08 14:44:03 +00:00
@property
2021-11-08 20:18:48 +00:00
def players(self) -> Iterable[Player]:
if self.player2 is None:
return self.player1,
2021-11-08 14:44:03 +00:00
return self.player1, self.player2
@property
2021-11-08 20:18:48 +00:00
def room(self) -> "RoundRoom":
for r in Game.INSTANCE.rounds:
2021-11-08 14:44:03 +00:00
for room in r.rooms:
if self in room.votes:
return room
@dataclass
class RoundRoom:
room: Room
vote1: RoundVote
vote2: RoundVote
@property
def votes(self):
return self.vote1, self.vote2
2021-11-08 20:18:48 +00:00
@property
def players(self) -> Generator[Player, None, None]:
for vote in self.votes:
yield from vote.players
2021-11-08 14:44:03 +00:00
@property
def round(self):
for r in Game.INSTANCE.rounds:
if self in r.rooms:
return r
@dataclass
class Round:
round: int
room_a: RoundRoom
room_b: RoundRoom
room_c: RoundRoom
@property
2021-11-08 20:18:48 +00:00
def rooms(self) -> tuple[RoundRoom, RoundRoom, RoundRoom]:
2021-11-08 14:44:03 +00:00
return self.room_a, self.room_b, self.room_c
@dataclass
class Game:
INSTANCE: ClassVar["Game"] = None
2021-11-08 15:29:50 +00:00
state: GameState = GameState.PREPARING
2021-11-08 14:44:03 +00:00
rounds: list[Round] = field(default_factory=list)
players: dict[str, Player] = field(default_factory=dict)
2021-11-12 13:26:01 +00:00
score_overrides: dict[Player, int] = field(default_factory=dict, init=False)
2021-11-08 14:44:03 +00:00
2021-11-12 13:26:01 +00:00
def __post_init__(self: "Game") -> None:
2021-11-08 14:44:03 +00:00
Game.INSTANCE = self
def register_player(self, name: str, vote_channel_id: int) -> Player:
player = Player(name, vote_channel_id)
self.players[name] = player
return player
def default_first_round(self) -> Round:
return Round(
round=1,
room_a=RoundRoom(room=Room.A,
vote1=RoundVote(player1=self.players['Tora']),
vote2=RoundVote(player1=self.players['Kamui'],
player2=self.players['Philia'])),
room_b=RoundRoom(room=Room.B,
2021-11-08 14:44:03 +00:00
vote1=RoundVote(player1=self.players['Dan']),
vote2=RoundVote(player1=self.players['Ennea'],
player2=self.players['Delphine'])),
room_c=RoundRoom(room=Room.C,
2021-11-08 14:44:03 +00:00
vote1=RoundVote(player1=self.players['Hanabi']),
vote2=RoundVote(player1=self.players['Nona'],
player2=self.players['Oji'])),
)
2021-11-12 13:32:02 +00:00
def save(self, filename: str | None = None) -> None:
2021-11-08 14:44:03 +00:00
"""
Uses pickle to save the current state of the game.
"""
2021-11-12 13:32:02 +00:00
if filename is None:
filename = Path(__file__).parent.parent / 'game.save'
2021-11-08 14:44:03 +00:00
with open(filename, 'wb') as f:
pickle.dump(self, f)
@classmethod
2021-11-12 13:32:02 +00:00
def load(cls, filename: str | None = None) -> "Game | None":
2021-11-08 14:44:03 +00:00
"""
Reload the game from a saved file.
"""
2021-11-12 13:32:02 +00:00
if filename is None:
filename = Path(__file__).parent.parent / 'game.save'
2021-11-08 14:44:03 +00:00
try:
with open(filename, 'rb') as f:
game = pickle.load(f)
Game.INSTANCE = game
return game
except FileNotFoundError:
return None