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, List 

8import logging 

9import random 

10 

11# -- third party -- 

12# -- own -- 

13from game.base import BootstrapAction, GameEnded, GameItem, InputTransaction, InterruptActionFlow 

14from game.base import Player, get_seed_for 

15from thb.actions import DistributeCards, MigrateCardsTransaction, PlayerDeath, PlayerTurn 

16from thb.actions import RevealRole, migrate_cards 

17from thb.cards.base import Deck 

18from thb.characters.base import Character 

19from thb.common import CharChoice, PlayerRole, build_choices, roll 

20from thb.inputlets import ChooseGirlInputlet, ChooseOptionInputlet, SortCharacterInputlet 

21from thb.mode import THBEventHandler, THBattle 

22from utils.misc import BatchList 

23 

24 

25# -- code -- 

26log = logging.getLogger('THBattle') 

27 

28 

29class DeathHandler(THBEventHandler): 

30 interested = ['action_after', 'action_apply'] 

31 game: 'THBattleFaith' 

32 

33 def handle(self, evt_type: str, act: PlayerDeath) -> PlayerDeath: 

34 if evt_type == 'action_apply' and isinstance(act, PlayerDeath): 

35 g = self.game 

36 

37 tgt = act.target 

38 role = g.roles[tgt.player].get() 

39 pool = g.pool[role] 

40 if len(pool) <= 1: 

41 raise GameEnded(g.forces[g.get_opponent_role(role)]) 

42 

43 elif evt_type == 'action_after' and isinstance(act, PlayerDeath): 

44 g = self.game 

45 

46 tgt = act.target 

47 role = g.roles[tgt.player].get() 

48 pool = g.pool[role] 

49 

50 mapping = {tgt.player: pool} 

51 with InputTransaction('ChooseGirl', [tgt.player], mapping=mapping) as trans: 

52 c = g.user_input([tgt.player], ChooseGirlInputlet(g, mapping), timeout=30, trans=trans) 

53 c = c or next(_c for _c in pool if not _c.chosen) 53 ↛ exitline 53 didn't finish the generator expression on line 53

54 c.chosen = tgt 

55 pool.remove(c) 

56 trans.notify('girl_chosen', (tgt.player, c)) 

57 

58 tgt = g.switch_character(tgt, c) 

59 g.process_action(DistributeCards(tgt, 4)) 

60 

61 if g.user_input([tgt], ChooseOptionInputlet(self, (False, True))): 

62 g.process_action(RedrawCards(tgt, tgt)) 

63 

64 return act 

65 

66 

67class RedrawCards(DistributeCards): 

68 def apply_action(self): 

69 tgt = self.target 

70 g = self.game 

71 

72 with MigrateCardsTransaction(self) as trans: 

73 g.players.reveal(list(tgt.cards)) 

74 migrate_cards(tgt.cards, g.deck.droppedcards, trans=trans) 

75 cards = g.deck.getcards(4) 

76 tgt.reveal(cards) 

77 migrate_cards(cards, tgt.cards, trans=trans) 

78 

79 return True 

80 

81 

82class THBFaithRole(Enum): 

83 HIDDEN = 0 

84 HAKUREI = 1 

85 MORIYA = 2 

86 

87 

88class THBattleFaithBootstrap(BootstrapAction): 

89 game: 'THBattleFaith' 

90 

91 def __init__(self, params: Dict[str, Any], 

92 items: Dict[Player, List[GameItem]], 

93 players: BatchList[Player]): 

94 self.source = self.target = None 

95 self.params = params 

96 self.items = items 

97 self.players = players 

98 

99 def apply_action(self) -> bool: 

100 g = self.game 

101 params = self.params 

102 pl = self.players 

103 

104 g.deck = Deck(g) 

105 g.roles = {} 

106 

107 H, M = THBFaithRole.HAKUREI, THBFaithRole.MORIYA 

108 if params['random_seat']: 108 ↛ 122line 108 didn't jump to line 122, because the condition on line 108 was never false

109 # reseat 

110 orig_pl = BatchList(pl) 

111 seed = get_seed_for(g, pl) 

112 random.Random(seed).shuffle(pl) 

113 g.emit_event('reseat', (orig_pl, pl)) 

114 

115 L = [[H, H, M, M, H, M], [H, M, H, M, H, M]] 

116 rnd = random.Random(get_seed_for(g, pl)) 

117 L = rnd.choice(L) * 2 

118 s = rnd.randrange(0, 6) 

119 rl = L[s:s+6] 

120 del L, s, rnd 

121 else: 

122 rl = [H, M, H, M, H, M] 

123 

124 for p, role in zip(pl, rl): 

125 g.roles[p] = PlayerRole(THBFaithRole) 

126 g.roles[p].set(role) 

127 g.process_action(RevealRole(g.roles[p], pl)) 

128 

129 g.forces = { 

130 H: BatchList(), 

131 M: BatchList(), 

132 } 

133 g.pool = { 

134 H: BatchList(), 

135 M: BatchList(), 

136 } 

137 

138 for p in pl: 

139 g.forces[g.roles[p].get()].append(p) 

140 

141 roll_rst = roll(g, pl, self.items) 

142 

143 # choose girls --> 

144 from . import characters 

145 chars = characters.get_characters('common', 'faith') 

146 

147 choices, _ = build_choices( 

148 g, pl, self.items, chars, 

149 spec={p: {'num': 4, 'akaris': 1} for p in pl} 

150 ) 

151 

152 rst = g.user_input(pl, SortCharacterInputlet(g, choices, 2), timeout=30, type='all') 

153 

154 g.players = BatchList([Character(p) for p in pl]) 

155 first: Character 

156 

157 for i, ch in enumerate(g.players): 

158 p = ch.player 

159 a, b = [choices[p][i] for i in rst[p][:2]] 

160 

161 ch = g.switch_character(ch, a) 

162 

163 if p is roll_rst[0]: 

164 first = ch 

165 first_index = i 

166 

167 g.players[i] = (ch) 

168 

169 b.chosen = None 

170 g.forces[g.roles[p].get()].reveal(b) 

171 g.pool[g.roles[p].get()].append(b) 

172 

173 order = BatchList(range(len(pl))).rotate_to(first_index) 

174 g.emit_event('game_begin', g) 

175 

176 for ch in g.players: 

177 g.process_action(DistributeCards(ch, amount=4)) 

178 

179 reordered = g.players.rotate_to(first) 

180 rst = g.user_input(reordered[1:], ChooseOptionInputlet(DeathHandler(g), (False, True)), type='all') 

181 

182 for p in reordered[1:]: 

183 rst.get(p) and g.process_action(RedrawCards(p, p)) 

184 

185 for i, idx in enumerate(cycle(order)): 185 ↛ 195line 185 didn't jump to line 195, because the loop on line 185 didn't complete

186 if i >= 6000: break 186 ↛ 195line 186 didn't jump to line 195, because the break on line 186 wasn't executed

187 ch = g.players[idx] 

188 if ch.dead: continue 188 ↛ 185line 188 didn't jump to line 185, because the continue on line 188 wasn't executed

189 

190 try: 

191 g.process_action(PlayerTurn(ch)) 

192 except InterruptActionFlow: 

193 pass 

194 

195 return True 

196 

197 

198class THBattleFaith(THBattle): 

199 n_persons = 6 

200 game_ehs = [DeathHandler] 

201 bootstrap = THBattleFaithBootstrap 

202 params_def = { 

203 'random_seat': (True, False), 

204 } 

205 

206 forces: Dict[THBFaithRole, BatchList[Player]] 

207 pool: Dict[THBFaithRole, List[CharChoice]] 

208 

209 def can_leave(g: THBattleFaith, p: Any): 

210 return False 

211 

212 def switch_character(g, old: Character, choice: CharChoice) -> Character: 

213 p = old.player 

214 choice.akari = False 

215 

216 g.players.player.reveal(choice) 

217 cls = choice.char_cls 

218 

219 assert cls 

220 

221 log.info('>> NewCharacter: %s %s', g.roles[p].get().name, cls.__name__) 

222 

223 new = cls(p) 

224 g.players.find_replace(lambda ch: ch.player is p, new) 

225 g.refresh_dispatcher() 

226 

227 g.emit_event('switch_character', (old, new)) 

228 

229 return new 

230 

231 def get_opponent_role(g, r: THBFaithRole) -> THBFaithRole: 

232 if r == THBFaithRole.MORIYA: 

233 return THBFaithRole.HAKUREI 

234 elif r == THBFaithRole.HAKUREI: 234 ↛ 237line 234 didn't jump to line 237, because the condition on line 234 was never false

235 return THBFaithRole.MORIYA 

236 else: 

237 assert False, f'WTF: {r}'