# Coverage for thb/characters/alice.py : 94%

## 195 statements 188 run 7 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

3 from typing import Sequence, Tuple, List

4# -- stdlib --

5# -- third party --

6# -- own --

7 from thb.actions import ActionStage, Damage, DrawCards, DropCardStage, DropCards, GenericAction

8 from thb.actions import LaunchCard, PlayerTurn, PostCardMigrationHandler, Reforge, UserAction, MigrateCardsTransaction

9 from thb.actions import random_choose_card, user_choose_cards, user_choose_players

10 from thb.cards.base import Skill, VirtualCard

11 from thb.cards.definition import EquipmentCard

12 from thb.cards.classes import AttackCard, DollControlCard, Heal, TreatAs, t_None

13 from thb.characters.base import Character, register_character_to

14 from thb.inputlets import ChooseOptionInputlet, ChoosePeerCardInputlet

15 from thb.mode import THBEventHandler

16

17

18# -- code --

19 class LittleLegionAttackCard(TreatAs, VirtualCard):

20 treat_as = AttackCard

21

22

23 class LittleLegionDollControlCard(TreatAs, VirtualCard):

24 treat_as = DollControlCard

25

26

27 class LittleLegion(Skill):

28 associated_action = None

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

30 target = t_None()

31

32

33 class LittleLegionAttackAction(UserAction):

34 def apply_action(self):

35 g = self.game

36 src = self.source

37 pl = [p for p in g.players if not p.dead and p is not src]

38 if not pl: 38 ↛ 39 line 38 didn't jump to line 39, because the condition on line 38 was never true

39 return False

40

41 victim, = user_choose_players(self, src, pl) or (None,)

42 if victim is None:

43 return False

44

45 lc = LaunchCard(src, [victim], LittleLegionAttackCard(src), bypass_check=True)

46 g.process_action(lc)

47 return True

48

49 def choose_player_target(self, tl):

50 if not tl: 50 ↛ 51 line 50 didn't jump to line 51, because the condition on line 50 was never true

51 return (tl, False)

52

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

54

55

56 class LittleLegionCoverEffect(Heal):

57 pass

58

59

60 class LittleLegionCoverAction(UserAction):

61 def apply_action(self):

62 g = self.game

63 src = self.source

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

65 if not pl:

66 return False

67

68 beneficiary, = user_choose_players(self, src, pl) or (None,)

69 if beneficiary is None:

70 return False

71

72 g.process_action(LittleLegionCoverEffect(src, beneficiary, 1))

73 return True

74

75 def choose_player_target(self, tl):

76 if not tl: 76 ↛ 77 line 76 didn't jump to line 77, because the condition on line 76 was never true

77 return (tl, False)

78

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

80

81

82 class LittleLegionHoldAction(UserAction):

83 def apply_action(self):

84 src = self.source

85

86 g = self.game

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

88

89 turn = PlayerTurn.get_current(g)

90 try:

91 turn.pending_stages.remove(DropCardStage)

92 except Exception:

93 pass

94

95 return True

96

97

98 class LittleLegionControlAction(UserAction):

99 def apply_action(self) -> bool:

100 g = self.game

101 src = self.source

102 pl = [p for p in g.players if not p.dead]

103 rst = user_choose_players(self, src, pl)

104

105 if not rst:

106 return False

107

108 attacker, victim = rst

109 self.target_list = attacker, victim

110

111 g.process_action(LaunchCard(src, [attacker, victim], LittleLegionDollControlCard(attacker)))

112 return True

113

114 def choose_player_target(self, tl: Sequence[Character]) -> Tuple[List[Character], bool]:

115 src = self.source

116 trimmed, rst = DollControlCard.target(None, src, tl)

117 return trimmed, rst and LaunchCard(src, trimmed, LittleLegionDollControlCard(src)).can_fire()

118

119

120 class LittleLegionHandler(THBEventHandler):

121 interested = ['action_after']

122

123 def handle(self, evt_type, act):

124 if evt_type == 'action_after' and isinstance(act, ActionStage):

125 tgt = act.target

126 if not tgt.has_skill(LittleLegion):

127 return act

128

129 c, = user_choose_cards(self, tgt, ('cards', 'showncards', 'equips')) or (None,)

130 if c is None:

131 return act

132

133 g = self.game

134

135 assert isinstance(c, EquipmentCard)

136 category = c.equipment_category

137

138 g.process_action(Reforge(tgt, tgt, c))

139

140 if tgt.dead: 140 ↛ 141 line 140 didn't jump to line 141, because the condition on line 140 was never true

141 return act

142

143 if category == 'weapon':

144 g.process_action(LittleLegionAttackAction(tgt, tgt))

145 elif category == 'shield':

146 g.process_action(LittleLegionCoverAction(tgt, tgt))

147 elif category == 'accessories':

148 g.process_action(LittleLegionHoldAction(tgt, tgt))

149 elif category in ('redufo', 'greenufo'): 149 ↛ 152 line 149 didn't jump to line 152, because the condition on line 149 was never false

150 g.process_action(LittleLegionControlAction(tgt, tgt))

151

152 return act

153

154 def cond(self, cl):

155 if len(cl) != 1:

156 return False

157

158 c, = cl

159 if c.is_card(VirtualCard):

160 return False

161

162 return 'equipment' in c.category

163

164

165 class DollBlast(Skill):

166 associated_action = None

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

168 target = t_None()

169

170

171 class DollBlastEffect(GenericAction):

172 def __init__(self, source, target, card, do_damage):

173 self.source = source

174 self.target = target

175 self.card = card

176 self.do_damage = do_damage

177

178 def apply_action(self):

179 g = self.game

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

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

182 if self.do_damage:

183 g.process_action(Damage(src, tgt, 1))

184

185 return True

186

187

188 class DollBlastAction(UserAction):

189 def __init__(self, source, target, cards):

190 self.source = source

191 self.target = target

192 self.cards = cards

193

194 def apply_action(self):

195 g = self.game

196 cl = self.cards

197 track_ids = {c.track_id for c in cl}

198

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

200 for c in cl:

201 c = g.user_input([src], ChoosePeerCardInputlet(self, tgt, ('cards', 'showncards', 'equips')))

202 c = c or random_choose_card(g, [tgt.cards, tgt.showncards, tgt.equips])

203 if not c: return True 203 ↛ exit line 203 didn't return from function 'apply_action', because the return on line 203 wasn't executed

204 g.players.reveal(c)

205 g.process_action(DollBlastEffect(src, tgt, c, c.track_id in track_ids))

206

207 return True

208

209

210 class DollBlastHandlerCommon(object):

211

212 def fire(self, src, tgt, cards):

213 self.target = tgt # for ui

214

215 g = self.game

216 if not g.user_input([src], ChooseOptionInputlet(self, (False, True))):

217 return

218

219 g.process_action(DollBlastAction(src, tgt, cards))

220

221

222 class DollBlastMigrationHandler(DollBlastHandlerCommon, THBEventHandler):

223 interested = ['post_card_migration']

224 arbiter = PostCardMigrationHandler

225

226 def handle(self, p, trans):

227 if not p.has_skill(DollBlast) or p.dead:

228 return True

229

230 equips = p.equips

231

232 assert isinstance(trans, MigrateCardsTransaction)

233 for m in trans.movements:

234 if m.fr is not equips:

235 continue

236

237 if not m.to.owner:

238 continue

239

240 tgt = m.to.owner

241

242 if tgt is m.fr.owner:

243 continue

244

245 if not (tgt.cards or tgt.showncards or tgt.equips): 245 ↛ 246 line 245 didn't jump to line 246, because the condition on line 245 was never true

246 continue

247

248 self.fire(p, tgt, [m.card])

249

250 return True

251

252

253 class DollBlastDropHandler(DollBlastHandlerCommon, THBEventHandler):

254 interested = ['action_before', 'action_after']

255

256 def handle(self, evt_type, act):

257 if evt_type == 'action_before' and isinstance(act, DropCards):

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

259

260 if not tgt.has_skill(DollBlast):

261 return act

262

263 if not src or src is tgt or not (src.cards or src.showncards or src.equips):

264 return act

265

266 if not any(c.resides_in.type == 'equips' for c in act.cards):

267 return act

268

269 act._['doll_blast'] = True

270

271 elif evt_type == 'action_after' and isinstance(act, DropCards) and act._['doll_blast']:

272 tgt = act.target

273 if tgt.has_skill(DollBlast): 273 ↛ 276 line 273 didn't jump to line 276, because the condition on line 273 was never false

274 self.fire(act.target, act.source, act.cards)

275

276 return act

277

278

279@register_character_to('common')

280 class Alice(Character):

281 skills = [DollBlast, LittleLegion]

282 eventhandlers = [

283 DollBlastMigrationHandler,

284 DollBlastDropHandler,

285 LittleLegionHandler,

286 ]

287 maxlife = 4