Coverage for thb/common.py : 39%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
2from __future__ import annotations
4# -- stdlib --
5from enum import Enum
6from itertools import cycle
7from typing import Any, Dict, Generic, Iterable, List, Optional, Sequence, TYPE_CHECKING, Tuple
8from typing import Type, TypeVar
9import logging
10import random
12# -- third party --
13from mypy_extensions import TypedDict
15# -- own --
16from game.base import GameViralContext, Player, get_seed_for, sync_primitive
17from thb.item import GameItem
18from thb.mode import THBattle
19from utils.misc import BatchList, partition
20import settings
22# -- typing --
23if TYPE_CHECKING:
24 from thb.characters.base import Character # noqa: F401
27# -- code --
28log = logging.getLogger('thb.common')
31class CharChoice(GameViralContext):
32 chosen: Any = None
33 char_cls: Optional[Type[Character]]
34 akari: bool = False
36 def __init__(self, char_cls=None, akari=False) -> None:
37 self.set(char_cls, akari)
39 def dump(self):
40 assert self.char_cls
41 return self.char_cls.__name__ if not self.akari else 'Akari'
43 def sync(self, data) -> None:
44 from thb.characters.base import Character
45 self.set(Character.classes[data], False)
47 def conceal(self) -> None:
48 self.char_cls = None
49 self.chosen = None
50 self.akari = False
52 def set(self, char_cls, akari=False) -> None:
53 self.char_cls = char_cls
54 g = self.game
56 if akari:
57 self.akari = True
58 if g.is_client_side():
59 from thb import characters
60 self.char_cls = characters.akari.Akari
62 def __repr__(self):
63 return '<Choice: {}{}>'.format(
64 'None' if not self.char_cls else self.char_cls.__name__,
65 '[Akari]' if self.akari else '',
66 )
69T = TypeVar('T', bound=Enum)
72class PlayerRole(Generic[T], GameViralContext):
73 _role: T
75 def __init__(self, typ: Type[T]):
76 self._typ = typ
77 self._role = typ(0)
79 def __str__(self) -> str:
80 return self._role.name
82 def __eq__(self, other: object) -> bool:
83 if not isinstance(other, self._typ):
84 return False
86 return self._role == other
88 def dump(self) -> Any:
89 return self._role.value
91 def sync(self, data) -> None:
92 self._role = self._typ(data)
94 '''
95 def is_type(self, t: Enum) -> bool:
96 g = self.game
97 pl = g.players
98 return sync_primitive(self.identity == t, pl)
99 '''
101 def set(self, t: T) -> None:
102 assert isinstance(t, self._typ)
103 if self.game.is_server_side(): 103 ↛ exitline 103 didn't return from function 'set', because the condition on line 103 was never false
104 self._role = self._typ(t)
106 def get(self) -> T:
107 return self._role
110def roll(g: THBattle, pl: BatchList[Player], items: Dict[Player, List[GameItem]]) -> BatchList[Player]:
111 from thb.item import European
112 roll = list(range(len(pl)))
113 g.random.shuffle(roll)
114 eu = European.get_european(g, items)
115 if eu:
116 i = pl.index(eu)
117 roll.remove(i)
118 roll.insert(0, i)
120 roll = sync_primitive(roll, pl)
121 roll = BatchList(pl[i] for i in roll)
122 g.emit_event('game_roll', roll)
123 return roll
126class BuildChoicesSpec(TypedDict):
127 num: int
128 akaris: int
131class VirtualPlayer(Player):
133 def __init__(self, players: Sequence[Player]):
134 self.players = players
135 super().__init__()
137 def reveal(self, obj: Any):
138 for p in self.players:
139 p.reveal(obj)
142def build_choices_shared(g: THBattle,
143 players: BatchList[Player],
144 items: Dict[Player, List[GameItem]],
145 candidates: List[Type[Character]],
146 spec: BuildChoicesSpec,
147 ) -> Tuple[List[CharChoice], Dict[Player, CharChoice]]:
148 p = VirtualPlayer(players)
149 choices, imperial = build_choices(g, players, items, candidates, {p: spec})
150 return choices[p], imperial
153def build_choices(g: THBattle,
154 players: BatchList[Player],
155 items: Dict[Player, List[GameItem]],
156 candidates: List[Type[Character]],
157 spec: Dict[Player, BuildChoicesSpec],
158 ) -> Tuple[Dict[Player, List[CharChoice]], Dict[Player, CharChoice]]:
160 from thb.item import ImperialChoice
162 # ----- testing -----
163 from thb.characters.base import Character
165 testing_lst: Iterable[str] = settings.TESTING_CHARACTERS
166 testing = list(Character.classes[i] for i in testing_lst)
167 candidates, _ = partition(lambda c: c not in testing, candidates)
169 if g.is_server_side():
170 candidates = list(candidates)
171 g.random.shuffle(candidates)
172 else:
173 candidates = [None] * len(candidates)
175 assert sum(s['num'] for p, s in spec.items()) <= len(candidates) + len(testing), 'Insufficient choices'
177 result: Dict[Player, List[CharChoice]] = {p: [] for p in spec}
179 players_for_testing = list(spec)
181 candidates = list(candidates)
182 seed = get_seed_for(g, players)
183 shuffler = random.Random(seed)
184 shuffler.shuffle(players_for_testing)
186 for e, cls in zip(cycle(players_for_testing), testing):
187 result[e].append(CharChoice(cls))
189 # ----- imperial (force chosen by ImperialChoice) -----
190 imperial = ImperialChoice.get_chosen(items, players)
191 imperial = {p: CharChoice(cls) for p, cls in imperial.items()}
193 for p, c in imperial.items():
194 result[p].append(c)
196 # ----- normal -----
197 for p, s in spec.items():
198 for _ in range(len(result[p]), s['num']):
199 result[p].append(CharChoice(candidates.pop()))
201 # ----- akaris -----
202 if g.is_server_side():
203 rest = candidates
204 else:
205 rest = [None] * len(candidates)
207 g.random.shuffle(rest)
209 for p, s in spec.items():
210 for i in range(-s['akaris'], 0):
211 result[p][i].set(rest.pop(), True)
213 # ----- compose final result, reveal, and return -----
214 for p, l in result.items():
215 p.reveal(l)
217 return result, imperial