Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ jobs:
strategy:
fail-fast: true
matrix:
php: ["8.1", "8.2", "8.3", "8.4"]
php: ["8.1", "8.2", "8.3", "8.4", "8.5"]

name: php-${{ matrix.php }}

steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v6

- name: Setup PHP
uses: shivammathur/setup-php@v2
Expand All @@ -28,7 +28,7 @@ jobs:

- name: Cache Composer packages
id: composer-cache
uses: actions/cache@v3
uses: actions/cache@v5
with:
path: vendor
key: ${{ matrix.php }}-php-${{ hashFiles('**/composer.lock') }}
Expand Down
2 changes: 1 addition & 1 deletion .php-cs-fixer.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
return (new PhpCsFixer\Config())
->setRules([
'@PSR12' => true,
'@PHP81Migration' => true,
'@PHP8x1Migration' => true,
'binary_operator_spaces' => ['operators' => ['=>' => 'single_space', '=' => 'single_space']],
'blank_line_before_statement' => ['statements' => ['return']],
'cast_spaces' => true,
Expand Down
10 changes: 5 additions & 5 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@
"ext-ctype": "*"
},
"require-dev": {
"ergebnis/phpunit-slow-test-detector": "^2.19",
"friendsofphp/php-cs-fixer": "^3.75",
"ergebnis/phpunit-slow-test-detector": "^2.20",
"friendsofphp/php-cs-fixer": "^3.92",
"imagine/imagine": "^1.5",
"phpbench/phpbench": "^1.4",
"phpstan/phpstan": "^1.12",
"phpunit/phpunit": "^10.5 || ^11.5 || ^12.2"
"phpstan/phpstan": "^2.0",
"phpstan/phpstan-strict-rules": "^2.0",
"phpunit/phpunit": "^10.5 || ^11.5 || ^12.5"
},
"suggest": {
"imagine/imagine": "To generate board images."
Expand All @@ -48,7 +49,6 @@
"PChess\\Chess\\Benchmark\\": "benchmarks/"
}
},
"minimum-stability": "stable",
"scripts": {
"benchmark": "./vendor/bin/phpbench run benchmarks/ --report=aggregate --retry-threshold=2",
"build": [
Expand Down
27 changes: 14 additions & 13 deletions docs/get-resources.sh
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
#!/bin/sh

dir=../resources/
uah='User-Agent: pchess/1.0 (https://github.com/p-chess/chess; [email protected]) p-chess/1.0'

mkdir -p $dir

curl https://source.winehq.org/git/wine.git/blob/HEAD:/fonts/tahoma.ttf -o $dir/font.ttf
curl https://upload.wikimedia.org/wikipedia/commons/thumb/f/f0/Chess_kdt45.svg/1920px-Chess_kdt45.svg.png -o $dir/bk.png
curl https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Chess_qdt45.svg/1024px-Chess_qdt45.svg.png -o $dir/bq.png
curl https://upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Chess_rdt45.svg/1024px-Chess_rdt45.svg.png -o $dir/br.png
curl https://upload.wikimedia.org/wikipedia/commons/thumb/9/98/Chess_bdt45.svg/1024px-Chess_bdt45.svg.png -o $dir/bb.png
curl https://upload.wikimedia.org/wikipedia/commons/thumb/e/ef/Chess_ndt45.svg/1024px-Chess_ndt45.svg.png -o $dir/bn.png
curl https://upload.wikimedia.org/wikipedia/commons/thumb/c/c7/Chess_pdt45.svg/1024px-Chess_pdt45.svg.png -o $dir/bp.png
curl https://upload.wikimedia.org/wikipedia/commons/thumb/4/42/Chess_klt45.svg/1024px-Chess_klt45.svg.png -o $dir/wk.png
curl https://upload.wikimedia.org/wikipedia/commons/thumb/1/15/Chess_qlt45.svg/1024px-Chess_qlt45.svg.png -o $dir/wq.png
curl https://upload.wikimedia.org/wikipedia/commons/thumb/7/72/Chess_rlt45.svg/1024px-Chess_rlt45.svg.png -o $dir/wr.png
curl https://upload.wikimedia.org/wikipedia/commons/thumb/b/b1/Chess_blt45.svg/1024px-Chess_blt45.svg.png -o $dir/wb.png
curl https://upload.wikimedia.org/wikipedia/commons/thumb/7/70/Chess_nlt45.svg/1024px-Chess_nlt45.svg.png -o $dir/wn.png
curl https://upload.wikimedia.org/wikipedia/commons/thumb/4/45/Chess_plt45.svg/1024px-Chess_plt45.svg.png -o $dir/wp.png
curl https://gitlab.winehq.org/wine/wine/-/raw/HEAD/fonts/tahoma.ttf -o $dir/font.ttf
curl -H '$uah' https://upload.wikimedia.org/wikipedia/commons/thumb/f/f0/Chess_kdt45.svg/1920px-Chess_kdt45.svg.png -o $dir/bk.png
curl -H '$uah' https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Chess_qdt45.svg/1024px-Chess_qdt45.svg.png -o $dir/bq.png
curl -H '$uah' https://upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Chess_rdt45.svg/1024px-Chess_rdt45.svg.png -o $dir/br.png
curl -H '$uah' https://upload.wikimedia.org/wikipedia/commons/thumb/9/98/Chess_bdt45.svg/1024px-Chess_bdt45.svg.png -o $dir/bb.png
curl -H '$uah' https://upload.wikimedia.org/wikipedia/commons/thumb/e/ef/Chess_ndt45.svg/1024px-Chess_ndt45.svg.png -o $dir/bn.png
curl -H '$uah' https://upload.wikimedia.org/wikipedia/commons/thumb/c/c7/Chess_pdt45.svg/1024px-Chess_pdt45.svg.png -o $dir/bp.png
curl -H '$uah' https://upload.wikimedia.org/wikipedia/commons/thumb/4/42/Chess_klt45.svg/1024px-Chess_klt45.svg.png -o $dir/wk.png
curl -H '$uah' https://upload.wikimedia.org/wikipedia/commons/thumb/1/15/Chess_qlt45.svg/1024px-Chess_qlt45.svg.png -o $dir/wq.png
curl -H '$uah' https://upload.wikimedia.org/wikipedia/commons/thumb/7/72/Chess_rlt45.svg/1024px-Chess_rlt45.svg.png -o $dir/wr.png
curl -H '$uah' https://upload.wikimedia.org/wikipedia/commons/thumb/b/b1/Chess_blt45.svg/1024px-Chess_blt45.svg.png -o $dir/wb.png
curl -H '$uah' https://upload.wikimedia.org/wikipedia/commons/thumb/7/70/Chess_nlt45.svg/1024px-Chess_nlt45.svg.png -o $dir/wn.png
curl -H '$uah' https://upload.wikimedia.org/wikipedia/commons/thumb/4/45/Chess_plt45.svg/1024px-Chess_plt45.svg.png -o $dir/wp.png

3 changes: 3 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ parameters:
paths:
- src
- tests

includes:
- vendor/phpstan/phpstan-strict-rules/rules.neon
23 changes: 5 additions & 18 deletions src/Board.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

/**
* @implements \ArrayAccess<int, ?Piece>
* @implements \Iterator<int, ?Piece>
* @implements \Iterator<?int, ?Piece>
*/
class Board implements \ArrayAccess, \Iterator, \JsonSerializable
{
Expand Down Expand Up @@ -78,37 +78,24 @@ class Board implements \ArrayAccess, \Iterator, \JsonSerializable

private bool $reversed = false;

/**
* @param int $offset
*/
public function offsetExists($offset): bool
public function offsetExists(mixed $offset): bool
{
return isset($this->squares[$offset]);
}

/**
* @param int $offset
*/
public function offsetGet($offset): ?Piece
public function offsetGet(mixed $offset): ?Piece
{
return $this->squares[$offset] ?? null;
}

/**
* @param int $offset
* @param ?Piece $value
*/
public function offsetSet($offset, $value): void
public function offsetSet(mixed $offset, mixed $value): void
{
if (\in_array($offset, self::SQUARES, true)) {
$this->squares[$offset] = $value;
}
}

/**
* @param int $offset
*/
public function offsetUnset($offset): void
public function offsetUnset(mixed $offset): void
{
unset($this->squares[$offset]);
}
Expand Down
68 changes: 34 additions & 34 deletions src/Chess.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,16 +99,16 @@ protected function load(string $fen): ?string
$this->turn = $tokens[1];

// castling options
if (\strpos($tokens[2], 'K') !== false) {
if (\str_contains($tokens[2], 'K')) {
$this->castling[Piece::WHITE] |= Move::BITS['KSIDE_CASTLE'];
}
if (\strpos($tokens[2], 'Q') !== false) {
if (\str_contains($tokens[2], 'Q')) {
$this->castling[Piece::WHITE] |= Move::BITS['QSIDE_CASTLE'];
}
if (\strpos($tokens[2], 'k') !== false) {
if (\str_contains($tokens[2], 'k')) {
$this->castling[Piece::BLACK] |= Move::BITS['KSIDE_CASTLE'];
}
if (\strpos($tokens[2], 'q') !== false) {
if (\str_contains($tokens[2], 'q')) {
$this->castling[Piece::BLACK] |= Move::BITS['QSIDE_CASTLE'];
}

Expand Down Expand Up @@ -139,7 +139,7 @@ public function fen(): string
$fen .= $piece->toAscii();
}

if (($i + 1) & 0x88) {
if ((($i + 1) & 0x88) > 0) {
if ($empty > 0) {
$fen .= $empty;
}
Expand All @@ -152,16 +152,16 @@ public function fen(): string
}

$cFlags = '';
if ($this->castling[Piece::WHITE] & Move::BITS['KSIDE_CASTLE']) {
if (($this->castling[Piece::WHITE] & Move::BITS['KSIDE_CASTLE']) > 0) {
$cFlags .= 'K';
}
if ($this->castling[Piece::WHITE] & Move::BITS['QSIDE_CASTLE']) {
if (($this->castling[Piece::WHITE] & Move::BITS['QSIDE_CASTLE']) > 0) {
$cFlags .= 'Q';
}
if ($this->castling[Piece::BLACK] & Move::BITS['KSIDE_CASTLE']) {
if (($this->castling[Piece::BLACK] & Move::BITS['KSIDE_CASTLE']) > 0) {
$cFlags .= 'k';
}
if ($this->castling[Piece::BLACK] & Move::BITS['QSIDE_CASTLE']) {
if (($this->castling[Piece::BLACK] & Move::BITS['QSIDE_CASTLE']) > 0) {
$cFlags .= 'q';
}
if ($cFlags === '') {
Expand Down Expand Up @@ -232,17 +232,17 @@ protected function makeMove(Move $move): void
$this->board[$move->fromSquare] = null;

// if flags:EP_CAPTURE (en passant), remove the captured pawn
if ($move->flags & Move::BITS['EP_CAPTURE']) {
if (($move->flags & Move::BITS['EP_CAPTURE']) > 0) {
$this->board[$move->toSquare + ($us === Piece::BLACK ? -16 : 16)] = null;
}

// if pawn promotion, replace with new piece
if ($move->flags & Move::BITS['PROMOTION']) {
if (($move->flags & Move::BITS['PROMOTION']) > 0) {
$this->board[$move->toSquare] = new Piece($move->promotion, $us);
}

// if big pawn move, update the en passant square
if ($move->flags & Move::BITS['BIG_PAWN']) {
if (($move->flags & Move::BITS['BIG_PAWN']) > 0) {
$this->epSquare = $move->toSquare + ($us === Piece::BLACK ? -16 : 16);
} else {
$this->epSquare = null;
Expand All @@ -251,7 +251,7 @@ protected function makeMove(Move $move): void
// reset the 50 move counter if a pawn is moved or piece is captured
if ($move->piece->isPawn()) {
$this->halfMoves = 0;
} elseif ($move->flags & (Move::BITS['CAPTURE'] | Move::BITS['EP_CAPTURE'])) {
} elseif (($move->flags & (Move::BITS['CAPTURE'] | Move::BITS['EP_CAPTURE'])) > 0) {
$this->halfMoves = 0;
} else {
++$this->halfMoves;
Expand All @@ -262,12 +262,12 @@ protected function makeMove(Move $move): void
$this->kings[$us] = $move->toSquare;

// if we castled, move the rook next to the king
if ($move->flags & Move::BITS['KSIDE_CASTLE']) {
if (($move->flags & Move::BITS['KSIDE_CASTLE']) > 0) {
$castlingTo = $move->toSquare - 1;
$castlingFrom = $move->toSquare + 1;
$this->board[$castlingTo] = $this->board[$castlingFrom];
$this->board[$castlingFrom] = null;
} elseif ($move->flags & Move::BITS['QSIDE_CASTLE']) {
} elseif (($move->flags & Move::BITS['QSIDE_CASTLE']) > 0) {
$castlingTo = $move->toSquare + 1;
$castlingFrom = $move->toSquare - 2;
$this->board[$castlingTo] = $this->board[$castlingFrom];
Expand All @@ -281,8 +281,8 @@ protected function makeMove(Move $move): void
if ($this->castling[$us] > 0) {
for ($i = 0, $len = \count(Board::ROOKS[$us]); $i < $len; ++$i) {
if (
$move->fromSquare === Board::ROOKS[$us][$i]['square'] &&
$this->castling[$us] & Board::ROOKS[$us][$i]['flag']
($move->fromSquare === Board::ROOKS[$us][$i]['square']) > 0 &&
($this->castling[$us] & Board::ROOKS[$us][$i]['flag']) > 0
) {
$this->castling[$us] ^= Board::ROOKS[$us][$i]['flag'];
break;
Expand All @@ -295,7 +295,7 @@ protected function makeMove(Move $move): void
for ($i = 0, $len = \count(Board::ROOKS[$them]); $i < $len; ++$i) {
if (
$move->toSquare === Board::ROOKS[$them][$i]['square'] &&
$this->castling[$them] & Board::ROOKS[$them][$i]['flag']
($this->castling[$them] & Board::ROOKS[$them][$i]['flag']) > 0
) {
$this->castling[$them] ^= Board::ROOKS[$them][$i]['flag'];
break;
Expand Down Expand Up @@ -354,21 +354,21 @@ protected function undoMove(): ?Move
$this->board[$move->toSquare] = null;

// if capture
if ($move->flags & Move::BITS['CAPTURE']) {
if (($move->flags & Move::BITS['CAPTURE']) > 0) {
$this->board[$move->toSquare] = new Piece($move->captured, $them);
} elseif ($move->flags & Move::BITS['EP_CAPTURE']) {
} elseif (($move->flags & Move::BITS['EP_CAPTURE']) > 0) {
$index = $move->toSquare + ($us === Piece::BLACK ? -16 : 16);
$this->board[$index] = new Piece(Piece::PAWN, $them);
}

// if castling
if ($move->flags & Move::BITS['KSIDE_CASTLE']) {
if (($move->flags & Move::BITS['KSIDE_CASTLE']) > 0) {
$castlingTo = $move->toSquare + 1;
$castlingFrom = $move->toSquare - 1;
$this->board[$castlingTo] = $this->board[$castlingFrom];
$this->board[$castlingFrom] = null;
}
if ($move->flags & Move::BITS['QSIDE_CASTLE']) {
if (($move->flags & Move::BITS['QSIDE_CASTLE']) > 0) {
$castlingTo = $move->toSquare - 2;
$castlingFrom = $move->toSquare + 1;
$this->board[$castlingTo] = $this->board[$castlingFrom];
Expand Down Expand Up @@ -418,7 +418,7 @@ protected function generateMoves(?int $square = null, bool $legal = true): array

// legal moves only?
for ($i = $firstSquare; $i <= $lastSquare; ++$i) {
if ($i & 0x88) {
if (($i & 0x88) > 0) {
$i += 7;
continue;
} // check edge of board
Expand All @@ -444,7 +444,7 @@ protected function generateMoves(?int $square = null, bool $legal = true): array
// pawn captures
for ($j = 2; $j < 4; ++$j) {
$square = $i + Piece::PAWN_OFFSETS[$us][$j];
if ($square & 0x88) {
if (($square & 0x88) > 0) {
continue;
}
if ($this->board[$square] !== null) {
Expand All @@ -462,7 +462,7 @@ protected function generateMoves(?int $square = null, bool $legal = true): array

while (true) {
$square += $offset;
if ($square & 0x88) {
if (($square & 0x88) > 0) {
break;
}

Expand All @@ -488,7 +488,7 @@ protected function generateMoves(?int $square = null, bool $legal = true): array
// a) we're generating all moves
// b) we're doing single square move generation on king's square
if (!$singleSquare || $lastSquare === $this->kings[$us]) {
if ($this->castling[$us] & Move::BITS['KSIDE_CASTLE']) {
if (($this->castling[$us] & Move::BITS['KSIDE_CASTLE']) > 0) {
$castlingFrom = $this->kings[$us];
$castlingTo = $castlingFrom + 2;

Expand All @@ -503,7 +503,7 @@ protected function generateMoves(?int $square = null, bool $legal = true): array
}
}

if ($this->castling[$us] & Move::BITS['QSIDE_CASTLE']) {
if (($this->castling[$us] & Move::BITS['QSIDE_CASTLE']) > 0) {
$castlingFrom = $this->kings[$us];
$castlingTo = $castlingFrom - 2;

Expand Down Expand Up @@ -607,7 +607,7 @@ public function moves(?int $square = null): array
protected function attacked(string $color, int $square): bool
{
for ($i = Board::SQUARES['a8']; $i <= Board::SQUARES['h1']; ++$i) {
if ($i & 0x88) {
if (($i & 0x88) > 0) {
$i += 7;
continue;
}
Expand All @@ -620,7 +620,7 @@ protected function attacked(string $color, int $square): bool
$difference = $i - $square;
$index = $difference + 119;

if (Board::ATTACKS[$index] & (1 << Piece::SHIFTS[$piece->getType()])) {
if ((Board::ATTACKS[$index] & (1 << Piece::SHIFTS[$piece->getType()])) > 0) {
if ($piece->isPawn()) {
if ($difference > 0) {
if ($piece->getColor() === Piece::WHITE) {
Expand Down Expand Up @@ -692,7 +692,7 @@ public function insufficientMaterial(): bool

for ($i = Board::SQUARES['a8']; $i <= Board::SQUARES['h1']; ++$i) {
$sqColor = ($sqColor + 1) % 2;
if ($i & 0x88) {
if (($i & 0x88) > 0) {
$i += 7;
continue;
}
Expand Down Expand Up @@ -846,9 +846,9 @@ protected function moveToSAN(Move $move): void
}

$output = '';
if ($move->flags & Move::BITS['KSIDE_CASTLE']) {
if (($move->flags & Move::BITS['KSIDE_CASTLE']) > 0) {
$output = 'O-O';
} elseif ($move->flags & Move::BITS['QSIDE_CASTLE']) {
} elseif (($move->flags & Move::BITS['QSIDE_CASTLE']) > 0) {
$output = 'O-O-O';
} else {
$disambiguator = $this->getDisambiguator($move);
Expand All @@ -859,7 +859,7 @@ protected function moveToSAN(Move $move): void
}

// x on capture
if ($move->flags & (Move::BITS['CAPTURE'] | Move::BITS['EP_CAPTURE'])) {
if (($move->flags & (Move::BITS['CAPTURE'] | Move::BITS['EP_CAPTURE'])) > 0) {
// pawn e5->d6 is "exd6"
if ($move->piece->isPawn()) {
$output .= $move->from[0];
Expand All @@ -871,7 +871,7 @@ protected function moveToSAN(Move $move): void
$output .= $move->to;

// promotion example: e8=Q
if ($move->flags & Move::BITS['PROMOTION']) {
if (($move->flags & Move::BITS['PROMOTION']) > 0) {
$output .= '='.\strtoupper($move->promotion);
}
}
Expand Down
Loading