# Coverage for thb/thbnewbie.py : 96%

## 422 statements 412 run 10 missing 0 excluded 6 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 absolute_import, annotations, division, print_function, unicode_literals

3

4# -- stdlib --

5 from enum import Enum

6 from itertools import chain, combinations, cycle

7 from typing import Any, Callable, Dict, List, cast

8 import logging

9 import random

10

11# -- third party --

12# -- own --

13 from game.base import BootstrapAction, GameEnded, GameItem, InputTransaction, InterruptActionFlow

14 from game.base import NPC, Player

15 from thb.actions import ActionStage, ActionStageLaunchCard, CardChooser, DrawCards, DropCards

16 from thb.actions import FatetellStage, FinalizeStage, GenericAction, LaunchCard, PlayerDeath

17 from thb.actions import PlayerTurn, PrepareStage, RevealRole, ShuffleHandler, ask_for_action

18 from thb.cards.base import Card, Deck

19 from thb.cards.classes import AskForHeal, AttackCard, Demolition, DemolitionCard

20 from thb.cards.classes import ElementalReactorCard, ExinwanCard, FrozenFrogCard, GrazeCard

21 from thb.cards.classes import GreenUFOCard, Heal, HealCard, LaunchGraze, MomijiShieldCard

22 from thb.cards.classes import NazrinRodCard, RedUFOCard, Reject, RejectCard, RejectHandler

23 from thb.cards.classes import SinsackCard, WineCard

24 from thb.common import PlayerRole

25 from thb.inputlets import ActionInputlet, GalgameDialogInputlet

26 from thb.mode import THBEventHandler, THBattle

27 from utils.misc import BatchList

28

29

30# -- code --

31 log = logging.getLogger('THBattleNewbie')

32

33

34 class OneShotActionStage(ActionStage):

35 one_shot = True

36

37

38 class DeathHandler(THBEventHandler):

39 interested = ['action_apply']

40

41 def handle(self, evt_type, act):

42 if evt_type != 'action_apply': return act 42 ↛ exit line 42 didn't return from function 'handle', because the return on line 42 wasn't executed

43 if not isinstance(act, PlayerDeath): return act

44 tgt = act.target

45

46 g = self.game

47 a, b = g.players

48

49 raise GameEnded([a.player if b is tgt else b.player])

50

51

52 class THBNewbieRole(Enum):

53 HIDDEN = 0

54 NEWBIE = 1

55 BAKA = 2

56

57

58 class CirnoAI(object):

59

60 def __init__(self, trans, ilet):

61 self.trans = trans

62 self.ilet = ilet

63

64 def entry(self):

65 ilet = self.ilet

66 trans = self.trans

67 p = ilet.actor

68

69 g = trans.game

70 g.pause(1.2)

71

72 if trans.name == 'ActionStageAction':

73 tl = g.players[1:]

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

75 if random.random() > 0.6:

76 return False

77

78 for c in cl:

79 if c.is_card(AttackCard):

80 if self.try_launch(c, tl[:1]): return True

81

82 elif trans.name == 'Action' and isinstance(ilet, ActionInputlet):

83 if not (ilet.categories and not ilet.candidates): 83 ↛ 84 line 83 didn't jump to line 84, because the condition on line 83 was never true

84 return True

85

86 if isinstance(ilet.initiator, AskForHeal):

87 return False

88

89 cond = cast(CardChooser, ilet.initiator).cond

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

91 _, C = chain, lambda r: combinations(cl, r)

92 for c in _(C(1), C(2)):

93 if cond(c):

94 ilet.set_result(skills=[], cards=c, characters=[])

95 return True

96

97 elif trans.name == 'ChoosePeerCard':

98 tgt = ilet.target

99 if tgt.cards: 99 ↛ 101 line 99 didn't jump to line 101, because the condition on line 99 was never false

100 ilet.set_card(tgt.cards[0])

101 elif tgt.showncards:

102 ilet.set_card(tgt.showncards[0])

103

104 def try_launch(self, c, tl, skills=[]):

105 p = self.ilet.actor

106 act = ActionStageLaunchCard(p, tl, c)

107 if act.can_fire():

108 self.ilet.set_result(skills=skills, cards=[c], characters=tl)

109 return True

110

111 return False

112

113 @classmethod

114 def ai_main(cls, trans, ilet):

115 cls(trans, ilet).entry()

116

117

118 class AdhocEventHandler(THBEventHandler):

119 def __init__(self, hook: Callable[[str, Any], Any]):

120 self._handle = hook # typing: ignore

121

122 def handle(self, evt_type: str, arg: Any) -> Any:

123 return self._handle(evt_type, arg)

124

125

126 class THBattleNewbieBootstrap(BootstrapAction):

127 def __init__(self, params: Dict[str, Any],

128 items: Dict[Player, List[GameItem]],

129 players: BatchList[Player]):

130 self.source = self.target = None

131 self.players = players

132 self.params = params

133 self.items = items

134

135 def apply_action(self):

136 g = self.game

137

138 from thb.characters.meirin import Meirin

139 from thb.characters.cirno import Cirno

140

141 # ----- Init -----

142 pl = self.players

143 g.deck = Deck(g)

144

145 cirno_p, meirin_p = pl

146

147 g.roles = {

148 cirno_p: PlayerRole(THBNewbieRole),

149 meirin_p: PlayerRole(THBNewbieRole),

150 }

151 g.roles[cirno_p].set(THBNewbieRole.BAKA)

152 g.roles[meirin_p].set(THBNewbieRole.NEWBIE)

153

154 g.process_action(RevealRole(g.roles[cirno_p], pl))

155 g.process_action(RevealRole(g.roles[meirin_p], pl))

156

157 cirno = Cirno(cirno_p)

158 meirin = Meirin(meirin_p)

159

160 g.players = BatchList([cirno, meirin])

161

162 g.refresh_dispatcher()

163 g.emit_event('switch_character', (None, cirno))

164 g.emit_event('switch_character', (None, meirin))

165

166 g.emit_event('game_begin', g)

167 # ----- End Init -----

168

169 pt = PlayerTurn(cirno)

170 pt.pending_stages = [ScriptedStage]

171 g.process_action(pt)

172

173 for i, idx in enumerate(cycle([1, 0])): 173 ↛ 181 line 173 didn't jump to line 181, because the loop on line 173 didn't complete

174 p = g.players[idx]

175 if i >= 6000: break 175 ↛ 181 line 175 didn't jump to line 181, because the break on line 175 wasn't executed

176 try:

177 g.process_action(PlayerTurn(p))

178 except InterruptActionFlow:

179 pass

180

181 return True

182

183

184 class ScriptedStage(GenericAction):

185 def __init__(self, target):

186 self.source = target

187 self.target = target

188

189 def apply_action(self):

190 g = self.game

191

192 from thb.characters.meirin import Meirin

193 from thb.characters.cirno import Cirno

194 from thb.characters.sakuya import Sakuya

195

196 g.dispatcher.remove_by_cls(ShuffleHandler)

197

198 def dialog(character, dialog, voice):

199 if voice is not None:

200 voice = 'thb-cv-newbie-%s-%s' % (character.__name__.lower(), ('000' + str(voice))[-3:])

201

202 g.user_input([meirin], GalgameDialogInputlet(g, character, dialog, voice), timeout=60)

203

204 def inject_eh(hook: Callable[[str, Any], Any]):

205 eh = AdhocEventHandler(hook)

206 g.dispatcher.add_adhoc(eh)

207 return eh

208

209 def remove_eh(eh):

210 try:

211 g.dispatcher.remove_adhoc(eh)

212 except Exception:

213 raise

214 pass

215

216 def fail():

217 dialog(Meirin, '喂剧本不是这么写的啊，重来重来！', 1)

218

219 cirno, meirin = g.players # update

220 turn = PlayerTurn.get_current(g)

221

222 dialog(Meirin, '一个pad，两个pad，三个pad……', 2)

223 dialog(Sakuya, '（唰', None)

224 dialog(Meirin, '啊，我头上的是……', 3)

225 dialog(Sakuya, '别做白日梦，起床了起床了。那边那个妖精可又来门口找麻烦了，为了你的晚餐考虑，还是去解决一下吧？', 1)

226 dialog(Meirin, '是是是……这都已经是第999次了吧，那家伙真是不知道什么叫做放弃吗……', 4)

227

228 dialog(Cirno, '俺又来啦，这次绝对要打赢你！赌上大酱的100场陪练！', 1)

229 dialog(Meirin, '前面998次你也都是这么说的……好了，废话少说，放马过来吧！', 5)

230 dialog(Cirno, '正合我意！', 2)

231

232 turn.target = cirno

233

234 c = g.deck.inject(AttackCard, Card.SPADE, 1)

235 g.process_action(DrawCards(cirno, 1))

236 dialog(Cirno, '“吃我大弹幕啦！”', 3)

237 g.process_action(LaunchCard(cirno, [meirin], c))

238

239 # 红美铃受到一点伤害

240

241 dialog(Meirin, '呜哇！？', 6)

242 dialog(Sakuya, '怎么搞的，被一只妖精弄伤了？', 2)

243 dialog(Meirin, '不不不，那个咲夜你听我说，这是偷袭……', 7)

244 dialog(Sakuya, '嗯……明明游戏已经开始了，不打起十二分的精神迎战可是不行的啊。在玩thb的时候请注意队友的感受，不要挂机喔。', 3)

245 dialog(Meirin, '啥啊又在对着不存在的人说些莫名其妙的东西……', 8)

246 dialog(Sakuya, '嗯？', 4)

247 dialog(Meirin, '我可什么都没说！', 9)

248

249 # 红美铃的回合【目的:使用基本牌（麻薯，弹幕）】

250 turn.target = meirin

251 c = g.deck.inject(HealCard, Card.HEART, 2)

252 g.process_action(DrawCards(meirin, 1))

253

254 while c in meirin.cards:

255 text = (

256 '总之，这里先回复……\n'

257 '（请使用麻薯）\n'

258 '（在PC版中鼠标移动到卡牌/人物上，或者手机版中长按卡牌/人物头像，就会弹出说明，很有用的）'

259 )

260 dialog(Meirin, text, 10)

261 g.process_action(OneShotActionStage(meirin))

262

263 atkcard = g.deck.inject(AttackCard, Card.SPADE, 3)

264 g.process_action(DrawCards(meirin, 1))

265

266 while atkcard in meirin.cards:

267 text = (

268 '好，状态全满！那边的妖精！吃我大弹幕啦！\n'

269 '（请首先点击弹幕，然后点击琪露诺，最后点击出牌）\n'

270 '（在PC版中鼠标移动到卡牌/人物上，或者手机版中长按卡牌/人物头像，就会弹出说明，很有用的）'

271 )

272 dialog(Meirin, text, 11)

273 g.process_action(OneShotActionStage(meirin))

274

275 dialog(Cirno, '哎呀！？', 4)

276 dialog(Sakuya, '啊啦，干的不错。', 5)

277 dialog(Meirin, '那是自然啦，对付一些这样的妖精还是不在话下的……', 12)

278 dialog(Cirno, '喂！悄悄话说的也太大声了！！', 5)

279

280 # 琪露诺的回合【目的:使用基本牌（擦弹）】【使用太极（1）】

281 turn.target = cirno

282 g.deck.inject(HealCard, Card.HEART, 4)

283 g.process_action(DrawCards(cirno, 1))

284 while True:

285 if meirin.life < meirin.maxlife:

286 g.process_action(Heal(meirin, meirin, meirin.maxlife - meirin.life))

287

288 if meirin.cards:

289 g.process_action(DropCards(meirin, meirin, meirin.cards))

290

291 atkcard = g.deck.inject(AttackCard, Card.SPADE, 5)

292 g.process_action(DrawCards(cirno, 1))

293 graze = g.deck.inject(GrazeCard, Card.DIAMOND, 6)

294 g.process_action(DrawCards(meirin, 1))

295

296 dialog(Cirno, '这回就轮到我了！接招！超⑨武神霸斩！', 6)

297

298 lc = LaunchCard(cirno, [meirin], atkcard)

299

300 @inject_eh

301 def resp(evt_type: str, act: Any):

302 if evt_type == 'choose_target' and act[0] is lc:

303 g.pause(1)

304 dialog(Meirin, '来的正好！', 13)

305

306 elif evt_type == 'action_after' and isinstance(act, LaunchGraze) and act.succeeded:

307 g.pause(1)

308 dialog(Cirno, '我的计谋竟被……', 7)

309 text = (

310 '还没完呐！\n'

311 '（使用技能，震飞琪露诺一张手牌）\n'

312 '（每一名角色都有自己独特的技能，灵活利用好这些技能是你取得胜利的关键所在）'

313 )

314 dialog(Meirin, text, 14)

315

316 return act

317

318 g.process_action(lc)

319 remove_eh(resp)

320

321 if graze in meirin.cards or cirno.cards:

322 fail()

323 continue

324

325 break

326

327 dialog(Cirno, '切，再让你嘚瑟一会儿，我还有自己的杀手锏呢……', 8)

328 dialog(Meirin, '在这种时候放空话可是谁都不会相信的啦。', 15)

329 green = g.deck.inject(GreenUFOCard, Card.DIAMOND, 7)

330 g.process_action(DrawCards(cirno, 1))

331 dialog(Cirno, '总之不能再让你这么自在地用弹幕打到俺了……看我的杀手锏！', 9)

332 g.process_action(LaunchCard(cirno, [cirno], green))

333

334 dialog(Meirin, '咦……', 16)

335 dialog(Sakuya, '不会连这个你也不清楚吧。', 6)

336 dialog(Meirin, '我记得是记得啦，似乎是我现在够不到琪露诺了之类的而琪露诺可以够到我……吧？说回来，这样的解释似乎很不科学诶，为什么我够不到但是对方够得到我啊，这个|G绿色UFO|r到底是什么东西……', 17)

337 dialog(Sakuya, '……你只要好好打完这局就可以了，再问这么多为什么，我可不保证你的脑门上会多出什么奇怪的金属制品。', 7)

338 dialog(Meirin, '是！', 18)

339

340 # 红美铃的回合【目的:使用延时符卡（冻青蛙），使用太极（2），使用红色UFO】'

341 turn.target = meirin

342 frozen = g.deck.inject(FrozenFrogCard, Card.SPADE, 8)

343 g.process_action(DrawCards(meirin, 1))

344

345 dialog(Meirin, '咦，这张牌是……', 19)

346 dialog(Sakuya, '这是|G冻青蛙|r。\n（咳嗽了一声）|G冻青蛙|r是一种|R延时符卡|r，它和|G封魔阵|r一样在使用时并不会立即发生作用，只有轮到了那个角色的行动回合时，才会进行一次判定来执行该符卡的后续效果。', 8)

347 dialog(Meirin, '原来是这样……那就先贴到她脸上再说！', 20)

348

349 g.process_action(OneShotActionStage(meirin))

350

351 dialog(Meirin, '这是怎么回事…为什么不能使用…那就先来一发|G弹幕|r好了！', 21)

352

353 atkcard = g.deck.inject(AttackCard, Card.SPADE, 9)

354 g.process_action(DrawCards(meirin, 1))

355

356 g.process_action(OneShotActionStage(meirin))

357

358 dialog(Meirin, '咲夜咲夜咲夜，我没法打她啊！', 22)

359 dialog(Sakuya, '……你忘了琪露诺的|G绿色UFO|r吗。现在从你这边看来和琪露诺的距离为2，也就是，赤手空拳的距离1是没有办法用|G弹幕|r打中她的。我记得鬼族有她们的方法，但是很显然你并不会。', 9)

360 dialog(Meirin, '好吧……所以？', 23)

361 dialog(Sakuya, '（掏出飞刀', None)

362 dialog(Meirin, '好啦好啦，我不问啦！', 24)

363

364 red = g.deck.inject(RedUFOCard, Card.HEART, 10)

365 g.process_action(DrawCards(meirin, 1))

366

367 while red in meirin.cards:

368 text = (

369 '哈，我找到了这个！\n'

370 '（红色UFO可以拉近其他角色与你的距离，快装备上吧）'

371 )

372 dialog(Meirin, text, 25)

373 g.process_action(OneShotActionStage(meirin))

374

375 dialog(Sakuya, '你这不是知道UFO的规则嘛。', 10)

376 dialog(Meirin, '只是想趁这次机会和咲夜多说说话啦，平时总是一副大忙人的样子来无影去无踪的……', 26)

377 dialog(Sakuya, '你以为奉承一下我就会给你涨工资吗。', 11)

378 dialog(Meirin, '啊哈，啊哈哈。', 27)

379

380 g.pause(1)

381

382 g.deck.inject(NazrinRodCard, Card.HEART, 11)

383 graze = g.deck.inject(GrazeCard, Card.DIAMOND, 12)

384 g.process_action(DrawCards(cirno, 2))

385

386 while True:

387 if atkcard not in meirin.cards:

388 atkcard = g.deck.inject(AttackCard, Card.SPADE, 9)

389 g.process_action(DrawCards(meirin, 1))

390

391 if graze not in cirno.cards:

392 graze = g.deck.inject(GrazeCard, Card.DIAMOND, 12)

393 g.process_action(DrawCards(cirno, 1))

394

395 dialog(Cirno, '喂，读条要过了你们在那儿干嘛呢，说好的弹幕呢！', 10)

396 dialog(Meirin, '我这辈子还没听过这么欠扁的要求！吃我一弹！', 28)

397

398 @inject_eh

399 def handle_longquan(evt_type: str, act: Any):

400 if evt_type == 'action_after' and isinstance(act, LaunchGraze) and act.target is cirno:

401 g.pause(1)

402 dialog(Cirno, '你以为只有你会闪开吗！', 11)

403 dialog(Meirin, '确实谁都会闪啦，不过接下来的，可就不是谁都会的咯？', 29)

404

405 return act

406

407 g.process_action(OneShotActionStage(meirin))

408 remove_eh(handle_longquan)

409

410 if atkcard in meirin.cards:

411 continue

412

413 if cirno.cards:

414 fail()

415 continue

416

417 break

418

419 g.pause(1)

420 dialog(Cirno, '呜哇你赖皮！', 12)

421 dialog(Meirin, '哪来的什么赖皮不赖皮，我有自己的能力，但是你也有啊。', 30)

422 dialog(Cirno, '可是我又不会用！你这不是赖皮是什么嘛！', 13)

423 dialog(Meirin, '……', None)

424 dialog(Sakuya, '……还是不要跟笨蛋说话的比较好，智商会下降的。', 12)

425

426 while frozen in meirin.cards: 426 ↛ 427 line 426 didn't jump to line 427, because the condition on line 426 was never true

427 dialog(Meirin, '那么，把这张|G冻青蛙|r也贴上去吧！', 31)

428 g.process_action(ActionStage(meirin))

429

430 turn.target = cirno

431 g.deck.inject(SinsackCard, Card.SPADE, 13)

432 g.process_action(FatetellStage(cirno))

433

434 g.pause(2)

435

436 dialog(Sakuya, '所谓“笨蛋的运气总会很不错”吗。', 13)

437 dialog(Cirno, '哼，俺可是自带⑨翅膀的天才呀！', 14)

438 dialog(Meirin, '可惜依旧是笨蛋。', 32)

439

440 shield = g.deck.inject(MomijiShieldCard, Card.SPADE, 1)

441 g.process_action(DrawCards(cirno, 1))

442 dialog(Cirno, '无路赛……我要出王牌啦！', 15)

443 g.process_action(LaunchCard(cirno, [cirno], shield))

444

445 dialog(Meirin, '这是……', 33)

446 dialog(Cirno, '这是我在妖怪之山的妖精那里【借】来的宝物，怎么样，接下来你就没办法用弹幕伤到俺了吧~', 16)

447 dialog(Sakuya, '只是黑色的弹幕无效而已。', 14)

448 dialog(Meirin, '对呀对呀。', 34)

449 dialog(Sakuya, '而且，只要拆掉的话不就好了吗。', 15)

450 dialog(Meirin, '对呀对……啥？', 35)

451 dialog(Sakuya, '真是的，你之前的998局到底是怎么赢的……', 16)

452

453 # 红美铃的回合【目的:使用符卡（城管执法，好人卡）】

454 turn.target = meirin

455

456 demolition = g.deck.inject(DemolitionCard, Card.CLUB, 2)

457 g.process_action(DrawCards(meirin, 1))

458 cirnoreject = g.deck.inject(RejectCard, Card.CLUB, 3)

459 g.process_action(DrawCards(cirno, 1))

460

461 while demolition in meirin.cards:

462 dialog(Meirin, '咲夜咲夜！', 36)

463 dialog(Sakuya, '是啦，这就是我说的那张符卡了。和其他的|R非延时符卡|r使用方法都一样，属于发动之后就会立即生效的类型。', 17)

464 dialog(Meirin, '……不是很好理解诶。', 37)

465

466 dialog(Sakuya, '用用看就知道了。快去卸掉她的|G天狗盾|r吧。', 18)

467

468 @inject_eh

469 def handle_rejectcard(evt_type: str, act: Any):

470 if evt_type == 'action_before' and isinstance(act, Demolition):

471 g.pause(1.5)

472 dialog(Cirno, '哈哈，早知道你会用这一招，怎能让你轻易得逞！', 17)

473 g.process_action(LaunchCard(cirno, [cirno], cirnoreject, Reject(cirno, act)))

474

475 elif evt_type == 'action_before' and isinstance(act, Reject) and act.associated_card is cirnoreject:

476 g.pause(1.5)

477 dialog(Meirin, '这又是怎么回事……好像|G城管执法|r并没有起作用？', 38)

478 dialog(Sakuya, '咦，这笨蛋居然知道用|G好人卡|r啊……', 19)

479 dialog(Cirno, '什么笨蛋，老娘是天才，天～才～', 18)

480 text = (

481 '|G好人卡|r的效果是|R抵消符卡效果|r，也就是说，你的|G城管执法|r的效果被无效化了。\n'

482 '（在PC版中鼠标移动到卡牌/人物上，或者手机版中长按卡牌/人物头像，就会弹出说明，很有用的）'

483 )

484 dialog(Sakuya, text, 20)

485 dialog(Sakuya, '但是，|G好人卡|r的“无效符卡”的效果，本身也是符卡效果，是可以被|G好人卡|r抵消的！', 21)

486

487 meirinreject = g.deck.inject(RejectCard, Card.CLUB, 4)

488 g.process_action(DrawCards(meirin, 1))

489

490 while not act.cancelled:

491 dialog(Meirin, '我知道了，我也用|G好人卡|r去抵消她的|G好人卡|r效果就好了！', 39)

492

493 rej = RejectHandler(g)

494 rej.target_act = act

495 with InputTransaction('AskForRejectAction', [meirin]) as trans:

496 p, rst = ask_for_action(rej, [meirin], ('cards', 'showncards'), [], trans=trans)

497

498 if not p: continue

499 assert rst

500 cards, _ = rst

501 assert cards[0] is meirinreject

502 g.process_action(LaunchCard(meirin, [cirno], meirinreject, Reject(meirin, act)))

503

504 return act

505

506 g.process_action(OneShotActionStage(meirin))

507 remove_eh(handle_rejectcard)

508

509 if shield in cirno.equips:

510 dialog(Sakuya, '喂，不是说要拆掉|G天狗盾|r吗，你明明装备着|G红色UFO|r的，她是在你的距离范围内的。', 22)

511 dialog(Meirin, '哎呀，手抖了的说……', 40)

512 else:

513 dialog(Cirno, '呜哇你赔我的|G天狗盾|r！', 19)

514 dialog(Meirin, '怪我咯！？', 41)

515

516 g.deck.inject(ExinwanCard, Card.CLUB, 3)

517 g.process_action(DrawCards(meirin, 1))

518

519 dialog(Meirin, '咦？咲夜，这张牌好奇怪……为什么是负面的效果？', 42)

520 dialog(Sakuya, '是|G恶心丸|r啊……你的运气不太好哦。不过尽管说是一张负面效果的牌，但是看发动条件的话，是可以恶心到别人的。情况允许的话就留在手里好了，直接吃掉肯定是不合算的。', 23)

521

522 turn.target = cirno

523 g.deck.inject(DemolitionCard, Card.CLUB, 4)

524 g.process_action(DrawCards(cirno, 1))

525

526 dialog(Cirno, '可恶，你到底有没有认真的在打啊！看我双倍奉还！', 20)

527 g.process_action(LaunchCard(cirno, [meirin], demolition))

528

529 dialog(Cirno, '呜哇，这是什么！不要来找我！', 21)

530 dialog(Meirin, '哈哈，说好的双倍奉还呢？', 43)

531

532 # 美铃的觉醒和主动技能

533 er = g.deck.inject(ElementalReactorCard, Card.DIAMOND, 5)

534 atks = [g.deck.inject(AttackCard, Card.SPADE, i) for i in (6, 7, 8, 9)]

535 g.process_action(DrawCards(cirno, 5))

536 dialog(Cirno, '真是伤脑筋……看来我要拿出真正的实力来了！', 22)

537 g.process_action(LaunchCard(cirno, [cirno], er))

538

539 dialog(Cirno, '接招啦！！', 23)

540

541 for c in atks[:-1]:

542 g.pause(0.5)

543 g.process_action(LaunchCard(cirno, [meirin], c))

544

545 dialog(Meirin, '唔，不好，再这样下去就要撑不住了……', 44)

546 wine = g.deck.inject(WineCard, Card.DIAMOND, 10)

547 g.process_action(DrawCards(meirin, 1))

548

549 while wine in meirin.cards:

550 dialog(Meirin, '没办法，这瓶|G酒|r本来是想留着|R来一发2点伤害的弹幕|r用的，现在只能用来|R抵挡1点致命伤害|r了……', 45)

551 g.process_action(ActionStage(meirin))

552

553 g.pause(0.5)

554 g.process_action(LaunchCard(cirno, [meirin], atks[-1]))

555

556 g.pause(0.5)

557 dialog(Cirno, '怎么样，老娘最强！', 24)

558

559 dialog(Meirin, '呼呼，撑过来了……', 46)

560 dialog(Meirin, '咲夜，这是怎么回事？|G弹幕|r不应该|R一回合只能使用一次|r吗？', 47)

561 dialog(Sakuya, '嗯……说的不错。|R一回合只能使用一次|r这个规则是没错啦，不过你看她的手上，是河童们研制的迷你|G八卦炉|r，只要装备上的话，一回合就可以使用任意次数的|G弹幕|r了。', 24)

562 dialog(Meirin, '你这家伙好像【借】来了不少东西嘛……不过笨蛋就是笨蛋，我怎么可能会输在笨蛋的手上！', 48)

563

564 pt = PlayerTurn(meirin)

565 pt.pending_stages = [PrepareStage, FinalizeStage]

566 g.process_action(pt) # awake!

567 g.pause(2)

568

569 dialog(Cirno, '你怎么会突然多出来一个技能，这一点也不公平！', 25)

570 dialog(Sakuya, '“笨蛋就是笨蛋”这句话说的一点没错……你像这样打伤美铃，触发了美铃的|R觉醒技|r。|R觉醒技|r通常都是很厉害的技能，但是要满足一定条件以后才可以开始使用。据我所知，那个三途川的草鞋船夫也有类似的能力，而美铃的觉醒技，就是|G太极|r了。', 25)

571 dialog(Cirno, '啊，原来是这样……不不，才不是笨蛋，老娘最强！', 26)

572

573 graze = g.deck.inject(GrazeCard, Card.DIAMOND, 11)

574 g.process_action(DrawCards(meirin, 1))

575

576 while graze in meirin.cards:

577 text = (

578 '现在说不是笨蛋是不是有点晚啊……见识一下吧，这来自古老东方的两仪|G太极|r之力！\n'

579 '（使用主动发动的技能：请点击技能按钮，然后选择擦弹，然后选择琪露诺，最后出牌）'

580 )

581 dialog(Meirin, text, 49)

582 g.process_action(OneShotActionStage(meirin))

583

584 dialog(Cirno, '呜啊……', 27)

585 dialog(Sakuya, '我要回去做晚饭了。一会儿到了饭点你还没有解决掉这个妖精，我可不会给你留吃的。', 26)

586 dialog(Meirin, '喂咲夜你先等等……', 50)

587 dialog(Sakuya, '（The World', None)

588 dialog(Meirin, '……好吧。那边的妖精！', 51)

589 dialog(Cirno, '诶？', 28)

590 dialog(Meirin, '以晚饭之名！我要制裁你！', 52)

591 dialog(Cirno, '虽然不知道为什么突然燃起了斗志，不过……', 29)

592 dialog(Cirno, '来吧！老娘是不可能会输的！', 30)

593

594 g.process_action(Heal(cirno, cirno, cirno.maxlife - cirno.life))

595 g.process_action(Heal(meirin, meirin, meirin.maxlife - meirin.life))

596 g.process_action(DrawCards(cirno, 4))

597 g.process_action(DrawCards(meirin, 4))

598

599 g.refresh_dispatcher()

600

601 return True

602

603

604 class THBattleNewbie(THBattle):

605 n_persons = 1

606 game_ehs = [DeathHandler]

607 npc_players = [NPC('琪露诺', CirnoAI.ai_main)]

608 bootstrap = THBattleNewbieBootstrap

609

610 def can_leave(g, p):

611 return True