$units * @param array $deploymentZones * @param array $objectives */ public function __construct( public string $id, public string $name, public Battlefield $battlefield, public array $units, public array $deploymentZones, public array $objectives, public VictoryCondition $victoryCondition, public int $holdRoundsRequired, ) { if ($id === '') { throw new InvalidArgumentException('Scenario id cannot be empty.'); } if ($name === '') { throw new InvalidArgumentException('Scenario name cannot be empty.'); } if (!array_is_list($units)) { // @phpstan-ignore function.alreadyNarrowedType (Runtime guard: PHPDoc is not a runtime contract for JSON-sourced arrays in Plan 4.) throw new InvalidArgumentException('Scenario units must be a list.'); } if ($deploymentZones === []) { throw new InvalidArgumentException('Scenario must declare at least one deployment zone.'); } if (count($deploymentZones) !== 2) { throw new InvalidArgumentException('Scenarios must declare exactly two deployment zones.'); } if ($holdRoundsRequired < 1) { throw new InvalidArgumentException('Hold rounds required must be at least 1.'); } if ($victoryCondition === VictoryCondition::HoldObjective && count($this->objectives) !== 1) { throw new InvalidArgumentException('Hold objective victory requires exactly one objective.'); } if ($victoryCondition === VictoryCondition::EliminateAll && $this->objectives !== []) { throw new InvalidArgumentException('Eliminate all victory does not allow objectives.'); } $unitsByTeam = $this->groupUnitsByTeam($units); foreach ($unitsByTeam as $teamId => $teamUnits) { if (count($teamUnits) < 3 || count($teamUnits) > 6) { throw new InvalidArgumentException("Team {$teamId} must have between 3 and 6 units."); } if (!isset($deploymentZones[$teamId])) { throw new InvalidArgumentException("Team {$teamId} is missing a deployment zone."); } } foreach ($units as $unit) { if (!$this->battlefield->contains($unit->position)) { throw new InvalidArgumentException("Unit {$unit->id} is outside the battlefield."); } $template = ArchetypeCatalog::templates()[$unit->archetype->value] ?? null; if ($template === null) { throw new InvalidArgumentException("Unit {$unit->id} uses an unknown archetype."); } if ($unit->maxHealth < $template->minHealth || $unit->maxHealth > $template->maxHealth) { throw new InvalidArgumentException("Unit {$unit->id} health is outside archetype bounds."); } if ($unit->attack < $template->minAttack || $unit->attack > $template->maxAttack) { throw new InvalidArgumentException("Unit {$unit->id} attack is outside archetype bounds."); } if ($unit->defense < $template->minDefense || $unit->defense > $template->maxDefense) { throw new InvalidArgumentException("Unit {$unit->id} defense is outside archetype bounds."); } if ($unit->speed < $template->minSpeed || $unit->speed > $template->maxSpeed) { throw new InvalidArgumentException("Unit {$unit->id} speed is outside archetype bounds."); } foreach ($unit->abilities as $ability) { if (!in_array($ability, $template->allowedAbilities, true)) { throw new InvalidArgumentException("Unit {$unit->id} has an ability that its archetype forbids."); } } $zone = $deploymentZones[$unit->teamId] ?? null; if ($zone === null || !$zone->contains($unit->position)) { throw new InvalidArgumentException("Unit {$unit->id} must start inside team {$unit->teamId} deployment zone."); } } foreach ($objectives as $objective) { if (!$this->battlefield->contains($objective->position)) { throw new InvalidArgumentException("Objective {$objective->id} is outside the battlefield."); } } } public function startMatch(string $activeTeamId): MatchState { $resetUnits = []; foreach ($this->units as $unit) { $resetUnits[] = new UnitState( $unit->id, $unit->teamId, $unit->position, $unit->maxHealth, $unit->maxHealth, $unit->attack, $unit->defense, $unit->speed, 2, false, $unit->archetype, $unit->abilities, 0, false, ); } return new MatchState( battlefield: $this->battlefield, units: $resetUnits, activeTeamId: $activeTeamId, objectives: $this->objectives, victoryCondition: $this->victoryCondition, holdRoundsRequired: $this->holdRoundsRequired, ); } /** * @param list $units * @return array> */ private function groupUnitsByTeam(array $units): array { $grouped = []; foreach ($units as $unit) { $grouped[$unit->teamId] ??= []; $grouped[$unit->teamId][] = $unit; } return $grouped; } }