expectException(InvalidArgumentException::class); new Battlefield(7, 16); } public function testItAcceptsMinimumAndMaximumDimensions(): void { $minimum = new Battlefield(8, 8); $maximum = new Battlefield(16, 16); self::assertSame([8, 8], [$minimum->width, $minimum->height]); self::assertSame([16, 16], [$maximum->width, $maximum->height]); } public function testItRejectsDimensionsAboveTheSupportedRange(): void { $this->expectException(InvalidArgumentException::class); new Battlefield(16, 17); } public function testItRejectsNonCanonicalTerrainCoordinates(): void { $this->expectException(InvalidArgumentException::class); new Battlefield(8, 8, ['01:0' => Terrain::Forest]); } public function testItRejectsMalformedTerrainCoordinates(): void { $this->expectException(InvalidArgumentException::class); new Battlefield(8, 8, ['not-a-coordinate' => Terrain::Forest]); } public function testItRejectsInvalidTerrainValues(): void { $this->expectException(InvalidArgumentException::class); // @phpstan-ignore argument.type new Battlefield(8, 8, ['0:0' => 'forest']); } public function testReachableRejectsAStartOutsideTheBattlefield(): void { $battlefield = new Battlefield(8, 8); $this->expectException(InvalidArgumentException::class); $battlefield->reachable(new Position(-1, 0), 2, []); } public function testReachableRejectsANegativeBudget(): void { $battlefield = new Battlefield(8, 8); $this->expectException(InvalidArgumentException::class); $battlefield->reachable(new Position(0, 0), -1, []); } public function testReachableRejectsOccupiedPositionsOutsideTheBattlefield(): void { $battlefield = new Battlefield(8, 8); $this->expectException(InvalidArgumentException::class); $battlefield->reachable(new Position(0, 0), 2, [new Position(8, 0)]); } public function testItFindsReachableTilesUsingTerrainCostsAndObstacles(): void { $battlefield = new Battlefield(8, 8, [ '1:0' => Terrain::Forest, '0:1' => Terrain::Rough, '2:0' => Terrain::Water, '1:1' => Terrain::Blocking, ]); $reachable = $battlefield->reachable( new Position(0, 0), 2, [new Position(0, 2)], ); self::assertSame([ '0:0' => 0, '1:0' => 2, '0:1' => 2, ], $reachable); } public function testReachableReplacesAHighCostRouteWithALowerCostDetour(): void { $battlefield = new Battlefield(8, 8, [ '1:0' => Terrain::Forest, '2:0' => Terrain::Forest, '3:0' => Terrain::Forest, ]); $reachable = $battlefield->reachable(new Position(0, 0), 7, []); self::assertSame(6, $reachable['4:0']); } }