Passed
Push — main ( f3cfb1...17781b )
by Emil
04:34
created

BlackJack::stateOfGame()   B

Complexity

Conditions 8
Paths 22

Size

Total Lines 43
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 8

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 8
eloc 29
nc 22
nop 0
dl 0
loc 43
rs 8.2114
c 1
b 1
f 0
ccs 30
cts 30
cp 1
crap 8
1
<?php
2
3
namespace App\Game;
4
5
use App\Cards\DeckOfCards;
6
use App\Game\BlackJack\Dealer;
7
use App\Game\BlackJack\Player;
8
9
/**
10
 * BlackJack.
11
 */
12
class BlackJack
13
{
14
    public const MAX_PLAYERS = 7;
15
    private int $numOfPlayers;
16
    private DeckOfCards $deck;
17
    private Dealer $dealer;
18
    /**
19
     * @var array<Player> Is an array that contains BlackJackPlayer objects
20
     */
21
    private array $players;
22
    /**
23
     * -2 = Stayed, -1 = Undecided, 0 = Tie, 1 = Player, 2 = Dealer.
24
     *
25
     * @var array<int> Is an array that contains the outcomes of the players games
26
     */
27
    private array $gameStates;
28
    private bool $dealersTurn;
29
30
    /**
31
     * __construct.
32
     *
33
     * Constructor of the class
34
     *
35
     * @return void
36
     */
37 15
    public function __construct(int $numOfPlayers = 1)
38
    {
39 15
        if ($numOfPlayers < 1) {
40 1
            throw new \RuntimeException("Can't have less then one player in Black Jack");
41
        }
42
43 14
        if ($numOfPlayers > self::MAX_PLAYERS) {
44 1
            throw new \RuntimeException('Maximum of '.self::MAX_PLAYERS.' players in Black Jack');
45
        }
46
47 13
        $this->numOfPlayers = $numOfPlayers;
48 13
        $this->deck = new DeckOfCards();
49 13
        $this->dealer = new Dealer();
50 13
        for ($i = 0; $i < $this->numOfPlayers; ++$i) {
51 13
            $this->players[$i] = new Player();
52 13
            $this->gameStates[$i] = -1;
53
        }
54
55 13
        $this->deck->shuffleDeck();
56
57 13
        $this->setupGame();
58
    }
59
60
    /**
61
     * setupGame.
62
     *
63
     * Sets up a game of Black Jack by dealing out the cards and setting upp the variables
64
     */
65 13
    private function setupGame(): void
66
    {
67
        // For 2 rounds of dealing a card
68 13
        for ($round = 0; $round < 2; ++$round) {
69 13
            for ($i = 0; $i < $this->numOfPlayers; ++$i) {
70 13
                $this->players[$i]->addCard($this->deck->drawCard());
71
            }
72 13
            $this->dealer->addCard($this->deck->drawCard());
73
        }
74
75 13
        for ($i = 0; $i < $this->numOfPlayers; ++$i) {
76 13
            $this->gameStates[$i] = -1;
77
        }
78
79 13
        $this->dealersTurn = false;
80
    }
81
82
    /**
83
     * calculateWinner.
84
     */
85 8
    private function calculateWinner(int $index): void
86
    {
87
        // Stayed = -2, -1 = Undecided, 0 = Tie, 1 = Player, 2 = Dealer
88
89
        // The winner is already decided
90 8
        if (2 === $this->gameStates[$index]) {
91 3
            $this->gameStates[$index] = 2;
92
93 3
            return;
94
        }
95
96
        // If dealer is bust: player wins
97 6
        if (true === $this->dealer->isBust()) {
98 2
            $this->gameStates[$index] = 1;
99
100 2
            return;
101
        }
102
103 4
        $playerHandValue = $this->players[$index]->getHandValue();
104 4
        $dealerHandValue = $this->dealer->getHandValue();
105
106
        // Both are not busts, compare their hand values
107 4
        if ($playerHandValue > $dealerHandValue) {
108 1
            $this->gameStates[$index] = 1; // player wins
109
110 1
            return; // Exit early after setting result
111
        }
112
113 3
        if ($playerHandValue < $dealerHandValue) {
114 2
            $this->gameStates[$index] = 2; // dealer wins
115
116 2
            return; // Exit early after setting result
117
        }
118
119
        // If neither of the above conditions matched, hand values are equal
120 1
        $this->gameStates[$index] = 0; // tie
121
    }
122
123
    /**
124
     * playDealer.
125
     *
126
     * The logic for the dealer to play
127
     */
128 5
    private function playDealer(): void
129
    {
130 5
        while (true === $this->dealer->play()) {
131 4
            $this->dealer->addCard($this->deck->drawCard());
132
        }
133
134
        // Update the state of game
135 5
        $this->stateOfGame();
136
    }
137
138
    /**
139
     * checkIfDealersTurn.
140
     */
141 5
    private function checkIfDealersTurn(): void
142
    {
143
        // Check if all players are done
144 5
        $allPlayersDone = !in_array(-1, $this->gameStates, true);
145
146 5
        if (true === $allPlayersDone) {
147 5
            $this->dealersTurn = true;
148 5
            $this->playDealer();
149
        }
150
    }
151
152
    /**
153
     * stateOfGame.
154
     *
155
     * Returns a array containing the current state of the game
156
     *
157
     * @return array{
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{ at position 2 could not be parsed: the token is null at position 2.
Loading history...
158
     *   numOfPlayers: string,
159
     *   playersCards: array<int, array<string>>,
160
     *   playersHandValue: array<string>,
161
     *   dealerCards: array<string>,
162
     *   dealerHandValue: string,
163
     *   gameStates: array<string>
164
     * }
165
     */
166 11
    public function stateOfGame(): array
167
    {
168 11
        $data = [
169 11
            'numOfPlayers' => strval($this->numOfPlayers),
170 11
            'playersCards' => [],
171 11
            'playersHandValue' => [],
172 11
            'dealerCards' => $this->dealer->getString(),
173 11
            'dealerHandValue' => strval($this->dealer->getHandValue()),
174 11
            'gameStates' => [],
175 11
        ];
176
177 11
        for ($i = 0; $i < $this->numOfPlayers; ++$i) {
178 11
            $data['playersCards'][$i] = $this->players[$i]->getString();
179 11
            $data['playersHandValue'][$i] = strval($this->players[$i]->getHandValue());
180
181
            // If all players are done
182 11
            if (true === $this->dealersTurn) {
183 5
                $this->calculateWinner($i);
184
            }
185
186 11
            switch ($this->gameStates[$i]) {
187
                case -1:
188 5
                    $data['gameStates'][$i] = 'Undecided';
189 5
                    break;
190 8
                case 0:
191 1
                    $data['gameStates'][$i] = 'Tie';
192 1
                    break;
193 7
                case 1:
194 3
                    $data['gameStates'][$i] = 'Player';
195 3
                    break;
196 4
                case 2:
197 4
                    $data['gameStates'][$i] = 'Dealer';
198 4
                    break;
199
            }
200
        }
201
202
        // If all players are not done
203 11
        if (false == $this->dealersTurn) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
204 8
            $data['dealerCards'] = [$this->dealer->getString()[0], '🂠'];
205 8
            $data['dealerHandValue'] = '0';
206
        }
207
208 11
        return $data;
209
    }
210
211
    /**
212
     * isPlayerBust.
213
     *
214
     * Returns if the player's hand value is over 21
215
     */
216 3
    public function isPlayerBust(int $index = 0): bool
217
    {
218 3
        return $this->players[$index]->isBust();
219
    }
220
221
    /**
222
     * isDealerBust.
223
     *
224
     * Returns if the dealer's hand value is over 21
225
     */
226 2
    public function isDealerBust(): bool
227
    {
228 2
        return $this->dealer->isBust();
229
    }
230
231
    /**
232
     * resetGame.
233
     *
234
     * Resets the game to a new game
235
     */
236 2
    public function resetGame(): void
237
    {
238 2
        $this->dealer = new Dealer();
239 2
        $this->players = [];
240 2
        for ($i = 0; $i < $this->numOfPlayers; ++$i) {
241 2
            $this->players[] = new Player();
242
        }
243
244 2
        $this->deck->reshuffleDeck();
245
246 2
        $this->setupGame();
247
    }
248
249
    /**
250
     * hitPlayer.
251
     */
252 2
    public function hitPlayer(int $index = 0): void
253
    {
254
        // If index is out of bounds
255 2
        if ($index < 0 or $index >= $this->numOfPlayers) {
256 1
            return;
257
        }
258
259
        // Stops drawing new cards if game is over
260 2
        if (-1 === $this->gameStates[$index]) {
261 2
            $this->players[$index]->addCard($this->deck->drawCard());
262
263 2
            if ($this->players[$index]->isBust()) {
264 2
                $this->gameStates[$index] = 2;
265 2
                $this->checkIfDealersTurn();
266
            }
267
        }
268
    }
269
270
    /**
271
     * stayPlayer.
272
     */
273 3
    public function stayPlayer(int $index = 0): void
274
    {
275
        // If index is out of bounds
276 3
        if ($index < 0 or $index >= $this->numOfPlayers) {
277 1
            return;
278
        }
279
280
        // If game not over
281 3
        if (-1 === $this->gameStates[$index]) {
282 3
            $this->gameStates[$index] = -2;
283 3
            $this->checkIfDealersTurn();
284
        }
285
    }
286
}
287