Files
initiative/packages/domain/src/advance-turn.ts
Lukas 36768d3aa1 Upgrade Biome to 2.4.7 and enable 54 additional lint rules
Add rules covering bug prevention (noLeakedRender, noFloatingPromises,
noImportCycles, noReactForwardRef), security (noScriptUrl, noAlert),
performance (noAwaitInLoops, useTopLevelRegex), and code style
(noNestedTernary, useGlobalThis, useNullishCoalescing, useSortedClasses,
plus ~40 more). Fix all violations: extract top-level regex constants,
guard React && renders with boolean coercion, refactor nested ternaries,
replace window with globalThis, sort Tailwind classes, and introduce
expectDomainError test helper to eliminate conditional expects.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 14:25:09 +01:00

65 lines
1.7 KiB
TypeScript

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";