Hide keyboard shortcuts

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 

3 

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 

11 

12# -- third party -- 

13from mypy_extensions import TypedDict 

14 

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 

21 

22# -- typing -- 

23if TYPE_CHECKING: 

24 from thb.characters.base import Character # noqa: F401 

25 

26 

27# -- code -- 

28log = logging.getLogger('thb.common') 

29 

30 

31class CharChoice(GameViralContext): 

32 chosen: Any = None 

33 char_cls: Optional[Type[Character]] 

34 akari: bool = False 

35 

36 def __init__(self, char_cls=None, akari=False) -> None: 

37 self.set(char_cls, akari) 

38 

39 def dump(self): 

40 assert self.char_cls 

41 return self.char_cls.__name__ if not self.akari else 'Akari' 

42 

43 def sync(self, data) -> None: 

44 from thb.characters.base import Character 

45 self.set(Character.classes[data], False) 

46 

47 def conceal(self) -> None: 

48 self.char_cls = None 

49 self.chosen = None 

50 self.akari = False 

51 

52 def set(self, char_cls, akari=False) -> None: 

53 self.char_cls = char_cls 

54 g = self.game 

55 

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 

61 

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 ) 

67 

68 

69T = TypeVar('T', bound=Enum) 

70 

71 

72class PlayerRole(Generic[T], GameViralContext): 

73 _role: T 

74 

75 def __init__(self, typ: Type[T]): 

76 self._typ = typ 

77 self._role = typ(0) 

78 

79 def __str__(self) -> str: 

80 return self._role.name 

81 

82 def __eq__(self, other: object) -> bool: 

83 if not isinstance(other, self._typ): 

84 return False 

85 

86 return self._role == other 

87 

88 def dump(self) -> Any: 

89 return self._role.value 

90 

91 def sync(self, data) -> None: 

92 self._role = self._typ(data) 

93 

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 ''' 

100 

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) 

105 

106 def get(self) -> T: 

107 return self._role 

108 

109 

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) 

119 

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 

124 

125 

126class BuildChoicesSpec(TypedDict): 

127 num: int 

128 akaris: int 

129 

130 

131class VirtualPlayer(Player): 

132 

133 def __init__(self, players: Sequence[Player]): 

134 self.players = players 

135 super().__init__() 

136 

137 def reveal(self, obj: Any): 

138 for p in self.players: 

139 p.reveal(obj) 

140 

141 

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 

151 

152 

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]]: 

159 

160 from thb.item import ImperialChoice 

161 

162 # ----- testing ----- 

163 from thb.characters.base import Character 

164 

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) 

168 

169 if g.is_server_side(): 

170 candidates = list(candidates) 

171 g.random.shuffle(candidates) 

172 else: 

173 candidates = [None] * len(candidates) 

174 

175 assert sum(s['num'] for p, s in spec.items()) <= len(candidates) + len(testing), 'Insufficient choices' 

176 

177 result: Dict[Player, List[CharChoice]] = {p: [] for p in spec} 

178 

179 players_for_testing = list(spec) 

180 

181 candidates = list(candidates) 

182 seed = get_seed_for(g, players) 

183 shuffler = random.Random(seed) 

184 shuffler.shuffle(players_for_testing) 

185 

186 for e, cls in zip(cycle(players_for_testing), testing): 

187 result[e].append(CharChoice(cls)) 

188 

189 # ----- imperial (force chosen by ImperialChoice) ----- 

190 imperial = ImperialChoice.get_chosen(items, players) 

191 imperial = {p: CharChoice(cls) for p, cls in imperial.items()} 

192 

193 for p, c in imperial.items(): 

194 result[p].append(c) 

195 

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())) 

200 

201 # ----- akaris ----- 

202 if g.is_server_side(): 

203 rest = candidates 

204 else: 

205 rest = [None] * len(candidates) 

206 

207 g.random.shuffle(rest) 

208 

209 for p, s in spec.items(): 

210 for i in range(-s['akaris'], 0): 

211 result[p][i].set(rest.pop(), True) 

212 

213 # ----- compose final result, reveal, and return ----- 

214 for p, l in result.items(): 

215 p.reveal(l) 

216 

217 return result, imperial