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, Set, Type 

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 DeadDropCards, DistributeCards, DrawCardStage, DrawCards 

16from thb.actions import MigrateCardsTransaction, PlayerDeath, PlayerTurn, RevealRole, UserAction 

17from thb.actions import migrate_cards 

18from thb.cards.base import Deck 

19from thb.characters.base import Character 

20from thb.common import CharChoice, PlayerRole, roll 

21from thb.inputlets import ChooseGirlInputlet, ChooseOptionInputlet 

22from thb.mode import THBEventHandler, THBattle 

23from utils.misc import BatchList, partition 

24import settings 

25 

26 

27# -- code -- 

28log = logging.getLogger('THBattle2v2') 

29 

30 

31class DeathHandler(THBEventHandler): 

32 interested = ['action_apply'] 

33 

34 def handle(self, evt_type, act): 

35 if evt_type != 'action_apply': return act 35 ↛ exitline 35 didn't return from function 'handle', because the return on line 35 wasn't executed

36 if not isinstance(act, PlayerDeath): return act 

37 

38 g = self.game 

39 tgt = act.target 

40 

41 tgt = act.target 

42 dead = lambda ch: ch.dead or g.is_dropped(ch.player) or ch is tgt 

43 

44 # see if game ended 

45 force1, force2 = list(g.forces.values()) 

46 if all(dead(ch) for ch in force1): 

47 raise GameEnded(force2.player) 

48 

49 if all(dead(ch) for ch in force2): 

50 raise GameEnded(force1.player) 

51 

52 return act 

53 

54 

55class HeritageAction(UserAction): 

56 def apply_action(self): 

57 src, tgt = self.source, self.target 

58 lists = [tgt.cards, tgt.showncards, tgt.equips] 

59 with MigrateCardsTransaction(self) as trans: 

60 for cl in lists: 

61 if not cl: continue 

62 cl = list(cl) 

63 src.reveal(cl) 

64 migrate_cards(cl, src.cards, unwrap=True, trans=trans) 

65 

66 return True 

67 

68 

69class HeritageHandler(THBEventHandler): 

70 interested = ['action_before'] 

71 execute_after = ['DeathHandler', 'SadistHandler'] 

72 

73 def handle(self, evt_type, act): 

74 if evt_type != 'action_before': return act 74 ↛ exitline 74 didn't return from function 'handle', because the return on line 74 wasn't executed

75 if not isinstance(act, DeadDropCards): return act 

76 

77 g = self.game 

78 tgt = act.target 

79 for f in g.forces.values(): 79 ↛ 83line 79 didn't jump to line 83, because the loop on line 79 didn't complete

80 if tgt in f: 

81 break 

82 else: 

83 assert False, 'WTF?!' 

84 

85 other = BatchList(f).exclude(tgt)[0] 

86 if other.dead: return act 86 ↛ exitline 86 didn't return from function 'handle', because the return on line 86 wasn't executed

87 

88 if g.user_input([other], ChooseOptionInputlet(self, ('inherit', 'draw'))) == 'inherit': 

89 g.process_action(HeritageAction(other, tgt)) 

90 

91 else: 

92 g.process_action(DrawCards(other, 2)) 

93 

94 return act 

95 

96 

97class ExtraCardHandler(THBEventHandler): 

98 interested = ['action_before'] 

99 

100 def handle(self, evt_type, act): 

101 if evt_type != 'action_before': 101 ↛ 102line 101 didn't jump to line 102, because the condition on line 101 was never true

102 return act 

103 

104 if not isinstance(act, DrawCardStage): 

105 return act 

106 

107 g = self.game 

108 if g.draw_extra: 108 ↛ 109line 108 didn't jump to line 109, because the condition on line 108 was never true

109 act.amount += 1 

110 

111 return act 

112 

113 

114class THB2v2Role(Enum): 

115 HIDDEN = 0 

116 HAKUREI = 1 

117 MORIYA = 2 

118 

119 

120class THBattle2v2Bootstrap(BootstrapAction): 

121 game: 'THBattle2v2' 

122 

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

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

125 players: BatchList[Player]): 

126 self.source = self.target = None 

127 self.params = params 

128 self.items = items 

129 self.players = players 

130 

131 def apply_action(self) -> bool: 

132 g = self.game 

133 params = self.params 

134 items = self.items 

135 

136 pl = self.players 

137 

138 g.deck = Deck(g) 

139 g.roles = {} 

140 

141 if params['random_force']: 141 ↛ 145line 141 didn't jump to line 145, because the condition on line 141 was never false

142 seed = get_seed_for(g, pl) 

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

144 

145 g.draw_extra = params['draw_extra_card'] 

146 

147 H, M = THB2v2Role.HAKUREI, THB2v2Role.MORIYA 

148 g.forces = {H: BatchList(), M: BatchList()} 

149 

150 for p, id in zip(pl, [H, H, M, M]): 

151 g.roles[p] = r = PlayerRole(THB2v2Role) 

152 g.roles[p].set(id) 

153 g.process_action(RevealRole(r, pl)) 

154 

155 roll_rst = roll(g, pl, items) 

156 ''' 

157 winner = g.forces[roll_rst[0].identity.value] 

158 f1, f2 = partition(lambda ch: g.forces[ch.identity.value] is winner, roll_rst) 

159 final_order = [f1[0], f2[0], f2[1], f1[1]] 

160 ''' 

161 g.emit_event('reseat', (pl, roll_rst)) 

162 pl = roll_rst 

163 

164 # ban / choose girls --> 

165 from . import characters 

166 chars = characters.get_characters('common', '2v2') 

167 

168 seed = get_seed_for(g, pl) 

169 random.Random(seed).shuffle(chars) 

170 

171 testing: List[str] = list(settings.TESTING_CHARACTERS) 

172 testing, chars = partition(lambda c: c.__name__ in testing, chars) 

173 chars.extend(testing) 

174 

175 chars = chars[-20:] 

176 choices = [CharChoice(cls) for cls in chars] 

177 

178 banned: Set[Type[Character]] = set() 

179 mapping = {p: choices for p in pl} 

180 with InputTransaction('BanGirl', pl, mapping=mapping) as trans: 

181 for p in pl: 

182 c: CharChoice 

183 c = g.user_input([p], ChooseGirlInputlet(self, mapping), timeout=30, trans=trans) 

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

185 c.chosen = p 

186 cls = c.char_cls 

187 assert cls 

188 banned.add(cls) 

189 trans.notify('girl_chosen', (p, c)) 

190 

191 assert len(banned) == 4 

192 

193 chars = [_c for _c in chars if _c not in banned] 

194 

195 g.random.shuffle(chars) 

196 

197 if g.is_client_side(): 

198 chars = [None] * len(chars) 

199 

200 mapping: Dict[Player, List[CharChoice]] = {} 

201 

202 for p in pl: 

203 mapping[p] = [CharChoice(cls) for cls in chars[-4:]] 

204 mapping[p][-1].akari = True 

205 

206 del chars[-4:] 

207 

208 p.reveal(mapping[p]) 

209 

210 g.pause(1) 

211 

212 with InputTransaction('ChooseGirl', pl, mapping=mapping) as trans: 

213 ilet = ChooseGirlInputlet(g, mapping) 

214 

215 @ilet.with_post_process 

216 def process(p, c): 

217 c = c or mapping[p][0] 

218 trans.notify('girl_chosen', (p, c)) 

219 return c 

220 

221 rst = g.user_input(pl, ilet, timeout=30, type='all', trans=trans) 

222 

223 # reveal 

224 g.players = BatchList() 

225 

226 for p in pl: 

227 c = rst[p] 

228 c.akari = False 

229 pl.reveal(c) 

230 assert c.char_cls 

231 ch = c.char_cls(p) 

232 g.players.append(ch) 

233 

234 g.refresh_dispatcher() 

235 

236 for p, ch in zip(pl, g.players): 

237 assert p is ch.player 

238 g.emit_event('switch_character', (p, ch)) 

239 

240 # ------- 

241 for ch in g.players: 

242 log.info( 

243 '>> Player: %s:%s', 

244 ch.__class__.__name__, 

245 g.roles[ch.player].get().name, 

246 ) 

247 # ------- 

248 

249 g.forces = {} 

250 for ch in g.players: 

251 g.forces.setdefault(g.roles[ch.player].get(), BatchList()).append(ch) 

252 

253 g.emit_event('game_begin', g) 

254 

255 for ch in g.players: 

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

257 

258 for i, ch in enumerate(cycle(g.players)): 258 ↛ 266line 258 didn't jump to line 266, because the loop on line 258 didn't complete

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

260 if not ch.dead: 

261 try: 

262 g.process_action(PlayerTurn(ch)) 

263 except InterruptActionFlow: 

264 pass 

265 

266 return True 

267 

268 

269class THBattle2v2(THBattle): 

270 n_persons = 4 

271 game_ehs = [ 

272 DeathHandler, 

273 HeritageHandler, 

274 ExtraCardHandler, 

275 ] 

276 bootstrap = THBattle2v2Bootstrap 

277 params_def = { 

278 'random_force': (True, False), 

279 'draw_extra_card': (False, True), 

280 } 

281 

282 forces: Dict[THB2v2Role, BatchList[Character]] 

283 

284 draw_extra: bool 

285 

286 def can_leave(g, p: Player): 

287 for ch in g.players: 

288 if ch.player is p: 

289 return ch.dead 

290 else: 

291 return False