Passed
Push — master ( 954430...fcf71b )
by Paweł
02:57
created

Fight   A

Complexity

Total Complexity 13

Size/Duplication

Total Lines 102
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 13
eloc 51
c 1
b 0
f 0
dl 0
loc 102
ccs 0
cts 54
cp 0
rs 10

5 Methods

Rating   Name   Duplication   Size   Complexity  
A askForAction() 0 14 3
A attack() 0 23 3
A isAbleForExtraAttack() 0 4 1
A __invoke() 0 41 5
A __construct() 0 5 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace AardsGerds\Game\Fight;
6
7
use AardsGerds\Game\Build\Attribute\Damage;
8
use AardsGerds\Game\Build\Attribute\Health;
9
use AardsGerds\Game\Build\Attribute\Initiative;
10
use AardsGerds\Game\Player\Player;
11
use AardsGerds\Game\Player\PlayerAction;
12
use AardsGerds\Game\Shared\IntegerValue;
13
use AardsGerds\Game\Shared\IntegerValueException;
14
15
final class Fight
16
{
17
    private IntegerValue $round;
18
19
    public function __construct(
20
        private Fighter $player,
21
        private Fighter $opponent,
22
    ) {
23
        $this->round = new IntegerValue(1);
24
    }
25
26
    public function __invoke(PlayerAction $playerAction): Fighter
27
    {
28
        $playerInitialHealth = new Health($this->player->getHealth()->get());
29
        $opponentInitialHealth = new Health($this->opponent->getHealth()->get());
30
31
        while (true) {
32
            $playerAction->newTour("Round {$this->round}:");
33
            $attackOrder = AttackOrder::resolve($this->player, $this->opponent);
34
35
            try {
36
                $this->attack($playerAction, $attackOrder->first(), $attackOrder->last());
37
                if ($this->isAbleForExtraAttack($attackOrder->first(), $attackOrder->last())) {
38
                    $this->attack($playerAction, $attackOrder->first(), $attackOrder->last());
39
                }
40
41
                $this->attack($playerAction, $attackOrder->last(), $attackOrder->first());
42
            } catch (IntegerValueException $exception) {
43
                // someone died
44
                break;
45
            }
46
47
            $playerAction->tell([
48
                '',
49
                "{$this->player->getName()} health: {$this->player->getHealth()}",
50
                "{$this->opponent->getName()} health: {$this->opponent->getHealth()}",
51
            ]);
52
53
            $this->round->increment();
54
        }
55
56
        $winner = $this->player->getHealth()->isGreaterThan($this->opponent->getHealth())
57
            ? $this->player
58
            : $this->opponent;
59
60
        $playerAction->tell("Winner is {$winner->getName()}");
61
        $playerAction->askForConfirmation('Continue?');
62
63
        $this->player->getHealth()->replaceWith($playerInitialHealth);
64
        $this->opponent->getHealth()->replaceWith($opponentInitialHealth);
65
66
        return $winner;
67
    }
68
69
    /**
70
     * @throws IntegerValueException if target's health drop below 0
71
     */
72
    private function attack(PlayerAction $playerAction, Fighter $attacker, Fighter $target): void
73
    {
74
        /** @var Attack $attack */
75
        $attack = $attacker instanceof Player
76
            ? $this->askForAction($playerAction, $attacker)
77
            : $attacker->getTalents()->filterAttacks()->getIterator()->current(); // todo
78
79
        $damage = match (true) {
80
            $attack instanceof MeleeAttack => $attack->getDamage(
0 ignored issues
show
Bug introduced by
The method getDamage() does not exist on AardsGerds\Game\Fight\Attack. Since it exists in all sub-types, consider adding an abstract or default implementation to AardsGerds\Game\Fight\Attack. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

80
            $attack instanceof MeleeAttack => $attack->/** @scrutinizer ignore-call */ getDamage(
Loading history...
81
                $attacker->getWeapon() ?? throw FightException::weaponRequired(),
82
            ),
83
            $attack instanceof EtherumAttack => $attack->getDamage(),
84
            default => new Damage(0),
85
        };
86
87
        try {
88
            $target->getHealth()->decreaseBy($damage);
89
        } catch (IntegerValueException $exception) {
90
            $playerAction->tell("{$attacker->getName()} uses {$attack} and deals {$damage} damage, which brings opponent to their knees!");
91
            throw $exception;
92
        }
93
94
        $playerAction->tell("{$attacker->getName()} uses {$attack} and deals {$damage} damage.");
95
    }
96
97
    private function isAbleForExtraAttack(Fighter $attacker, Fighter $target): bool
98
    {
99
        return (new Initiative((int) $attacker->getInitiative()->get() / 2))
100
            ->isGreaterThan($target->getInitiative());
101
    }
102
103
    private function askForAction(PlayerAction $playerAction, Player $player): Attack
104
    {
105
        /** @var Attack $attack */
106
        $attack = $playerAction->askForChoice(
107
            'Select action',
108
            $player->getTalents()->filterAttacks()->getItems(),
109
        );
110
111
        if ($attack instanceof MeleeAttack && $player->getWeapon() === null) {
112
            $playerAction->note('This attack requires weapon equipped.');
113
            return $this->askForAction($playerAction, $player);
114
        }
115
116
        return $attack;
117
    }
118
}
119