Coverage for thb/thb2v2.py : 19%
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, Set, Type
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 DeadDropCards, DistributeCards, DrawCardStage, DrawCards
16from thb.actions import MigrateCardsTransaction, PlayerDeath, PlayerTurn, RevealRole, UserAction
17from thb.actions import migrate_cards
18from thb.cards.base import Deck
19from thb.characters.base import Character
20from thb.common import CharChoice, PlayerRole, roll
21from thb.inputlets import ChooseGirlInputlet, ChooseOptionInputlet
22from thb.mode import THBEventHandler, THBattle
23from utils.misc import BatchList, partition
24import settings
27# -- code --
28log = logging.getLogger('THBattle2v2')
31class DeathHandler(THBEventHandler):
32 game: THBattle2v2
33 interested = ['action_apply']
35 def handle(self, evt_type, act):
36 if evt_type != 'action_apply': return act
37 if not isinstance(act, PlayerDeath): return act
39 g = self.game
40 tgt = act.target
42 tgt = act.target
43 dead = lambda ch: ch.dead or g.is_dropped(ch.player) or ch is tgt
45 # see if game ended
46 force1, force2 = list(g.forces.values())
47 if all(dead(ch) for ch in force1):
48 raise GameEnded(force2.player)
50 if all(dead(ch) for ch in force2):
51 raise GameEnded(force1.player)
53 return act
56class HeritageAction(UserAction):
57 def apply_action(self):
58 src, tgt = self.source, self.target
59 lists = [tgt.cards, tgt.showncards, tgt.equips]
60 with MigrateCardsTransaction(self) as trans:
61 for cl in lists:
62 if not cl: continue
63 cards = list(cl)
64 src.reveal(cards)
65 migrate_cards(cards, src.cards, unwrap=True, trans=trans)
67 return True
70class HeritageHandler(THBEventHandler):
71 game: THBattle2v2
72 interested = ['action_before']
73 execute_after = ['DeathHandler', 'SadistHandler']
75 def handle(self, evt_type, act):
76 if evt_type != 'action_before': return act
77 if not isinstance(act, DeadDropCards): return act
79 g = self.game
80 tgt = act.target
81 for f in g.forces.values():
82 if tgt in f:
83 break
84 else:
85 assert False, 'WTF?!'
87 other = BatchList(f).exclude(tgt)[0]
88 if other.dead: return act
90 if g.user_input([other], ChooseOptionInputlet(self, ('inherit', 'draw'))) == 'inherit':
91 g.process_action(HeritageAction(other, tgt))
93 else:
94 g.process_action(DrawCards(other, 2))
96 return act
99class ExtraCardHandler(THBEventHandler):
100 game: THBattle2v2
101 interested = ['action_before']
103 def handle(self, evt_type, act):
104 if evt_type != 'action_before':
105 return act
107 if not isinstance(act, DrawCardStage):
108 return act
110 g = self.game
111 if g.draw_extra:
112 act.amount += 1
114 return act
117class THB2v2Role(Enum):
118 HIDDEN = 0
119 HAKUREI = 1
120 MORIYA = 2
123class THBattle2v2Bootstrap(BootstrapAction):
124 game: 'THBattle2v2'
126 def __init__(self, params: Dict[str, Any],
127 items: Dict[Player, List[GameItem]],
128 players: BatchList[Player]):
129 self.source = self.target = None
130 self.params = params
131 self.items = items
132 self.players = players
134 def apply_action(self) -> bool:
135 g = self.game
136 params = self.params
137 items = self.items
139 pl = self.players
141 g.deck = Deck(g)
142 g.roles = {}
144 if params['random_force']:
145 seed = get_seed_for(g, pl)
146 random.Random(seed).shuffle(pl)
148 g.draw_extra = params['draw_extra_card']
150 H, M = THB2v2Role.HAKUREI, THB2v2Role.MORIYA
151 g.forces = {H: BatchList(), M: BatchList()}
153 for p, id in zip(pl, [H, H, M, M]):
154 g.roles[p] = r = PlayerRole(THB2v2Role)
155 g.roles[p].set(id)
156 g.process_action(RevealRole(p, r, pl))
158 roll_rst = roll(g, pl, items)
159 '''
160 winner = g.forces[roll_rst[0].identity.value]
161 f1, f2 = partition(lambda ch: g.forces[ch.identity.value] is winner, roll_rst)
162 final_order = [f1[0], f2[0], f2[1], f1[1]]
163 '''
164 g.emit_event('reseat', (pl, roll_rst))
165 pl = roll_rst
167 # ban / choose girls -->
168 from . import characters
169 chars = characters.get_characters('common', '2v2')
171 seed = get_seed_for(g, pl)
172 random.Random(seed).shuffle(chars)
174 testing: List[str] = list(settings.TESTING_CHARACTERS)
175 testing, chars = partition(lambda c: c.__name__ in testing, chars)
176 chars.extend(testing)
178 chars = chars[-20:]
179 choices = [CharChoice(cls) for cls in chars]
181 banned: Set[Type[Character]] = set()
182 mapping = {p: choices for p in pl}
183 with InputTransaction('BanGirl', pl, mapping=mapping) as trans:
184 for p in pl:
185 c: CharChoice
186 c = g.user_input([p], ChooseGirlInputlet(self, mapping), timeout=30, trans=trans)
187 c = c or next(_c for _c in choices if not _c.chosen)
188 c.chosen = p
189 cls = c.char_cls
190 assert cls
191 banned.add(cls)
192 trans.notify('girl_chosen', (p, c))
194 assert len(banned) == 4
196 chars = [_c for _c in chars if _c not in banned]
198 g.random.shuffle(chars)
200 if g.is_client_side():
201 chars = [None] * len(chars)
203 mapping: Dict[Player, List[CharChoice]] = {}
205 for p in pl:
206 mapping[p] = [CharChoice(cls) for cls in chars[-4:]]
207 mapping[p][-1].akari = True
209 del chars[-4:]
211 p.reveal(mapping[p])
213 g.pause(1)
215 with InputTransaction('ChooseGirl', pl, mapping=mapping) as trans:
216 ilet = ChooseGirlInputlet(g, mapping)
218 @ilet.with_post_process
219 def process(p, c):
220 c = c or mapping[p][0]
221 trans.notify('girl_chosen', (p, c))
222 return c
224 rst = g.user_input(pl, ilet, timeout=30, type='all', trans=trans)
226 # reveal
227 g.players = BatchList()
229 for p in pl:
230 c = rst[p]
231 c.akari = False
232 pl.reveal(c)
233 assert c.char_cls
234 ch = c.char_cls(p)
235 g.players.append(ch)
237 g.refresh_dispatcher()
239 for p, ch in zip(pl, g.players):
240 assert p is ch.player
241 g.emit_event('switch_character', (p, ch))
243 # -------
244 for ch in g.players:
245 log.info(
246 '>> Player: %s:%s',
247 ch.__class__.__name__,
248 g.roles[ch.player].get().name,
249 )
250 # -------
252 g.forces = {}
253 for ch in g.players:
254 g.forces.setdefault(g.roles[ch.player].get(), BatchList()).append(ch)
256 g.emit_event('game_begin', g)
258 for ch in g.players:
259 g.process_action(DistributeCards(ch, amount=4))
261 for i, ch in enumerate(cycle(g.players)):
262 if i >= 6000: break
263 if not ch.dead:
264 try:
265 g.process_action(PlayerTurn(ch))
266 except InterruptActionFlow:
267 pass
269 return True
272class THBattle2v2(THBattle):
273 n_persons = 4
274 game_ehs = [
275 DeathHandler,
276 HeritageHandler,
277 ExtraCardHandler,
278 ]
279 bootstrap = THBattle2v2Bootstrap
280 params_def = {
281 'random_force': (True, False),
282 'draw_extra_card': (False, True),
283 }
285 forces: Dict[THB2v2Role, BatchList[Character]]
287 draw_extra: bool
289 def can_leave(g, p: Player):
290 for ch in g.players:
291 if ch.player is p:
292 return ch.dead
293 else:
294 return False