Coverage for thb/inputlets.py : 18%
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 typing import Any, Dict, Iterable, List, TYPE_CHECKING, Type, Union, Optional
6import logging
8# -- third party --
9# -- own --
10from game.base import Inputlet, Player
11from thb.cards.base import Card, Skill
12from thb.common import CharChoice
13from utils.check import CheckFailed, check, check_type
15# -- typing --
16if TYPE_CHECKING:
17 from thb.actions import CardChooser, CharacterChooser # noqa: F401
18 from thb.characters.base import Character # noqa: F401
19 from thb.mode import THBattle # noqa: F401
22# -- code --
23log = logging.getLogger('Inputlets')
26class ChooseOptionInputlet(Inputlet):
27 def __init__(self, initiator: Any, options: Iterable):
28 self.initiator = initiator
29 self.options = options
30 self.result = None
32 def parse(self, data):
33 if data not in self.options:
34 return None
36 return data
38 def data(self):
39 return self.result
41 def set_option(self, value):
42 'For UI'
43 self.result = value
46class ActionInputlet(Inputlet):
47 initiator: Union[CardChooser, CharacterChooser]
48 actor: Character
49 game: THBattle
51 def __init__(self, initiator: Union[CardChooser, CharacterChooser], categories: Iterable[str], candidates: Iterable[object]):
52 self.initiator = initiator
54 self.categories = categories
55 self.candidates = candidates
57 self.skills: List[Type[Skill]] = []
58 self.cards: List[Card] = []
59 self.characters: List[Character] = []
60 self.params: Dict[str, Any] = {}
62 def parse(self, data):
63 # data = [
64 # [skill_index1, ...],
65 # [card_sync_id1, ...],
66 # [player_id1, ...],
67 # {'action_param1': 'AttackCard'},
68 # ]
70 actor = self.actor
71 g = self.game
72 categories = self.categories
73 categories = [getattr(actor, i) for i in categories] if categories else None
74 candidates = self.candidates
76 skills: List[Type[Skill]] = []
77 cards: List[Card] = []
78 characters: List[Character] = []
79 params: Dict[str, Any] = {}
81 try:
82 check_type([[int, ...]] * 3 + [dict], data) # type: ignore
84 sid_list, cid_list, pid_list, params = data
86 if candidates:
87 check(candidates)
88 check(all(0 <= i < len(g.players) for i in pid_list))
89 pl = [g.players[i] for i in pid_list]
90 check(all([p in candidates for p in pl]))
91 characters = pl
93 if categories:
94 cards = [c for c in [g.deck.lookup(i) for i in cid_list] if c]
95 check(len(cards) == len(cid_list)) # Invalid id
97 cs = set(cards)
98 check(len(cs) == len(cid_list)) # repeated ids
100 if sid_list:
101 assert actor.cards in categories or actor.showncards in categories
102 check(all(cat.owner is actor for cat in categories))
103 check(all(c.resides_in.owner is actor for c in cards)) # Cards belong to actor?
104 for skill_id in sid_list:
105 check(0 <= skill_id < len(actor.skills))
106 skills = [actor.skills[i] for i in sid_list]
107 else:
108 check(all(c.resides_in in categories for c in cards)) # Cards in desired categories?
110 return (skills, cards, characters, params)
112 except CheckFailed:
113 return None
115 def data(self):
116 g = self.game
117 actor_skills = self.actor.skills
118 sid_list = [actor_skills.index(s) for s in self.skills]
119 cid_list = [c.sync_id for c in self.cards]
120 pid_list = [g.players.index(p) for p in self.characters]
121 return [sid_list, cid_list, pid_list, self.params]
123 def set_result(self, skills, cards, characters, params=None):
124 self.skills = skills
125 self.cards = cards
126 self.characters = characters
127 self.params = params or {}
130class ChooseIndividualCardInputlet(Inputlet):
131 def __init__(self, initiator: Any, cards: List[Card]):
132 self.initiator = initiator
133 self.cards = cards
134 self.selected: Optional[Card] = None
136 def parse(self, data):
137 try:
138 cid = data
139 check(isinstance(cid, int))
140 cards = [c for c in self.cards if c.sync_id == cid]
141 check(len(cards)) # Invalid id
142 return cards[0]
144 except CheckFailed:
145 return None
147 def data(self):
148 sel = self.selected
149 return sel.sync_id if sel else None
151 def set_card(self, c):
152 assert c in self.cards
153 self.selected = c
155 def post_process(self, actor, card):
156 if card:
157 log.debug('ChooseIndividualCardInputlet: detaching %r!', card)
158 card.detach()
160 return card
163class ChoosePeerCardInputlet(Inputlet):
164 game: THBattle
166 def __init__(self, initiator: Any, target: Character, categories: Iterable[str]):
167 self.initiator = initiator
168 self.target = target
169 self.categories = categories
170 self.selected: Optional[Card] = None
172 def parse(self, data):
173 target = self.target
174 categories = self.categories
175 categories = [getattr(target, i) for i in categories]
177 assert all(c.owner is target for c in categories)
178 try:
179 check(sum(len(c) for c in categories)) # no cards at all
181 cid = data
182 g = self.game
184 check(isinstance(cid, int))
186 card = g.deck.lookup(cid)
187 if not card:
188 # not using check(...) because stupid mypy
189 raise CheckFailed # Invalid id
191 check(card.resides_in.owner is target)
192 check(card.resides_in in categories)
194 return card
196 except CheckFailed:
197 return None
199 def data(self) -> Optional[int]:
200 sel = self.selected
201 return sel.sync_id if sel else None
203 def set_card(self, c):
204 assert c.resides_in.type in self.categories
205 self.selected = c
207 def post_process(self, actor, card):
208 if card:
209 log.debug('ChoosePeerCardInputlet: detaching %r!', card)
210 card.detach()
212 return card
215class ProphetInputlet(Inputlet):
216 '''For Ran'''
217 def __init__(self, initiator: Any, cards: List[Card]):
218 self.initiator = initiator
219 self.cards = cards
220 self.upcards: List[Card] = []
221 self.downcards: List[Card] = []
223 def parse(self, data):
224 try:
225 check_type([[int, ...]] * 2, data)
226 upcards = data[0]
227 downcards = data[1]
228 check(sorted(upcards + downcards) == list(range(len(self.cards))))
229 except CheckFailed:
230 return [self.cards, []]
232 cards = self.cards
233 upcards = [cards[i] for i in upcards]
234 downcards = [cards[i] for i in downcards]
236 return [upcards, downcards]
238 def data(self):
239 cards = self.cards
240 upcards = self.upcards
241 downcards = self.downcards
242 if not set(cards) == set(upcards + downcards):
243 return [list(range(len(self.cards))), []]
245 upcards = [cards.index(c) for c in upcards]
246 downcards = [cards.index(c) for c in downcards]
247 return [upcards, downcards]
249 def set_result(self, upcards, downcards):
250 assert set(self.cards) == set(upcards + downcards)
251 self.upcards = upcards
252 self.downcards = downcards
255class ChooseGirlInputlet(Inputlet):
257 def __init__(self, initiator: Any, mapping: Dict[Player, List[CharChoice]]):
258 self.initiator = initiator
260 m = dict(mapping)
261 for k in m:
262 assert all([isinstance(i, CharChoice) for i in m[k]])
263 m[k] = m[k][:]
265 self.mapping: Dict[Player, List[CharChoice]] = m
266 self.choice = None
268 def parse(self, i):
269 m = self.mapping
270 actor = self.actor
271 try:
272 check(actor in m)
273 check_type(int, i)
274 check(0 <= i < len(m[actor]))
275 choice = m[actor][i]
276 check(not choice.chosen)
277 return choice
278 except CheckFailed:
279 return None
281 def data(self):
282 if self.choice is None:
283 return None
285 try:
286 return self.mapping[self.actor].index(self.choice)
287 except Exception:
288 log.exception('WTF?!')
289 return None
291 def set_choice(self, choice):
292 assert choice in self.mapping[self.actor]
293 self.choice = choice
296class SortCharacterInputlet(Inputlet):
297 def __init__(self, initiator: Any, mapping: Dict[Player, List[CharChoice]], limit: int = 10000):
298 self.initiator = initiator
300 s = {len(l) for l in list(mapping.values())}
301 assert(len(s) == 1)
302 self.num = n = s.pop()
303 self.limit = limit if n >= limit else n
304 self.mapping = mapping
305 self.result = list(range(n))
307 def parse(self, data):
308 n = self.num
309 try:
310 check(data)
311 check_type([int] * n, data)
312 check(set(data) == set(range(n)))
313 return data
315 except CheckFailed:
316 return list(range(n))
318 def data(self):
319 assert set(self.result) == set(range(self.num))
320 return self.result
322 def set_result(self, result):
323 assert set(result) == set(range(self.num))
324 self.result = result
327class HopeMaskInputlet(Inputlet):
328 '''For Kokoro'''
329 game: THBattle
331 def __init__(self, initiator: Any, cards: List[Card]):
332 self.initiator = initiator
333 self.cards = cards
334 self.putback: List[Card] = []
335 self.acquire: List[Card] = []
337 def parse(self, data):
338 try:
339 check_type([[int, ...]] * 2, data)
340 putback = data[0]
341 acquire = data[1]
342 check(sorted(putback+acquire) == list(range(len(self.cards))))
344 cards = self.cards
345 putback = [cards[i] for i in putback]
346 acquire = [cards[i] for i in acquire]
348 except CheckFailed:
349 return [self.cards, []]
351 return [putback, acquire]
353 def is_valid(self, putback, acquire):
354 if not set(self.cards) == set(putback + acquire):
355 return False
357 if acquire:
358 suit = acquire[0].suit
359 if not all([c.suit == suit for c in acquire]):
360 return False
362 return True
364 def data(self):
365 cards = self.cards
366 putback = self.putback
367 acquire = self.acquire
368 if not set(cards) == set(putback + acquire):
369 return [list(range(len(self.cards))), []]
371 putback = [cards.index(c) for c in putback]
372 acquire = [cards.index(c) for c in acquire]
373 return [putback, acquire]
375 def set_result(self, putback, acquire):
376 assert self.is_valid(putback, acquire)
377 self.putback = putback
378 self.acquire = acquire
380 def post_process(self, actor, rst):
381 g = self.game
382 putback, acquire = rst
383 g.players.exclude(actor).reveal(acquire)
385 try:
386 check(self.is_valid(putback, acquire))
387 except CheckFailed:
388 return [self.cards, []]
390 return rst
393class HopeMaskKOFInputlet(HopeMaskInputlet):
395 @classmethod
396 def tag(cls):
397 return 'HopeMask'
399 def is_valid(self, putback, acquire):
400 if not set(self.cards) == set(putback + acquire):
401 return False
403 if len(acquire) > 1:
404 return False
406 return True
409class GalgameDialogInputlet(Inputlet):
410 def __init__(self, initiator: Any, character: Character, dialog: str, voice: str):
411 self.initiator = initiator
412 self.character = character
413 self.dialog = dialog
414 self.result = None
415 self.voice = voice
417 def parse(self, data):
418 return data
420 def data(self):
421 return self.result
423 def set_result(self, value):
424 'For UI'
425 self.result = value
427 def __repr__(self):
428 return f'<{self.dialog}>'