# Coverage for thb/inputlets.py : 18%

## 299 statements 70 run 229 missing 6 excluded 0 partial

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 -*-

2 from __future__ import annotations

3

4# -- stdlib --

5 from typing import Any, Dict, Iterable, List, TYPE_CHECKING, Type, Union, Optional

6 import logging

7

8# -- third party --

9# -- own --

10 from game.base import Inputlet, Player

11 from thb.cards.base import Card, Skill

12 from thb.common import CharChoice

13 from utils.check import CheckFailed, check, check_type

14

15# -- typing --

16 if 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

20

21

22# -- code --

23 log = logging.getLogger('Inputlets')

24

25

26 class ChooseOptionInputlet(Inputlet):

27 def __init__(self, initiator: Any, options: Iterable):

28 self.initiator = initiator

29 self.options = options

30 self.result = None

31

32 def parse(self, data):

33 if data not in self.options:

34 return None

35

36 return data

37

38 def data(self):

39 return self.result

40

41 def set_option(self, value):

42 'For UI'

43 self.result = value

44

45

46 class ActionInputlet(Inputlet):

47 initiator: Union[CardChooser, CharacterChooser]

48 actor: Character

49 game: THBattle

50

51 def __init__(self, initiator: Union[CardChooser, CharacterChooser], categories: Iterable[str], candidates: Iterable[object]):

52 self.initiator = initiator

53

54 self.categories = categories

55 self.candidates = candidates

56

57 self.skills: List[Type[Skill]] = []

58 self.cards: List[Card] = []

59 self.characters: List[Character] = []

60 self.params: Dict[str, Any] = {}

61

62 def parse(self, data):

63 # data = [

64 # [skill_index1, ...],

65 # [card_sync_id1, ...],

66 # [player_id1, ...],

67 # {'action_param1': 'AttackCard'},

68 # ]

69

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

75

76 skills: List[Type[Skill]] = []

77 cards: List[Card] = []

78 characters: List[Character] = []

79 params: Dict[str, Any] = {}

80

81 try:

82 check_type([[int, ...]] * 3 + [dict], data) # type: ignore

83

84 sid_list, cid_list, pid_list, params = data

85

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

92

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

96

97 cs = set(cards)

98 check(len(cs) == len(cid_list)) # repeated ids

99

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?

109

110 return (skills, cards, characters, params)

111

112 except CheckFailed:

113 return None

114

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]

122

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 {}

128

129

130 class 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

135

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]

143

144 except CheckFailed:

145 return None

146

147 def data(self):

148 sel = self.selected

149 return sel.sync_id if sel else None

150

151 def set_card(self, c):

152 assert c in self.cards

153 self.selected = c

154

155 def post_process(self, actor, card):

156 if card:

157 log.debug('ChooseIndividualCardInputlet: detaching %r!', card)

158 card.detach()

159

160 return card

161

162

163 class ChoosePeerCardInputlet(Inputlet):

164 game: THBattle

165

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

171

172 def parse(self, data):

173 target = self.target

174 categories = self.categories

175 categories = [getattr(target, i) for i in categories]

176

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

180

181 cid = data

182 g = self.game

183

184 check(isinstance(cid, int))

185

186 card = g.deck.lookup(cid)

187 if not card:

188 # not using check(...) because stupid mypy

189 raise CheckFailed # Invalid id

190

191 check(card.resides_in.owner is target)

192 check(card.resides_in in categories)

193

194 return card

195

196 except CheckFailed:

197 return None

198

199 def data(self) -> Optional[int]:

200 sel = self.selected

201 return sel.sync_id if sel else None

202

203 def set_card(self, c):

204 assert c.resides_in.type in self.categories

205 self.selected = c

206

207 def post_process(self, actor, card):

208 if card:

209 log.debug('ChoosePeerCardInputlet: detaching %r!', card)

210 card.detach()

211

212 return card

213

214

215 class 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] = []

222

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, []]

231

232 cards = self.cards

233 upcards = [cards[i] for i in upcards]

234 downcards = [cards[i] for i in downcards]

235

236 return [upcards, downcards]

237

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))), []]

244

245 upcards = [cards.index(c) for c in upcards]

246 downcards = [cards.index(c) for c in downcards]

247 return [upcards, downcards]

248

249 def set_result(self, upcards, downcards):

250 assert set(self.cards) == set(upcards + downcards)

251 self.upcards = upcards

252 self.downcards = downcards

253

254

255 class ChooseGirlInputlet(Inputlet):

256

257 def __init__(self, initiator: Any, mapping: Dict[Player, List[CharChoice]]):

258 self.initiator = initiator

259

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][:]

264

265 self.mapping: Dict[Player, List[CharChoice]] = m

266 self.choice = None

267

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

280

281 def data(self):

282 if self.choice is None:

283 return None

284

285 try:

286 return self.mapping[self.actor].index(self.choice)

287 except Exception:

288 log.exception('WTF?!')

289 return None

290

291 def set_choice(self, choice):

292 assert choice in self.mapping[self.actor]

293 self.choice = choice

294

295

296 class SortCharacterInputlet(Inputlet):

297 def __init__(self, initiator: Any, mapping: Dict[Player, List[CharChoice]], limit: int = 10000):

298 self.initiator = initiator

299

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))

306

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

314

315 except CheckFailed:

316 return list(range(n))

317

318 def data(self):

319 assert set(self.result) == set(range(self.num))

320 return self.result

321

322 def set_result(self, result):

323 assert set(result) == set(range(self.num))

324 self.result = result

325

326

327 class HopeMaskInputlet(Inputlet):

328 '''For Kokoro'''

329 game: THBattle

330

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] = []

336

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))))

343

344 cards = self.cards

345 putback = [cards[i] for i in putback]

346 acquire = [cards[i] for i in acquire]

347

348 except CheckFailed:

349 return [self.cards, []]

350

351 return [putback, acquire]

352

353 def is_valid(self, putback, acquire):

354 if not set(self.cards) == set(putback + acquire):

355 return False

356

357 if acquire:

358 suit = acquire[0].suit

359 if not all([c.suit == suit for c in acquire]):

360 return False

361

362 return True

363

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))), []]

370

371 putback = [cards.index(c) for c in putback]

372 acquire = [cards.index(c) for c in acquire]

373 return [putback, acquire]

374

375 def set_result(self, putback, acquire):

376 assert self.is_valid(putback, acquire)

377 self.putback = putback

378 self.acquire = acquire

379

380 def post_process(self, actor, rst):

381 g = self.game

382 putback, acquire = rst

383 g.players.exclude(actor).reveal(acquire)

384

385 try:

386 check(self.is_valid(putback, acquire))

387 except CheckFailed:

388 return [self.cards, []]

389

390 return rst

391

392

393 class HopeMaskKOFInputlet(HopeMaskInputlet):

394

395 @classmethod

396 def tag(cls):

397 return 'HopeMask'

398

399 def is_valid(self, putback, acquire):

400 if not set(self.cards) == set(putback + acquire):

401 return False

402

403 if len(acquire) > 1:

404 return False

405

406 return True

407

408

409 class 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

416

417 def parse(self, data):

418 return data

419

420 def data(self):

421 return self.result

422

423 def set_result(self, value):

424 'For UI'

425 self.result = value

426

427 def __repr__(self):

428 return f'<{self.dialog}>'