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 

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

130class 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 ↛ 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() 

159 

160 return card 

161 

162 

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 

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 

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

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 ↛ 241line 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 

251class 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 ↛ 280line 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 

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

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

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 ↛ 348line 347 didn't jump to line 348, because the condition on line 347 was never true

348 return False 

349 

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 

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 ↛ 364line 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 

386class 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): 

394 return False 

395 

396 if len(acquire) > 1: 

397 return False 

398 

399 return True 

400 

401 

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 

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