# Coverage for thb/inputlets.py : 83%

## 296 statements 253 run 43 missing 6 excluded 8 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

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

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

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 = [g.deck.lookup(i) for i in cid_list]

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 = 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: 156 ↛ 157 line 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()

159

160 return card

161

162

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

169

170 def parse(self, data):

171 target = self.target

172 categories = self.categories

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

174

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

178

179 cid = data

180 g = self.game

181

182 check(isinstance(cid, int))

183

184 card = g.deck.lookup(cid)

185 check(card) # Invalid id

186

187 check(card.resides_in.owner is target)

188 check(card.resides_in in categories)

189

190 return card

191

192 except CheckFailed:

193 return None

194

195 def data(self):

196 sel = self.selected

197 return sel.sync_id if sel else None

198

199 def set_card(self, c):

200 assert c.resides_in.type in self.categories

201 self.selected = c

202

203 def post_process(self, actor, card):

204 if card:

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

206 card.detach()

207

208 return card

209

210

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

218

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

227

228 cards = self.cards

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

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

231

232 return [upcards, downcards]

233

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 ↛ 241 line 238 didn't jump to line 241, because the condition on line 238 was never false

239 return [list(range(len(self.cards))), []]

240

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

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

243 return [upcards, downcards]

244

245 def set_result(self, upcards, downcards):

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

247 self.upcards = upcards

248 self.downcards = downcards

249

250

251 class ChooseGirlInputlet(Inputlet):

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

253 self.initiator = initiator

254

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

259

260 self.mapping = m

261 self.choice = None

262

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

275

276 def data(self):

277 if self.choice is None: 277 ↛ 280 line 277 didn't jump to line 280, because the condition on line 277 was never false

278 return None

279

280 try:

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

282 except Exception:

283 log.exception('WTF?!')

284 return None

285

286 def set_choice(self, choice):

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

288 self.choice = choice

289

290

291 class SortCharacterInputlet(Inputlet):

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

293 self.initiator = initiator

294

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

301

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

309

310 except CheckFailed:

311 return list(range(n))

312

313 def data(self):

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

315 return self.result

316

317 def set_result(self, result):

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

319 self.result = result

320

321

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

329

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

336

337 cards = self.cards

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

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

340

341 except CheckFailed:

342 return [self.cards, []]

343

344 return [putback, acquire]

345

346 def is_valid(self, putback, acquire):

347 if not set(self.cards) == set(putback + acquire): 347 ↛ 348 line 347 didn't jump to line 348, because the condition on line 347 was never true

348 return False

349

350 if acquire: 350 ↛ 351 line 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

354

355 return True

356

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 ↛ 364 line 361 didn't jump to line 364, because the condition on line 361 was never false

362 return [list(range(len(self.cards))), []]

363

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

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

366 return [putback, acquire]

367

368 def set_result(self, putback, acquire):

369 assert self.is_valid(putback, acquire)

370 self.putback = putback

371 self.acquire = acquire

372

373 def post_process(self, actor, rst):

374 g = self.game

375 putback, acquire = rst

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

377

378 try:

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

380 except CheckFailed:

381 return [self.cards, []]

382

383 return rst

384

385

386 class HopeMaskKOFInputlet(HopeMaskInputlet):

387

388 @classmethod

389 def tag(cls):

390 return 'HopeMask'

391

392 def is_valid(self, putback, acquire):

393 if not set(self.cards) == set(putback + acquire): 393 ↛ 394 line 393 didn't jump to line 394, because the condition on line 393 was never true

394 return False

395

396 if len(acquire) > 1: 396 ↛ 397 line 396 didn't jump to line 397, because the condition on line 396 was never true

397 return False

398

399 return True

400

401

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

409

410 def parse(self, data):

411 return data

412

413 def data(self):

414 return self.result

415

416 def set_result(self, value):

417 'For UI'

418 self.result = value

419

420 def __repr__(self):

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