feat: validate scenario deployment zones and objectives

This commit is contained in:
Keith Solomon
2026-07-05 20:11:35 -05:00
parent 84fb175c0c
commit 6fffaa2306
2 changed files with 244 additions and 0 deletions
+82
View File
@@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
namespace BattleForge\Domain;
final class ScenarioValidator
{
/**
* @return list<string>
*/
public static function validate(Scenario $scenario): array
{
$errors = [];
$occupied = [];
foreach ($scenario->deploymentZones as $teamId => $zone) {
foreach ($zone->positions as $position) {
if (!$scenario->battlefield->contains($position)) {
$errors[] = "Deployment zone for {$teamId} includes a position outside the battlefield.";
continue;
}
if ($scenario->battlefield->terrainAt($position)->movementCost() === null) {
$errors[] = "Deployment zone for {$teamId} sits on impassable terrain at {$position->key()}.";
}
if (isset($occupied[$position->key()])) {
$errors[] = "Deployment zones overlap at {$position->key()}.";
}
$occupied[$position->key()] = $teamId;
}
}
$reachableFromDeployment = self::reachableFromDeployment($scenario, $occupied);
foreach ($scenario->objectives as $objective) {
if (!$scenario->battlefield->contains($objective->position)) {
$errors[] = "Objective {$objective->id} is outside the battlefield.";
continue;
}
if ($scenario->battlefield->terrainAt($objective->position)->movementCost() === null) {
$errors[] = "Objective {$objective->id} sits on impassable terrain.";
}
if (!isset($reachableFromDeployment[$objective->position->key()])) {
$errors[] = "Objective {$objective->id} is unreachable from any deployment zone.";
}
}
return $errors;
}
/**
* @param array<string, string> $occupied
* @return array<string, true>
*/
private static function reachableFromDeployment(Scenario $scenario, array $occupied): array
{
$merged = $scenario->units;
$allReachable = [];
foreach ($merged as $unit) {
$others = [];
foreach ($merged as $other) {
if ($other->id === $unit->id || $other->isDefeated()) {
continue;
}
$others[] = $other->position;
}
$reachable = $scenario->battlefield->reachable($unit->position, $unit->speed, $others);
foreach ($reachable as $key => $_cost) {
if (!isset($occupied[$key])) {
$allReachable[$key] = true;
}
}
}
return $allReachable;
}
}