# Coverage for thb/characters/kanako.py : 35%

## 154 statements 65 run 89 missing 2 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 typing import Any, TYPE_CHECKING, Type

6

7# -- third party --

8# -- own --

9 from thb.actions import Damage, DrawCardStage, DrawCards, DropCards, FinalizeStage, ForEach

10 from thb.actions import LaunchCard, ShowCards, UserAction, migrate_cards, random_choose_card, ttags

11 from thb.actions import user_choose_cards, user_choose_players

12 from thb.cards.base import Card, Skill, VirtualCard

13 from thb.cards.classes import AttackCard, DuelCard, TreatAs, t_None

14 from thb.characters.base import Character, register_character_to

15 from thb.inputlets import ChooseOptionInputlet, ChoosePeerCardInputlet

16 from thb.mode import THBEventHandler

17

18# -- typing --

19 if TYPE_CHECKING:

20 from thb.thbkof import THBattleKOF # noqa: F401

21

22

23# -- code --

24 class KanakoFaithCheers(UserAction):

25 def apply_action(self):

26 return self.game.process_action(DrawCards(self.target, 1))

27

28

29 class KanakoFaithAttack(TreatAs, VirtualCard):

30 treat_as = AttackCard

31

32

33 class KanakoFaithDuel(TreatAs, VirtualCard):

34 treat_as = DuelCard

35

36

37 class KanakoFaithCounteract(UserAction):

38 def apply_action(self):

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

40 g = self.game

41 src.dead or tgt.dead or g.process_action(KanakoFaithCounteractPart1(src, tgt))

42 src.dead or tgt.dead or g.process_action(KanakoFaithCounteractPart2(src, tgt))

43 return True

44

45

46 class KanakoFaithCounteractPart1(UserAction):

47

48 def apply_action(self):

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

50 g = self.game

51

52 catnames = ('cards', 'showncards', 'equips')

53 cats = [getattr(tgt, i) for i in catnames]

54

55 card = g.user_input([src], ChoosePeerCardInputlet(self, tgt, catnames))

56 card = card or random_choose_card(g, cats)

57

58 assert card

59 self.card = card

60

61 g.players.reveal(card)

62 g.process_action(DropCards(src, tgt, [card]))

63 return True

64

65

66 class KanakoFaithCounteractPart2(UserAction):

67 def apply_action(self):

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

69 g = self.game

70

71 choice = g.user_input([tgt], ChooseOptionInputlet(self, ('duel', 'attack')))

72

73 cls: Type[Any]

74 if choice == 'duel':

75 cls = KanakoFaithDuel

76 elif choice == 'attack':

77 cls = KanakoFaithAttack

78 else:

79 cls = KanakoFaithAttack

80

81 g.process_action(LaunchCard(tgt, [src], cls(tgt), bypass_check=True))

82

83 return True

84

85

86 class KanakoFaithEffect(UserAction):

87 card_usage = 'drop'

88

89 def apply_action(self):

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

91 g = self.game

92

93 has_card = src.cards or src.showncards or src.equips

94

95 if has_card and g.user_input([tgt], ChooseOptionInputlet(self, ('drop', 'draw'))) == 'drop':

96 g.process_action(KanakoFaithCounteract(tgt, src))

97 else:

98 g.process_action(KanakoFaithCheers(tgt, src))

99

100 return True

101

102

103 class KanakoFaithAction(ForEach):

104 action_cls = KanakoFaithEffect

105

106 def prepare(self):

107 self.source.tags['kanako_faith'] = True

108

109 def is_valid(self):

110 return not self.source.tags['kanako_faith']

111

112

113 class KanakoFaith(Skill):

114 associated_action = KanakoFaithAction

115 skill_category = ['character', 'active', 'once']

116

117 def check(self):

118 return not self.associated_cards

119

120 def target(self, p, tl):

121 g = self.game

122 l = g.players.rotate_to(p)

123 del l[0]

124

125 dists = LaunchCard.calc_raw_distance(g, p, AttackCard())

126 return ([t for t in l if not t.dead and dists[t] <= 1], True)

127

128

129 class VirtueAction(UserAction):

130 card_usage = 'handover'

131

132 def apply_action(self):

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

134 g = self.game

135 g.process_action(DrawCards(tgt, 2))

136

137 cl = user_choose_cards(self, tgt, ('cards', 'showncards', 'equips'))

138 c = cl[0] if cl else random_choose_card(g, [tgt.cards, tgt.showncards, tgt.equips])

139

140 if not c: return False

141

142 g.players.reveal(c)

143 g.process_action(ShowCards(tgt, [c]))

144

145 self.card_shadow = Card.copy(c)

146

147 migrate_cards([c], src.cards)

148

149 if c.suit == Card.HEART:

150 g.process_action(DrawCards(src, 1))

151

152 return True

153

154 def cond(self, cl):

155 return len(cl) == 1 and not cl[0].is_card(VirtualCard)

156

157

158 class VirtueHandler(THBEventHandler):

159 interested = ['action_before']

160

161 def handle(self, evt_type, act):

162 if evt_type == 'action_before' and isinstance(act, DrawCardStage):

163 if act.cancelled:

164 return act

165

166 tgt = act.target

167 if not tgt.has_skill(Virtue):

168 return act

169

170 g = self.game

171 pl = [p for p in g.players if not p.dead and p is not tgt]

172 pl = pl and user_choose_players(self, tgt, pl)

173 if not pl:

174 return act

175

176 act.cancelled = True

177 g.process_action(VirtueAction(tgt, pl[0]))

178

179 return act

180

181 def choose_player_target(self, tl):

182 if not tl:

183 return (tl, False)

184

185 return (tl[-1:], True)

186

187

188 class KanakoFaithKOF(Skill):

189 associated_action = None

190 skill_category = ['character', 'passive', 'compulsory']

191 target = t_None()

192

193

194 class KanakoFaithKOFAction(DrawCards):

195 pass

196

197

198 class KanakoFaithKOFHandler(THBEventHandler):

199 interested = ['action_before', 'action_apply']

200 game: THBattleKOF

201

202 def handle(self, evt_type, act):

203 if evt_type == 'action_before' and isinstance(act, FinalizeStage):

204 tgt = act.target

205 if not tgt.has_skill(KanakoFaithKOF):

206 return act

207

208 g = self.game

209 op = g.get_opponent(tgt)

210

211 if tgt.life > op.life or ttags(tgt)['kanako_faith_kof']:

212 n = tgt.life - (len(tgt.cards) + len(tgt.showncards))

213 if n > 0:

214 g.process_action(KanakoFaithKOFAction(tgt, n))

215

216 elif evt_type == 'action_apply' and isinstance(act, Damage):

217 src, tgt = act.source, act.target

218 g = self.game

219

220 if src and src.has_skill(KanakoFaithKOF) and tgt is g.get_opponent(src):

221 ttags(src)['kanako_faith_kof'] = True

222

223 return act

224

225

226 class Virtue(Skill):

227 associated_action = None

228 skill_category = ['character', 'passive']

229 target = t_None()

230

231

232@register_character_to('common', '-kof')

233 class Kanako(Character):

234 skills = [Virtue, KanakoFaith]

235 eventhandlers = [VirtueHandler]

236 maxlife = 4

237

238

239# @register_character_to('kof')

240 class KanakoKOF(Character):

241 skills = [KanakoFaithKOF]

242 eventhandlers = [KanakoFaithKOFHandler]

243 maxlife = 4