Download Game! Currently 82 players. Last logged in:MutaLouisianaEndureDesert

Blitzer's Blog >> 72678

Back to blogs index
Posted: 21 Sep 2026 23:46 [ permalink ]
Yes, mapping the top-level architecture and designing the abstracted intent
layer first is the absolute best software engineering approach.

By separating intent generation (who wants to do what) from game state
mutation (applying the action), your game will natively support local humans,
rule-based AI bots, or network packets over WebSockets with zero architectural
changes.

1. The High-Level Loop

The game operates as a state machine where a single Tick advances through
discrete stages.

[ Setup Phase ] : ( Choose Characters & Set Faction slots to Human/ < AI ) : [
Round Start ] (Clear temporary status    <  [ Order Phase ] (Wait for ALL
slots to submit an Intent)    <  [ Execution Phase ] (Sort Intents by
Priority, run Interceptions, apply Damage)    <  [ Production Phase ]
(Calculate $ yields, run AI purchases, process building)    < g [ Check Match
End ] (If players left > 1, repeat loop) rids) 

2. The Abstracted Intent Scheme

Instead of the UI or the AI editing the game state directly, everything must
route through a unified, serializable Intent Object. An Intent is a simple
payload specifying who is acting, what they are doing, and what assets they
are committing.

The Intent Schema

{ playerId: 0, // Who is issuing the action actionId: "LAUNCH_STRIKE", //
LAUNCH_STRIKE, PROPAGANDA, ACTIVATE_DEFENSE, FORTIFY targetPlayerId: 1, //
Target (null if self/non-targeted) assets: { vehicleId: "icbm", // Required if
actionId needs a delivery vehicle payloadId: "warhead_50mt" // Required if
actionId needs a warhead } } 

3. Pseudocode: The Game State Engine

This conceptual architecture handles state initialization and processes
intents cleanly through abstraction.

Initialization Architecture

/ Global Match State Tracker const GameEngine = { state: { roundNumber: 1,
players: [], // Array of active player instances incomingQueue: [], // Tracks
multi-turn missiles mid-flight isCompleted: false }, // 1. PRE-START: Slot and
Character Binding initializeMatch(playerSetupConfigs) { // playerSetupConfigs
= [{ name: "Alice", characterId: "mao_down", type: "human" }, ...]
this.state.players = playerSetupConfigs.map((config, index) => { const
characterTemplate = CHARACTER_REGISTRY.find(c => c.id === config.characterId);
return { id: index, name: config.name, controllerType: config.type, // "human"
| "ai" | "network" character: characterTemplate, resourcePoints: 5, inventory:
{ ...characterTemplate.startingInventory }, cities: [ { name: "Capital City",
population: 20, intact: true }, { name: "Industrial Zone", population: 15,
intact: true }, { name: "Coastal Hub", population: 15, intact: true } ],
currentTurnIntent: null // Slot to store the locked-in intent for this round
}; }); this.startRound(); }, // 2. ROUND MANAGEMENT startRound() {
console.log(`Starting Round ${this.state.roundNumber}`); // Reset current-turn
choices this.state.players.forEach(p => p.currentTurnIntent = null); //
Trigger Intent Requests this.gatherPlayerIntents(); }, // 3. ABSTRACTED INTENT
INTAKE // This routine does not care if the intent data came from a mouse
click, an AI function, or a websocket! submitIntent(intentPayload) { const
player = this.state.players[intentPayload.playerId]; // Validate intent
constraints against current inventory if (!this.validateIntent(player,
intentPayload)) { console.error(`Invalid intent rejected for player
${player.name}`); return; } // Lock it in player.currentTurnIntent =
intentPayload; // Check if all players have declared their actions const
allIntentsCollected = this.state.players.every(p => p.currentTurnIntent !==
null || p.cities.every(c => !c.intact)); if (allIntentsCollected) {
this.executeResolutionPhase(); } }, validateIntent(player, intent) { // If the
action requires structural hardware, c
onfirm it is in the inventory if (intent.actionId === "LAUNCH_STRIKE") { if
(player.inventory[intent.assets.vehicleId] <= 0) return false; if
(player.inventory[intent.assets.payloadId] <= 0) return false; } if
(intent.actionId === "ACTIVATE_DEFENSE") { if (player.inventory.abm_battery <=
0) return false; } return true; }, // 4. COLLECTING INTENTS FROM CONTROLLERS
gatherPlayerIntents() { this.state.players.forEach(player => { // If player is
dead, skip them const isDead = player.cities.every(c => !c.intact); if
(isDead) return; if (player.controllerType === "ai") { // Route to the
automated bot routine const aiIntent = BotBrain.generateIntent(player,
this.state); this.submitIntent(aiIntent); } else if (player.controllerType ===
"human") { // Do nothing. Wait for the HTML UI buttons to fire the click event
// which packages up the selections and pushes them to GameEngine.submitIntent(
) UIManager.enableControlsForPlayer(player.id); } else if (player.controllerTyp
e === "network") { // Do nothing. Wait for the WebSocket server message event
to push data here. } }); }, // 5. RESOLUTION PHASE executeResolutionPhase() {
console.log("--- RESOLVING TURN ACTIONS ---"); // Step A: Deduct inventory
assets spent this round this.state.players.forEach(player => { const intent =
player.currentTurnIntent; if (!intent) return; if (intent.actionId ===
"LAUNCH_STRIKE") { player.inventory[intent.assets.vehicleId]--;
player.inventory[intent.assets.payloadId]--; } else if (intent.actionId ===
"ACTIVATE_DEFENSE") { player.inventory.abm_battery--; } }); // Step B: Route
to combat algorithms (Interceptions, Casualties, Propaganda) CombatSystem.resol
veIntents(this.state.players, this.state.incomingQueue); // Step C: Trigger
Production phase EconomySystem.processProduction(this.state.players); // Step
D: Clean up and iterate this.state.roundNumber++; this.checkMatchEnd() ?
this.endMatch() : this.startRound(); } }; 

4. Intent Pipeline Example (How Data Flows)

To visualize how this abstracts away the inputs, imagine Player 1 is a human
clicking a menu, and Player 2 is an AI bot.

Human Pipeline: Player clicks button  Launch : Selects ICBM : Selects 50MT :
Clicks on Player 2 Box : UI script generates intent block : Calls
GameEngine.submitIntent(payload).

AI Pipeline: Engine runs BotBrain.generateIntent(player2) : Algorithm checks
weights and remaining stock : Automatically returns structured intent block :
Calls GameEngine.submitIntent(payload).

Both paths feed the exact same data format into submitIntent(), entirely
hiding the implementation details of who or what generated the choice.

Would you like to design the BotBrain architecture next to see how an AI
chooses its goals, or should we design the CombatSystem.resolveIntents code to
manage missile queues and calculations?