import { BadRequestException, Inject, Injectable } from '@nestjs/common'; import { randomUUID } from 'crypto'; import { UserWallets } from '../../core/db/user_wallets/user_wallets.entity'; import chapters from './adventure_chapters.json'; const QUESTIONS = [ { text: 'Python 中列表通常使用哪一种括号表示?', options: ['{}', '[]', '()', '<>'], answer: 1 }, { text: 'Python 中哪个关键字用于定义函数?', options: ['class', 'for', 'def', 'return'], answer: 2 }, { text: 'Python 中 len([1, 2, 3]) 的结果是什么?', options: ['3', '2', '1', '0'], answer: 0 }, { text: 'Python 中哪个值表示逻辑真?', options: ['None', 'False', '0', 'True'], answer: 3 }, ]; export interface AdventureCommand { action: string; encounter?: string; session?: string; turn?: number; choice?: number; chapter?: string; answers?: number[]; } @Injectable() export class AdventureService { constructor(@Inject('IUserWalletsService') private readonly wallets: { mutateAdventure(id: bigint, operation: (wallet: UserWallets) => T): Promise; }) {} execute(userId: bigint, command: AdventureCommand) { return this.wallets.mutateAdventure(userId, wallet => { const s = wallet.adventure ||= { level: 1, experience: 0, health: 10, chapters: {}, defeated: {}, sequence: 0, battle: null }; const maxHealth = () => 10 + (s.level - 1) * 5; const spend = (amount: number) => { if (wallet.balance < amount) throw new BadRequestException('鲸币余额不足'); wallet.balance -= amount; }; let correct: boolean | undefined; const active = s.battle?.status === 'active'; if (active && ['rest', 'recover', 'enroll', 'complete'].includes(command.action)) throw new BadRequestException('请先结束战斗'); switch (command.action) { case 'snapshot': break; case 'start': { if (!command.encounter || !/^(starting_encounter|-?\d+_-?\d+)$/.test(command.encounter)) throw new BadRequestException('无效遭遇'); if (active) { if (s.battle.encounter !== command.encounter) throw new BadRequestException('已有进行中的战斗'); break; } if (s.defeated[command.encounter]) throw new BadRequestException('该怪物已被击败'); if (s.health <= 0) throw new BadRequestException('请返回营地恢复'); s.battle = { id: randomUUID(), encounter: command.encounter, turn: 0, hearts: 3, status: 'active', last: null }; break; } case 'answer': { const b = s.battle; if (!b || command.session !== b.id) throw new BadRequestException('战斗不存在'); if (b.last && command.turn === b.last.turn) { if (command.choice !== b.last.choice) throw new BadRequestException('本回合已结算'); correct = b.last.correct; break; } if (b.status !== 'active' || command.turn !== b.turn || !Number.isInteger(command.choice) || command.choice < 0 || command.choice > 3) throw new BadRequestException('回合或答案无效'); correct = command.choice === QUESTIONS[b.turn % QUESTIONS.length].answer; b.last = { turn: b.turn, choice: command.choice, correct }; b.turn++; if (correct) b.hearts--; else s.health = Math.max(0, s.health - 10); if (b.hearts === 0) { b.status = 'victory'; s.defeated[b.encounter] = true; wallet.balance += 10; } else if (!s.health) b.status = 'defeat'; break; } case 'escape': if (!s.battle || s.battle.id !== command.session) throw new BadRequestException('战斗不存在'); if (active) s.battle.status = 'escaped'; break; case 'rest': s.health = maxHealth(); break; case 'recover': if (s.health < maxHealth()) { spend(5); s.health = Math.min(maxHealth(), s.health + 30); } break; case 'enroll': case 'complete': { const chapter = (chapters as Record)[command.chapter]; if (!chapter) throw new BadRequestException('章节不存在'); if (command.action === 'enroll') { if (!s.chapters[command.chapter]) { spend(20); s.chapters[command.chapter] = 'enrolled'; } } else { if (!s.chapters[command.chapter]) throw new BadRequestException('请先报名'); if (s.chapters[command.chapter] === 'completed') break; if (!Array.isArray(command.answers) || command.answers.length !== chapter.answers.length || chapter.answers.some((answer, i) => command.answers[i] !== answer)) throw new BadRequestException('请完成章节练习'); s.chapters[command.chapter] = 'completed'; if (s.level < 5) s.experience += 20; while (s.level < 5 && s.experience >= 20 * (s.level + 1)) { s.experience -= 20 * (s.level + 1); s.level++; s.health += 5; } } break; } default: throw new BadRequestException('未知操作'); } s.sequence++; const b = s.battle; const question = QUESTIONS[(b?.turn || 0) % QUESTIONS.length]; return { wallet: { user_id: userId.toString(), balance: wallet.balance, currency: 'whale_coin' }, progress: { level: s.level, experience: s.experience, health: s.health, max_health: maxHealth(), chapters: s.chapters, defeated: s.defeated }, battle: b ? { id: b.id, encounter: b.encounter, turn: b.turn, hearts: b.hearts, status: b.status, question: { text: question.text, options: question.options } } : null, correct, }; }); } }