# Coverage for thb/thbkof.py : 22%

## 156 statements 42 run 114 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

8 import logging

9

10# -- third party --

11# -- own --

12 from game.base import BootstrapAction, GameEnded, InputTransaction, InterruptActionFlow, Player

13 from game.base import list_shuffle

14 from thb.actions import DistributeCards, PlayerDeath, PlayerTurn, RevealRole

15 from thb.cards.base import Deck

16 from thb.cards.definition import kof_card_definition

17 from thb.characters.base import Character

18 from thb.common import CharChoice, PlayerRole, build_choices_shared, roll

19 from thb.inputlets import ChooseGirlInputlet

20 from thb.item import GameItem

21 from thb.mode import THBEventHandler, THBattle

22 from utils.misc import BatchList

23

24

25# -- code --

26 log = logging.getLogger('THBattleKOF')

27

28

29 class DeathHandler(THBEventHandler):

30 interested = ['action_apply']

31

32 game: 'THBattleKOF'

33

34 def handle(self, evt_type: str, act: PlayerDeath):

35 if evt_type != 'action_apply': return act

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

37 tgt = act.target

38 p = tgt.player

39

40 g = self.game

41 pl = g.players.player

42

43 if len(g.chosen[p]) <= 2: # 5(total chosen) - 3(available characters) = 2

44 pl.remove(p)

45 raise GameEnded(pl)

46

47 if g.is_dropped(pl[0]):

48 raise GameEnded([pl[1]])

49

50 if g.is_dropped(pl[1]):

51 raise GameEnded([pl[0]])

52

53 return act

54

55

56 class KOFCharacterSwitchHandler(THBEventHandler):

57 interested = ['action_after', 'action_before', 'action_stage_action']

58 game: THBattleKOF

59

60 def handle(self, evt_type, act):

61 cond = evt_type in ('action_before', 'action_after')

62 cond = cond and isinstance(act, PlayerTurn)

63 cond = cond or evt_type == 'action_stage_action'

64 cond and self.do_switch_dead()

65 return act

66

67 def do_switch_dead(self):

68 g = self.game

69

70 # for p in [p for p in g.players if p.dead and p.chosen]:

71 for ch in [ch for ch in g.players if ch.dead]:

72 new = self.switch(ch)

73 g.process_action(DistributeCards(new, 4))

74 g.emit_event('character_debut', (ch, new))

75

76 def switch(self, old: Character):

77 g = self.game

78 p = old.player

79 mapping = {p: g.chosen[p]}

80

81 with InputTransaction('ChooseGirl', [p], mapping=mapping) as trans:

82 choice = g.user_input([p], ChooseGirlInputlet(g, mapping), timeout=30, trans=trans)

83 choice = choice or g.chosen[p][0]

84

85 g.players.reveal(choice)

86 assert choice.char_cls

87 new = choice.char_cls(p)

88 g.players.replace(old, new)

89 g.refresh_dispatcher()

90 g.chosen[p].remove(choice)

91 return new

92

93

94 class THBKOFRole(Enum):

95 HIDDEN = 0

96 HAKUREI = 1

97 MORIYA = 2

98

99

100 class THBattleKOFBootstrap(BootstrapAction):

101 game: 'THBattleKOF'

102

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

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

105 players: BatchList[Player]):

106 self.source = self.target = None

107 self.params = params

108 self.items = items

109 self.players = players

110

111 def apply_action(self) -> bool:

112 g = self.game

113

114 g.deck = Deck(g, kof_card_definition)

115 pl = self.players

116 A, B = pl

117 g.roles = {

118 A: PlayerRole(THBKOFRole),

119 B: PlayerRole(THBKOFRole),

120 }

121 g.roles[A].set(THBKOFRole.HAKUREI)

122 g.roles[B].set(THBKOFRole.MORIYA)

123

124 # choose girls -->

125 from thb.characters import get_characters

126 chars = get_characters('common', 'kof')

127

128 A, B = roll(g, pl, self.items)

129 order = [A, B, B, A, A, B, B, A, A, B]

130

131 choices, imperial_choices = build_choices_shared(

132 g, pl, self.items,

133 candidates=chars, spec={'num': 10, 'akaris': 4},

134 )

135

136 g.chosen = {A: [], B: []}

137

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

139 for p, c in imperial_choices.items():

140 c.chosen = p

141 g.chosen[p].append(c)

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

143 order.remove(p)

144

145 for p in order:

146 c = g.user_input([p], ChooseGirlInputlet(g, {p: choices}), 10, 'single', trans)

147 # c = c or next(choices[p], lambda c: not c.chosen, None)

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

149

150 c.chosen = p

151 g.chosen[p].append(c)

152

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

154

155 # reveal akaris for themselves

156 for p in [A, B]:

157 for c in g.chosen[p]:

158 c.akari = False

159 p.reveal(c)

160 del c.chosen

161

162 list_shuffle(g, g.chosen[A], A)

163 list_shuffle(g, g.chosen[B], B)

164

165 with InputTransaction('ChooseGirl', pl, mapping=g.chosen) as trans:

166 ilet = ChooseGirlInputlet(g, g.chosen)

167 ilet.with_post_process(lambda p, rst: trans.notify('girl_chosen', (p, rst)) or rst)

168 rst = g.user_input([A, B], ilet, type='all', trans=trans)

169

170 def s(p):

171 c = rst[p] or g.chosen[p][0]

172 g.chosen[p].remove(c)

173

174 c.akari = False

175 pl.reveal(c)

176 cls = c.char_cls

177 assert cls

178 ch = cls(p)

179 g.refresh_dispatcher()

180 g.emit_event('switch_character', (None, ch))

181

182 return ch

183

184 cA, cB = g.players = BatchList([s(A), s(B)])

185

186 for p in pl:

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

188

189 g.refresh_dispatcher()

190 g.emit_event('game_begin', g)

191

192 g.process_action(DistributeCards(cA, amount=4))

193 g.process_action(DistributeCards(cB, amount=3))

194

195 for ch in g.players:

196 g.emit_event('character_debut', (None, ch))

197

198 for i in cycle([0, 1]):

199 ch = g.players[i]

200 if i >= 6000: break

201 if ch.dead:

202 handler = g.dispatcher.find_by_cls(KOFCharacterSwitchHandler)

203 assert handler, 'WTF?!'

204 handler.do_switch_dead()

205 ch = g.players[i] # player changed

206

207 assert not ch.dead

208

209 try:

210 g.process_action(PlayerTurn(ch))

211 except InterruptActionFlow:

212 pass

213

214 return True

215

216

217 class THBattleKOF(THBattle):

218 n_persons = 2

219 game_ehs = [

220 DeathHandler,

221 KOFCharacterSwitchHandler,

222 ]

223 bootstrap = THBattleKOFBootstrap

224

225 chosen: Dict[Player, List[CharChoice]]

226

227 def get_opponent(g: THBattleKOF, ch: Character):

228 a, b = g.players

229 return {a: b, b: a}[ch]

230

231 def can_leave(g: THBattle, p: Player) -> bool:

232 return False