# Coverage for thb/characters/sanae.py : 96%

## 178 statements 174 run 4 missing 0 excluded 5 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

3# -- stdlib --

4# -- third party --

5# -- own --

6 from typing import cast

7 from thb.actions import ActionStage, AskForCard, DistributeCards, DrawCardStage, DrawCards, MigrateCardsTransaction

8 from thb.actions import GenericAction, PlayerTurn, Reforge, UserAction, migrate_cards

9 from thb.actions import random_choose_card, ttags, user_choose_cards, user_choose_players

10 from thb.cards.base import Skill

11 from thb.cards.classes import Heal, PhysicalCard, t_None, t_Self

12 from thb.characters.base import Character, register_character_to

13 from thb.mode import THBEventHandler

14

15

16# -- code --

17 class MiracleHeal(Heal):

18 pass

19

20

21 class MiracleAction(UserAction):

22 def apply_action(self):

23 tgt = self.target

24 g = self.game

25 g.process_action(DrawCards(tgt, 1))

26

27 ttags(tgt)['miracle_times'] += 1

28

29 if ttags(tgt)['miracle_times'] == 3:

30 candidates = [p for p in g.players if not p.dead and p.life < p.maxlife]

31 if candidates:

32 beneficiery, = user_choose_players(self, tgt, candidates) or (None,)

33 if beneficiery: 33 ↛ 36 line 33 didn't jump to line 36, because the condition on line 33 was never false

34 g.process_action(MiracleHeal(tgt, beneficiery))

35

36 return True

37

38 def choose_player_target(self, tl):

39 return tl[-1:], bool(tl)

40

41 def cond(self, cl):

42 return True

43

44 def is_valid(self):

45 tgt = self.target

46 return len(self.associated_card.associated_cards) == ttags(tgt)['miracle_times'] + 1

47

48

49 class Miracle(Skill):

50 associated_action = MiracleAction

51 skill_category = ['character', 'active']

52 target = t_Self()

53 usage = 'drop'

54

55 def check(self):

56 cl = self.associated_cards

57 return cl and all(

58 c.resides_in is not None and

59 c.resides_in.type in (

60 'cards', 'showncards', 'equips'

61 ) for c in self.associated_cards

62 )

63

64

65 class SanaeFaithCollectCardAction(GenericAction):

66 card_usage = 'handover'

67 no_reveal = True

68

69 def apply_action(self) -> bool:

70 g = self.game

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

72 cards = user_choose_cards(self, tgt, ('cards', 'showncards'))

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

74 src.player.reveal(c)

75 migrate_cards([c], src.cards)

76

77 return True

78

79 def cond(self, cl):

80 return len(cl) == 1 and \

81 cl[0].resides_in.type in ('cards', 'showncards') and \

82 not cl[0].is_card(Skill)

83

84

85 class SanaeFaithReturnCardAction(GenericAction):

86 card_usage = 'handover'

87 no_reveal = True

88

89 def apply_action(self) -> bool:

90 g = self.game

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

92 cards = user_choose_cards(self, src, ('cards', 'showncards', 'equips'))

93 c = cards[0] if cards else random_choose_card(g, [src.cards, src.showncards, src.equips])

94 if not c: 94 ↛ 95 line 94 didn't jump to line 95, because the condition on line 94 was never true

95 return False

96

97 tgt.player.reveal(c)

98 migrate_cards([c], tgt.cards)

99

100 return True

101

102 def cond(self, cl):

103 return len(cl) == 1 and not cl[0].is_card(Skill)

104

105

106 class SanaeFaithAction(UserAction):

107 def apply_action(self):

108 src = self.source

109 tl = self.target_list

110 g = self.game

111

112 for p in tl:

113 g.process_action(SanaeFaithCollectCardAction(src, p))

114

115 g.deck.shuffle(src.cards)

116

117 for p in tl:

118 g.process_action(SanaeFaithReturnCardAction(src, p))

119

120 ttags(src)['faith'] = True

121

122 return True

123

124 def is_valid(self):

125 src = self.source

126 return not ttags(src)['faith']

127

128

129 class SanaeFaith(Skill):

130 associated_action = SanaeFaithAction

131 skill_category = ['character', 'active']

132 usage = 'launch'

133

134 def target(self, src, tl):

135 tl = [t for t in tl if not t.dead and (t.cards or t.showncards)]

136 try:

137 tl.remove(src)

138 except ValueError:

139 pass

140

141 return (tl[:2], bool(len(tl)))

142

143 def check(self):

144 return not self.associated_cards

145

146

147 class SanaeFaithKOF(Skill):

148 associated_action = None

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

150 target = t_None()

151

152

153 class SanaeFaithKOFDrawCards(DrawCards):

154 pass

155

156

157 class SanaeFaithKOFHandler(THBEventHandler):

158 interested = ['post_card_migration']

159

160 def handle(self, evt_type, arg):

161 if evt_type == 'post_card_migration': 161 ↛ 195 line 161 didn't jump to line 195, because the condition on line 161 was never false

162 trans = cast(MigrateCardsTransaction, arg)

163 if isinstance(trans.action, (DistributeCards, DrawCardStage)):

164 return arg

165

166 g = self.game

167

168 for m in trans.movements:

169 if not m.to.owner:

170 continue

171

172 if m.to.type not in ('cards', 'showncards', 'equips'):

173 continue

174

175 if m.fr.owner is m.to.owner:

176 continue

177

178 tgt = g.get_opponent(m.to.owner)

179

180 if not tgt.has_skill(SanaeFaithKOF):

181 continue

182

183 turn = PlayerTurn.get_current(g)

184 if not turn: 184 ↛ 185 line 184 didn't jump to line 185, because the condition on line 184 was never true

185 continue

186

187 stage = turn.current_stage

188 if stage.target is not m.to.owner or not isinstance(stage, ActionStage):

189 continue

190

191 g.process_action(SanaeFaithKOFDrawCards(tgt, 1))

192

193 break

194

195 return arg

196

197

198 class GodDescendant(Skill):

199 associated_action = None

200 target = t_None()

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

202

203

204 class GodDescendantEffect(UserAction):

205 def __init__(self, source, target, target_list, card):

206 self.source = source

207 self.target = target

208 self.target_list = target_list

209 self.card = card

210

211 def apply_action(self):

212 g = self.game

213 src, tgt, tl = self.source, self.target, self.target_list

214 g.process_action(Reforge(src, tgt, self.card))

215 assert tgt in tl

216 tl.remove(tgt)

217 return True

218

219

220 class GodDescendantAction(AskForCard):

221 card_usage = 'reforge'

222

223 def __init__(self, target, target_list):

224 AskForCard.__init__(self, target, target, PhysicalCard, ('cards', 'showncards', 'equips'))

225 self.target_list = target_list

226

227 def process_card(self, c):

228 g = self.game

229 return g.process_action(GodDescendantEffect(self.source, self.target, self.target_list, c))

230

231

232 class GodDescendantHandler(THBEventHandler):

233 interested = ['choose_target']

234 execute_before = ['MaidenCostumeHandler']

235

236 def handle(self, evt_type, arg):

237 if evt_type == 'choose_target': 237 ↛ 249 line 237 didn't jump to line 249, because the condition on line 237 was never false

238 act, tl = arg

239 if 'group_effect' not in act.card.category:

240 return arg

241

242 g = self.game

243 for tgt in tl:

244 if not tgt.has_skill(GodDescendant):

245 continue

246

247 g.process_action(GodDescendantAction(tgt, tl))

248

249 return arg

250

251 def cond(self, cl):

252 return len(cl) == 1

253

254

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

256 class Sanae(Character):

257 skills = [Miracle, SanaeFaith, GodDescendant]

258 eventhandlers = [GodDescendantHandler]

259 maxlife = 3

260

261

262@register_character_to('kof')

263 class SanaeKOF(Character):

264 skills = [Miracle, SanaeFaithKOF, GodDescendant]

265 eventhandlers = [SanaeFaithKOFHandler, GodDescendantHandler]

266 maxlife = 3