Files
whale-town-end-v2/src/business/player/adventure.service.ts

103 lines
5.6 KiB
TypeScript

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<T>(id: bigint, operation: (wallet: UserWallets) => T): Promise<T>;
}) {}
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<string, { answers: number[] }>)[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,
};
});
}
}