Passed
Push — main ( 057454...9ca083 )
by Karl
05:44
created

Game::isValidForm()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 3
eloc 4
c 2
b 1
f 0
nc 3
nop 1
dl 0
loc 6
rs 10
ccs 4
cts 4
cp 1
crap 3
1
<?php
2
3
namespace App\Model;
4
5
use App\Model\DeckOfCards;
6
use App\Model\FrenchSuitedDeck;
7
use App\Model\DeckShuffler;
8
use SplSubject;
9
use Random\Randomizer;
0 ignored issues
show
Bug introduced by
The type Random\Randomizer was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
10
use SplObjectStorage;
11
use Exception;
12
use App\Model\BetManager;
13
use SplObserver;
14
15
class Game implements SplSubject
16
{
17
18
    private ?DeckOfCards $deck;
19
    /**
20
     * @var array<?Player> The players of the game.
21
     */
22
    private array $players;
23
    private string $currentPlayer;
24
    private string $gameStatus;
25
    public BetManager $betManager;
26
    private DetermineWinner $determineWinner;
27
28
    protected SplObjectStorage $observers;
29
    /**
30
     * Constructor for the Game class.
31
     * @param array<?Player> $players The players of the game.
32
     * @param DeckOfCards|null $deck The deck of cards to be used in the game.
33
     * @param array<SplObserver> $observers The observers to be attached to the game.
34
     * @param string $gameStatus The status of the game.
35
     */
36 9
    public function __construct(
37
        array $players = [],
38
        DeckOfCards $deck = null,
39
        array $observers = [],
40
        string $gameStatus = 'ongoing'
41
    ) {
42
43 9
        $this->observers = new SplObjectStorage();
44
45 9
        foreach ($observers as $observer) {
46
            $this->attach($observer);
47
        }
48 9
        $this->betManager = new BetManager();
49 9
        $this->deck = $deck;
50 9
        $this->players = $players;
51
52 9
        if (count($players) > 0) {
53 7
            $this->currentPlayer = $players[0]->getName();
54
        }
55 9
        $this->setGameStatus($gameStatus);
56 9
        $this->determineWinner = new DetermineWinner();
57
58
59 9
        $this->notify();
60
    }
61
62 1
    public function getObservers(): SplObjectStorage
63
    {
64 1
        return $this->observers;
65
    }
66
67 1
    public function attach(SplObserver $observer): void
68
    {
69 1
        $this->observers->attach($observer);
70
    }
71
72 1
    public function detach(SplObserver $observer): void
73
    {
74 1
        $this->observers->detach($observer);
75
    }
76
77 9
    public function notify(): void
78
    {
79 9
        foreach ($this->observers as $observer) {
80
            $observer->update($this->getGameState());
81
        }
82
    }
83
84
    /**
85
     * Creates a new Game object from a saved game state.
86
     *
87
     * @param array<string, mixed> $gameState The saved game state.
88
     * @return Game The newly created Game object.
89
     */
90 3
    public static function createFromSavedState(array $gameState): Game
91
    {
92 3
        $game = new Game();
93 3
        $game->deck = FrenchSuitedDeck::createFromSession($gameState['deck']);
94 3
        foreach ($gameState['players'] as $player) {
95 3
            $game->players[$player->getName()] = $player;
96
        }
97 3
        $game->betManager = new BetManager($gameState['pot']);
98 3
        $game->currentPlayer = $gameState['currentPlayer'];
99 3
        $game->setGameStatus($gameState['gameStatus']);
100 3
        return $game;
101
    }
102
103
    /**
104
     * Get the current state of the game.
105
     *
106
     * @return array<string, mixed> The game state, including the deck, players, current player, pot, and game status.
107
     */
108 3
    public function getGameState(): array
109
    {
110 3
        return [
111 3
            'deck' => $this->deck ? $this->deck->getDeck() : null,
112 3
            'players' => $this->players,
113 3
            'currentPlayer' => $this->currentPlayer,
114 3
            'pot' => $this->betManager->getPot(),
115 3
            'gameStatus' => $this->getGameStatus()
116 3
        ];
117
    }
118
119 3
    public function getGameStatus(): string
120
    {
121 3
        return $this->gameStatus;
122
    }
123
124 9
    private function setGameStatus(string $gameStatus): void
125
    {
126 9
        $this->gameStatus = $gameStatus;
127 9
        $this->notify();
128
    }
129
130
    /**
131
     * Plays a round of the game based on the provided form data.
132
     *
133
     * @param array<string, string> $formData The form data containing the player's move and action.
134
     * @return void
135
     * 
136 3
     * @throws Exception If the form data is invalid.
137
     */
138
    public function playRound(array $formData): void
139 3
    {
140
        $formValidator = new GameFormValidator();
141 2
        $formValidator->isValidForm($formData);
142 1
143
        if ($formData['action'] !== 'restart') {
144 1
            $this->processMove($formData);
145
146 1
            $winner = $this->determineWinner->getWinner($this->players, $this->currentPlayer);
147 1
148 1
            if ($winner !== null) {
149 1
                $this->betManager->payOut($this->players[$winner]);
150 1
                $this->setGameStatus('ended');
151
                $this->notify();
152
                return;
153
            }
154 2
        }
155 1
156 1
        if ($formData['action'] == 'restart') {
157
            $this->reset();
158
            return;
159
        }
160 1
    }
161
162 1
    public function getWinnerBasedOnHand(): string
163
    {
164
        return $this->determineWinner->getWinnerBasedOnHand($this->players);
165
    }
166
167
    /**
168
     * Process the move based on the given form data.
169
     *
170
     * @param array<string, mixed> $formData The form data containing the action and bet (if applicable).
171
     * @return void
172 1
     * @throws Exception If the action is invalid.
173
     */
174 1
    private function processMove(array $formData): void
175 1
    {
176 1
        $action = $this->players[$this->currentPlayer] instanceof HumanPlayer ?
177 1
            $formData['action'] :
178 1
                ($this->players[$this->currentPlayer] instanceof AiPlayerInterface ?
179
                    $this->players[$this->currentPlayer]->makeMove() :
180 1
                    throw new Exception('Invalid player type.'));
181 1
        switch ($action) {
182 1
            case 'hit':
183 1
                $this->dealCard();
184 1
                break;
185 1
            case 'stand':
186 1
                $this->players[$this->currentPlayer]->stand();
187 1
                $this->notify();
188
                if ($this->hasNextPlayer()) {
189 1
                    $this->nextPlayer();
190
                }
191
                break;
192
            case 'bet':
193
                $this->betManager->setPot($formData['bet'], $this->players);
194
                $this->notify();
195
                break;
196
            default:
197
                throw new Exception('Invalid action.');
198
        }
199 1
    }
200
201 1
    private function reset(): void
202
    {
203
        if (!$this->isRestartable()) {
204 1
            throw new Exception('Cannot restart when there are money in the pot.');
205 1
        }
206 1
        $this->setGameStatus('ongoing');
207 1
        $this->deck = FrenchSuitedDeck::create();
208 1
        $deckShuffler = new DeckShuffler(new Randomizer());
209 1
        $deckShuffler->shuffle($this->deck);
0 ignored issues
show
Bug introduced by
It seems like $this->deck can also be of type null; however, parameter $deck of App\Model\DeckShuffler::shuffle() does only seem to accept App\Model\DeckOfCards, maybe add an additional type check? ( Ignorable by Annotation )

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

209
        $deckShuffler->shuffle(/** @scrutinizer ignore-type */ $this->deck);
Loading history...
210
        foreach ($this->players as $player) {
211 1
            $player->resetHand();
212 1
            $player->resetStanding();
213
        }
214
        $this->currentPlayer = 'human';
215 1
        $this->notify();
216
    }
217 1
218
    public function isRestartable(): bool
219
    {
220
        return $this->betManager->getPot() === null;
221
    }
222
223
    /**
224
     * Get the players of the game.
225 1
     *
226
     * @return array<Player> The array of players.
227 1
     */
228
    public function getPlayers(): array
229
    {
230
        return $this->players;
231
    }
232
233
    // /**
234
    //  * Validates the form data.
235
    //  *
236
    //  * @param array<string, string> $formData The form data to validate.
237 3
    //  *
238
    //  * @throws Exception If an invalid form name is found.
239 3
    //  */
240 3
    // private function isValidForm(array $formData): void
241 3
    // {
242 1
    //     $formKeys = array_keys($formData);
243
    //     foreach ($formKeys as $key) {
244
    //         if (!in_array($key, self::VALID_FORM_NAMES)) {
245
    //             throw new Exception('Invalid form name.');
246
    //         }
247
248
    //         // ignorantly accepting all values...
249 1
    //     }
250
    // }
251 1
252
    public function dealCard(): void
253
    {
254 1
        if ($this->deck === null) {
255 1
            throw new Exception('Cannot deal cards from a non existing deck.');
256
        }
257
        $this->players[$this->currentPlayer]->addCardsToHand($this->deck->cardCollection->drawCards(1));
258 1
        $this->notify();
259
    }
260 1
261
    public function getCurrentPlayer(): string
262
    {
263 1
        return $this->currentPlayer;
264
    }
265 1
266
    public function nextPlayer(): void
267
    {
268 1
        if (!$this->hasNextPlayer()) {
269 1
            throw new Exception('No next player.');
270 1
        }
271 1
        $playerIndex = array_search($this->currentPlayer, array_keys($this->players));
272
        $nextPlayerIndex = $playerIndex + 1;
273
        $this->currentPlayer = array_keys($this->players)[$nextPlayerIndex];
274 1
        $this->notify();
275
    }
276 1
277 1
    private function hasNextPlayer(): bool
278
    {
279
        $playerIndex = array_search($this->currentPlayer, array_keys($this->players));
280
        return $playerIndex < count($this->players) - 1;
281
    }
282
}
283