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

## 114 statements 45 run 69 missing 0 excluded 0 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, DropCards, GenericAction, LaunchCard, MaxLifeChange

8 from thb.actions import PrepareStage, UserAction, migrate_cards, random_choose_card

9 from thb.cards.base import Skill

10 from thb.cards.classes import t_None, t_OtherOne

11 from thb.characters.base import Character, register_character_to

12 from thb.inputlets import ChooseOptionInputlet, ChoosePeerCardInputlet

13 from thb.mode import THBEventHandler

14

15

16# -- code --

17 class RiversideAction(UserAction):

18 def apply_action(self):

19 g = self.game

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

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

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

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

24 if tgt.life == minhp:

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

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

27 self.action = 'drop'

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

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

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

31 g.players.reveal(card)

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

33 else:

34 self.action = 'draw'

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

36

37 return True

38

39 def is_valid(self):

40 src = self.source

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

42 # assert src.has_skill(Riverside)

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

44

45

46 class RiversideHandler(THBEventHandler):

47 interested = ['calcdistance']

48

49 def handle(self, evt_type, arg):

50 if evt_type == 'calcdistance':

51 src, card, dist = arg

52 if not src.has_skill(Riverside):

53 return arg

54

55 turn_count = self.game.turn_count

56 for p in dist:

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

58 dist[p] -= 10000

59

60 return arg

61

62

63 class Riverside(Skill):

64 associated_action = RiversideAction

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

66 target = t_OtherOne()

67 usage = 'drop'

68

69 def check(self):

70 cl = self.associated_cards

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

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

73

74

75 class ReturningAwake(GenericAction):

76 def apply_action(self):

77 g = self.game

78 tgt = self.target

79 tgt.skills.remove(Returning)

80 tgt.skills.append(FerryFee)

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

82

83 return True

84

85

86 class ReturningHandler(THBEventHandler):

87 interested = ['action_before']

88

89 def handle(self, evt_type, act):

90 if evt_type == 'action_before' and isinstance(act, PrepareStage):

91 tgt = act.target

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

93 g = self.game

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

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

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

97

98 return act

99

100

101 class Returning(Skill):

102 associated_action = None

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

104 target = t_None()

105

106

107 class FerryFee(Skill):

108 associated_action = None

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

110 target = t_None()

111 distance = 1

112

113

114 class FerryFeeEffect(UserAction):

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

116 self.source = source

117 self.target = target

118 self.card = card

119

120 def apply_action(self):

121 src = self.source

122 card = self.card

123 src.reveal(card)

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

125 return True

126

127

128 class FerryFeeHandler(THBEventHandler):

129 interested = ['action_after']

130

131 def handle(self, evt_type, act):

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

133 src = act.source

134 tgt = act.target

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

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

137 g = self.game

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

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

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

141 g = self.game

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

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

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

145 if not card: return act

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

147

148 return act

149

150

151@register_character_to('common')

152 class Komachi(Character):

153 skills = [Riverside, Returning]

154 eventhandlers = [RiversideHandler, ReturningHandler, FerryFeeHandler]

155 maxlife = 4