Passed
Push — master ( 0b1636...daadce )
by Paweł
02:53
created

Fight::roundActions()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

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

126
            $attack->/** @scrutinizer ignore-call */ 
127
                     getDamage($fighter->getWeapon() ?? throw FightException::weaponRequired())
Loading history...
127
                ->increaseBy($fighter->getStrength()),
128
            $attack instanceof EtherumAttack => $attack->getDamage(),
129
            default => new Damage(0),
130
        };
131
    }
132
}
133