Coverage for thb/thbkof.py : 22%
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
10# -- third party --
11# -- own --
12from game.base import BootstrapAction, GameEnded, InputTransaction, InterruptActionFlow, Player
13from game.base import list_shuffle
14from thb.actions import DistributeCards, PlayerDeath, PlayerTurn, RevealRole
15from thb.cards.base import Deck
16from thb.cards.definition import kof_card_definition
17from thb.characters.base import Character
18from thb.common import CharChoice, PlayerRole, build_choices_shared, roll
19from thb.inputlets import ChooseGirlInputlet
20from thb.item import GameItem
21from thb.mode import THBEventHandler, THBattle
22from utils.misc import BatchList
25# -- code --
26log = logging.getLogger('THBattleKOF')
29class DeathHandler(THBEventHandler):
30 interested = ['action_apply']
32 game: 'THBattleKOF'
34 def handle(self, evt_type: str, act: PlayerDeath):
35 if evt_type != 'action_apply': return act
36 if not isinstance(act, PlayerDeath): return act
37 tgt = act.target
38 p = tgt.player
40 g = self.game
41 pl = g.players.player
43 if len(g.chosen[p]) <= 2: # 5(total chosen) - 3(available characters) = 2
44 pl.remove(p)
45 raise GameEnded(pl)
47 if g.is_dropped(pl[0]):
48 raise GameEnded([pl[1]])
50 if g.is_dropped(pl[1]):
51 raise GameEnded([pl[0]])
53 return act
56class KOFCharacterSwitchHandler(THBEventHandler):
57 interested = ['action_after', 'action_before', 'action_stage_action']
58 game: THBattleKOF
60 def handle(self, evt_type, act):
61 cond = evt_type in ('action_before', 'action_after')
62 cond = cond and isinstance(act, PlayerTurn)
63 cond = cond or evt_type == 'action_stage_action'
64 cond and self.do_switch_dead()
65 return act
67 def do_switch_dead(self):
68 g = self.game
70 # for p in [p for p in g.players if p.dead and p.chosen]:
71 for ch in [ch for ch in g.players if ch.dead]:
72 new = self.switch(ch)
73 g.process_action(DistributeCards(new, 4))
74 g.emit_event('character_debut', (ch, new))
76 def switch(self, old: Character):
77 g = self.game
78 p = old.player
79 mapping = {p: g.chosen[p]}
81 with InputTransaction('ChooseGirl', [p], mapping=mapping) as trans:
82 choice = g.user_input([p], ChooseGirlInputlet(g, mapping), timeout=30, trans=trans)
83 choice = choice or g.chosen[p][0]
85 g.players.reveal(choice)
86 assert choice.char_cls
87 new = choice.char_cls(p)
88 g.players.replace(old, new)
89 g.refresh_dispatcher()
90 g.chosen[p].remove(choice)
91 return new
94class THBKOFRole(Enum):
95 HIDDEN = 0
96 HAKUREI = 1
97 MORIYA = 2
100class THBattleKOFBootstrap(BootstrapAction):
101 game: 'THBattleKOF'
103 def __init__(self, params: Dict[str, Any],
104 items: Dict[Player, List[GameItem]],
105 players: BatchList[Player]):
106 self.source = self.target = None
107 self.params = params
108 self.items = items
109 self.players = players
111 def apply_action(self) -> bool:
112 g = self.game
114 g.deck = Deck(g, kof_card_definition)
115 pl = self.players
116 A, B = pl
117 g.roles = {
118 A: PlayerRole(THBKOFRole),
119 B: PlayerRole(THBKOFRole),
120 }
121 g.roles[A].set(THBKOFRole.HAKUREI)
122 g.roles[B].set(THBKOFRole.MORIYA)
124 # choose girls -->
125 from thb.characters import get_characters
126 chars = get_characters('common', 'kof')
128 A, B = roll(g, pl, self.items)
129 order = [A, B, B, A, A, B, B, A, A, B]
131 choices, imperial_choices = build_choices_shared(
132 g, pl, self.items,
133 candidates=chars, spec={'num': 10, 'akaris': 4},
134 )
136 g.chosen = {A: [], B: []}
138 with InputTransaction('ChooseGirl', pl, mapping=choices) as trans:
139 for p, c in imperial_choices.items():
140 c.chosen = p
141 g.chosen[p].append(c)
142 trans.notify('girl_chosen', (p, c))
143 order.remove(p)
145 for p in order:
146 c = g.user_input([p], ChooseGirlInputlet(g, {p: choices}), 10, 'single', trans)
147 # c = c or next(choices[p], lambda c: not c.chosen, None)
148 c = c or next(c for c in choices if not c.chosen)
150 c.chosen = p
151 g.chosen[p].append(c)
153 trans.notify('girl_chosen', (p, c))
155 # reveal akaris for themselves
156 for p in [A, B]:
157 for c in g.chosen[p]:
158 c.akari = False
159 p.reveal(c)
160 del c.chosen
162 list_shuffle(g, g.chosen[A], A)
163 list_shuffle(g, g.chosen[B], B)
165 with InputTransaction('ChooseGirl', pl, mapping=g.chosen) as trans:
166 ilet = ChooseGirlInputlet(g, g.chosen)
167 ilet.with_post_process(lambda p, rst: trans.notify('girl_chosen', (p, rst)) or rst)
168 rst = g.user_input([A, B], ilet, type='all', trans=trans)
170 def s(p):
171 c = rst[p] or g.chosen[p][0]
172 g.chosen[p].remove(c)
174 c.akari = False
175 pl.reveal(c)
176 cls = c.char_cls
177 assert cls
178 ch = cls(p)
179 g.refresh_dispatcher()
180 g.emit_event('switch_character', (None, ch))
182 return ch
184 cA, cB = g.players = BatchList([s(A), s(B)])
186 for p in pl:
187 g.process_action(RevealRole(p, g.roles[p], pl))
189 g.refresh_dispatcher()
190 g.emit_event('game_begin', g)
192 g.process_action(DistributeCards(cA, amount=4))
193 g.process_action(DistributeCards(cB, amount=3))
195 for ch in g.players:
196 g.emit_event('character_debut', (None, ch))
198 for i in cycle([0, 1]):
199 ch = g.players[i]
200 if i >= 6000: break
201 if ch.dead:
202 handler = g.dispatcher.find_by_cls(KOFCharacterSwitchHandler)
203 assert handler, 'WTF?!'
204 handler.do_switch_dead()
205 ch = g.players[i] # player changed
207 assert not ch.dead
209 try:
210 g.process_action(PlayerTurn(ch))
211 except InterruptActionFlow:
212 pass
214 return True
217class THBattleKOF(THBattle):
218 n_persons = 2
219 game_ehs = [
220 DeathHandler,
221 KOFCharacterSwitchHandler,
222 ]
223 bootstrap = THBattleKOFBootstrap
225 chosen: Dict[Player, List[CharChoice]]
227 def get_opponent(g: THBattleKOF, ch: Character):
228 a, b = g.players
229 return {a: b, b: a}[ch]
231 def can_leave(g: THBattle, p: Player) -> bool:
232 return False