# Coverage for thb/thb2v2.py : 19%

## 187 statements 50 run 137 missing 0 excluded 0 partial

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

2 from __future__ import annotations

3

4# -- stdlib --

5 from enum import Enum

6 from itertools import cycle

7 from typing import Any, Dict, List, Set, Type

8 import logging

9 import random

10

11# -- third party --

12# -- own --

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

14 from game.base import Player, get_seed_for

15 from thb.actions import DeadDropCards, DistributeCards, DrawCardStage, DrawCards

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

17 from thb.actions import migrate_cards

18 from thb.cards.base import Deck

19 from thb.characters.base import Character

20 from thb.common import CharChoice, PlayerRole, roll

21 from thb.inputlets import ChooseGirlInputlet, ChooseOptionInputlet

22 from thb.mode import THBEventHandler, THBattle

23 from utils.misc import BatchList, partition

24 import settings

25

26

27# -- code --

28 log = logging.getLogger('THBattle2v2')

29

30

31 class 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

56 class 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

70 class 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

99 class 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

117 class THB2v2Role(Enum):

118 HIDDEN = 0

119 HAKUREI = 1

120 MORIYA = 2

121

122

123 class 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

272 class 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