Board::place()   B
last analyzed

Complexity

Conditions 7
Paths 5

Size

Total Lines 32
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 21
c 1
b 0
f 0
dl 0
loc 32
rs 8.6506
cc 7
nc 5
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Arp\DominoGame\Value;
6
7
use Arp\DominoGame\Exception\InvalidArgumentException;
8
9
/**
10
 * Value object that represents the placed dominoes of the game. Internally we manage a collection of dominoes that have
11
 * been matched by the number of dots on the exposed tile.
12
 *
13
 * @author  Alex Patterson <[email protected]>
14
 * @package Arp\DominoGame\Value
15
 */
16
class Board
17
{
18
    /**
19
     * The 'start' of our stack is treated as the 'left' and the 'end' is treated as the right.
20
     *
21
     * @var DominoCollection
22
     */
23
    private DominoCollection $placed;
24
25
    /**
26
     * In memory representation of the exposed domino tiles.
27
     *
28
     * @var Domino|null
29
     */
30
    private ?Domino $virtualDomino;
31
32
    /**
33
     * Prepare the board dependencies.
34
     */
35
    public function __construct()
36
    {
37
        $this->placed = new DominoCollection();
38
        $this->virtualDomino = null;
39
    }
40
41
    /**
42
     * Check if the board is empty (zero placed pieces)
43
     *
44
     * @return bool
45
     */
46
    public function isEmpty(): bool
47
    {
48
        return (0 === $this->placed->count());
49
    }
50
51
    /**
52
     * Return the placed dominoes collection.
53
     *
54
     * @return DominoCollection
55
     */
56
    public function getPlaced(): DominoCollection
57
    {
58
        return $this->placed;
59
    }
60
61
    /**
62
     * Place the provided domino on the board. If we have existing pieces on the board we generate a 'virtual' domino
63
     * that represents both left and right tiles that can be matches against.
64
     *
65
     * If the provided domino matches
66
     *
67
     * @param Domino $domino
68
     *
69
     * @throws InvalidArgumentException If the provided domino is invalid
70
     */
71
    public function place(Domino $domino): void
72
    {
73
        // If there is no virtual domino we can directly add to the board as it is our first piece.
74
        if (0 === $this->placed->count()) {
75
            if (! $domino->isDouble()) {
76
                throw new InvalidArgumentException(
77
                    sprintf('The first domino place must be a double \'%s\' provided', $domino->getName())
78
                );
79
            }
80
            $this->placed->addToStart($domino);
81
            $this->virtualDomino = new Domino($domino->getTopTile(), $domino->getBottomTile());
82
            return;
83
        }
84
85
        $left = $this->virtualDomino->getTopTile();
0 ignored issues
show
Bug introduced by
The method getTopTile() does not exist on null. ( Ignorable by Annotation )

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

85
        /** @scrutinizer ignore-call */ 
86
        $left = $this->virtualDomino->getTopTile();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
86
        if ($left === $domino->getTopTile() || $left === $domino->getBottomTile()) {
87
            $this->placeLeft($domino);
88
            return;
89
        }
90
91
        $right = $this->virtualDomino->getBottomTile();
92
        if ($right === $domino->getBottomTile() || $right === $domino->getTopTile()) {
93
            $this->placeRight($domino);
94
            return;
95
        }
96
97
        throw new InvalidArgumentException(
98
            sprintf(
99
                'The domino \'%s\' cannot be placed on the board; tiles must match values \'%d\' or \'%d\'',
100
                $domino->getName(),
101
                $this->getLeftTile(),
102
                $this->getRightTile()
103
            )
104
        );
105
    }
106
107
    /**
108
     * Attempt to place the provided domino with the left side of the board's pieces. We can flip the domino
109
     * both ways so we should check both ways before updating the board.
110
     *
111
     * @param Domino $domino The domino that is being placed.
112
     */
113
    private function placeLeft(Domino $domino): void
114
    {
115
        $leftTitle = $this->virtualDomino->getTopTile();    // Represents left placement
116
        $rightTile = $this->virtualDomino->getBottomTile(); // Represents right placement
117
118
        // Does the top tile match the left side of the placed pieces?
119
        if ($domino->getTopTile() === $leftTitle) {
120
            $this->placed->addToStart($domino);
121
            $this->virtualDomino = new Domino($domino->getBottomTile(), $rightTile);
122
            return;
123
        }
124
125
        // Does the bottom tile match the left side of the placed pieces?
126
        if ($domino->getBottomTile() === $leftTitle) {
127
            $this->placed->addToStart($domino);
128
            $this->virtualDomino = new Domino($domino->getTopTile(), $rightTile);
129
            return;
130
        }
131
    }
132
133
    /**
134
     * Attempt to place the provided domino with the right side of the board's pieces. We can flip the domino
135
     * both ways so we should check both ways before updating the board.
136
     *
137
     * @param Domino $domino The domino that is being placed.
138
     */
139
    private function placeRight(Domino $domino): void
140
    {
141
        $rightTile = $this->virtualDomino->getBottomTile(); // Represents right placement
142
        $leftTitle = $this->virtualDomino->getTopTile();    // Represents left placement
143
144
        // Does the top tile match the right side of the placed pieces?
145
        if ($domino->getTopTile() === $rightTile) {
146
            $this->placed->addToEnd($domino);
147
            $this->virtualDomino = new Domino($leftTitle, $domino->getBottomTile());
148
            return;
149
        }
150
151
        // Does the bottom tile match the right side of the placed pieces?
152
        if ($domino->getBottomTile() === $rightTile) {
153
            $this->placed->addToEnd($domino);
154
            $this->virtualDomino = new Domino($leftTitle, $domino->getTopTile());
155
            return;
156
        }
157
    }
158
159
    /**
160
     * Remove all elements from the board and reset the virtual domino to null.
161
     */
162
    public function clearBoard(): void
163
    {
164
        $this->placed->removeElements(null);
165
        $this->virtualDomino = null;
166
    }
167
168
    /**
169
     * Return the domino that is places on the left most position (first position in the collection).
170
     *
171
     * @return Domino|null
172
     */
173
    public function getLeft(): ?Domino
174
    {
175
        /** @var Domino $domino */
176
        $domino = $this->placed->first();
177
        return $domino;
178
    }
179
180
    /**
181
     * Return the value of the tile that is exposed in the left most position. This is one of the targets that players
182
     * must match a double against and matches the value of the 'top' tile.
183
     *
184
     * @return int|null
185
     */
186
    public function getLeftTile(): ?int
187
    {
188
        return isset($this->virtualDomino) ? $this->virtualDomino->getTopTile() : null;
189
    }
190
191
    /**
192
     * Return the domino that is places on the right most position (last position in the collection).
193
     *
194
     * @return Domino|null
195
     */
196
    public function getRight(): ?Domino
197
    {
198
        /** @var Domino $domino */
199
        $domino = $this->placed->last();
200
201
        return $domino;
202
    }
203
204
    /**
205
     * Return the value of the tile that is exposed in the right most position. This is one of the targets that players
206
     * must match a double against and matches the value of the 'bottom' tile.
207
     *
208
     * @return int|null
209
     */
210
    public function getRightTile(): ?int
211
    {
212
        return isset($this->virtualDomino) ? $this->virtualDomino->getBottomTile() : null;
213
    }
214
}
215