feat: resolve attacks and elimination victory
This commit is contained in:
@@ -12,11 +12,7 @@ final class CombatEngine
|
||||
{
|
||||
$this->assertMatchActive($match);
|
||||
|
||||
try {
|
||||
$unit = $match->unit($unitId);
|
||||
} catch (InvalidArgumentException $exception) {
|
||||
throw new CombatException("Unknown unit: {$unitId}.", previous: $exception);
|
||||
}
|
||||
$unit = $this->unitOrFail($match, $unitId);
|
||||
$this->assertCanAct($match, $unit);
|
||||
|
||||
$occupied = [];
|
||||
@@ -40,6 +36,56 @@ final class CombatEngine
|
||||
return $next->copy(actionLog: $actionLog);
|
||||
}
|
||||
|
||||
public function attack(MatchState $match, string $attackerId, string $targetId): MatchState
|
||||
{
|
||||
$this->assertMatchActive($match);
|
||||
|
||||
$attacker = $this->unitOrFail($match, $attackerId);
|
||||
$this->assertCanAct($match, $attacker);
|
||||
|
||||
if ($attacker->hasAttacked) {
|
||||
throw new CombatException('Unit has already attacked this turn.');
|
||||
}
|
||||
|
||||
$target = $this->unitOrFail($match, $targetId);
|
||||
|
||||
if ($target->teamId === $attacker->teamId || $target->isDefeated()) {
|
||||
throw new CombatException('Target must be an active enemy unit.');
|
||||
}
|
||||
|
||||
$distance = abs($attacker->position->x - $target->position->x)
|
||||
+ abs($attacker->position->y - $target->position->y);
|
||||
|
||||
if ($distance !== 1) {
|
||||
throw new CombatException('Target is outside attack range.');
|
||||
}
|
||||
|
||||
$terrainDefense = $match->battlefield->terrainAt($target->position)->defenseBonus();
|
||||
$damage = max(1, $attacker->attack - $target->defense - $terrainDefense);
|
||||
$updatedAttacker = $attacker->markAttacked()->spendAction();
|
||||
$updatedTarget = $target->takeDamage($damage);
|
||||
$next = $match->withUnit($updatedAttacker)->withUnit($updatedTarget);
|
||||
$actionLog = [...$match->actionLog, "{$attacker->id} attacked {$target->id} for {$damage} damage"];
|
||||
$next = $next->copy(actionLog: $actionLog);
|
||||
|
||||
foreach ($next->units as $unit) {
|
||||
if ($unit->teamId !== $attacker->teamId && !$unit->isDefeated()) {
|
||||
return $next;
|
||||
}
|
||||
}
|
||||
|
||||
return $next->withWinner($attacker->teamId);
|
||||
}
|
||||
|
||||
private function unitOrFail(MatchState $match, string $unitId): UnitState
|
||||
{
|
||||
try {
|
||||
return $match->unit($unitId);
|
||||
} catch (InvalidArgumentException $exception) {
|
||||
throw new CombatException("Unknown unit: {$unitId}.", previous: $exception);
|
||||
}
|
||||
}
|
||||
|
||||
private function assertMatchActive(MatchState $match): void
|
||||
{
|
||||
if ($match->winnerTeamId !== null) {
|
||||
|
||||
Reference in New Issue
Block a user