Completed
Push — master ( 5b464c...5e1a02 )
by Walter
16:59 queued 02:28
created

Notation::getTargetRow()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
c 0
b 0
f 0
rs 10
cc 1
eloc 1
nc 1
nop 0
1
<?php declare(strict_types=1);
2
/**
3
 * standard-algebraic-notation (https://github.com/chesszebra/standard-algebraic-notation)
4
 *
5
 * @link https://github.com/chesszebra/standard-algebraic-notation for the canonical source repository
6
 * @copyright Copyright (c) 2017 Chess Zebra (https://chesszebra.com)
7
 * @license https://github.com/chesszebra/standard-algebraic-notation/blob/master/LICENSE.md MIT
8
 */
9
10
namespace ChessZebra\StandardAlgebraicNotation;
11
12
use ChessZebra\StandardAlgebraicNotation\Exception\InvalidArgumentException;
13
use ChessZebra\StandardAlgebraicNotation\Exception\RuntimeException;
14
15
/**
16
 * The representation of a Standard Algebraic Notation (SAN) notation.
17
 */
18
final class Notation
19
{
20
    const PIECE_PAWN = null;
21
    const PIECE_BISHOP = 'B';
22
    const PIECE_KING = 'K';
23
    const PIECE_KNIGHT = 'N';
24
    const PIECE_QUEEN = 'Q';
25
    const PIECE_ROOK = 'R';
26
27
    const CASTLING_NONE = null;
28
    const CASTLING_KING_SIDE = 'O-O';
29
    const CASTLING_QUEEN_SIDE = 'O-O-O';
30
31
    /**
32
     * The original value.
33
     *
34
     * @var string
35
     */
36
    private $value;
37
38
    /**
39
     * The type of castling move that was made.
40
     *
41
     * @var null|string
42
     */
43
    private $castling;
44
45
    /**
46
     * The target column.
47
     *
48
     * @var string
49
     */
50
    private $targetColumn;
51
52
    /**
53
     * The target row.
54
     *
55
     * @var int
56
     */
57
    private $targetRow;
58
59
    /**
60
     * The piece that was moved.
61
     *
62
     * @var string
63
     */
64
    private $movedPiece;
65
66
    /**
67
     * The column from where the move was made.
68
     *
69
     * @var string
70
     */
71
    private $movedPieceDisambiguationColumn;
72
73
    /**
74
     * The row from where the move was made.
75
     *
76
     * @var int
77
     */
78
    private $movedPieceDisambiguationRow;
79
80
    /**
81
     * The piece to which the pawn was promoted into.
82
     *
83
     * @var string
84
     */
85
    private $promotedPiece;
86
87
    /**
88
     * A flag that indicates whether or not a piece was captured.
89
     *
90
     * @var bool
91
     */
92
    private $capture;
93
94
    /**
95
     * A flag that indicates that a check move was made.
96
     *
97
     * @var bool
98
     */
99
    private $check;
100
101
    /**
102
     * A flag that indicates that a checkmate move was made.
103
     *
104
     * @var bool
105
     */
106
    private $checkmate;
107
108
    /**
109
     * Initializes a new instance of this class.
110
     *
111
     * @param string $value
112
     * @throws InvalidArgumentException Thrown when an invalid value is provided.
113
     */
114
    public function __construct(string $value)
115
    {
116
        $this->value = $value;
117
        $this->capture = false;
118
        $this->check = false;
119
        $this->checkmate = false;
120
121
        $this->parse($value);
122
    }
123
124
    /**
125
     * Parses a SAN value.
126
     *
127
     * @param string $value The value to parse.
128
     * @throws InvalidArgumentException Thrown when an invalid value is provided.
129
     */
130
    private function parse(string $value): void
131
    {
132
        // Check for castling:
133
        if (preg_match('/^(O-O|O-O-O)(\+|\#?)$/', $value, $matches)) {
134
            $this->castling = $matches[1];
135
            $this->check = $matches[2] === '+';
136
            $this->checkmate = $matches[2] === '#';
137
            return;
138
        }
139
140
        // Pawn movement:
141
        if (preg_match('/^([a-h])([1-8])(\+|\#?)$/', $value, $matches)) {
142
            $this->targetColumn = $matches[1];
143
            $this->targetRow = (int)$matches[2];
144
            $this->check = $matches[3] === '+';
145
            $this->checkmate = $matches[3] === '#';
146
            $this->movedPiece = self::PIECE_PAWN;
147
            return;
148
        }
149
150
        // Piece movement:
151
        if (preg_match('/^([KQBNR])([a-h])([1-8])(\+|\#?)$/', $value, $matches)) {
152
            $this->movedPiece = $matches[1];
153
            $this->targetColumn = $matches[2];
154
            $this->targetRow = (int)$matches[3];
155
            $this->check = $matches[4] === '+';
156
            $this->checkmate = $matches[4] === '#';
157
            return;
158
        }
159
160
        // Piece movement from a specific column:
161
        if (preg_match('/^([KQBNR])([a-h])([a-h])([1-8])(\+|\#?)$/', $value, $matches)) {
162
            $this->movedPiece = $matches[1];
163
            $this->movedPieceDisambiguationColumn = $matches[2];
164
            $this->targetColumn = $matches[3];
165
            $this->targetRow = (int)$matches[4];
166
            $this->check = $matches[5] === '+';
167
            $this->checkmate = $matches[5] === '#';
168
            return;
169
        }
170
171
        // Piece movement from a specific row:
172
        if (preg_match('/^([KQBNR])([0-9])([a-h])([1-8])(\+|\#?)$/', $value, $matches)) {
173
            $this->movedPiece = $matches[1];
174
            $this->movedPieceDisambiguationRow = (int)$matches[2];
175
            $this->targetColumn = $matches[3];
176
            $this->targetRow = (int)$matches[4];
177
            $this->check = $matches[5] === '+';
178
            $this->checkmate = $matches[5] === '#';
179
            return;
180
        }
181
182
        // Pawn capture:
183
        if (preg_match('/^([a-h])x([a-h])([1-8])(?:=([KQBNR]))?(\+|\#?)$/', $value, $matches)) {
184
            $this->targetColumn = $matches[2];
185
            $this->targetRow = (int)$matches[3];
186
            $this->movedPiece = self::PIECE_PAWN;
187
            $this->movedPieceDisambiguationColumn = $matches[1];
188
            $this->capture = true;
189
            $this->promotedPiece = $matches[4] ?: null;
190
            $this->check = $matches[5] === '+';
191
            $this->checkmate = $matches[5] === '#';
192
            return;
193
        }
194
195
        // Piece capture:
196
        if (preg_match('/^([KQBNR])x([a-h])([1-8])(\+|\#?)$/', $value, $matches)) {
197
            $this->movedPiece = $matches[1];
198
            $this->targetColumn = $matches[2];
199
            $this->targetRow = (int)$matches[3];
200
            $this->check = $matches[4] === '+';
201
            $this->checkmate = $matches[4] === '#';
202
            $this->capture = true;
203
            return;
204
        }
205
206
        // Piece capture from a specific column:
207
        if (preg_match('/^([KQBNR])([a-h])x([a-h])([1-8])(\+|\#?)$/', $value, $matches)) {
208
            $this->movedPiece = $matches[1];
209
            $this->movedPieceDisambiguationColumn = $matches[2];
210
            $this->targetColumn = $matches[3];
211
            $this->targetRow = (int)$matches[4];
212
            $this->check = $matches[5] === '+';
213
            $this->checkmate = $matches[5] === '#';
214
            $this->capture = true;
215
            return;
216
        }
217
218
        // Piece capture from a specific column:
219
        if (preg_match('/^([KQBNR])([0-9])x([a-h])([1-8])(\+|\#?)$/', $value, $matches)) {
220
            $this->movedPiece = $matches[1];
221
            $this->movedPieceDisambiguationRow = (int)$matches[2];
222
            $this->targetColumn = $matches[3];
223
            $this->targetRow = (int)$matches[4];
224
            $this->check = $matches[5] === '+';
225
            $this->checkmate = $matches[5] === '#';
226
            $this->capture = true;
227
            return;
228
        }
229
230
        // Check for pawn promotion:
231
        if (preg_match('/^([a-h])([1-8])=([KQBNR])(\+|\#?)$/', $value, $matches)) {
232
            $this->movedPiece = self::PIECE_PAWN;
233
            $this->targetColumn = $matches[1];
234
            $this->targetRow = (int)$matches[2];
235
            $this->promotedPiece = $matches[3];
236
            $this->check = $matches[4] === '+';
237
            $this->checkmate = $matches[4] === '#';
238
            return;
239
        }
240
241
        throw new InvalidArgumentException(sprintf(
242
            'The value "%s" could not be parsed.',
243
            $value
244
        ));
245
    }
246
247
    /**
248
     * Checks if this move is a castling move.
249
     *
250
     * @return bool Returns true when this is a castling move; false otherwise.
251
     */
252
    public function isCastlingMove(): bool
253
    {
254
        return $this->isCastlingTowardsKingSide() || $this->isCastlingTowardsQueenSide();
255
    }
256
257
    /**
258
     * Checks if this is a castling move towards the king side.
259
     *
260
     * @return bool Returns true if this is a castling move; false otherwise.
261
     */
262
    public function isCastlingTowardsKingSide(): bool
263
    {
264
        return $this->castling === self::CASTLING_KING_SIDE;
265
    }
266
267
    /**
268
     * Checks if this is a castling move towards the queen side.
269
     *
270
     * @return bool Returns true if this is a castling move; false otherwise.
271
     */
272
    public function isCastlingTowardsQueenSide(): bool
273
    {
274
        return $this->castling === self::CASTLING_QUEEN_SIDE;
275
    }
276
277
    /**
278
     * Gets the original value.
279
     *
280
     * @return string
281
     */
282
    public function getValue(): string
283
    {
284
        return $this->value;
285
    }
286
287
    /**
288
     * Gets the castling value.
289
     *
290
     * @return null|string
291
     */
292
    public function getCastling(): ?string
293
    {
294
        return $this->castling;
295
    }
296
297
    /**
298
     * Gets the column to where the move was made.
299
     *
300
     * @return null|string
301
     */
302
    public function getTargetColumn(): ?string
303
    {
304
        return $this->targetColumn;
305
    }
306
307
    /**
308
     * Gets the column index to where the move was made.
309
     *
310
     * @return int|null
311
     */
312
    public function getTargetColumnIndex(): ?int
313
    {
314
        if ($this->targetColumn === null) {
315
            return null;
316
        }
317
318
        return ord($this->targetColumn) - 97;
319
    }
320
321
    /**
322
     * Duplicates the notation with a column based on an index from 0 to 7.
323
     *
324
     * @param int $index The index of the column to use.
325
     * @return Notation
326
     * @throws InvalidArgumentException Thrown when an invalid SAN value is created.
327
     * @throws RuntimeException Thrown when no target row is set.
328
     */
329
    public function withTargetColumnIndex(int $index): Notation
330
    {
331
        if (!$this->getTargetRow()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->getTargetRow() of type null|integer is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
332
            throw new RuntimeException('No row has been set.');
333
        }
334
335
        $notation = chr(97 + $index) . $this->getTargetRow();
336
337
        return new self($notation);
338
    }
339
340
    /**
341
     * Gets the row number to where the move was made.
342
     *
343
     * @return int|null
344
     */
345
    public function getTargetRow(): ?int
346
    {
347
        return $this->targetRow;
348
    }
349
350
    /**
351
     * Duplicates the notation with a new row.
352
     *
353
     * @param int $row A value between 1 and 8.
354
     * @return Notation
355
     * @throws InvalidArgumentException Thrown when an invalid SAN value is created.
356
     */
357
    public function withTargetRow(int $row): Notation
358
    {
359
        $notation = $this->getTargetColumn() . $row;
360
361
        return new self($notation);
362
    }
363
364
    /**
365
     * Gets the target notation.
366
     *
367
     * @return string
368
     */
369
    public function getTargetNotation(): string
370
    {
371
        return $this->getTargetColumn() . $this->getTargetRow();
372
    }
373
374
    /**
375
     * Gets the piece that was moved.
376
     *
377
     * @return null|string Returns the piece or null when a pawn was moved.
378
     */
379
    public function getMovedPiece(): ?string
380
    {
381
        return $this->movedPiece;
382
    }
383
384
    /**
385
     * Gets the disambiguation column.
386
     *
387
     * @return null|string
388
     */
389
    public function getMovedPieceDisambiguationColumn(): ?string
390
    {
391
        return $this->movedPieceDisambiguationColumn;
392
    }
393
394
    /**
395
     * Sets the disambiguation column.
396
     *
397
     * @param null|string $movedPieceDisambiguationColumn
398
     */
399
    public function setMovedPieceDisambiguationColumn(?string $movedPieceDisambiguationColumn): void
400
    {
401
        $this->movedPieceDisambiguationColumn = $movedPieceDisambiguationColumn;
402
    }
403
404
    /**
405
     * Gets the disambiguation row.
406
     *
407
     * @return int|null
408
     */
409
    public function getMovedPieceDisambiguationRow(): ?int
410
    {
411
        return $this->movedPieceDisambiguationRow;
412
    }
413
414
    /**
415
     * Sets the disambiguation row.
416
     *
417
     * @param int|null $movedPieceDisambiguationRow
418
     */
419
    public function setMovedPieceDisambiguationRow(?int $movedPieceDisambiguationRow): void
420
    {
421
        $this->movedPieceDisambiguationRow = $movedPieceDisambiguationRow;
422
    }
423
424
    /**
425
     * Gets the flag that indicates if this was a capture move.
426
     *
427
     * @return bool
428
     */
429
    public function isCapture(): bool
430
    {
431
        return $this->capture;
432
    }
433
434
    /**
435
     * Gets the piece into which the pawn was promoted.
436
     *
437
     * @return null|string
438
     */
439
    public function getPromotedPiece(): ?string
440
    {
441
        return $this->promotedPiece;
442
    }
443
444
    /**
445
     * Gets the flag that indicates whether or not the move returned into a check state.
446
     *
447
     * @return bool
448
     */
449
    public function isCheck(): bool
450
    {
451
        return $this->check;
452
    }
453
454
    /**
455
     * Gets the flag that indicates whether or not the move returned into a checkmate state.
456
     *
457
     * @return bool
458
     */
459
    public function isCheckmate(): bool
460
    {
461
        return $this->checkmate;
462
    }
463
464
    /**
465
     * Converts the move to a string.
466
     *
467
     * @return string
468
     */
469
    public function toString(): string
470
    {
471
        return $this->getValue();
472
    }
473
474
    /**
475
     * Converts the move to a string.
476
     *
477
     * @return string
478
     */
479
    public function __toString()
480
    {
481
        return $this->toString();
482
    }
483
}
484