T007–T011: implement AdvanceTurn domain logic (pure function, events, invariants, 8 acceptance tests)
This commit is contained in:
65
packages/domain/src/advance-turn.ts
Normal file
65
packages/domain/src/advance-turn.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import type { DomainEvent } from "./events.js";
|
||||
import type { DomainError, Encounter } from "./types.js";
|
||||
import { isDomainError } 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 };
|
||||
Reference in New Issue
Block a user