refactor: add rng & seeding

This commit is contained in:
2026-04-07 15:32:06 +08:00
parent 6b736ab083
commit 10393f45b6
6 changed files with 98 additions and 46 deletions
+7 -3
View File
@@ -2,7 +2,8 @@ import {
OnitamaGame,
OnitamaState,
PlayerType,
prompts
prompts,
initializeCards
} from "./types";
import {createGameCommandRegistry} from "@/core/game";
import {moveToRegion} from "@/core/region";
@@ -342,14 +343,17 @@ const turn = registry.register({
* 开始游戏主循环
*/
export async function start(game: OnitamaGame) {
// Initialize cards with RNG at game start
initializeCards(game);
while (true) {
const currentPlayer = game.value.currentPlayer;
const turnOutput = await turn(game, currentPlayer);
if (turnOutput.winner) {
break;
}
}
return game.value;
}
+51 -26
View File
@@ -125,27 +125,13 @@ export function createInitialState() {
const regions = createRegions();
const pawns = createPawns();
const cards = createCards();
// Distribute cards: 2 to each player, 1 spare
const cardNames = Object.keys(cards);
const shuffled = [...cardNames].sort(() => Math.random() - 0.5);
const redCards = shuffled.slice(0, 2);
const blackCards = shuffled.slice(2, 4);
const spareCard = shuffled[4];
// Set card regions
for(const cardName of redCards){
cards[cardName].regionId = 'red';
regions.red.childIds.push(cardName);
}
for(const cardName of blackCards){
cards[cardName].regionId = 'black';
regions.black.childIds.push(cardName);
}
cards[spareCard].regionId = 'spare';
regions.spare.childIds.push(spareCard);
// Cards will be shuffled and distributed in start()
// Initialize with empty card lists
const redCards: string[] = [];
const blackCards: string[] = [];
const spareCard = '';
// Populate board region childIds
for(const pawn of Object.values(pawns)){
if(pawn.regionId === 'board'){
@@ -153,15 +139,12 @@ export function createInitialState() {
regions.board.partMap[pawn.position.join(',')] = pawn.id;
}
}
// Determine starting player from spare card
const startingPlayer = cards[spareCard].startingPlayer;
return {
regions,
pawns,
cards,
currentPlayer: startingPlayer,
currentPlayer: 'red' as PlayerType,
winner: null as PlayerType | null,
spareCard,
redCards,
@@ -170,5 +153,47 @@ export function createInitialState() {
};
}
/**
* 洗牌并分配卡牌,设置起始玩家
*/
export function initializeCards(game: OnitamaGame) {
const state = game.value;
const rng = game.rng;
const cardNames = Object.keys(state.cards);
// Fisher-Yates shuffle using game.rng
const shuffled = [...cardNames];
for (let i = shuffled.length - 1; i > 0; i--) {
const j = rng.nextInt(i + 1);
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}
const redCards = shuffled.slice(0, 2);
const blackCards = shuffled.slice(2, 4);
const spareCard = shuffled[4];
game.produce(state => {
// Set card regions
for(const cardName of redCards){
state.cards[cardName].regionId = 'red';
state.regions.red.childIds.push(cardName);
}
for(const cardName of blackCards){
state.cards[cardName].regionId = 'black';
state.regions.black.childIds.push(cardName);
}
state.cards[spareCard].regionId = 'spare';
state.regions.spare.childIds.push(spareCard);
// Set card lists
state.redCards = redCards;
state.blackCards = blackCards;
state.spareCard = spareCard;
// Determine starting player from spare card
state.currentPlayer = state.cards[spareCard].startingPlayer;
});
}
export type OnitamaState = ReturnType<typeof createInitialState>;
export type OnitamaGame = IGameContext<OnitamaState>;