Passed
Push — master ( daadce...90a788 )
by Paweł
02:56
created

Fight::__invoke()   A

Complexity

Conditions 5
Paths 9

Size

Total Lines 28
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 16
c 4
b 0
f 0
dl 0
loc 28
ccs 0
cts 16
cp 0
rs 9.4222
cc 5
nc 9
nop 0
crap 30
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
use function Lambdish\Phunctional\map;
16
use function Lambdish\Phunctional\not;
17
18
final class Fight
19
{
20
    private IntegerValue $round;
21
22
    public function __construct(
23
        private Player $player,
24
        private FighterCollection $opponents,
25
        private PlayerAction $playerAction,
26
    ) {
27
        $this->round = new IntegerValue(1);
28
    }
29
30
    public function __invoke(): void
31
    {
32
        while ($this->opponents->count() > 0) {
33
            $fighters = new FighterCollection(
34
                array_merge([$this->player], $this->opponents->getItems()),
35
            );
36
37
            $this->playerAction->newRound("Round {$this->round}:");
38
            $this->playerAction->list(map(
39
                static fn(Fighter $fighter) => [$fighter->getName() => "{$fighter->getHealth()} health"],
40
                $fighters,
41
            ));
42
43
            try {
44
                foreach ($fighters->order() as $fighter) {
45
                    $this->action($fighter);
46
                }
47
            } catch (IntegerValueException $exception) {
48
                $isDead = static fn(Fighter $fighter): bool => $fighter->getHealth()->isLowerThan(new Health(1));
49
50
                if ($isDead($this->player)) {
51
                    throw PlayerException::death();
52
                }
53
54
                $this->opponents = $this->opponents->filter(not($isDead));
55
            }
56
57
            $this->round->increment();
58
        }
59
    }
60
61
    /**
62
     * @throws IntegerValueException if target's health hits 0
63
     */
64
    private function action(Fighter $fighter): void
65
    {
66
        if ($fighter instanceof Player) {
67
            $action = $this->askForAction();
68
            if ($action instanceof Usable) {
69
                $action->use($fighter, $this->playerAction);
70
                return;
71
            }
72
        } else {
73
            $action = $fighter->getTalents()->filterAttacks()->getIterator()->current();
74
        }
75
76
        $damage = $this->calculateDamage($action, $fighter);
77
78
        try {
79
            $this->findOpponent($fighter)->getHealth()->decreaseBy($damage);
80
        } catch (IntegerValueException $exception) {
81
            $this->playerAction->tell("{$fighter->getName()} uses {$action} and deals {$damage} damage, which brings opponent to their knees!");
82
            throw $exception;
83
        }
84
85
        $this->playerAction->tell("{$fighter->getName()} uses {$action} and deals {$damage} damage.");
86
    }
87
88
    private function askForAction(): Attack|Usable
89
    {
90
        $action = $this->playerAction->askForChoice(
91
            'Select action',
92
            array_merge($this->player->getTalents()->filterAttacks()->getItems(), ['Go to inventory']),
93
        );
94
95
        if ($action === 'Go to inventory') {
96
            $selectedItem = $this->playerAction->askForChoice(
97
                'Select item to use',
98
                array_merge($this->player->getInventory()->filterUsable()->getItems(), ['Back']),
99
            );
100
101
            if ($selectedItem === 'Back') {
102
                return $this->askForAction();
103
            }
104
105
            return $selectedItem;
106
        }
107
108
        if ($action instanceof MeleeAttack && $this->player->getWeapon() === null) {
109
            $this->playerAction->note('This attack requires weapon equipped.');
110
            return $this->askForAction();
111
        }
112
113
        return $action;
114
    }
115
116
    private function findOpponent(Fighter $fighter): Fighter
117
    {
118
        if ($fighter instanceof Player) {
119
            return $this->opponents->count() > 1
120
                ? $this->playerAction->askForChoice('Select opponent to attack', $this->opponents->getItems())
121
                : $this->opponents->getIterator()->current();
122
        }
123
124
        return $this->player;
125
    }
126
127
    private function calculateDamage(Attack $attack, Fighter $fighter): Damage
128
    {
129
        return match (true) {
130
            $attack instanceof MeleeAttack =>
131
                $attack->getDamage($fighter->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

131
                $attack->/** @scrutinizer ignore-call */ 
132
                         getDamage($fighter->getWeapon() ?? throw FightException::weaponRequired())
Loading history...
132
                    ->increaseBy($fighter->getStrength()),
133
            $attack instanceof EtherumAttack => $attack->getDamage(),
134
            default => new Damage(0),
135
        };
136
    }
137
}
138