# Coverage for thb/thbfaith.py : 21%

## 155 statements 41 run 114 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 from enum import Enum

6 from itertools import cycle

7 from typing import Any, Dict, List

8 import logging

9 import random

10

11# -- third party --

12# -- own --

13 from game.base import BootstrapAction, GameEnded, GameItem, InputTransaction, InterruptActionFlow

14 from game.base import Player, get_seed_for

15 from thb.actions import DistributeCards, MigrateCardsTransaction, PlayerDeath, PlayerTurn

16 from thb.actions import RevealRole, migrate_cards

17 from thb.cards.base import Deck

18 from thb.characters.base import Character

19 from thb.common import CharChoice, PlayerRole, build_choices, roll

20 from thb.inputlets import ChooseGirlInputlet, ChooseOptionInputlet, SortCharacterInputlet

21 from thb.mode import THBEventHandler, THBattle

22 from utils.misc import BatchList

23

24

25# -- code --

26 log = logging.getLogger('THBattle')

27

28

29 class DeathHandler(THBEventHandler):

30 interested = ['action_after', 'action_apply']

31 game: 'THBattleFaith'

32

33 def handle(self, evt_type: str, act: PlayerDeath) -> PlayerDeath:

34 if evt_type == 'action_apply' and isinstance(act, PlayerDeath):

35 g = self.game

36

37 tgt = act.target

38 role = g.roles[tgt.player].get()

39 pool = g.pool[role]

40 if len(pool) <= 1:

41 raise GameEnded(g.forces[g.get_opponent_role(role)])

42

43 elif evt_type == 'action_after' and isinstance(act, PlayerDeath):

44 g = self.game

45

46 tgt = act.target

47 role = g.roles[tgt.player].get()

48 pool = g.pool[role]

49

50 mapping = {tgt.player: pool}

51 with InputTransaction('ChooseGirl', [tgt.player], mapping=mapping) as trans:

52 c = g.user_input([tgt.player], ChooseGirlInputlet(g, mapping), timeout=30, trans=trans)

53 c = c or next(_c for _c in pool if not _c.chosen)

54 c.chosen = tgt

55 pool.remove(c)

56 trans.notify('girl_chosen', (tgt.player, c))

57

58 tgt = g.switch_character(tgt, c)

59 g.process_action(DistributeCards(tgt, 4))

60

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

62 g.process_action(RedrawCards(tgt, tgt))

63

64 return act

65

66

67 class RedrawCards(DistributeCards):

68 def apply_action(self):

69 tgt = self.target

70 g = self.game

71

72 with MigrateCardsTransaction(self) as trans:

73 g.players.reveal(list(tgt.cards))

74 migrate_cards(tgt.cards, g.deck.droppedcards, trans=trans)

75 cards = g.deck.getcards(4)

76 tgt.reveal(cards)

77 migrate_cards(cards, tgt.cards, trans=trans)

78

79 return True

80

81

82 class THBFaithRole(Enum):

83 HIDDEN = 0

84 HAKUREI = 1

85 MORIYA = 2

86

87

88 class THBattleFaithBootstrap(BootstrapAction):

89 game: 'THBattleFaith'

90

91 def __init__(self, params: Dict[str, Any],

92 items: Dict[Player, List[GameItem]],

93 players: BatchList[Player]):

94 self.source = self.target = None

95 self.params = params

96 self.items = items

97 self.players = players

98

99 def apply_action(self) -> bool:

100 g = self.game

101 params = self.params

102 pl = self.players

103

104 g.deck = Deck(g)

105 g.roles = {}

106

107 H, M = THBFaithRole.HAKUREI, THBFaithRole.MORIYA

108 if params['random_seat']:

109 # reseat

110 orig_pl = BatchList(pl)

111 seed = get_seed_for(g, pl)

112 random.Random(seed).shuffle(pl)

113 g.emit_event('reseat', (orig_pl, pl))

114

115 L = [[H, H, M, M, H, M], [H, M, H, M, H, M]]

116 rnd = random.Random(get_seed_for(g, pl))

117 L = rnd.choice(L) * 2

118 s = rnd.randrange(0, 6)

119 rl = L[s: s+6]

120 del L, s, rnd

121 else:

122 rl = [H, M, H, M, H, M]

123

124 for p, role in zip(pl, rl):

125 g.roles[p] = PlayerRole(THBFaithRole)

126 g.roles[p].set(role)

127 g.process_action(RevealRole(p, g.roles[p], pl))

128

129 g.forces = {

130 H: BatchList(),

131 M: BatchList(),

132 }

133 g.pool = {

134 H: BatchList(),

135 M: BatchList(),

136 }

137

138 for p in pl:

139 g.forces[g.roles[p].get()].append(p)

140

141 roll_rst = roll(g, pl, self.items)

142

143 # choose girls -->

144 from . import characters

145 chars = characters.get_characters('common', 'faith')

146

147 choices, _ = build_choices(

148 g, pl, self.items, chars,

149 spec={p: {'num': 4, 'akaris': 1} for p in pl}

150 )

151

152 rst = g.user_input(pl, SortCharacterInputlet(g, choices, 2), timeout=30, type='all')

153

154 g.players = BatchList([Character(p) for p in pl])

155 first: Character

156

157 for i, ch in enumerate(g.players):

158 p = ch.player

159 a, b = [choices[p][i] for i in rst[p][:2]]

160

161 ch = g.switch_character(ch, a)

162

163 if p is roll_rst[0]:

164 first = ch

165 first_index = i

166

167 g.players[i] = (ch)

168

169 b.chosen = None

170 g.forces[g.roles[p].get()].reveal(b)

171 g.pool[g.roles[p].get()].append(b)

172

173 order = BatchList(range(len(pl))).rotate_to(first_index)

174 g.emit_event('game_begin', g)

175

176 for ch in g.players:

177 g.process_action(DistributeCards(ch, amount=4))

178

179 reordered = g.players.rotate_to(first)

180 rst = g.user_input(reordered[1:], ChooseOptionInputlet(DeathHandler(g), (False, True)), type='all')

181

182 for p in reordered[1:]:

183 rst.get(p) and g.process_action(RedrawCards(p, p))

184

185 for i, idx in enumerate(cycle(order)):

186 if i >= 6000: break

187 ch = g.players[idx]

188 if ch.dead: continue

189

190 try:

191 g.process_action(PlayerTurn(ch))

192 except InterruptActionFlow:

193 pass

194

195 return True

196

197

198 class THBattleFaith(THBattle):

199 n_persons = 6

200 game_ehs = [DeathHandler]

201 bootstrap = THBattleFaithBootstrap

202 params_def = {

203 'random_seat': (True, False),

204 }

205

206 forces: Dict[THBFaithRole, BatchList[Player]]

207 pool: Dict[THBFaithRole, List[CharChoice]]

208

209 def can_leave(g: THBattleFaith, p: Any):

210 return False

211

212 def switch_character(g, old: Character, choice: CharChoice) -> Character:

213 p = old.player

214 choice.akari = False

215

216 g.players.player.reveal(choice)

217 cls = choice.char_cls

218

219 assert cls

220

221 log.info('>> NewCharacter: %s %s', g.roles[p].get().name, cls.__name__)

222

223 new = cls(p)

224 g.players.find_replace(lambda ch: ch.player is p, new)

225 g.refresh_dispatcher()

226

227 g.emit_event('switch_character', (old, new))

228

229 return new

230

231 def get_opponent_role(g, r: THBFaithRole) -> THBFaithRole:

232 if r == THBFaithRole.MORIYA:

233 return THBFaithRole.HAKUREI

234 elif r == THBFaithRole.HAKUREI:

235 return THBFaithRole.MORIYA

236 else:

237 assert False, f'WTF: {r}'