Hide keyboard shortcuts

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 

3 

4# -- stdlib -- 

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

6import logging 

7 

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 

14 

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 

20 

21 

22# -- code -- 

23log = logging.getLogger('Inputlets') 

24 

25 

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

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

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 

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 

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

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

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 

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

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

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

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

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 

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