game-state
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGame State Management Skill
游戏状态管理技能
Overview
概述
This skill provides expertise for managing complex game state in digital board games. It covers state structure design, action validation, phase management, and patterns for implementing intricate game rules reliably.
本技能为数字桌游中复杂游戏状态的管理提供专业方案,涵盖状态结构设计、操作验证、阶段管理以及可靠实现复杂游戏规则的模式。
Core Principles
核心原则
Immutable State
不可变状态
Always treat game state as immutable. Create new state objects rather than mutating existing ones:
javascript
// BAD: Mutating state
function addMoney(state, playerId, amount) {
state.players[playerId].money += amount;
return state;
}
// GOOD: Creating new state
function addMoney(state, playerId, amount) {
return {
...state,
players: {
...state.players,
[playerId]: {
...state.players[playerId],
money: state.players[playerId].money + amount
}
}
};
}始终将游戏状态视为不可变对象,创建新的状态对象而非修改现有对象:
javascript
// BAD: Mutating state
function addMoney(state, playerId, amount) {
state.players[playerId].money += amount;
return state;
}
// GOOD: Creating new state
function addMoney(state, playerId, amount) {
return {
...state,
players: {
...state.players,
[playerId]: {
...state.players[playerId],
money: state.players[playerId].money + amount
}
}
};
}Single Source of Truth
单一数据源
All game state should live in one canonical object:
javascript
const gameState = {
// Metadata
id: 'game-123',
version: 42,
phase: 'worker-placement',
currentPlayer: 'player-1',
turnNumber: 5,
age: 2,
// Player states
players: {
'player-1': { /* player state */ },
'player-2': { /* player state */ }
},
// Shared state
board: { /* board state */ },
market: { /* market state */ },
decks: { /* deck state */ }
};所有游戏状态应存储在一个标准对象中:
javascript
const gameState = {
// Metadata
id: 'game-123',
version: 42,
phase: 'worker-placement',
currentPlayer: 'player-1',
turnNumber: 5,
age: 2,
// Player states
players: {
'player-1': { /* player state */ },
'player-2': { /* player state */ }
},
// Shared state
board: { /* board state */ },
market: { /* market state */ },
decks: { /* deck state */ }
};State Structure Design
状态结构设计
Normalize Nested Data
规范化嵌套数据
Avoid deeply nested structures. Use ID references instead:
javascript
// BAD: Deeply nested
const state = {
players: [{
ships: [{
upgrades: [{ type: 'engine', stats: {...} }]
}]
}]
};
// GOOD: Normalized with references
const state = {
players: {
'p1': { id: 'p1', shipIds: ['ship-1', 'ship-2'] }
},
ships: {
'ship-1': { id: 'ship-1', ownerId: 'p1', upgradeIds: ['upg-1'] }
},
upgrades: {
'upg-1': { id: 'upg-1', type: 'engine', stats: {...} }
}
};避免深度嵌套结构,改用ID引用:
javascript
// BAD: Deeply nested
const state = {
players: [{
ships: [{
upgrades: [{ type: 'engine', stats: {...} }]
}]
}]
};
// GOOD: Normalized with references
const state = {
players: {
'p1': { id: 'p1', shipIds: ['ship-1', 'ship-2'] }
},
ships: {
'ship-1': { id: 'ship-1', ownerId: 'p1', upgradeIds: ['upg-1'] }
},
upgrades: {
'upg-1': { id: 'upg-1', type: 'engine', stats: {...} }
}
};Separate Derived State
分离派生状态
Don't store computed values in state:
javascript
// BAD: Storing computed values
const playerState = {
money: 100,
income: 15,
totalMoney: 115 // Don't store this!
};
// GOOD: Compute when needed
function getTotalMoney(player) {
return player.money + player.income;
}
// Or use selectors
const selectors = {
totalLift: (state, playerId) => {
const player = state.players[playerId];
return player.gasCubes * 5;
},
canLaunch: (state, playerId) => {
const lift = selectors.totalLift(state, playerId);
const weight = selectors.totalWeight(state, playerId);
return lift >= weight;
}
};不要在状态中存储计算得出的值:
javascript
// BAD: Storing computed values
const playerState = {
money: 100,
income: 15,
totalMoney: 115 // Don't store this!
};
// GOOD: Compute when needed
function getTotalMoney(player) {
return player.money + player.income;
}
// Or use selectors
const selectors = {
totalLift: (state, playerId) => {
const player = state.players[playerId];
return player.gasCubes * 5;
},
canLaunch: (state, playerId) => {
const lift = selectors.totalLift(state, playerId);
const weight = selectors.totalWeight(state, playerId);
return lift >= weight;
}
};Action/Reducer Pattern
Action/Reducer模式
Action Structure
Action结构
javascript
// Actions describe what happened
const action = {
type: 'PLACE_WORKER',
playerId: 'player-1',
payload: {
workerId: 'agent-1',
locationId: 'construction-hall'
}
};
// Action creators for consistency
const actions = {
placeWorker: (playerId, workerId, locationId) => ({
type: 'PLACE_WORKER',
playerId,
payload: { workerId, locationId }
}),
acquireTechnology: (playerId, techId, cost) => ({
type: 'ACQUIRE_TECHNOLOGY',
playerId,
payload: { techId, cost }
})
};javascript
// Actions describe what happened
const action = {
type: 'PLACE_WORKER',
playerId: 'player-1',
payload: {
workerId: 'agent-1',
locationId: 'construction-hall'
}
};
// Action creators for consistency
const actions = {
placeWorker: (playerId, workerId, locationId) => ({
type: 'PLACE_WORKER',
playerId,
payload: { workerId, locationId }
}),
acquireTechnology: (playerId, techId, cost) => ({
type: 'ACQUIRE_TECHNOLOGY',
playerId,
payload: { techId, cost }
})
};Reducer Implementation
Reducer实现
javascript
function gameReducer(state, action) {
switch (action.type) {
case 'PLACE_WORKER':
return placeWorkerReducer(state, action);
case 'ACQUIRE_TECHNOLOGY':
return acquireTechnologyReducer(state, action);
case 'LAUNCH_SHIP':
return launchShipReducer(state, action);
default:
return state;
}
}
function placeWorkerReducer(state, action) {
const { playerId, payload: { workerId, locationId } } = action;
return {
...state,
workers: {
...state.workers,
[workerId]: {
...state.workers[workerId],
locationId,
available: false
}
},
locations: {
...state.locations,
[locationId]: {
...state.locations[locationId],
workerIds: [...state.locations[locationId].workerIds, workerId]
}
}
};
}javascript
function gameReducer(state, action) {
switch (action.type) {
case 'PLACE_WORKER':
return placeWorkerReducer(state, action);
case 'ACQUIRE_TECHNOLOGY':
return acquireTechnologyReducer(state, action);
case 'LAUNCH_SHIP':
return launchShipReducer(state, action);
default:
return state;
}
}
function placeWorkerReducer(state, action) {
const { playerId, payload: { workerId, locationId } } = action;
return {
...state,
workers: {
...state.workers,
[workerId]: {
...state.workers[workerId],
locationId,
available: false
}
},
locations: {
...state.locations,
[locationId]: {
...state.locations[locationId],
workerIds: [...state.locations[locationId].workerIds, workerId]
}
}
};
}Action Validation
操作验证
Validation Layer
验证层
Always validate before applying actions:
javascript
function validateAction(state, action) {
const validator = validators[action.type];
if (!validator) {
return { valid: false, reason: 'Unknown action type' };
}
return validator(state, action);
}
const validators = {
PLACE_WORKER: (state, action) => {
const { playerId, payload: { workerId, locationId } } = action;
// Check it's player's turn
if (state.currentPlayer !== playerId) {
return { valid: false, reason: 'Not your turn' };
}
// Check worker belongs to player and is available
const worker = state.workers[workerId];
if (!worker || worker.ownerId !== playerId) {
return { valid: false, reason: 'Invalid worker' };
}
if (!worker.available) {
return { valid: false, reason: 'Worker already placed' };
}
// Check location exists and has space
const location = state.locations[locationId];
if (!location) {
return { valid: false, reason: 'Invalid location' };
}
if (location.workerIds.length >= location.capacity) {
return { valid: false, reason: 'Location is full' };
}
return { valid: true };
}
};应用操作前务必进行验证:
javascript
function validateAction(state, action) {
const validator = validators[action.type];
if (!validator) {
return { valid: false, reason: 'Unknown action type' };
}
return validator(state, action);
}
const validators = {
PLACE_WORKER: (state, action) => {
const { playerId, payload: { workerId, locationId } } = action;
// Check it's player's turn
if (state.currentPlayer !== playerId) {
return { valid: false, reason: 'Not your turn' };
}
// Check worker belongs to player and is available
const worker = state.workers[workerId];
if (!worker || worker.ownerId !== playerId) {
return { valid: false, reason: 'Invalid worker' };
}
if (!worker.available) {
return { valid: false, reason: 'Worker already placed' };
}
// Check location exists and has space
const location = state.locations[locationId];
if (!location) {
return { valid: false, reason: 'Invalid location' };
}
if (location.workerIds.length >= location.capacity) {
return { valid: false, reason: 'Location is full' };
}
return { valid: true };
}
};Process Action Flow
操作处理流程
javascript
async function processAction(gameId, playerId, action) {
// 1. Load current state
const state = await loadGameState(gameId);
// 2. Validate
const validation = validateAction(state, action);
if (!validation.valid) {
return { success: false, error: validation.reason };
}
// 3. Apply action
let newState = gameReducer(state, action);
// 4. Check for triggered effects
newState = processTriggers(newState, action);
// 5. Check for phase transitions
newState = checkPhaseTransition(newState);
// 6. Increment version
newState = { ...newState, version: newState.version + 1 };
// 7. Persist
await saveGameState(gameId, newState);
return { success: true, newState };
}javascript
async function processAction(gameId, playerId, action) {
// 1. Load current state
const state = await loadGameState(gameId);
// 2. Validate
const validation = validateAction(state, action);
if (!validation.valid) {
return { success: false, error: validation.reason };
}
// 3. Apply action
let newState = gameReducer(state, action);
// 4. Check for triggered effects
newState = processTriggers(newState, action);
// 5. Check for phase transitions
newState = checkPhaseTransition(newState);
// 6. Increment version
newState = { ...newState, version: newState.version + 1 };
// 7. Persist
await saveGameState(gameId, newState);
return { success: true, newState };
}Phase & Turn Management
阶段与回合管理
State Machine for Phases
阶段状态机
javascript
const phases = {
SETUP: 'setup',
WORKER_PLACEMENT: 'worker-placement',
REVEAL: 'reveal',
LAUNCH: 'launch',
INCOME: 'income',
CLEANUP: 'cleanup',
AGE_TRANSITION: 'age-transition',
GAME_END: 'game-end'
};
const phaseTransitions = {
[phases.SETUP]: {
next: phases.WORKER_PLACEMENT,
canTransition: (state) => allPlayersReady(state)
},
[phases.WORKER_PLACEMENT]: {
next: phases.REVEAL,
canTransition: (state) => allWorkersPlaced(state)
},
[phases.REVEAL]: {
next: phases.LAUNCH,
canTransition: (state) => allCardsRevealed(state)
},
// ... etc
};
function checkPhaseTransition(state) {
const currentPhase = phaseTransitions[state.phase];
if (currentPhase && currentPhase.canTransition(state)) {
return transitionToPhase(state, currentPhase.next);
}
return state;
}javascript
const phases = {
SETUP: 'setup',
WORKER_PLACEMENT: 'worker-placement',
REVEAL: 'reveal',
LAUNCH: 'launch',
INCOME: 'income',
CLEANUP: 'cleanup',
AGE_TRANSITION: 'age-transition',
GAME_END: 'game-end'
};
const phaseTransitions = {
[phases.SETUP]: {
next: phases.WORKER_PLACEMENT,
canTransition: (state) => allPlayersReady(state)
},
[phases.WORKER_PLACEMENT]: {
next: phases.REVEAL,
canTransition: (state) => allWorkersPlaced(state)
},
[phases.REVEAL]: {
next: phases.LAUNCH,
canTransition: (state) => allCardsRevealed(state)
},
// ... etc
};
function checkPhaseTransition(state) {
const currentPhase = phaseTransitions[state.phase];
if (currentPhase && currentPhase.canTransition(state)) {
return transitionToPhase(state, currentPhase.next);
}
return state;
}Turn Order Management
回合顺序管理
javascript
function getNextPlayer(state) {
const playerOrder = state.playerOrder;
const currentIndex = playerOrder.indexOf(state.currentPlayer);
const nextIndex = (currentIndex + 1) % playerOrder.length;
return playerOrder[nextIndex];
}
function advanceTurn(state) {
const nextPlayer = getNextPlayer(state);
const isNewRound = nextPlayer === state.playerOrder[0];
return {
...state,
currentPlayer: nextPlayer,
turnNumber: isNewRound ? state.turnNumber + 1 : state.turnNumber
};
}javascript
function getNextPlayer(state) {
const playerOrder = state.playerOrder;
const currentIndex = playerOrder.indexOf(state.currentPlayer);
const nextIndex = (currentIndex + 1) % playerOrder.length;
return playerOrder[nextIndex];
}
function advanceTurn(state) {
const nextPlayer = getNextPlayer(state);
const isNewRound = nextPlayer === state.playerOrder[0];
return {
...state,
currentPlayer: nextPlayer,
turnNumber: isNewRound ? state.turnNumber + 1 : state.turnNumber
};
}Complex Game Logic
复杂游戏逻辑
Calculating Ship Stats
计算飞船属性
javascript
function calculateShipStats(state, playerId) {
const player = state.players[playerId];
const blueprint = state.blueprints[player.blueprintId];
// Start with baseline stats
let stats = { ...blueprint.baselineStats };
// Add stats from installed upgrades
for (const slotId of blueprint.slotIds) {
const slot = state.slots[slotId];
if (slot.upgradeId) {
const upgrade = state.upgrades[slot.upgradeId];
stats = mergeStats(stats, upgrade.stats);
}
}
// Calculate lift from gas cubes
const gasCubes = countGasCubes(state, playerId);
stats.lift = gasCubes * 5;
return stats;
}
function canLaunch(state, playerId) {
const stats = calculateShipStats(state, playerId);
// Physics check: Lift >= Weight
if (stats.lift < stats.weight) {
return { can: false, reason: 'Insufficient lift' };
}
// Must have pilot
const player = state.players[playerId];
if (player.pilots < 1) {
return { can: false, reason: 'No pilot available' };
}
// Must have ship in hangar
if (player.launchHangar.length === 0) {
return { can: false, reason: 'No ships in hangar' };
}
return { can: true };
}javascript
function calculateShipStats(state, playerId) {
const player = state.players[playerId];
const blueprint = state.blueprints[player.blueprintId];
// Start with baseline stats
let stats = { ...blueprint.baselineStats };
// Add stats from installed upgrades
for (const slotId of blueprint.slotIds) {
const slot = state.slots[slotId];
if (slot.upgradeId) {
const upgrade = state.upgrades[slot.upgradeId];
stats = mergeStats(stats, upgrade.stats);
}
}
// Calculate lift from gas cubes
const gasCubes = countGasCubes(state, playerId);
stats.lift = gasCubes * 5;
return stats;
}
function canLaunch(state, playerId) {
const stats = calculateShipStats(state, playerId);
// Physics check: Lift >= Weight
if (stats.lift < stats.weight) {
return { can: false, reason: 'Insufficient lift' };
}
// Must have pilot
const player = state.players[playerId];
if (player.pilots < 1) {
return { can: false, reason: 'No pilot available' };
}
// Must have ship in hangar
if (player.launchHangar.length === 0) {
return { can: false, reason: 'No ships in hangar' };
}
return { can: true };
}Hazard Check Resolution
危险事件检查处理
javascript
function resolveHazardCheck(state, playerId, hazardCard) {
const shipStats = calculateShipStats(state, playerId);
const results = [];
for (const check of hazardCard.checks) {
const playerValue = shipStats[check.stat];
const passed = playerValue >= check.difficulty;
results.push({
stat: check.stat,
required: check.difficulty,
actual: playerValue,
passed
});
}
const allPassed = results.every(r => r.passed);
return {
hazardCard,
results,
outcome: allPassed ? 'success' : hazardCard.failureEffect
};
}javascript
function resolveHazardCheck(state, playerId, hazardCard) {
const shipStats = calculateShipStats(state, playerId);
const results = [];
for (const check of hazardCard.checks) {
const playerValue = shipStats[check.stat];
const passed = playerValue >= check.difficulty;
results.push({
stat: check.stat,
required: check.difficulty,
actual: playerValue,
passed
});
}
const allPassed = results.every(r => r.passed);
return {
hazardCard,
results,
outcome: allPassed ? 'success' : hazardCard.failureEffect
};
}Undo/Redo Support
撤销/重做支持
Action History
操作历史
javascript
const gameWithHistory = {
...gameState,
history: {
past: [], // Previous states
future: [] // States after undo (for redo)
}
};
function applyActionWithHistory(state, action) {
// Save current state to history
const newPast = [...state.history.past, state];
// Apply action
const newState = gameReducer(state, action);
return {
...newState,
history: {
past: newPast,
future: [] // Clear redo stack on new action
}
};
}
function undo(state) {
if (state.history.past.length === 0) return state;
const previous = state.history.past[state.history.past.length - 1];
const newPast = state.history.past.slice(0, -1);
return {
...previous,
history: {
past: newPast,
future: [state, ...state.history.future]
}
};
}javascript
const gameWithHistory = {
...gameState,
history: {
past: [], // Previous states
future: [] // States after undo (for redo)
}
};
function applyActionWithHistory(state, action) {
// Save current state to history
const newPast = [...state.history.past, state];
// Apply action
const newState = gameReducer(state, action);
return {
...newState,
history: {
past: newPast,
future: [] // Clear redo stack on new action
}
};
}
function undo(state) {
if (state.history.past.length === 0) return state;
const previous = state.history.past[state.history.past.length - 1];
const newPast = state.history.past.slice(0, -1);
return {
...previous,
history: {
past: newPast,
future: [state, ...state.history.future]
}
};
}Testing Game Logic
游戏逻辑测试
Unit Testing Reducers
Reducer单元测试
javascript
describe('placeWorkerReducer', () => {
it('should place worker at location', () => {
const state = createTestState();
const action = actions.placeWorker('p1', 'worker-1', 'location-a');
const newState = gameReducer(state, action);
expect(newState.workers['worker-1'].locationId).toBe('location-a');
expect(newState.locations['location-a'].workerIds).toContain('worker-1');
});
it('should not mutate original state', () => {
const state = createTestState();
const original = JSON.stringify(state);
const action = actions.placeWorker('p1', 'worker-1', 'location-a');
gameReducer(state, action);
expect(JSON.stringify(state)).toBe(original);
});
});javascript
describe('placeWorkerReducer', () => {
it('should place worker at location', () => {
const state = createTestState();
const action = actions.placeWorker('p1', 'worker-1', 'location-a');
const newState = gameReducer(state, action);
expect(newState.workers['worker-1'].locationId).toBe('location-a');
expect(newState.locations['location-a'].workerIds).toContain('worker-1');
});
it('should not mutate original state', () => {
const state = createTestState();
const original = JSON.stringify(state);
const action = actions.placeWorker('p1', 'worker-1', 'location-a');
gameReducer(state, action);
expect(JSON.stringify(state)).toBe(original);
});
});Integration Testing Game Flow
游戏流程集成测试
javascript
describe('full game round', () => {
it('should complete worker placement phase', () => {
let state = createGameState({ playerCount: 2 });
// Each player places 3 workers
for (let i = 0; i < 6; i++) {
const playerId = state.currentPlayer;
const worker = getAvailableWorker(state, playerId);
const location = getValidLocation(state);
state = processAction(state, actions.placeWorker(
playerId, worker.id, location.id
));
}
expect(state.phase).toBe('reveal');
});
});javascript
describe('full game round', () => {
it('should complete worker placement phase', () => {
let state = createGameState({ playerCount: 2 });
// Each player places 3 workers
for (let i = 0; i < 6; i++) {
const playerId = state.currentPlayer;
const worker = getAvailableWorker(state, playerId);
const location = getValidLocation(state);
state = processAction(state, actions.placeWorker(
playerId, worker.id, location.id
));
}
expect(state.phase).toBe('reveal');
});
});When This Skill Activates
本技能适用场景
Use this skill when:
- Designing game state structure
- Implementing action/reducer logic
- Building validation for game rules
- Managing phase and turn transitions
- Calculating derived game values
- Implementing undo/redo
- Testing game logic
在以下场景中使用本技能:
- 设计游戏状态结构
- 实现Action/Reducer逻辑
- 构建游戏规则验证机制
- 管理阶段与回合转换
- 计算派生游戏数值
- 实现撤销/重做功能
- 测试游戏逻辑