import type { DomainEvent } from "./events.js"; import type { PlayerCharacter, PlayerCharacterId, } from "./player-character-types.js"; import { VALID_PLAYER_COLORS, VALID_PLAYER_ICONS, } from "./player-character-types.js"; import type { DomainError } from "./types.js"; export interface CreatePlayerCharacterSuccess { readonly characters: readonly PlayerCharacter[]; readonly events: DomainEvent[]; } export function createPlayerCharacter( characters: readonly PlayerCharacter[], id: PlayerCharacterId, name: string, ac: number, maxHp: number, color: string | undefined, icon: string | undefined, ): CreatePlayerCharacterSuccess | DomainError { const trimmed = name.trim(); if (trimmed === "") { return { kind: "domain-error", code: "invalid-name", message: "Player character name must not be empty", }; } if (!Number.isInteger(ac) || ac < 0) { return { kind: "domain-error", code: "invalid-ac", message: "AC must be a non-negative integer", }; } if (!Number.isInteger(maxHp) || maxHp < 1) { return { kind: "domain-error", code: "invalid-max-hp", message: "Max HP must be a positive integer", }; } if (color !== undefined && !VALID_PLAYER_COLORS.has(color)) { return { kind: "domain-error", code: "invalid-color", message: `Invalid color: ${color}`, }; } if (icon !== undefined && !VALID_PLAYER_ICONS.has(icon)) { return { kind: "domain-error", code: "invalid-icon", message: `Invalid icon: ${icon}`, }; } const newCharacter: PlayerCharacter = { id, name: trimmed, ac, maxHp, color: color as PlayerCharacter["color"], icon: icon as PlayerCharacter["icon"], }; return { characters: [...characters, newCharacter], events: [ { type: "PlayerCharacterCreated", playerCharacterId: id, name: trimmed, }, ], }; }