Coverage for thb/thbfaith.py : 94%
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 -*-
2from __future__ import annotations
4# -- stdlib --
5from enum import Enum
6from itertools import cycle
7from typing import Any, Dict, List
8import logging
9import random
11# -- third party --
12# -- own --
13from game.base import BootstrapAction, GameEnded, GameItem, InputTransaction, InterruptActionFlow
14from game.base import Player, get_seed_for
15from thb.actions import DistributeCards, MigrateCardsTransaction, PlayerDeath, PlayerTurn
16from thb.actions import RevealRole, migrate_cards
17from thb.cards.base import Deck
18from thb.characters.base import Character
19from thb.common import CharChoice, PlayerRole, build_choices, roll
20from thb.inputlets import ChooseGirlInputlet, ChooseOptionInputlet, SortCharacterInputlet
21from thb.mode import THBEventHandler, THBattle
22from utils.misc import BatchList
25# -- code --
26log = logging.getLogger('THBattle')
29class DeathHandler(THBEventHandler):
30 interested = ['action_after', 'action_apply']
31 game: 'THBattleFaith'
33 def handle(self, evt_type: str, act: PlayerDeath) -> PlayerDeath:
34 if evt_type == 'action_apply' and isinstance(act, PlayerDeath):
35 g = self.game
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)])
43 elif evt_type == 'action_after' and isinstance(act, PlayerDeath):
44 g = self.game
46 tgt = act.target
47 role = g.roles[tgt.player].get()
48 pool = g.pool[role]
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) 53 ↛ exitline 53 didn't finish the generator expression on line 53
54 c.chosen = tgt
55 pool.remove(c)
56 trans.notify('girl_chosen', (tgt.player, c))
58 tgt = g.switch_character(tgt, c)
59 g.process_action(DistributeCards(tgt, 4))
61 if g.user_input([tgt], ChooseOptionInputlet(self, (False, True))):
62 g.process_action(RedrawCards(tgt, tgt))
64 return act
67class RedrawCards(DistributeCards):
68 def apply_action(self):
69 tgt = self.target
70 g = self.game
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)
79 return True
82class THBFaithRole(Enum):
83 HIDDEN = 0
84 HAKUREI = 1
85 MORIYA = 2
88class THBattleFaithBootstrap(BootstrapAction):
89 game: 'THBattleFaith'
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
99 def apply_action(self) -> bool:
100 g = self.game
101 params = self.params
102 pl = self.players
104 g.deck = Deck(g)
105 g.roles = {}
107 H, M = THBFaithRole.HAKUREI, THBFaithRole.MORIYA
108 if params['random_seat']: 108 ↛ 122line 108 didn't jump to line 122, because the condition on line 108 was never false
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))
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]
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(g.roles[p], pl))
129 g.forces = {
130 H: BatchList(),
131 M: BatchList(),
132 }
133 g.pool = {
134 H: BatchList(),
135 M: BatchList(),
136 }
138 for p in pl:
139 g.forces[g.roles[p].get()].append(p)
141 roll_rst = roll(g, pl, self.items)
143 # choose girls -->
144 from . import characters
145 chars = characters.get_characters('common', 'faith')
147 choices, _ = build_choices(
148 g, pl, self.items, chars,
149 spec={p: {'num': 4, 'akaris': 1} for p in pl}
150 )
152 rst = g.user_input(pl, SortCharacterInputlet(g, choices, 2), timeout=30, type='all')
154 g.players = BatchList([Character(p) for p in pl])
155 first: Character
157 for i, ch in enumerate(g.players):
158 p = ch.player
159 a, b = [choices[p][i] for i in rst[p][:2]]
161 ch = g.switch_character(ch, a)
163 if p is roll_rst[0]:
164 first = ch
165 first_index = i
167 g.players[i] = (ch)
169 b.chosen = None
170 g.forces[g.roles[p].get()].reveal(b)
171 g.pool[g.roles[p].get()].append(b)
173 order = BatchList(range(len(pl))).rotate_to(first_index)
174 g.emit_event('game_begin', g)
176 for ch in g.players:
177 g.process_action(DistributeCards(ch, amount=4))
179 reordered = g.players.rotate_to(first)
180 rst = g.user_input(reordered[1:], ChooseOptionInputlet(DeathHandler(g), (False, True)), type='all')
182 for p in reordered[1:]:
183 rst.get(p) and g.process_action(RedrawCards(p, p))
185 for i, idx in enumerate(cycle(order)): 185 ↛ 195line 185 didn't jump to line 195, because the loop on line 185 didn't complete
186 if i >= 6000: break 186 ↛ 195line 186 didn't jump to line 195, because the break on line 186 wasn't executed
187 ch = g.players[idx]
188 if ch.dead: continue 188 ↛ 185line 188 didn't jump to line 185, because the continue on line 188 wasn't executed
190 try:
191 g.process_action(PlayerTurn(ch))
192 except InterruptActionFlow:
193 pass
195 return True
198class THBattleFaith(THBattle):
199 n_persons = 6
200 game_ehs = [DeathHandler]
201 bootstrap = THBattleFaithBootstrap
202 params_def = {
203 'random_seat': (True, False),
204 }
206 forces: Dict[THBFaithRole, BatchList[Player]]
207 pool: Dict[THBFaithRole, List[CharChoice]]
209 def can_leave(g: THBattleFaith, p: Any):
210 return False
212 def switch_character(g, old: Character, choice: CharChoice) -> Character:
213 p = old.player
214 choice.akari = False
216 g.players.player.reveal(choice)
217 cls = choice.char_cls
219 assert cls
221 log.info('>> NewCharacter: %s %s', g.roles[p].get().name, cls.__name__)
223 new = cls(p)
224 g.players.find_replace(lambda ch: ch.player is p, new)
225 g.refresh_dispatcher()
227 g.emit_event('switch_character', (old, new))
229 return new
231 def get_opponent_role(g, r: THBFaithRole) -> THBFaithRole:
232 if r == THBFaithRole.MORIYA:
233 return THBFaithRole.HAKUREI
234 elif r == THBFaithRole.HAKUREI: 234 ↛ 237line 234 didn't jump to line 237, because the condition on line 234 was never false
235 return THBFaithRole.MORIYA
236 else:
237 assert False, f'WTF: {r}'