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>
65 lines
1.7 KiB
TypeScript
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";
|