163 lines
5.7 KiB
PHP
163 lines
5.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace BattleForge\Tests\Unit\Domain;
|
|
|
|
use BattleForge\Domain\Archetype;
|
|
use BattleForge\Domain\Battlefield;
|
|
use BattleForge\Domain\DeploymentZone;
|
|
use BattleForge\Domain\ObjectiveMarker;
|
|
use BattleForge\Domain\Position;
|
|
use BattleForge\Domain\Scenario;
|
|
use BattleForge\Domain\ScenarioValidator;
|
|
use BattleForge\Domain\Terrain;
|
|
use BattleForge\Domain\UnitState;
|
|
use BattleForge\Domain\VictoryCondition;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
final class ScenarioValidatorTest extends TestCase
|
|
{
|
|
public function testItReturnsAnEmptyListForAValidScenario(): void
|
|
{
|
|
$scenario = $this->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<string, Terrain> $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<string, DeploymentZone> */
|
|
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<UnitState> */
|
|
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),
|
|
];
|
|
}
|
|
}
|