forked from xiangwang25/whale-town-end-v2
103 lines
5.6 KiB
TypeScript
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,
|
|
};
|
|
});
|
|
}
|
|
}
|