expectException(InvalidArgumentException::class); new MatchState( new Battlefield(8, 8), [$this->unit('unit-1', 'alpha'), $this->unit('unit-1', 'bravo')], 'alpha', ); } public function testItRejectsAnUnknownUnitLookup(): void { $match = $this->match(); $this->expectException(InvalidArgumentException::class); $match->unit('unknown'); } public function testItRejectsAnUnknownReplacement(): void { $match = $this->match(); $this->expectException(InvalidArgumentException::class); $match->withUnit($this->unit('unknown', 'alpha')); } public function testReplacingAUnitDoesNotMutateTheMatch(): void { $match = $this->match(); $replacement = $match->unit('alpha-1')->moveTo(new Position(2, 0)); $replaced = $match->withUnit($replacement); self::assertSame('0:0', $match->unit('alpha-1')->position->key()); self::assertSame('2:0', $replaced->unit('alpha-1')->position->key()); } public function testCopyReplacesMutableMatchProgressIncludingAnEmptyLog(): void { $match = $this->match(actionLog: ['started']); $copied = $match->copy(activeTeamId: 'bravo', round: 2, actionLog: []); self::assertSame('alpha', $match->activeTeamId); self::assertSame(1, $match->round); self::assertSame(['started'], $match->actionLog); self::assertSame('bravo', $copied->activeTeamId); self::assertSame(2, $copied->round); self::assertSame([], $copied->actionLog); } public function testWinnerCanBeSetAndClearedWithoutChangingOtherState(): void { $match = $this->match(actionLog: ['started']); $won = $match->withWinner('alpha'); $cleared = $won->withWinner(null); self::assertSame('alpha', $won->winnerTeamId); self::assertNull($cleared->winnerTeamId); self::assertSame($match->battlefield, $cleared->battlefield); self::assertSame($match->units, $cleared->units); self::assertSame($match->activeTeamId, $cleared->activeTeamId); self::assertSame($match->round, $cleared->round); self::assertSame($match->actionLog, $cleared->actionLog); } public function testItRejectsANonListUnitsArray(): void { $this->expectException(InvalidArgumentException::class); // @phpstan-ignore argument.type new MatchState(new Battlefield(8, 8), ['first' => $this->unit('alpha-1', 'alpha')], 'alpha'); } public function testItRejectsAnEmptyUnitsArray(): void { $this->expectException(InvalidArgumentException::class); new MatchState(new Battlefield(8, 8), [], 'alpha'); } public function testItRejectsANonUnitMember(): void { $this->expectException(InvalidArgumentException::class); // @phpstan-ignore argument.type new MatchState(new Battlefield(8, 8), ['not-a-unit'], 'alpha'); } public function testItRejectsAnEmptyActiveTeam(): void { $this->expectException(InvalidArgumentException::class); new MatchState(new Battlefield(8, 8), [$this->unit('alpha-1', 'alpha')], ''); } public function testItRejectsAnActiveTeamWithoutAUnit(): void { $this->expectException(InvalidArgumentException::class); new MatchState(new Battlefield(8, 8), [$this->unit('alpha-1', 'alpha')], 'bravo'); } public function testItRejectsARoundBelowOne(): void { $this->expectException(InvalidArgumentException::class); new MatchState(new Battlefield(8, 8), [$this->unit('alpha-1', 'alpha')], 'alpha', 0); } public function testItRejectsANonListActionLog(): void { $this->expectException(InvalidArgumentException::class); new MatchState( new Battlefield(8, 8), [$this->unit('alpha-1', 'alpha')], 'alpha', // @phpstan-ignore argument.type actionLog: ['first' => 'started'], ); } public function testItRejectsANonStringActionLogEntry(): void { $this->expectException(InvalidArgumentException::class); // @phpstan-ignore argument.type new MatchState(new Battlefield(8, 8), [$this->unit('alpha-1', 'alpha')], 'alpha', actionLog: [1]); } public function testItRejectsAnEmptyWinnerTeam(): void { $this->expectException(InvalidArgumentException::class); new MatchState(new Battlefield(8, 8), [$this->unit('alpha-1', 'alpha')], 'alpha', winnerTeamId: ''); } public function testItRejectsAWinnerWithoutAUnit(): void { $this->expectException(InvalidArgumentException::class); new MatchState( new Battlefield(8, 8), [$this->unit('alpha-1', 'alpha')], 'alpha', winnerTeamId: 'bravo', ); } /** * @param list $actionLog */ private function match(array $actionLog = []): MatchState { return new MatchState( new Battlefield(8, 8), [$this->unit('alpha-1', 'alpha'), $this->unit('bravo-1', 'bravo')], 'alpha', actionLog: $actionLog, ); } private function unit(string $id, string $teamId): UnitState { return new UnitState($id, $teamId, new Position(0, 0), 10, 10, 4, 2, 4, 2); } }