Passed
Push — master ( 06ba98...d5fecc )
by Paweł
02:51
created

Fight::action()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 28
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
eloc 19
c 0
b 0
f 0
dl 0
loc 28
ccs 0
cts 17
cp 0
rs 9.6333
cc 4
nc 5
nop 2
crap 20
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\Inventory\Usable;
11
use AardsGerds\Game\Player\Player;
12
use AardsGerds\Game\Player\PlayerAction;
13
use AardsGerds\Game\Player\PlayerException;
14
use AardsGerds\Game\Shared\IntegerValue;
15
use AardsGerds\Game\Shared\IntegerValueException;
16
17
final class Fight
18
{
19
    private IntegerValue $round;
20
21
    public function __construct(
22
        private Fighter $player,
23
        private Fighter $opponent,
24
        private PlayerAction $playerAction,
25
    ) {
26
        $this->round = new IntegerValue(1);
27
    }
28
29
    public function __invoke(): void
30
    {
31
        $playerInitialHealth = new Health($this->player->getHealth()->get());
32
33
        while (true) {
34
            $this->playerAction->newRound("Round {$this->round}:");
35
            $attackOrder = AttackOrder::resolve($this->player, $this->opponent); // todo refactor
36
37
            try {
38
                $this->action($attackOrder->first(), $attackOrder->last());
39
                if ($this->isAbleForExtraAction($attackOrder->first(), $attackOrder->last())) {
40
                    $this->action($attackOrder->first(), $attackOrder->last());
41
                }
42
43
                $this->action($attackOrder->last(), $attackOrder->first());
44
            } catch (IntegerValueException $exception) {
45
                if ($this->player->getHealth()->isLowerThan(new Health(1))) {
46
                    throw PlayerException::death();
47
                }
48
49
                break;
50
            }
51
52
            $this->playerAction->tell([
53
                '',
54
                "{$this->player->getName()} health: {$this->player->getHealth()}",
55
                "{$this->opponent->getName()} health: {$this->opponent->getHealth()}",
56
            ]);
57
58
            $this->round->increment();
59
        }
60
61
        $this->playerAction->askForConfirmation('Continue?');
62
        $this->player->getHealth()->replaceWith($playerInitialHealth);
63
    }
64
65
    /**
66
     * @throws IntegerValueException if target's health hits 0
67
     */
68
    private function action(Fighter $attacker, Fighter $target): void
69
    {
70
        if ($attacker instanceof Player) {
71
            $action = $this->askForAction($attacker);
72
            if ($action instanceof Usable) {
73
                $action->use($attacker, $this->playerAction);
74
                return;
75
            }
76
        } else {
77
            $action = $attacker->getTalents()->filterAttacks()->getIterator()->current(); // todo
78
        }
79
80
        $damage = match (true) {
81
            $action instanceof MeleeAttack => (new Damage(
82
                    $action->getDamage($attacker->getWeapon() ?? throw FightException::weaponRequired())->get()
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

82
                    $action->/** @scrutinizer ignore-call */ 
83
                             getDamage($attacker->getWeapon() ?? throw FightException::weaponRequired())->get()
Loading history...
83
                ))->increaseBy($attacker->getStrength()),
84
            $action instanceof EtherumAttack => $action->getDamage(),
85
            default => new Damage(0),
86
        };
87
88
        try {
89
            $target->getHealth()->decreaseBy($damage);
90
        } catch (IntegerValueException $exception) {
91
            $this->playerAction->tell("{$attacker->getName()} uses {$action} and deals {$damage} damage, which brings opponent to their knees!");
92
            throw $exception;
93
        }
94
95
        $this->playerAction->tell("{$attacker->getName()} uses {$action} and deals {$damage} damage.");
96
    }
97
98
    private function askForAction(Player $player): Attack|Usable
99
    {
100
        $action = $this->playerAction->askForChoice(
101
            'Select action',
102
            array_merge($player->getTalents()->filterAttacks()->getItems(), ['Go to inventory']),
103
        );
104
105
        if ($action === 'Go to inventory') {
106
            $selectedItem = $this->playerAction->askForChoice(
107
                'Select item to use',
108
                array_merge($player->getInventory()->filterUsable()->getItems(), ['Back']),
109
            );
110
111
            if ($selectedItem === 'Back') {
112
                return $this->askForAction($player);
113
            }
114
115
            return $selectedItem;
116
        }
117
118
        if ($action instanceof MeleeAttack && $player->getWeapon() === null) {
119
            $this->playerAction->note('This attack requires weapon equipped.');
120
            return $this->askForAction($player);
121
        }
122
123
        return $action;
124
    }
125
126
    private function isAbleForExtraAction(Fighter $attacker, Fighter $target): bool
127
    {
128
        return (new Initiative((int) $attacker->getInitiative()->get() / 2))
129
            ->isGreaterThan($target->getInitiative());
130
    }
131
}
132