feat: add alternating team turns
This commit is contained in:
@@ -76,6 +76,39 @@ final class CombatEngine
|
|||||||
return $next->withWinner($attacker->teamId);
|
return $next->withWinner($attacker->teamId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function endTurn(MatchState $match): MatchState
|
||||||
|
{
|
||||||
|
$this->assertMatchActive($match);
|
||||||
|
|
||||||
|
$teamIds = [];
|
||||||
|
|
||||||
|
foreach ($match->units as $unit) {
|
||||||
|
$teamIds[$unit->teamId] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$teamIds = array_keys($teamIds);
|
||||||
|
sort($teamIds);
|
||||||
|
|
||||||
|
if ($teamIds !== ['alpha', 'bravo']) {
|
||||||
|
throw new CombatException('Matches require alpha and bravo teams.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$endingTeamId = $match->activeTeamId;
|
||||||
|
$nextTeamId = $endingTeamId === 'alpha' ? 'bravo' : 'alpha';
|
||||||
|
$units = [];
|
||||||
|
|
||||||
|
foreach ($match->units as $unit) {
|
||||||
|
$units[] = $unit->teamId === $nextTeamId ? $unit->startTurn() : $unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $match->copy(
|
||||||
|
units: $units,
|
||||||
|
activeTeamId: $nextTeamId,
|
||||||
|
round: $match->round + ($nextTeamId === 'alpha' ? 1 : 0),
|
||||||
|
actionLog: [...$match->actionLog, "{$endingTeamId} ended turn"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private function unitOrFail(MatchState $match, string $unitId): UnitState
|
private function unitOrFail(MatchState $match, string $unitId): UnitState
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -473,6 +473,147 @@ final class CombatEngineTest extends TestCase
|
|||||||
self::assertNull($next->winnerTeamId);
|
self::assertNull($next->winnerTeamId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testEndTurnPassesControlToBravoAndRefreshesItsLivingUnits(): void
|
||||||
|
{
|
||||||
|
$match = new MatchState(
|
||||||
|
new Battlefield(8, 8),
|
||||||
|
[
|
||||||
|
$this->unit(
|
||||||
|
'alpha-1',
|
||||||
|
'alpha',
|
||||||
|
new Position(0, 0),
|
||||||
|
actionsRemaining: 1,
|
||||||
|
hasAttacked: true,
|
||||||
|
),
|
||||||
|
$this->unit(
|
||||||
|
'bravo-1',
|
||||||
|
'bravo',
|
||||||
|
new Position(7, 7),
|
||||||
|
actionsRemaining: 0,
|
||||||
|
hasAttacked: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
'alpha',
|
||||||
|
actionLog: ['match started'],
|
||||||
|
);
|
||||||
|
|
||||||
|
$next = (new CombatEngine())->endTurn($match);
|
||||||
|
|
||||||
|
self::assertSame('alpha', $match->activeTeamId);
|
||||||
|
self::assertSame(1, $match->round);
|
||||||
|
self::assertSame(1, $match->unit('alpha-1')->actionsRemaining);
|
||||||
|
self::assertTrue($match->unit('alpha-1')->hasAttacked);
|
||||||
|
self::assertSame(0, $match->unit('bravo-1')->actionsRemaining);
|
||||||
|
self::assertTrue($match->unit('bravo-1')->hasAttacked);
|
||||||
|
self::assertSame(['match started'], $match->actionLog);
|
||||||
|
self::assertSame('bravo', $next->activeTeamId);
|
||||||
|
self::assertSame(1, $next->round);
|
||||||
|
self::assertSame(1, $next->unit('alpha-1')->actionsRemaining);
|
||||||
|
self::assertTrue($next->unit('alpha-1')->hasAttacked);
|
||||||
|
self::assertSame(2, $next->unit('bravo-1')->actionsRemaining);
|
||||||
|
self::assertFalse($next->unit('bravo-1')->hasAttacked);
|
||||||
|
self::assertSame(['match started', 'alpha ended turn'], $next->actionLog);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEndTurnPassesControlToAlphaAndStartsANewRound(): void
|
||||||
|
{
|
||||||
|
$match = new MatchState(
|
||||||
|
new Battlefield(8, 8),
|
||||||
|
[
|
||||||
|
$this->unit(
|
||||||
|
'alpha-1',
|
||||||
|
'alpha',
|
||||||
|
new Position(0, 0),
|
||||||
|
actionsRemaining: 0,
|
||||||
|
hasAttacked: true,
|
||||||
|
),
|
||||||
|
$this->unit('bravo-1', 'bravo', new Position(7, 7), actionsRemaining: 1),
|
||||||
|
],
|
||||||
|
'bravo',
|
||||||
|
round: 1,
|
||||||
|
actionLog: ['alpha ended turn'],
|
||||||
|
);
|
||||||
|
|
||||||
|
$next = (new CombatEngine())->endTurn($match);
|
||||||
|
|
||||||
|
self::assertSame('alpha', $next->activeTeamId);
|
||||||
|
self::assertSame(2, $next->round);
|
||||||
|
self::assertSame(2, $next->unit('alpha-1')->actionsRemaining);
|
||||||
|
self::assertFalse($next->unit('alpha-1')->hasAttacked);
|
||||||
|
self::assertSame(1, $next->unit('bravo-1')->actionsRemaining);
|
||||||
|
self::assertSame(['alpha ended turn', 'bravo ended turn'], $next->actionLog);
|
||||||
|
self::assertSame('bravo', $match->activeTeamId);
|
||||||
|
self::assertSame(1, $match->round);
|
||||||
|
self::assertSame(0, $match->unit('alpha-1')->actionsRemaining);
|
||||||
|
self::assertTrue($match->unit('alpha-1')->hasAttacked);
|
||||||
|
self::assertSame(['alpha ended turn'], $match->actionLog);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEndTurnRejectsCompletedMatchBeforeValidatingTeams(): void
|
||||||
|
{
|
||||||
|
$match = new MatchState(
|
||||||
|
new Battlefield(8, 8),
|
||||||
|
[$this->unit('alpha-1', 'alpha', new Position(0, 0))],
|
||||||
|
'alpha',
|
||||||
|
winnerTeamId: 'alpha',
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->expectException(CombatException::class);
|
||||||
|
$this->expectExceptionMessage('The match is already complete.');
|
||||||
|
|
||||||
|
(new CombatEngine())->endTurn($match);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEndTurnRejectsMatchesWithoutExactlyAlphaAndBravoTeams(): void
|
||||||
|
{
|
||||||
|
$match = new MatchState(
|
||||||
|
new Battlefield(8, 8),
|
||||||
|
[
|
||||||
|
$this->unit('alpha-1', 'alpha', new Position(0, 0)),
|
||||||
|
$this->unit('charlie-1', 'charlie', new Position(7, 7)),
|
||||||
|
],
|
||||||
|
'alpha',
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->expectException(CombatException::class);
|
||||||
|
$this->expectExceptionMessage('Matches require alpha and bravo teams.');
|
||||||
|
|
||||||
|
(new CombatEngine())->endTurn($match);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEndTurnDoesNotRefreshDefeatedUnitsOnIncomingTeam(): void
|
||||||
|
{
|
||||||
|
$match = new MatchState(
|
||||||
|
new Battlefield(8, 8),
|
||||||
|
[
|
||||||
|
$this->unit('alpha-1', 'alpha', new Position(0, 0)),
|
||||||
|
$this->unit(
|
||||||
|
'bravo-defeated',
|
||||||
|
'bravo',
|
||||||
|
new Position(6, 7),
|
||||||
|
health: 0,
|
||||||
|
actionsRemaining: 0,
|
||||||
|
hasAttacked: true,
|
||||||
|
),
|
||||||
|
$this->unit(
|
||||||
|
'bravo-living',
|
||||||
|
'bravo',
|
||||||
|
new Position(7, 7),
|
||||||
|
actionsRemaining: 0,
|
||||||
|
hasAttacked: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
'alpha',
|
||||||
|
);
|
||||||
|
|
||||||
|
$next = (new CombatEngine())->endTurn($match);
|
||||||
|
|
||||||
|
self::assertSame(0, $next->unit('bravo-defeated')->actionsRemaining);
|
||||||
|
self::assertTrue($next->unit('bravo-defeated')->hasAttacked);
|
||||||
|
self::assertSame(2, $next->unit('bravo-living')->actionsRemaining);
|
||||||
|
self::assertFalse($next->unit('bravo-living')->hasAttacked);
|
||||||
|
}
|
||||||
|
|
||||||
private function match(): MatchState
|
private function match(): MatchState
|
||||||
{
|
{
|
||||||
return new MatchState(
|
return new MatchState(
|
||||||
|
|||||||
Reference in New Issue
Block a user