# Coverage for thb/characters/komachi.py : 99%

## 113 statements 113 run 0 missing 0 excluded 2 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 thb.actions import Damage, DrawCards, DropCards, GenericAction, LaunchCard, MaxLifeChange

7 from thb.actions import PlayerTurn, UserAction, migrate_cards, random_choose_card

8 from thb.cards.base import Skill

9 from thb.cards.classes import t_None, t_OtherOne

10 from thb.characters.base import Character, register_character_to

11 from thb.inputlets import ChooseOptionInputlet, ChoosePeerCardInputlet

12 from thb.mode import THBEventHandler

13

14

15# -- code --

16 class RiversideAction(UserAction):

17 def apply_action(self):

18 g = self.game

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

20 src.tags['riverside_tag'] = src.tags['turn_count']

21 tgt.tags['riverside_target'] = g.turn_count

22 minhp = min([p.life for p in g.players if not p.dead])

23 if tgt.life == minhp:

24 has_card = tgt.cards or tgt.showncards or tgt.equips

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

26 self.action = 'drop'

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

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

29 card = card or random_choose_card(g, [tgt.cards, tgt.showncards, tgt.equips])

30 g.players.reveal(card)

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

32 else:

33 self.action = 'draw'

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

35

36 return True

37

38 def is_valid(self):

39 src = self.source

40 # Fire with Exinwan -> dead -> skills cleared -> assertion fail

41 # assert src.has_skill(Riverside)

42 return not src.tags['riverside_tag'] >= src.tags['turn_count']

43

44

45 class RiversideHandler(THBEventHandler):

46 interested = ['calcdistance']

47

48 def handle(self, evt_type, arg):

49 if evt_type == 'calcdistance': 49 ↛ 59 line 49 didn't jump to line 59, because the condition on line 49 was never false

50 src, card, dist = arg

51 if not src.has_skill(Riverside):

52 return arg

53

54 turn_count = self.game.turn_count

55 for p in dist:

56 if p.tags.get('riverside_target') == turn_count:

57 dist[p] -= 10000

58

59 return arg

60

61

62 class Riverside(Skill):

63 associated_action = RiversideAction

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

65 target = t_OtherOne()

66 usage = 'drop'

67

68 def check(self):

69 cl = self.associated_cards

70 if len(cl) != 1: return False

71 return cl[0].resides_in.type in ('cards', 'showncards', 'equips')

72

73

74 class ReturningAwake(GenericAction):

75 def apply_action(self):

76 g = self.game

77 tgt = self.target

78 tgt.skills.remove(Returning)

79 tgt.skills.append(FerryFee)

80 g.process_action(MaxLifeChange(tgt, tgt, -1))

81

82 return True

83

84

85 class ReturningHandler(THBEventHandler):

86 interested = ['action_before']

87

88 def handle(self, evt_type, act):

89 if evt_type == 'action_before' and isinstance(act, PlayerTurn):

90 tgt = act.target

91 if not tgt.has_skill(Returning): return act

92 g = self.game

93 ncards = len(tgt.cards) + len(tgt.showncards)

94 if tgt.life <= 2 and tgt.life < ncards:

95 g.process_action(ReturningAwake(tgt, tgt))

96

97 return act

98

99

100 class Returning(Skill):

101 associated_action = None

102 skill_category = ['character', 'passive', 'awake']

103 target = t_None()

104

105

106 class FerryFee(Skill):

107 associated_action = None

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

109 target = t_None()

110 distance = 1

111

112

113 class FerryFeeEffect(UserAction):

114 def __init__(self, source, target, card):

115 self.source = source

116 self.target = target

117 self.card = card

118

119 def apply_action(self):

120 src = self.source

121 card = self.card

122 src.reveal(card)

123 migrate_cards([card], src.cards, unwrap=True)

124 return True

125

126

127 class FerryFeeHandler(THBEventHandler):

128 interested = ['action_after']

129

130 def handle(self, evt_type, act):

131 if evt_type == 'action_after' and isinstance(act, Damage):

132 src = act.source

133 tgt = act.target

134 if not (src and src.has_skill(FerryFee)): return act

135 if not (tgt.cards or tgt.showncards or tgt.equips): return act

136 g = self.game

137 dist = LaunchCard.calc_distance(g, src, FerryFee(src))

138 if not dist.get(tgt, 10000) <= 0: return act

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

140 g = self.game

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

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

143 card = card or random_choose_card(g, [tgt.cards, tgt.showncards, tgt.equips])

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

145 g.process_action(FerryFeeEffect(src, tgt, card))

146

147 return act

148

149

150@register_character_to('common')

151 class Komachi(Character):

152 skills = [Riverside, Returning]

153 eventhandlers = [RiversideHandler, ReturningHandler, FerryFeeHandler]

154 maxlife = 4