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 game: THBattle2v2 

33 interested = ['action_apply'] 

34 

35 def handle(self, evt_type, act): 

36 if evt_type != 'action_apply': return act 

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

38 

39 g = self.game 

40 tgt = act.target 

41 

42 tgt = act.target 

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

44 

45 # see if game ended 

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

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

48 raise GameEnded(force2.player) 

49 

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

51 raise GameEnded(force1.player) 

52 

53 return act 

54 

55 

56class HeritageAction(UserAction): 

57 def apply_action(self): 

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

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

60 with MigrateCardsTransaction(self) as trans: 

61 for cl in lists: 

62 if not cl: continue 

63 cards = list(cl) 

64 src.reveal(cards) 

65 migrate_cards(cards, src.cards, unwrap=True, trans=trans) 

66 

67 return True 

68 

69 

70class HeritageHandler(THBEventHandler): 

71 game: THBattle2v2 

72 interested = ['action_before'] 

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

74 

75 def handle(self, evt_type, act): 

76 if evt_type != 'action_before': return act 

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

78 

79 g = self.game 

80 tgt = act.target 

81 for f in g.forces.values(): 

82 if tgt in f: 

83 break 

84 else: 

85 assert False, 'WTF?!' 

86 

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

88 if other.dead: return act 

89 

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

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

92 

93 else: 

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

95 

96 return act 

97 

98 

99class ExtraCardHandler(THBEventHandler): 

100 game: THBattle2v2 

101 interested = ['action_before'] 

102 

103 def handle(self, evt_type, act): 

104 if evt_type != 'action_before': 

105 return act 

106 

107 if not isinstance(act, DrawCardStage): 

108 return act 

109 

110 g = self.game 

111 if g.draw_extra: 

112 act.amount += 1 

113 

114 return act 

115 

116 

117class THB2v2Role(Enum): 

118 HIDDEN = 0 

119 HAKUREI = 1 

120 MORIYA = 2 

121 

122 

123class THBattle2v2Bootstrap(BootstrapAction): 

124 game: 'THBattle2v2' 

125 

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

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

128 players: BatchList[Player]): 

129 self.source = self.target = None 

130 self.params = params 

131 self.items = items 

132 self.players = players 

133 

134 def apply_action(self) -> bool: 

135 g = self.game 

136 params = self.params 

137 items = self.items 

138 

139 pl = self.players 

140 

141 g.deck = Deck(g) 

142 g.roles = {} 

143 

144 if params['random_force']: 

145 seed = get_seed_for(g, pl) 

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

147 

148 g.draw_extra = params['draw_extra_card'] 

149 

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

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

152 

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

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

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

156 g.process_action(RevealRole(p, r, pl)) 

157 

158 roll_rst = roll(g, pl, items) 

159 ''' 

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

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

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

163 ''' 

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

165 pl = roll_rst 

166 

167 # ban / choose girls --> 

168 from . import characters 

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

170 

171 seed = get_seed_for(g, pl) 

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

173 

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

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

176 chars.extend(testing) 

177 

178 chars = chars[-20:] 

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

180 

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

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

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

184 for p in pl: 

185 c: CharChoice 

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

187 c = c or next(_c for _c in choices if not _c.chosen) 

188 c.chosen = p 

189 cls = c.char_cls 

190 assert cls 

191 banned.add(cls) 

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

193 

194 assert len(banned) == 4 

195 

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

197 

198 g.random.shuffle(chars) 

199 

200 if g.is_client_side(): 

201 chars = [None] * len(chars) 

202 

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

204 

205 for p in pl: 

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

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

208 

209 del chars[-4:] 

210 

211 p.reveal(mapping[p]) 

212 

213 g.pause(1) 

214 

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

216 ilet = ChooseGirlInputlet(g, mapping) 

217 

218 @ilet.with_post_process 

219 def process(p, c): 

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

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

222 return c 

223 

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

225 

226 # reveal 

227 g.players = BatchList() 

228 

229 for p in pl: 

230 c = rst[p] 

231 c.akari = False 

232 pl.reveal(c) 

233 assert c.char_cls 

234 ch = c.char_cls(p) 

235 g.players.append(ch) 

236 

237 g.refresh_dispatcher() 

238 

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

240 assert p is ch.player 

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

242 

243 # ------- 

244 for ch in g.players: 

245 log.info( 

246 '>> Player: %s:%s', 

247 ch.__class__.__name__, 

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

249 ) 

250 # ------- 

251 

252 g.forces = {} 

253 for ch in g.players: 

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

255 

256 g.emit_event('game_begin', g) 

257 

258 for ch in g.players: 

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

260 

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

262 if i >= 6000: break 

263 if not ch.dead: 

264 try: 

265 g.process_action(PlayerTurn(ch)) 

266 except InterruptActionFlow: 

267 pass 

268 

269 return True 

270 

271 

272class THBattle2v2(THBattle): 

273 n_persons = 4 

274 game_ehs = [ 

275 DeathHandler, 

276 HeritageHandler, 

277 ExtraCardHandler, 

278 ] 

279 bootstrap = THBattle2v2Bootstrap 

280 params_def = { 

281 'random_force': (True, False), 

282 'draw_extra_card': (False, True), 

283 } 

284 

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

286 

287 draw_extra: bool 

288 

289 def can_leave(g, p: Player): 

290 for ch in g.players: 

291 if ch.player is p: 

292 return ch.dead 

293 else: 

294 return False