Coverage for thb/inputlets.py : 83%
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
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[Player] = []
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[Player] = []
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 = [g.deck.lookup(i) for i in cid_list]
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 = 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: 156 ↛ 157line 156 didn't jump to line 157, because the condition on line 156 was never true
157 log.debug('ChooseIndividualCardInputlet: detaching %r!', card)
158 card.detach()
160 return card
163class ChoosePeerCardInputlet(Inputlet):
164 def __init__(self, initiator: Any, target: Character, categories: Iterable[str]):
165 self.initiator = initiator
166 self.target = target
167 self.categories = categories
168 self.selected = None
170 def parse(self, data):
171 target = self.target
172 categories = self.categories
173 categories = [getattr(target, i) for i in categories]
175 assert all(c.owner is target for c in categories)
176 try:
177 check(sum(len(c) for c in categories)) # no cards at all
179 cid = data
180 g = self.game
182 check(isinstance(cid, int))
184 card = g.deck.lookup(cid)
185 check(card) # Invalid id
187 check(card.resides_in.owner is target)
188 check(card.resides_in in categories)
190 return card
192 except CheckFailed:
193 return None
195 def data(self):
196 sel = self.selected
197 return sel.sync_id if sel else None
199 def set_card(self, c):
200 assert c.resides_in.type in self.categories
201 self.selected = c
203 def post_process(self, actor, card):
204 if card:
205 log.debug('ChoosePeerCardInputlet: detaching %r!', card)
206 card.detach()
208 return card
211class ProphetInputlet(Inputlet):
212 '''For Ran'''
213 def __init__(self, initiator: Any, cards: List[Card]):
214 self.initiator = initiator
215 self.cards = cards
216 self.upcards: List[Card] = []
217 self.downcards: List[Card] = []
219 def parse(self, data):
220 try:
221 check_type([[int, ...]] * 2, data)
222 upcards = data[0]
223 downcards = data[1]
224 check(sorted(upcards + downcards) == list(range(len(self.cards))))
225 except CheckFailed:
226 return [self.cards, []]
228 cards = self.cards
229 upcards = [cards[i] for i in upcards]
230 downcards = [cards[i] for i in downcards]
232 return [upcards, downcards]
234 def data(self):
235 cards = self.cards
236 upcards = self.upcards
237 downcards = self.downcards
238 if not set(cards) == set(upcards + downcards): 238 ↛ 241line 238 didn't jump to line 241, because the condition on line 238 was never false
239 return [list(range(len(self.cards))), []]
241 upcards = [cards.index(c) for c in upcards]
242 downcards = [cards.index(c) for c in downcards]
243 return [upcards, downcards]
245 def set_result(self, upcards, downcards):
246 assert set(self.cards) == set(upcards + downcards)
247 self.upcards = upcards
248 self.downcards = downcards
251class ChooseGirlInputlet(Inputlet):
252 def __init__(self, initiator: Any, mapping: Dict[Player, List[CharChoice]]):
253 self.initiator = initiator
255 m = dict(mapping)
256 for k in m:
257 assert all([isinstance(i, CharChoice) for i in m[k]])
258 m[k] = m[k][:]
260 self.mapping = m
261 self.choice = None
263 def parse(self, i):
264 m = self.mapping
265 actor = self.actor
266 try:
267 check(actor in m)
268 check_type(int, i)
269 check(0 <= i < len(m[actor]))
270 choice = m[actor][i]
271 check(not choice.chosen)
272 return choice
273 except CheckFailed:
274 return None
276 def data(self):
277 if self.choice is None: 277 ↛ 280line 277 didn't jump to line 280, because the condition on line 277 was never false
278 return None
280 try:
281 return self.mapping[self.actor].index(self.choice)
282 except Exception:
283 log.exception('WTF?!')
284 return None
286 def set_choice(self, choice):
287 assert choice in self.mapping[self.actor]
288 self.choice = choice
291class SortCharacterInputlet(Inputlet):
292 def __init__(self, initiator: Any, mapping: Dict[Player, List[CharChoice]], limit: int = 10000):
293 self.initiator = initiator
295 s = {len(l) for l in list(mapping.values())}
296 assert(len(s) == 1)
297 self.num = n = s.pop()
298 self.limit = limit if n >= limit else n
299 self.mapping = mapping
300 self.result = list(range(n))
302 def parse(self, data):
303 n = self.num
304 try:
305 check(data)
306 check_type([int] * n, data)
307 check(set(data) == set(range(n)))
308 return data
310 except CheckFailed:
311 return list(range(n))
313 def data(self):
314 assert set(self.result) == set(range(self.num))
315 return self.result
317 def set_result(self, result):
318 assert set(result) == set(range(self.num))
319 self.result = result
322class HopeMaskInputlet(Inputlet):
323 '''For Kokoro'''
324 def __init__(self, initiator: Any, cards: List[Card]):
325 self.initiator = initiator
326 self.cards = cards
327 self.putback: List[Card] = []
328 self.acquire: List[Card] = []
330 def parse(self, data):
331 try:
332 check_type([[int, ...]] * 2, data)
333 putback = data[0]
334 acquire = data[1]
335 check(sorted(putback+acquire) == list(range(len(self.cards))))
337 cards = self.cards
338 putback = [cards[i] for i in putback]
339 acquire = [cards[i] for i in acquire]
341 except CheckFailed:
342 return [self.cards, []]
344 return [putback, acquire]
346 def is_valid(self, putback, acquire):
347 if not set(self.cards) == set(putback + acquire): 347 ↛ 348line 347 didn't jump to line 348, because the condition on line 347 was never true
348 return False
350 if acquire: 350 ↛ 351line 350 didn't jump to line 351, because the condition on line 350 was never true
351 suit = acquire[0].suit
352 if not all([c.suit == suit for c in acquire]):
353 return False
355 return True
357 def data(self):
358 cards = self.cards
359 putback = self.putback
360 acquire = self.acquire
361 if not set(cards) == set(putback + acquire): 361 ↛ 364line 361 didn't jump to line 364, because the condition on line 361 was never false
362 return [list(range(len(self.cards))), []]
364 putback = [cards.index(c) for c in putback]
365 acquire = [cards.index(c) for c in acquire]
366 return [putback, acquire]
368 def set_result(self, putback, acquire):
369 assert self.is_valid(putback, acquire)
370 self.putback = putback
371 self.acquire = acquire
373 def post_process(self, actor, rst):
374 g = self.game
375 putback, acquire = rst
376 g.players.exclude(actor).reveal(acquire)
378 try:
379 check(self.is_valid(putback, acquire))
380 except CheckFailed:
381 return [self.cards, []]
383 return rst
386class HopeMaskKOFInputlet(HopeMaskInputlet):
388 @classmethod
389 def tag(cls):
390 return 'HopeMask'
392 def is_valid(self, putback, acquire):
393 if not set(self.cards) == set(putback + acquire): 393 ↛ 394line 393 didn't jump to line 394, because the condition on line 393 was never true
394 return False
396 if len(acquire) > 1: 396 ↛ 397line 396 didn't jump to line 397, because the condition on line 396 was never true
397 return False
399 return True
402class GalgameDialogInputlet(Inputlet):
403 def __init__(self, initiator: Any, character: Character, dialog: str, voice: str):
404 self.initiator = initiator
405 self.character = character
406 self.dialog = dialog
407 self.result = None
408 self.voice = voice
410 def parse(self, data):
411 return data
413 def data(self):
414 return self.result
416 def set_result(self, value):
417 'For UI'
418 self.result = value
420 def __repr__(self):
421 return f'<{self.dialog}>'