import type { DomainEvent } from "./events.js"; import type { DomainError, Encounter } from "./types.js"; interface AdvanceTurnSuccess { readonly encounter: Encounter; readonly events: DomainEvent[]; } /** * Pure function that advances the turn to the next combatant. * * FR-001: Accepts an Encounter and returns next state + events. * FR-002: Increments activeIndex by 1, wrapping to 0. * FR-003: When wrapping, increments roundNumber by 1. * FR-004: Empty encounter returns error (no state change, no events). * FR-005: Events returned as values, not dispatched via side effects. */ export function advanceTurn( encounter: Encounter, ): AdvanceTurnSuccess | DomainError { // FR-004 / INV-1: reject empty encounters if (encounter.combatants.length === 0) { return { kind: "domain-error", code: "invalid-encounter", message: "Cannot advance turn on an encounter with no combatants", }; } const previousIndex = encounter.activeIndex; const nextIndex = (previousIndex + 1) % encounter.combatants.length; const wraps = nextIndex === 0; const newRoundNumber = wraps ? encounter.roundNumber + 1 : encounter.roundNumber; const events: DomainEvent[] = [ { type: "TurnAdvanced", previousCombatantId: encounter.combatants[previousIndex].id, newCombatantId: encounter.combatants[nextIndex].id, roundNumber: newRoundNumber, }, ]; // Event ordering contract: TurnAdvanced first, then RoundAdvanced if (wraps) { events.push({ type: "RoundAdvanced", newRoundNumber, }); } return { encounter: { combatants: encounter.combatants, activeIndex: nextIndex, roundNumber: newRoundNumber, }, events, }; } export { isDomainError } from "./types.js";