# Coverage for thb/characters/eirin.py : 95%

## 112 statements 109 run 3 missing 0 excluded 4 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# -- third party --

6# -- own --

7 from game.base import EventHandler

8 from thb.actions import DropCards, GenericAction, LaunchCard, ShowCards, UserAction, migrate_cards

9 from thb.actions import random_choose_card, ttags

10 from thb.cards.base import Card, HiddenCard, PhysicalCard, Skill, TreatAs, VirtualCard, t_One

11 from thb.cards.basic import Heal

12 from thb.cards.definition import AttackCard

13 from thb.characters.base import Character, register_character_to

14 from thb.inputlets import ChooseOptionInputlet, ChoosePeerCardInputlet

15

16

17# -- code --

18 class EirinHeal(Heal):

19 pass

20

21

22 class SkySilkAction(UserAction):

23 def apply_action(self):

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

25 g = self.game

26

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

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

29 g.players.reveal(c)

30

31 g.process_action(DropCards(src, tgt, [c]))

32 if tgt.dead: return False

33

34 action = 'draw'

35 if tgt.life < tgt.maxlife:

36 action = g.user_input([tgt], ChooseOptionInputlet(self, ('heal', 'draw'))) or 'draw'

37

38 if action == 'heal':

39 g.process_action(EirinHeal(src, tgt))

40 else:

41 g.deck.getcards(3)

42 g.deck.cards.rotate(3)

43 cl = g.deck.getcards(3)

44 g.process_action(ShowCards(tgt, cl))

45

46 drop = [c for c in cl if 'basic' in c.category]

47 get = [c for c in cl if c not in drop]

48

49 if get:

50 migrate_cards(get, tgt.cards)

51

52 if drop:

53 migrate_cards(drop, g.deck.droppedcards)

54

55 ttags(src)['sky_silk'] = True

56 return True

57

58 def is_valid(self):

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

60 return (tgt.cards or tgt.showncards or tgt.equips) and not ttags(src)['sky_silk']

61

62

63 class SkySilk(Skill):

64 associated_action = SkySilkAction

65 skill_category = ('character', 'active')

66 target = t_One()

67 usage = 'drop'

68

69 def check(self):

70 cl = self.associated_cards

71 return bool(not cl)

72

73

74 class LunaStringPlaceCard(GenericAction):

75 def __init__(self, target, card):

76 self.source = self.target = target

77 self.card = card

78

79 def apply_action(self):

80 g = self.game

81 tgt = self.target

82 direction = g.user_input([tgt], ChooseOptionInputlet(self, ('front', 'back')))

83 sk = self.card

84 assert sk.is_card(LunaString)

85 c = sk.associated_cards[0]

86 sk.associated_cards[:] = []

87 sk.cost_detached = True

88 self.direction = direction # for ui

89 migrate_cards([c], g.deck.cards, unwrap=True, direction=direction)

90 return True

91

92

93 class LunaStringLaunchCardHandler(EventHandler):

94 interested = ('action_before',)

95 execute_before = ('SolidShieldHandler',)

96

97 def handle(self, evt_type, act):

98 if evt_type == 'action_before': 98 ↛ 126 line 98 didn't jump to line 126, because the condition on line 98 was never false

99 if not isinstance(act, LaunchCard):

100 return act

101

102 c = act.card

103

104 def walk(c):

105 if c.is_card(LunaString):

106 return c

107

108 if c.is_card(PhysicalCard) or c.is_card(HiddenCard):

109 return None

110

111 assert isinstance(c, VirtualCard)

112

113 for c1 in c.associated_cards:

114 r = walk(c1)

115 if r: 115 ↛ 116 line 115 didn't jump to line 116, because the condition on line 115 was never true

116 return r

117

118 return None

119

120 c = walk(c)

121

122 if c:

123 g = self.game

124 g.process_action(LunaStringPlaceCard(c.character, c))

125

126 return act

127

128

129 class LunaString(TreatAs, Skill):

130 treat_as = AttackCard

131

132 skill_category = ('character', 'active')

133 no_reveal = True

134 use_action = LunaStringPlaceCard # UseCard hook

135

136 color = Card.NOTSET

137 suit = Card.NOTSET

138 number = 0

139

140 cost_detached = False

141

142 def check(self):

143 if self.cost_detached: 143 ↛ 144 line 143 didn't jump to line 144, because the condition on line 143 was never true

144 return True

145

146 cl = self.associated_cards

147 if not len(cl) == 1:

148 return False

149

150 c = cl[0]

151

152 if not (c.is_card(PhysicalCard) or c.is_card(HiddenCard)): # HiddenCard for other viewpoints 152 ↛ 153 line 152 didn't jump to line 153, because the condition on line 152 was never true

153 return False

154

155 return c.resides_in.type in ('cards', 'showncards')

156

157

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

159 class Eirin(Character):

160 skills = [SkySilk, LunaString]

161 eventhandlers = [LunaStringLaunchCardHandler]

162 maxlife = 3