Passed
Push — master ( 3b7ca4...0b1636 )
by Paweł
02:57
created

Fight::__invoke()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 27
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

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

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