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

## 119 statements 116 run 3 missing 0 excluded 7 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 thb.actions import Damage, DrawCards, LaunchCard, LifeLost, PlayerTurn, UserAction

8 from thb.actions import migrate_cards, skill_check, skill_wrap, user_choose_cards

9 from thb.cards.base import Card, Skill, VirtualCard, t_None

10 from thb.cards.classes import Heal, SealingArrayCard, TreatAs

11 from thb.cards.definition import BasicCard

12 from thb.characters.base import Character, register_character_to

13 from thb.inputlets import ChooseOptionInputlet

14 from thb.mode import THBEventHandler

15

16

17# -- code --

18 class Dilemma(Skill):

19 associated_action = None

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

21 target = t_None()

22

23

24 class DilemmaDamageAction(UserAction):

25 card_usage = 'handover'

26

27 def apply_action(self):

28 src = self.source

29 tgt = self.target

30

31 cards = user_choose_cards(self, tgt, ('cards', 'showncards', 'equips'))

32 g = self.game

33 if cards:

34 self.peer_action = 'card'

35 g.players.exclude(tgt).reveal(cards)

36 migrate_cards(cards, src.cards)

37 else:

38 self.peer_action = 'life'

39 g.process_action(LifeLost(src, tgt, 1))

40

41 return True

42

43 def cond(self, cards):

44 if len(cards) != 1: return False

45 card = cards[0]

46 if card.is_card(Skill):

47 return False

48

49 if card.resides_in.type not in ( 49 ↛ exit line 49 didn't jump to the function exit

50 'cards', 'showncards', 'equips'

51 ): return False

52

53 return card.suit == Card.DIAMOND

54

55

56 class DilemmaHealAction(DrawCards):

57 def __init__(self, source, target, amount=2):

58 self.source = source

59 self.target = target

60 self.amount = amount

61 self.back = False

62

63

64 class DilemmaHandler(THBEventHandler):

65 interested = ('action_after',)

66 execute_after = (

67 'DyingHandler',

68 'AyaRoundfanHandler',

69 'NenshaPhoneHandler',

70 )

71

72 def handle(self, evt_type, act):

73 if evt_type != 'action_after': return act 73 ↛ exit line 73 didn't return from function 'handle', because the return on line 73 wasn't executed

74 if not isinstance(act, (Damage, Heal)): return act

75

76 src = act.source

77 tgt = act.target

78 if tgt.dead: return act

79 if not tgt.has_skill(Dilemma): return act

80 if not src: return act

81

82 self.dilemma_type = 'negative' if isinstance(act, Damage) else 'positive'

83 g = self.game

84 if not g.user_input([tgt], ChooseOptionInputlet(self, (False, True))):

85 return act

86

87 if isinstance(act, Damage):

88 g.process_action(DilemmaDamageAction(tgt, src))

89 else: # Heal

90 g.process_action(DilemmaHealAction(tgt, src, 1))

91

92 return act

93

94

95 class ImperishableNight(TreatAs, Skill):

96 treat_as = SealingArrayCard

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

98

99 def check(self):

100 g = self.game

101 current = PlayerTurn.get_current(g).target

102 return current is not self.character

103

104

105 class ImperishableNightHandler(THBEventHandler):

106 interested = ['action_after']

107 card_usage = 'launch'

108

109 def handle(self, evt_type, act):

110 if evt_type != 'action_after': return act 110 ↛ exit line 110 didn't return from function 'handle', because the return on line 110 wasn't executed

111 if not isinstance(act, LaunchCard): return act

112

113 g = self.game

114

115 card = act.card

116 if not card: return act 116 ↛ exit line 116 didn't return from function 'handle', because the return on line 116 wasn't executed

117 if not isinstance(card, BasicCard): return act

118 if card.color != Card.RED: return act

119

120 if card.is_card(VirtualCard): 120 ↛ 121 line 120 didn't jump to line 121, because the condition on line 120 was never true

121 rawcards = VirtualCard.unwrap([card])

122 else:

123 rawcards = [card]

124

125 if not all( 125 ↛ exit line 125 didn't jump to the function exit

126 c.resides_in is None or c.resides_in.type == 'droppedcard'

127 for c in rawcards

128 ): return act

129

130 tgt = act.source

131 self.target = tgt # for ui

132

133 if tgt.dead: return act

134

135 try:

136 current = PlayerTurn.get_current(g).target

137 except IndexError:

138 current = None

139

140 for p in g.players:

141 if p.dead or p is tgt: continue

142 if not p.has_skill(ImperishableNight): continue

143 if p is current: continue

144

145 if not g.user_input([p], ChooseOptionInputlet(self, (False, True))):

146 continue

147

148 cards = user_choose_cards(self, p, ('cards', 'showncards', 'equips'))

149

150 if cards:

151 g.players.reveal(cards)

152 skill = skill_wrap(p, [ImperishableNight], cards, {})

153 assert skill_check(skill) # should not fail

154 g.deck.register_vcard(skill)

155 rst = g.process_action(LaunchCard(p, [tgt], skill))

156 assert rst

157

158 return act

159

160 def cond(self, cards):

161 if len(cards) != 1: return False

162 card = cards[0]

163 if card.resides_in.type not in ( 163 ↛ exit line 163 didn't jump to the function exit

164 'cards', 'showncards', 'equips'

165 ): return False

166 if 'skill' in card.category: return False

167 if card.color != Card.RED: return False

168 return bool(set(card.category) & {'basic', 'equipment'})

169

170 def ask_for_action_verify(self, p, cl, tl):

171 tgt = self.target

172 skill = skill_wrap(p, [ImperishableNight], cl, {})

173 return LaunchCard(p, [tgt], skill).can_fire()

174

175

176@register_character_to('common')

177 class Kaguya(Character):

178 skills = [Dilemma, ImperishableNight]

179 eventhandlers = [DilemmaHandler, ImperishableNightHandler]

180 maxlife = 3