boardgame-core/src/samples/regicide/game.ts

240 lines
7.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {IGameContext} from "@/core/game";
import {RegicideState} from "@/samples/regicide/state";
import {buildEnemyDeck, buildTavernDeck, createAllCards, getPlayerHandRegionId} from "@/samples/regicide/utils";
import {INITIAL_HAND_SIZE} from "@/samples/regicide/constants";
import {Enemy, PlayerType, RegicideCard} from "@/samples/regicide/types";
export type RegicideGame = IGameContext<RegicideState>;
/**
* 初始化游戏设置
* @param game 游戏上下文
* @param playerCount 玩家数量1-4
* @param seed 随机种子(可选)
*/
export async function setupGame(game: RegicideGame, playerCount: number, seed?: number) {
if (playerCount < 1 || playerCount > 4) {
throw new Error('玩家数量必须为 1-4 人');
}
if (seed) {
// RNG seeding handled by game context
}
// 创建所有卡牌
const allCards = createAllCards();
// 构建敌人牌堆J/Q/K
const enemyDeck = buildEnemyDeck(game._rng);
// 构建酒馆牌堆A-10
const tavernDeck = buildTavernDeck(game._rng);
// 初始化游戏状态
await game.produceAsync(state => {
state.cards = allCards;
state.playerCount = playerCount;
state.currentPlayerIndex = 0;
state.enemyDeck = enemyDeck;
// 设置酒馆牌堆区域
for (const card of tavernDeck) {
card.regionId = 'tavernDeck';
state.regions.tavernDeck.childIds.push(card.id);
}
// 设置敌人牌堆区域只存储ID敌人是独立对象
state.regions.enemyDeck.childIds = enemyDeck.map(e => e.id);
// 给每个玩家发牌
const players: PlayerType[] = ['player1', 'player2', 'player3', 'player4'];
for (let i = 0; i < playerCount; i++) {
const player = players[i];
const regionId = getPlayerHandRegionId(player);
for (let j = 0; j < INITIAL_HAND_SIZE; j++) {
if (tavernDeck.length === 0) break;
const card = tavernDeck.shift()!;
card.regionId = regionId;
state.playerHands[player].push(card.id);
const region = state.regions[regionId as keyof typeof state.regions];
region.childIds.push(card.id);
}
}
// 翻开第一个敌人
if (enemyDeck.length > 0) {
const firstEnemy = enemyDeck.shift()!;
state.currentEnemy = firstEnemy;
}
});
}
/**
* 启动游戏主循环
*/
export async function start(game: RegicideGame) {
const state = game.value;
// 检查游戏是否已设置
if (!state.currentEnemy) {
throw new Error('请先调用 setupGame 初始化游戏');
}
const players: PlayerType[] = ['player1', 'player2', 'player3', 'player4'];
// 主游戏循环
while (state.phase === 'playing') {
const currentPlayerIndex = state.currentPlayerIndex;
const currentPlayer = players[currentPlayerIndex];
// 检查当前玩家是否有手牌
const currentHand = state.playerHands[currentPlayer];
if (currentHand.length === 0) {
// 玩家没有手牌,跳过回合
await game.produceAsync(state => {
state.currentPlayerIndex = (state.currentPlayerIndex + 1) % state.playerCount;
});
continue;
}
// 等待玩家输入(出牌或让过)
// 这里需要外部通过 prompt 系统获取输入
// 实际使用时由 UI 或测试代码提供输入
// 循环会在外部调用 play/pass 命令后继续
// 当 phase 变为 'victory' 或 'defeat' 时退出
break;
}
return game.value;
}
/**
* 处理完整的玩家回合
*/
export async function playTurn(game: RegicideGame, player: PlayerType, action: 'play' | 'pass', cardId?: string, secondCardId?: string) {
const state = game.value;
if (state.phase !== 'playing') {
return {success: false, error: '游戏已结束'};
}
if (!state.currentEnemy) {
return {success: false, error: '没有活跃的敌人'};
}
let playResult: any;
// 执行玩家动作
if (action === 'play' && cardId) {
// 检查是否是A配合另一张牌
const card = state.cards[cardId];
if (card.rank === 'A' && secondCardId) {
playResult = await game.run(`play-with-a ${player} ${cardId} ${secondCardId}`);
} else {
playResult = await game.run(`play ${player} ${cardId}`);
}
} else {
// 让过
playResult = await game.run(`pass ${player}`);
}
if (!playResult.success) {
return playResult;
}
// 检查敌人是否被击败
const checkResult = await game.run<{defeated: boolean; currentEnemy?: any; nextEnemy?: any; defeatedEnemy?: any; enemiesRemaining?: number}>('check-enemy');
if (!checkResult.success) {
return checkResult;
}
// 如果敌人未被击败,处理反击
if (!checkResult.result.defeated) {
// 反击逻辑需要玩家选择弃牌,这里返回状态让外部处理
return {
success: true,
result: {
playResult: playResult.result,
enemyDefeated: false,
needsDiscard: true,
enemyAttack: state.currentEnemy.value,
playerHand: state.playerHands[player]
}
};
}
// 敌人被击败,检查是否还有更多敌人
if (state.enemyDeck.length === 0 && state.currentEnemy && state.currentEnemy.hp <= 0) {
await game.produceAsync(state => {
state.phase = 'victory';
state.winner = true;
});
return {
success: true,
result: {
playResult: playResult.result,
enemyDefeated: true,
gameWon: true
}
};
}
// 切换到下一个玩家
await game.run('next-turn');
return {
success: true,
result: {
playResult: playResult.result,
enemyDefeated: true,
nextEnemy: state.currentEnemy
}
};
}
/**
* 处理反击阶段的弃牌
*/
export async function handleCounterattack(game: RegicideGame, player: PlayerType, discardCardIds: string[]) {
const result = await game.run(`counterattack ${player} ${JSON.stringify(discardCardIds)}`);
if (!result.success) {
// 弃牌失败(点数和不足),游戏失败
await game.produceAsync(state => {
state.phase = 'defeat';
state.winner = false;
});
return result;
}
// 弃牌成功,切换到下一个玩家
await game.run('next-turn');
return result;
}
/**
* 获取当前游戏状态摘要
*/
export function getGameStatus(game: RegicideGame) {
const state = game.value;
return {
phase: state.phase,
currentPlayer: ['player1', 'player2', 'player3', 'player4'][state.currentPlayerIndex],
currentEnemy: state.currentEnemy ? {
...state.currentEnemy,
hpPercent: Math.round((state.currentEnemy.hp / state.currentEnemy.maxHp) * 100)
} : null,
enemiesRemaining: state.enemyDeck.length,
tavernDeckCount: state.regions.tavernDeck.childIds.length,
discardPileCount: state.regions.discardPile.childIds.length,
playerHands: Object.fromEntries(
Object.entries(state.playerHands).map(([player, hand]) => [player, hand.length])
),
winner: state.winner
};
}