feat: model scenarios and start match snapshots
This commit is contained in:
@@ -11,6 +11,8 @@ final readonly class MatchState
|
|||||||
/**
|
/**
|
||||||
* @param list<UnitState> $units
|
* @param list<UnitState> $units
|
||||||
* @param list<string> $actionLog
|
* @param list<string> $actionLog
|
||||||
|
* @param array<string, ObjectiveMarker> $objectives
|
||||||
|
* @param array<string, int> $objectiveControl
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public Battlefield $battlefield,
|
public Battlefield $battlefield,
|
||||||
@@ -19,6 +21,10 @@ final readonly class MatchState
|
|||||||
public int $round = 1,
|
public int $round = 1,
|
||||||
public ?string $winnerTeamId = null,
|
public ?string $winnerTeamId = null,
|
||||||
public array $actionLog = [],
|
public array $actionLog = [],
|
||||||
|
public array $objectives = [],
|
||||||
|
public VictoryCondition $victoryCondition = VictoryCondition::EliminateAll,
|
||||||
|
public int $holdRoundsRequired = 1,
|
||||||
|
public array $objectiveControl = [],
|
||||||
) {
|
) {
|
||||||
if (!self::isList($units)) {
|
if (!self::isList($units)) {
|
||||||
throw new InvalidArgumentException('Match units must be a list.');
|
throw new InvalidArgumentException('Match units must be a list.');
|
||||||
@@ -92,6 +98,28 @@ final readonly class MatchState
|
|||||||
if ($winnerTeamId !== null && !isset($teamIds[$winnerTeamId])) {
|
if ($winnerTeamId !== null && !isset($teamIds[$winnerTeamId])) {
|
||||||
throw new InvalidArgumentException("Winner team has no units: {$winnerTeamId}.");
|
throw new InvalidArgumentException("Winner team has no units: {$winnerTeamId}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($holdRoundsRequired < 1) {
|
||||||
|
throw new InvalidArgumentException('Hold rounds required must be at least 1.');
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($objectives as $objective) {
|
||||||
|
if (!$objective instanceof ObjectiveMarker) { // @phpstan-ignore instanceof.alwaysTrue (Runtime guard: PHPDoc is not a runtime contract for JSON-sourced arrays in Plan 4.)
|
||||||
|
throw new InvalidArgumentException('Objectives must be ObjectiveMarker instances.');
|
||||||
|
}
|
||||||
|
if (!$battlefield->contains($objective->position)) {
|
||||||
|
throw new InvalidArgumentException("Objective {$objective->id} is outside the battlefield.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($objectiveControl as $teamId => $count) {
|
||||||
|
if (!self::isString($teamId) || $teamId === '') {
|
||||||
|
throw new InvalidArgumentException('Objective control team id must be a non-empty string.');
|
||||||
|
}
|
||||||
|
if (!self::isInt($count) || $count < 0) {
|
||||||
|
throw new InvalidArgumentException('Objective control count must be a non-negative integer.');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function isUnitState(mixed $unit): bool
|
private static function isUnitState(mixed $unit): bool
|
||||||
@@ -104,6 +132,11 @@ final readonly class MatchState
|
|||||||
return is_string($value);
|
return is_string($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static function isInt(mixed $value): bool
|
||||||
|
{
|
||||||
|
return is_int($value);
|
||||||
|
}
|
||||||
|
|
||||||
private static function isList(mixed $value): bool
|
private static function isList(mixed $value): bool
|
||||||
{
|
{
|
||||||
return is_array($value) && array_is_list($value);
|
return is_array($value) && array_is_list($value);
|
||||||
@@ -144,18 +177,45 @@ final readonly class MatchState
|
|||||||
$this->round,
|
$this->round,
|
||||||
$winnerTeamId,
|
$winnerTeamId,
|
||||||
$this->actionLog,
|
$this->actionLog,
|
||||||
|
$this->objectives,
|
||||||
|
$this->victoryCondition,
|
||||||
|
$this->holdRoundsRequired,
|
||||||
|
$this->objectiveControl,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, int> $objectiveControl
|
||||||
|
*/
|
||||||
|
public function withObjectiveControl(array $objectiveControl): self
|
||||||
|
{
|
||||||
|
return new self(
|
||||||
|
$this->battlefield,
|
||||||
|
$this->units,
|
||||||
|
$this->activeTeamId,
|
||||||
|
$this->round,
|
||||||
|
$this->winnerTeamId,
|
||||||
|
$this->actionLog,
|
||||||
|
$this->objectives,
|
||||||
|
$this->victoryCondition,
|
||||||
|
$this->holdRoundsRequired,
|
||||||
|
$objectiveControl,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param list<UnitState>|null $units
|
* @param list<UnitState>|null $units
|
||||||
* @param list<string>|null $actionLog
|
* @param list<string>|null $actionLog
|
||||||
|
* @param array<string, ObjectiveMarker>|null $objectives
|
||||||
|
* @param array<string, int>|null $objectiveControl
|
||||||
*/
|
*/
|
||||||
public function copy(
|
public function copy(
|
||||||
?array $units = null,
|
?array $units = null,
|
||||||
?string $activeTeamId = null,
|
?string $activeTeamId = null,
|
||||||
?int $round = null,
|
?int $round = null,
|
||||||
?array $actionLog = null,
|
?array $actionLog = null,
|
||||||
|
?array $objectives = null,
|
||||||
|
?array $objectiveControl = null,
|
||||||
): self {
|
): self {
|
||||||
return new self(
|
return new self(
|
||||||
$this->battlefield,
|
$this->battlefield,
|
||||||
@@ -164,6 +224,10 @@ final readonly class MatchState
|
|||||||
$round ?? $this->round,
|
$round ?? $this->round,
|
||||||
$this->winnerTeamId,
|
$this->winnerTeamId,
|
||||||
$actionLog ?? $this->actionLog,
|
$actionLog ?? $this->actionLog,
|
||||||
|
$objectives ?? $this->objectives,
|
||||||
|
$this->victoryCondition,
|
||||||
|
$this->holdRoundsRequired,
|
||||||
|
$objectiveControl ?? $this->objectiveControl,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace BattleForge\Domain;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
|
||||||
|
final readonly class Scenario
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param list<UnitState> $units
|
||||||
|
* @param array<string, DeploymentZone> $deploymentZones
|
||||||
|
* @param array<string, ObjectiveMarker> $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.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ArchetypeCatalog::templates()[$unit->archetype->value] ?? false) { // @phpstan-ignore nullCoalesce.expr, if.alwaysFalse, booleanNot.alwaysFalse (Runtime guard: defense in depth against an unknown Archetype enum value.)
|
||||||
|
throw new InvalidArgumentException("Unit {$unit->id} uses an unknown archetype.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$template = ArchetypeCatalog::templates()[$unit->archetype->value];
|
||||||
|
|
||||||
|
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<UnitState> $units
|
||||||
|
* @return array<string, list<UnitState>>
|
||||||
|
*/
|
||||||
|
private function groupUnitsByTeam(array $units): array
|
||||||
|
{
|
||||||
|
$grouped = [];
|
||||||
|
|
||||||
|
foreach ($units as $unit) {
|
||||||
|
$grouped[$unit->teamId] ??= [];
|
||||||
|
$grouped[$unit->teamId][] = $unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $grouped;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,245 @@
|
|||||||
|
<?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\MatchState;
|
||||||
|
use BattleForge\Domain\ObjectiveMarker;
|
||||||
|
use BattleForge\Domain\Position;
|
||||||
|
use BattleForge\Domain\Scenario;
|
||||||
|
use BattleForge\Domain\UnitState;
|
||||||
|
use BattleForge\Domain\VictoryCondition;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
final class ScenarioTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testItRejectsNonListUnits(): void
|
||||||
|
{
|
||||||
|
$this->expectException(InvalidArgumentException::class);
|
||||||
|
|
||||||
|
new Scenario(
|
||||||
|
id: 'demo',
|
||||||
|
name: 'Demo',
|
||||||
|
battlefield: new Battlefield(8, 8),
|
||||||
|
units: ['alpha-1' => $this->unit('alpha-1', 'alpha', new Position(0, 0))], // @phpstan-ignore argument.type (Test fixture intentionally passes a non-list units array to verify constructor validation.)
|
||||||
|
deploymentZones: ['alpha' => new DeploymentZone('alpha', [new Position(0, 0)])],
|
||||||
|
objectives: [],
|
||||||
|
victoryCondition: VictoryCondition::EliminateAll,
|
||||||
|
holdRoundsRequired: 1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItRejectsTeamsOutsideTheThreeToSixSizeWindow(): void
|
||||||
|
{
|
||||||
|
$this->expectException(InvalidArgumentException::class);
|
||||||
|
$this->expectExceptionMessage('Team alpha must have between 3 and 6 units.');
|
||||||
|
|
||||||
|
$units = [
|
||||||
|
$this->unit('alpha-1', 'alpha', new Position(0, 0)),
|
||||||
|
$this->unit('alpha-2', 'alpha', new Position(1, 0)),
|
||||||
|
$this->unit('bravo-1', 'bravo', new Position(7, 7)),
|
||||||
|
$this->unit('bravo-2', 'bravo', new Position(6, 7)),
|
||||||
|
$this->unit('bravo-3', 'bravo', new Position(5, 7)),
|
||||||
|
];
|
||||||
|
|
||||||
|
new Scenario(
|
||||||
|
id: 'demo',
|
||||||
|
name: 'Demo',
|
||||||
|
battlefield: new Battlefield(8, 8),
|
||||||
|
units: $units,
|
||||||
|
deploymentZones: [
|
||||||
|
'alpha' => new DeploymentZone('alpha', [new Position(0, 0), new Position(1, 0)]),
|
||||||
|
'bravo' => new DeploymentZone('bravo', [new Position(7, 7), new Position(6, 7), new Position(5, 7)]),
|
||||||
|
],
|
||||||
|
objectives: [],
|
||||||
|
victoryCondition: VictoryCondition::EliminateAll,
|
||||||
|
holdRoundsRequired: 1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItRejectsAbilitiesOutsideTheArchetypeAllowlist(): void
|
||||||
|
{
|
||||||
|
$this->expectException(InvalidArgumentException::class);
|
||||||
|
$this->expectExceptionMessage('Unit alpha-1 has an ability that its archetype forbids.');
|
||||||
|
|
||||||
|
$bad = $this->unit('alpha-1', 'alpha', new Position(0, 0), archetype: Archetype::Scout, abilities: ['heal'], attack: 3, defense: 1, speed: 5, maxHealth: 6);
|
||||||
|
$alpha2 = $this->unit('alpha-2', 'alpha', new Position(1, 0));
|
||||||
|
$alpha3 = $this->unit('alpha-3', 'alpha', new Position(2, 0));
|
||||||
|
$bravo = $this->unit('bravo-1', 'bravo', new Position(7, 7));
|
||||||
|
$bravo2 = $this->unit('bravo-2', 'bravo', new Position(6, 7));
|
||||||
|
$bravo3 = $this->unit('bravo-3', 'bravo', new Position(5, 7));
|
||||||
|
|
||||||
|
new Scenario(
|
||||||
|
id: 'demo',
|
||||||
|
name: 'Demo',
|
||||||
|
battlefield: new Battlefield(8, 8),
|
||||||
|
units: [$bad, $alpha2, $alpha3, $bravo, $bravo2, $bravo3],
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItRejectsUnitsOutsideTheirDeploymentZone(): void
|
||||||
|
{
|
||||||
|
$this->expectException(InvalidArgumentException::class);
|
||||||
|
$this->expectExceptionMessage('Unit alpha-1 must start inside team alpha deployment zone.');
|
||||||
|
|
||||||
|
$bad = $this->unit('alpha-1', 'alpha', new Position(3, 0));
|
||||||
|
$alpha2 = $this->unit('alpha-2', 'alpha', new Position(0, 0));
|
||||||
|
$alpha3 = $this->unit('alpha-3', 'alpha', new Position(1, 0));
|
||||||
|
$bravo = $this->unit('bravo-1', 'bravo', new Position(7, 7));
|
||||||
|
$bravo2 = $this->unit('bravo-2', 'bravo', new Position(6, 7));
|
||||||
|
$bravo3 = $this->unit('bravo-3', 'bravo', new Position(5, 7));
|
||||||
|
|
||||||
|
new Scenario(
|
||||||
|
id: 'demo',
|
||||||
|
name: 'Demo',
|
||||||
|
battlefield: new Battlefield(8, 8),
|
||||||
|
units: [$bad, $alpha2, $alpha3, $bravo, $bravo2, $bravo3],
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHoldObjectiveRequiresExactlyOneObjective(): void
|
||||||
|
{
|
||||||
|
$this->expectException(InvalidArgumentException::class);
|
||||||
|
$this->expectExceptionMessage('Hold objective victory requires exactly one objective.');
|
||||||
|
|
||||||
|
$units = $this->validUnitSet();
|
||||||
|
$zones = $this->validZones();
|
||||||
|
|
||||||
|
new Scenario(
|
||||||
|
id: 'demo',
|
||||||
|
name: 'Demo',
|
||||||
|
battlefield: new Battlefield(8, 8),
|
||||||
|
units: $units,
|
||||||
|
deploymentZones: $zones,
|
||||||
|
objectives: [],
|
||||||
|
victoryCondition: VictoryCondition::HoldObjective,
|
||||||
|
holdRoundsRequired: 3,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testStartMatchReturnsAFreshMatchStateWithResetUnits(): void
|
||||||
|
{
|
||||||
|
$scenario = $this->validScenario();
|
||||||
|
|
||||||
|
$match = $scenario->startMatch('alpha');
|
||||||
|
|
||||||
|
self::assertInstanceOf(MatchState::class, $match);
|
||||||
|
self::assertSame('alpha', $match->activeTeamId);
|
||||||
|
self::assertSame(1, $match->round);
|
||||||
|
self::assertNull($match->winnerTeamId);
|
||||||
|
foreach ($match->units as $unit) {
|
||||||
|
self::assertSame(2, $unit->actionsRemaining);
|
||||||
|
self::assertFalse($unit->hasAttacked);
|
||||||
|
self::assertSame(0, $unit->attackBonus);
|
||||||
|
self::assertFalse($unit->hasUsedAbility);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testStartMatchDoesNotShareUnitInstancesWithTheScenario(): void
|
||||||
|
{
|
||||||
|
$scenario = $this->validScenario();
|
||||||
|
|
||||||
|
$match = $scenario->startMatch('alpha');
|
||||||
|
|
||||||
|
$original = $scenario->units[0];
|
||||||
|
$fromMatch = $match->unit($original->id);
|
||||||
|
|
||||||
|
self::assertNotSame($original, $fromMatch);
|
||||||
|
self::assertSame($original->id, $fromMatch->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return list<UnitState> */
|
||||||
|
private function validUnitSet(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
$this->unit('alpha-1', 'alpha', new Position(0, 0)),
|
||||||
|
$this->unit('alpha-2', 'alpha', new Position(1, 0)),
|
||||||
|
$this->unit('alpha-3', 'alpha', new Position(2, 0)),
|
||||||
|
$this->unit('bravo-1', 'bravo', new Position(7, 7)),
|
||||||
|
$this->unit('bravo-2', 'bravo', new Position(6, 7)),
|
||||||
|
$this->unit('bravo-3', 'bravo', new Position(5, 7)),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return array<string, DeploymentZone> */
|
||||||
|
private function validZones(): 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),
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function validScenario(): Scenario
|
||||||
|
{
|
||||||
|
return new Scenario(
|
||||||
|
id: 'demo',
|
||||||
|
name: 'Demo',
|
||||||
|
battlefield: new Battlefield(8, 8),
|
||||||
|
units: $this->validUnitSet(),
|
||||||
|
deploymentZones: $this->validZones(),
|
||||||
|
objectives: [],
|
||||||
|
victoryCondition: VictoryCondition::EliminateAll,
|
||||||
|
holdRoundsRequired: 1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<string> $abilities
|
||||||
|
*/
|
||||||
|
private function unit(
|
||||||
|
string $id,
|
||||||
|
string $team,
|
||||||
|
Position $position,
|
||||||
|
Archetype $archetype = Archetype::Defender,
|
||||||
|
array $abilities = [],
|
||||||
|
int $attack = 4,
|
||||||
|
int $defense = 3,
|
||||||
|
int $speed = 3,
|
||||||
|
int $maxHealth = 10,
|
||||||
|
): UnitState {
|
||||||
|
return new UnitState(
|
||||||
|
$id,
|
||||||
|
$team,
|
||||||
|
$position,
|
||||||
|
$maxHealth,
|
||||||
|
$maxHealth,
|
||||||
|
$attack,
|
||||||
|
$defense,
|
||||||
|
$speed,
|
||||||
|
2,
|
||||||
|
false,
|
||||||
|
$archetype,
|
||||||
|
$abilities,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user