fix: enforce immutable match state invariants
This commit is contained in:
@@ -20,15 +20,74 @@ final readonly class MatchState
|
||||
public ?string $winnerTeamId = null,
|
||||
public array $actionLog = [],
|
||||
) {
|
||||
if (!self::isList($units)) {
|
||||
throw new InvalidArgumentException('Match units must be a list.');
|
||||
}
|
||||
|
||||
if ($units === []) {
|
||||
throw new InvalidArgumentException('Match must contain at least one unit.');
|
||||
}
|
||||
|
||||
$unitIds = [];
|
||||
$teamIds = [];
|
||||
|
||||
foreach ($units as $unit) {
|
||||
if (!self::isUnitState($unit)) {
|
||||
throw new InvalidArgumentException('Match units must contain only UnitState instances.');
|
||||
}
|
||||
|
||||
if (isset($unitIds[$unit->id])) {
|
||||
throw new InvalidArgumentException("Duplicate unit id: {$unit->id}.");
|
||||
}
|
||||
|
||||
$unitIds[$unit->id] = true;
|
||||
$teamIds[$unit->teamId] = true;
|
||||
}
|
||||
|
||||
if ($activeTeamId === '') {
|
||||
throw new InvalidArgumentException('Active team id cannot be empty.');
|
||||
}
|
||||
|
||||
if (!isset($teamIds[$activeTeamId])) {
|
||||
throw new InvalidArgumentException("Active team has no units: {$activeTeamId}.");
|
||||
}
|
||||
|
||||
if ($round < 1) {
|
||||
throw new InvalidArgumentException('Match round must be at least 1.');
|
||||
}
|
||||
|
||||
if (!self::isList($actionLog)) {
|
||||
throw new InvalidArgumentException('Match action log must be a list.');
|
||||
}
|
||||
|
||||
foreach ($actionLog as $entry) {
|
||||
if (!self::isString($entry)) {
|
||||
throw new InvalidArgumentException('Match action log entries must be strings.');
|
||||
}
|
||||
}
|
||||
|
||||
if ($winnerTeamId !== null && $winnerTeamId === '') {
|
||||
throw new InvalidArgumentException('Winner team id cannot be empty.');
|
||||
}
|
||||
|
||||
if ($winnerTeamId !== null && !isset($teamIds[$winnerTeamId])) {
|
||||
throw new InvalidArgumentException("Winner team has no units: {$winnerTeamId}.");
|
||||
}
|
||||
}
|
||||
|
||||
private static function isUnitState(mixed $unit): bool
|
||||
{
|
||||
return $unit instanceof UnitState;
|
||||
}
|
||||
|
||||
private static function isString(mixed $value): bool
|
||||
{
|
||||
return is_string($value);
|
||||
}
|
||||
|
||||
private static function isList(mixed $value): bool
|
||||
{
|
||||
return is_array($value) && array_is_list($value);
|
||||
}
|
||||
|
||||
public function unit(string $id): UnitState
|
||||
@@ -57,6 +116,18 @@ final readonly class MatchState
|
||||
throw new InvalidArgumentException("Unknown unit id: {$replacement->id}.");
|
||||
}
|
||||
|
||||
public function withWinner(?string $winnerTeamId): self
|
||||
{
|
||||
return new self(
|
||||
$this->battlefield,
|
||||
$this->units,
|
||||
$this->activeTeamId,
|
||||
$this->round,
|
||||
$winnerTeamId,
|
||||
$this->actionLog,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<UnitState>|null $units
|
||||
* @param list<string>|null $actionLog
|
||||
@@ -65,7 +136,6 @@ final readonly class MatchState
|
||||
?array $units = null,
|
||||
?string $activeTeamId = null,
|
||||
?int $round = null,
|
||||
?string $winnerTeamId = null,
|
||||
?array $actionLog = null,
|
||||
): self {
|
||||
return new self(
|
||||
@@ -73,7 +143,7 @@ final readonly class MatchState
|
||||
$units ?? $this->units,
|
||||
$activeTeamId ?? $this->activeTeamId,
|
||||
$round ?? $this->round,
|
||||
$winnerTeamId ?? $this->winnerTeamId,
|
||||
$this->winnerTeamId,
|
||||
$actionLog ?? $this->actionLog,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user