274 lines
6.9 KiB
TypeScript
274 lines
6.9 KiB
TypeScript
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";
|
||
import { checkEnemy, nextTurn, pass, playWithA } from "./commands";
|
||
import { enemyCounterattackCmd, playCmd } from ".";
|
||
|
||
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 playWithA(game, player, cardId, secondCardId);
|
||
} else {
|
||
playResult = await playCmd(game, player, cardId);
|
||
}
|
||
} else {
|
||
// 让过
|
||
playResult = await pass(game, player);
|
||
}
|
||
|
||
if (!playResult.success) {
|
||
return playResult;
|
||
}
|
||
|
||
// 检查敌人是否被击败
|
||
const checkResult = await checkEnemy(game);
|
||
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 nextTurn(game);
|
||
|
||
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 enemyCounterattackCmd(game, player, discardCardIds);
|
||
if (!result.success) {
|
||
// 弃牌失败(点数和不足),游戏失败
|
||
await game.produceAsync((state) => {
|
||
state.phase = "defeat";
|
||
state.winner = false;
|
||
});
|
||
return result;
|
||
}
|
||
|
||
// 弃牌成功,切换到下一个玩家
|
||
await nextTurn(game);
|
||
|
||
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,
|
||
};
|
||
}
|