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 itertools import chain, combinations as C, permutations as P 

6from typing import Any, cast 

7import logging 

8import random 

9 

10# -- third party -- 

11import gevent 

12 

13# -- own -- 

14from game.base import EventHandler 

15from thb.actions import ActionStageLaunchCard, CardChooser, skill_wrap, CharacterChooser 

16from thb.inputlets import ActionInputlet, ChooseOptionInputlet, ChoosePeerCardInputlet 

17 

18 

19# -- code -- 

20log = logging.getLogger('UserInputFuzzingHandler') 

21 

22 

23def let_it_go(*cores): 

24 while True: 

25 gevent.idle(-100) 

26 

27 for i in range(10): 27 ↛ 33line 27 didn't jump to line 33, because the loop on line 27 didn't complete

28 if any(c.server._ep.active for c in cores): 

29 break 

30 

31 gevent.sleep(0.05) 

32 else: 

33 raise Exception('STUCK!') 

34 

35 for c in cores: 

36 c.server._ep.active = False 

37 

38 

39class UserInputFuzzingHandler(EventHandler): 

40 

41 def handle(self, evt: str, arg: Any) -> Any: 

42 if evt == 'user_input': 

43 trans, ilet = arg 

44 self.react(trans, ilet) 

45 elif evt in ('action_before', 'action_apply', 'action_after'): 

46 self.effect_meta(evt, arg) 

47 

48 return arg 

49 

50 def effect_meta(self, evt: str, act: Any): 

51 while hasattr(act, 'ui_meta'): 

52 _type = { 

53 'action_before': 'effect_string_before', 

54 'action_apply': 'effect_string_apply', 

55 'action_after': 'effect_string', 

56 }[evt] 

57 prompt = getattr(act.ui_meta, _type, None) 

58 if not prompt: return 

59 prompt(act) 

60 break 

61 

62 while hasattr(act, 'ui_meta'): 

63 if evt == 'action_before': 

64 rays = getattr(act.ui_meta, 'ray', None) 

65 rays = rays(act) if rays else [] 

66 

67 _type = { 

68 'action_before': 'sound_effect_before', 

69 'action_apply': 'sound_effect_apply', 

70 'action_after': 'sound_effect', 

71 }[evt] 

72 se = getattr(act.ui_meta, _type, None) 

73 se = se and se(act) 

74 break 

75 

76 def react(self, trans, ilet): 

77 p = ilet.actor 

78 g = self.game 

79 

80 if trans.name == 'ActionStageAction': 

81 if random.random() < 0.05: 

82 return False 

83 

84 cards = list(p.showncards) + list(p.cards) + list(p.equips) 

85 

86 while random.random() < 0.5: 

87 # Skill 

88 skl = [sk for sk in p.skills if 't_None' not in sk.target.__name__] 

89 [sk.ui_meta.clickable() for sk in skl] 

90 if skl: 

91 sk = random.choice(skl) 

92 else: 

93 break 

94 

95 # Card 

96 cc = list(chain(*[C(cards, i) for i in range(4)])) 

97 random.shuffle(cc) 

98 

99 for cl in cc: 

100 for tl in self.possible_targets(g, p, sk): 

101 c = skill_wrap(p, [sk], cl, {}) 

102 

103 ok, reason = self.ui_meta_walk_wrapped([c]) 

104 if not ok: 104 ↛ 105line 104 didn't jump to line 105, because the condition on line 104 was never true

105 assert not c.check(), (c, c.associated_cards, c.check(), ok, reason) 

106 continue 

107 

108 try: 

109 tl, ok = c.target(p, tl) 

110 except Exception as e: 

111 raise Exception(f"{c}.target: {c.target} failed") from e 

112 

113 try: 

114 ok2, reason = c.ui_meta.is_action_valid(c, tl) 

115 except Exception as e: 

116 raise Exception(f"{c}.ui_meta.is_action_valid failed") from e 

117 

118 # This can happen 

119 # assert (ok and bool(c.check())) == ok2, { 

120 # 'ok': ok, 

121 # 'c.check()': c.check(), 

122 # 'ok2': ok2, 

123 # 'c': c, 

124 # 'tl': tl, 

125 # 'reason': reason, 

126 # } 

127 

128 if not ok: 

129 continue 

130 

131 # This can happen too 

132 # assert c.ui_meta.clickable(), c 

133 

134 if self.try_launch(ilet, cl, tl, skills=[sk]): 

135 return 

136 break 

137 

138 for c in cards: 

139 if not c.associated_action: 

140 continue 

141 

142 for t in self.possible_targets(g, p, c): 

143 if self.try_launch(ilet, [c], t): 

144 return True 

145 

146 elif trans.name in ('Action', 'AskForRejectAction') and isinstance(ilet, ActionInputlet): 

147 rst = { 

148 'skills': [], 

149 'cards': [], 

150 'characters': [], 

151 } 

152 

153 if ilet.categories: 

154 initiator = cast(CardChooser, ilet.initiator) 

155 cond = initiator.cond 

156 cl = list(p.showncards) + list(p.cards) 

157 

158 found = False 

159 for skcls in ilet.actor.skills: 

160 sk = skcls(ilet.actor) 

161 if not sk.ui_meta.clickable(): 

162 continue 

163 

164 uiok, uireason = self.ui_meta_walk_wrapped([sk]) 

165 cct_ok, cct_reason = initiator.ui_meta.choose_card_text(initiator, [sk]) 

166 if cond([sk]) and random.random() < 0.5: 

167 for c in chain([], C(cl, 1), C(cl, 2), [cl]): 167 ↛ 177line 167 didn't jump to line 177, because the loop on line 167 didn't complete

168 sk = skill_wrap(p, [skcls], c, {}) 

169 uiok, uireason = self.ui_meta_walk_wrapped([sk]) 

170 assert uiok 

171 assert cond([sk]) 

172 rst['skills'] = [skcls] 

173 rst['cards'] = c 

174 found = True 

175 break 

176 

177 if found: 

178 break 

179 else: 

180 for c in chain(C(cl, 1), C(cl, 2), [cl]): 

181 cct_ok, cct_reason = initiator.ui_meta.choose_card_text(initiator, c) 

182 if cond(c): 

183 assert cct_ok, (c, cct_reason) 

184 rst['cards'] = c 

185 break 

186 

187 if ilet.candidates: 

188 initiator = cast(CharacterChooser, ilet.initiator) 

189 target = initiator.choose_player_target 

190 # (self, pl: Sequence[Character]) -> Tuple[List[Character], bool]: ... 

191 pl = ilet.candidates 

192 for p in chain(C(pl, 1), C(pl, 2), [pl]): 

193 p, ok = target(p) 

194 ccp_ok, ccp_reason = initiator.ui_meta.target(p) 

195 if ok: 

196 assert ccp_ok, (p, ccp_reason) 

197 rst['characters'] = p 

198 break 

199 

200 if rst: 200 ↛ 203line 200 didn't jump to line 203, because the condition on line 200 was never false

201 ilet.set_result(**rst) 

202 

203 return True 

204 

205 elif trans.name == 'ChooseOption' and isinstance(ilet, ChooseOptionInputlet): 

206 ilet.set_option(random.choice(list(ilet.options) * 2 + [None])) 

207 

208 elif trans.name == 'ChoosePeerCard' and isinstance(ilet, ChoosePeerCardInputlet): 

209 tgt = ilet.target 

210 if random.random() < 0.9: 

211 cats = [getattr(tgt, i) for i in ilet.categories] 

212 cl = list(chain(*cats)) 

213 if cl: 

214 ilet.set_card(random.choice(cl)) 

215 elif trans.name == 'SortCharacter': 

216 pass 

217 elif trans.name == 'ChooseGirl': 

218 from settings import TESTING_CHARACTERS as TESTS 

219 choices = [c for c in ilet.mapping[ilet.actor] if c.char_cls and c.char_cls.__name__ in TESTS] 

220 if choices: 220 ↛ 221line 220 didn't jump to line 221, because the condition on line 220 was never true

221 c = random.choice(choices) 

222 log.info('Got %s', c.char_cls) 

223 ilet.set_choice(c) 

224 elif trans.name == 'HarvestChoose': 

225 pass 

226 elif trans.name == 'Pindian': 

227 pass 

228 elif trans.name == 'HopeMask': 

229 pass 

230 elif trans.name == 'Prophet': 

231 pass 

232 elif trans.name == 'ChooseIndividualCard': 232 ↛ 233line 232 didn't jump to line 233, because the condition on line 232 was never true

233 pass 

234 elif trans.name == 'BanGirl': 

235 pass 

236 elif trans.name == 'GalgameDialog': 236 ↛ 239line 236 didn't jump to line 239, because the condition on line 236 was never false

237 pass 

238 else: 

239 log.warning('Not processing %s transaction', trans.name) 

240 1/0 

241 

242 def ui_meta_walk_wrapped(self, cl, check_is_complete=False): 

243 from thb.cards.base import Skill 

244 for c in cl: 

245 if not isinstance(c, Skill): 

246 continue 

247 

248 if check_is_complete: 248 ↛ 249line 248 didn't jump to line 249, because the condition on line 248 was never true

249 rst, reason = c.ui_meta.is_complete(c) 

250 if not rst: 

251 return rst, reason 

252 

253 rst, reason = self.ui_meta_walk_wrapped(c.associated_cards, True) 

254 if not rst: 254 ↛ 255line 254 didn't jump to line 255, because the condition on line 254 was never true

255 return rst, reason 

256 

257 return True, 'OK' 

258 

259 def try_launch(self, ilet, cl, tl, skills=[]): 

260 p = ilet.actor 

261 if skills: 

262 c = skill_wrap(p, skills, cl, {}) 

263 else: 

264 assert len(cl) == 1, cl 

265 c, = cl 

266 

267 act = ActionStageLaunchCard(p, tl, c) 

268 if act.can_fire(): 

269 ilet.set_result(skills=skills, cards=cl, characters=tl) 

270 return True 

271 

272 return False 

273 

274 def possible_targets(self, g, me, c): 

275 target = c.target 

276 tn = target.__name__ 

277 pl = g.players 

278 

279 if tn == 't_None': 279 ↛ 280line 279 didn't jump to line 280, because the condition on line 279 was never true

280 raise Exception('Fuck') 

281 

282 elif tn == 't_Self': 282 ↛ 283line 282 didn't jump to line 283, because the condition on line 282 was never true

283 rl = [[me]] 

284 

285 elif tn == 't_OtherOne': 285 ↛ 286line 285 didn't jump to line 286, because the condition on line 285 was never true

286 rl = list(pl) 

287 random.shuffle(rl) 

288 rl = [[i] for i in rl if i is not me] 

289 

290 elif tn == 't_One': 290 ↛ 291line 290 didn't jump to line 291, because the condition on line 290 was never true

291 rl = list(pl) 

292 random.shuffle(rl) 

293 rl = [[i] for i in rl] 

294 

295 elif tn == 't_All': 295 ↛ 296line 295 didn't jump to line 296, because the condition on line 295 was never true

296 rl = list(pl) 

297 rl.remove(me) 

298 rl = [rl] 

299 

300 elif tn == 't_AllInclusive': 300 ↛ 301line 300 didn't jump to line 301, because the condition on line 300 was never true

301 rl = [pl] 

302 

303 elif tn == '_t_OtherLessEqThanN': 303 ↛ 304line 303 didn't jump to line 304, because the condition on line 303 was never true

304 n = target._for_test_OtherLessEqThanN 

305 rl = [] 

306 for i in range(n+1): 

307 rl.extend(list(P(pl, i))) 

308 random.shuffle(rl) 

309 

310 elif tn == 't_OneOrNone': 310 ↛ 311line 310 didn't jump to line 311, because the condition on line 310 was never true

311 rl = [[i] for i in pl] 

312 rl.append([]) 

313 random.shuffle(rl) 

314 

315 elif tn == '_t_OtherN': 315 ↛ 316line 315 didn't jump to line 316, because the condition on line 315 was never true

316 n = target._for_test_OtherN 

317 rl = list(P(pl, n)) 

318 random.shuffle(rl) 

319 rl = [list(i) for i in rl] 

320 else: 

321 rl = [] 

322 for i in range(3): # HACK: should be enough 

323 rl.extend(list(P(pl, i))) 

324 random.shuffle(rl) 

325 

326 return rl