# Coverage for thb/thbkof.py : 89%

## 155 statements 142 run 13 missing 0 excluded 8 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 35 ↛ exit line 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 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]): 47 ↛ 48 line 47 didn't jump to line 48, because the condition on line 47 was never true

48 raise GameEnded([pl[1]])

49

50 if g.is_dropped(pl[1]): 50 ↛ 51 line 50 didn't jump to line 51, because the condition on line 50 was never true

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 new = choice.char_cls(p)

87 g.players.replace(old, new)

88 g.refresh_dispatcher()

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

90 return new

91

92

93 class THBKOFRole(Enum):

94 HIDDEN = 0

95 HAKUREI = 1

96 MORIYA = 2

97

98

99 class THBattleKOFBootstrap(BootstrapAction):

100 game: 'THBattleKOF'

101

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

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

104 players: BatchList[Player]):

105 self.source = self.target = None

106 self.params = params

107 self.items = items

108 self.players = players

109

110 def apply_action(self) -> bool:

111 g = self.game

112

113 g.deck = Deck(g, kof_card_definition)

114 pl = self.players

115 A, B = pl

116 g.roles = {

117 A: PlayerRole(THBKOFRole),

118 B: PlayerRole(THBKOFRole),

119 }

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

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

122

123 # choose girls -->

124 from thb.characters import get_characters

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

126

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

128 order = [A, B, B, A, A, B, B, A, A, B]

129

130 choices, imperial_choices = build_choices_shared(

131 g, pl, self.items,

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

133 )

134

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

136

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

138 for p, c in imperial_choices.items(): 138 ↛ 139 line 138 didn't jump to line 139, because the loop on line 138 never started

139 c.chosen = p

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

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

142 order.remove(p)

143

144 for p in order:

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

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

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

148

149 c.chosen = p

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

151

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

153

154 # reveal akaris for themselves

155 for p in [A, B]:

156 for c in g.chosen[p]:

157 c.akari = False

158 p.reveal(c)

159 del c.chosen

160

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

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

163

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

165 ilet = ChooseGirlInputlet(g, g.chosen)

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

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

168

169 def s(p):

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

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

172

173 c.akari = False

174 pl.reveal(c)

175 cls = c.char_cls

176 assert cls

177 ch = cls(p)

178 g.refresh_dispatcher()

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

180

181 return ch

182

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

184

185 for p in pl:

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

187

188 g.refresh_dispatcher()

189 g.emit_event('game_begin', g)

190

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

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

193

194 for ch in g.players:

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

196

197 for i in cycle([0, 1]): 197 ↛ 213 line 197 didn't jump to line 213, because the loop on line 197 didn't complete

198 ch = g.players[i]

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

200 if ch.dead: 200 ↛ 201 line 200 didn't jump to line 201, because the condition on line 200 was never true

201 handler = g.dispatcher.find_by_cls(KOFCharacterSwitchHandler)

202 assert handler, 'WTF?!'

203 handler.do_switch_dead()

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

205

206 assert not ch.dead

207

208 try:

209 g.process_action(PlayerTurn(ch))

210 except InterruptActionFlow:

211 pass

212

213 return True

214

215

216 class THBattleKOF(THBattle):

217 n_persons = 2

218 game_ehs = [

219 DeathHandler,

220 KOFCharacterSwitchHandler,

221 ]

222 bootstrap = THBattleKOFBootstrap

223

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

225

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

227 a, b = g.players

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

229

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

231 return False