validScenario(); self::assertSame([], ScenarioValidator::validate($scenario)); } public function testItRejectsAnObjectiveOnImpassableTerrain(): void { $scenario = $this->validScenario( terrain: ['4:4' => Terrain::Blocking], objectivePosition: new Position(4, 4), ); $errors = ScenarioValidator::validate($scenario); self::assertNotEmpty($errors); self::assertTrue((bool) array_filter($errors, static fn (string $error): bool => str_contains($error, 'impassable'))); } public function testItRejectsAnObjectiveUnreachableFromAnyDeploymentZone(): void { $scenario = $this->validScenario( terrain: [ '3:4' => Terrain::Blocking, '4:3' => Terrain::Blocking, '5:4' => Terrain::Blocking, '4:5' => Terrain::Blocking, ], objectivePosition: new Position(4, 4), ); $errors = ScenarioValidator::validate($scenario); self::assertTrue((bool) array_filter($errors, static fn (string $error): bool => str_contains($error, 'unreachable'))); } public function testItRejectsOverlappingDeploymentZones(): void { $units = $this->unitSet(); $battlefield = new Battlefield(8, 8); $scenario = new Scenario( id: 'demo', name: 'Demo', battlefield: $battlefield, units: $units, deploymentZones: [ 'alpha' => new DeploymentZone('alpha', [new Position(0, 0), new Position(1, 0), new Position(2, 0)]), 'bravo' => new DeploymentZone('bravo', [new Position(1, 0), new Position(2, 0), new Position(7, 7), new Position(6, 7), new Position(5, 7)]), ], objectives: [], victoryCondition: VictoryCondition::EliminateAll, holdRoundsRequired: 1, ); $errors = ScenarioValidator::validate($scenario); self::assertTrue((bool) array_filter($errors, static fn (string $error): bool => str_contains($error, 'overlap'))); } public function testItRejectsDeploymentPositionsOnImpassableTerrain(): void { $units = $this->unitSet(); $battlefield = new Battlefield(8, 8, ['0:0' => Terrain::Blocking]); $scenario = new Scenario( id: 'demo', name: 'Demo', battlefield: $battlefield, units: $units, deploymentZones: [ 'alpha' => new DeploymentZone('alpha', [new Position(0, 0), new Position(1, 0), new Position(2, 0)]), 'bravo' => new DeploymentZone('bravo', [new Position(7, 7), new Position(6, 7), new Position(5, 7)]), ], objectives: [], victoryCondition: VictoryCondition::EliminateAll, holdRoundsRequired: 1, ); $errors = ScenarioValidator::validate($scenario); self::assertTrue((bool) array_filter($errors, static fn (string $error): bool => str_contains($error, 'impassable'))); } /** * @param array $terrain */ private function validScenario( array $terrain = [], ?Position $objectivePosition = null, ): Scenario { $units = $this->unitSet(); $battlefield = new Battlefield(8, 8, $terrain); $objectives = []; if ($objectivePosition !== null) { $objectives = ['objective-1' => new ObjectiveMarker('objective-1', $objectivePosition)]; } return new Scenario( id: 'demo', name: 'Demo', battlefield: $battlefield, units: $units, deploymentZones: $this->zones(), objectives: $objectives, victoryCondition: $objectivePosition === null ? VictoryCondition::EliminateAll : VictoryCondition::HoldObjective, holdRoundsRequired: 1, ); } /** @return array */ private function zones(): array { return [ 'alpha' => new DeploymentZone('alpha', [ new Position(0, 0), new Position(1, 0), new Position(2, 0), ]), 'bravo' => new DeploymentZone('bravo', [ new Position(7, 7), new Position(6, 7), new Position(5, 7), ]), ]; } /** @return list */ private function unitSet(): array { return [ new UnitState('alpha-1', 'alpha', new Position(0, 0), 10, 10, 4, 3, 2, 2, false, Archetype::Defender), new UnitState('alpha-2', 'alpha', new Position(1, 0), 8, 8, 5, 2, 3, 2, false, Archetype::Striker), new UnitState('alpha-3', 'alpha', new Position(2, 0), 7, 7, 2, 2, 3, 2, false, Archetype::Support), new UnitState('bravo-1', 'bravo', new Position(7, 7), 10, 10, 4, 3, 2, 2, false, Archetype::Defender), new UnitState('bravo-2', 'bravo', new Position(6, 7), 8, 8, 5, 2, 3, 2, false, Archetype::Striker), new UnitState('bravo-3', 'bravo', new Position(5, 7), 6, 6, 3, 1, 5, 2, false, Archetype::Scout), ]; } }