diff --git a/src/Game/FieldBitMap.php b/src/Game/FieldBitMap.php index 39eeaf7..2f9e163 100644 --- a/src/Game/FieldBitMap.php +++ b/src/Game/FieldBitMap.php @@ -35,6 +35,10 @@ class FieldBitMap { } } + public function invert(): FieldBitMap { + return new FieldBitMap(~$this->map); + } + public function intersect(FieldBitMap $map): FieldBitMap { return new FieldBitMap($this->map & $map->map); } diff --git a/src/Game/King.php b/src/Game/King.php index ba21a92..ea954ad 100644 --- a/src/Game/King.php +++ b/src/Game/King.php @@ -32,4 +32,31 @@ class King extends Piece { public function isInCheck(FieldBitMap $captureable): bool { return $captureable->has($this->position); } + + public function canCastle( + FieldBitMap $occupied, + FieldBitMap $captureable, + FieldBitMap $threatened, + Rook $rook, + ): bool { + if ($this->position->rank != $rook->position->rank) { + return false; + } + if ($this->hasMoved || $rook->hasMoved) { + return false; + } + + $increment = $rook->position->file <=> $this->position->file; + for ($file = $this->position->file + $increment; $file != $rook->position->file; $file += $increment) { + $square = new Position($file, $this->position->rank); + if ($occupied->has($square) || $captureable->has($square)) { + return false; + } + if (abs($file - $this->position->file) <= 2 && $threatened->has($square)) { + return false; + } + } + + return true; + } } \ No newline at end of file diff --git a/tests/Game/KingTest.php b/tests/Game/KingTest.php index 20c99e3..beecbff 100644 --- a/tests/Game/KingTest.php +++ b/tests/Game/KingTest.php @@ -99,4 +99,424 @@ final class KingTest extends TestCase { ])) ); } + + public function testCanCastle_long() { + $king = new King( + new Position(4, 0), + Side::WHITE + ); + + $rook = new Rook( + new Position(0, 0), + Side::WHITE, + ); + + $maximumOccupiedMap = + FieldBitMap::full()->intersect((new FieldBitMap([ + new Position(1, 0), + new Position(2, 0), + new Position(3, 0), + ]))->invert()); + $maximumThreatenedMap = + FieldBitMap::full()->intersect((new FieldBitMap([ + new Position(2, 0), + new Position(3, 0), + ]))->invert()); + + $this->assertTrue($king->canCastle( + $maximumOccupiedMap, + $maximumOccupiedMap, + $maximumThreatenedMap, + $rook, + )); + } + + public function testCanCastle_short() { + $king = new King( + new Position(4, 0), + Side::WHITE + ); + + $rook = new Rook( + new Position(7, 0), + Side::WHITE, + ); + + $maximumMap = + FieldBitMap::full()->intersect((new FieldBitMap([ + new Position(6, 0), + new Position(5, 0), + ]))->invert()); + + $this->assertTrue($king->canCastle( + $maximumMap, + $maximumMap, + $maximumMap, + $rook, + )); + } + + public function testCanCastle_long_occupied1() { + $king = new King( + new Position(4, 0), + Side::WHITE + ); + + $rook = new Rook( + new Position(0, 0), + Side::WHITE, + ); + + $maximumOccupiedMap = + FieldBitMap::full()->intersect((new FieldBitMap([ + new Position(2, 0), + new Position(3, 0), + ]))->invert()); + $maximumCaptureableMap = + FieldBitMap::full()->intersect((new FieldBitMap([ + new Position(1, 0), + new Position(2, 0), + new Position(3, 0), + ]))->invert()); + $maximumThreatenedMap = + FieldBitMap::full()->intersect((new FieldBitMap([ + new Position(2, 0), + new Position(3, 0), + ]))->invert()); + + $this->assertFalse($king->canCastle( + $maximumOccupiedMap, + $maximumCaptureableMap, + $maximumThreatenedMap, + $rook, + )); + } + public function testCanCastle_long_occupied2() { + $king = new King( + new Position(4, 0), + Side::WHITE + ); + + $rook = new Rook( + new Position(0, 0), + Side::WHITE, + ); + + $maximumOccupiedMap = + FieldBitMap::full()->intersect((new FieldBitMap([ + new Position(1, 0), + new Position(3, 0), + ]))->invert()); + $maximumCaptureableMap = + FieldBitMap::full()->intersect((new FieldBitMap([ + new Position(1, 0), + new Position(2, 0), + new Position(3, 0), + ]))->invert()); + $maximumThreatenedMap = + FieldBitMap::full()->intersect((new FieldBitMap([ + new Position(2, 0), + new Position(3, 0), + ]))->invert()); + + $this->assertFalse($king->canCastle( + $maximumOccupiedMap, + $maximumCaptureableMap, + $maximumThreatenedMap, + $rook, + )); + } + + public function testCanCastle_long_occupied3() { + $king = new King( + new Position(4, 0), + Side::WHITE + ); + + $rook = new Rook( + new Position(0, 0), + Side::WHITE, + ); + + $maximumOccupiedMap = + FieldBitMap::full()->intersect((new FieldBitMap([ + new Position(1, 0), + new Position(2, 0), + ]))->invert()); + $maximumCaptureableMap = + FieldBitMap::full()->intersect((new FieldBitMap([ + new Position(1, 0), + new Position(2, 0), + new Position(3, 0), + ]))->invert()); + $maximumThreatenedMap = + FieldBitMap::full()->intersect((new FieldBitMap([ + new Position(2, 0), + new Position(3, 0), + ]))->invert()); + + $this->assertFalse($king->canCastle( + $maximumOccupiedMap, + $maximumCaptureableMap, + $maximumThreatenedMap, + $rook, + )); + } + + public function testCanCastle_long_captureable1() { + $king = new King( + new Position(4, 0), + Side::WHITE + ); + + $rook = new Rook( + new Position(0, 0), + Side::WHITE, + ); + + $maximumOccupiedMap = + FieldBitMap::full()->intersect((new FieldBitMap([ + new Position(1, 0), + new Position(2, 0), + new Position(3, 0), + ]))->invert()); + $maximumCaptureableMap = + FieldBitMap::full()->intersect((new FieldBitMap([ + new Position(2, 0), + new Position(3, 0), + ]))->invert()); + $maximumThreatenedMap = + FieldBitMap::full()->intersect((new FieldBitMap([ + new Position(2, 0), + new Position(3, 0), + ]))->invert()); + + $this->assertFalse($king->canCastle( + $maximumOccupiedMap, + $maximumCaptureableMap, + $maximumThreatenedMap, + $rook, + )); + } + + public function testCanCastle_long_captureable2() { + $king = new King( + new Position(4, 0), + Side::WHITE + ); + + $rook = new Rook( + new Position(0, 0), + Side::WHITE, + ); + + $maximumOccupiedMap = + FieldBitMap::full()->intersect((new FieldBitMap([ + new Position(1, 0), + new Position(2, 0), + new Position(3, 0), + ]))->invert()); + $maximumCaptureableMap = + FieldBitMap::full()->intersect((new FieldBitMap([ + new Position(1, 0), + new Position(3, 0), + ]))->invert()); + $maximumThreatenedMap = + FieldBitMap::full()->intersect((new FieldBitMap([ + new Position(2, 0), + new Position(3, 0), + ]))->invert()); + + $this->assertFalse($king->canCastle( + $maximumOccupiedMap, + $maximumCaptureableMap, + $maximumThreatenedMap, + $rook, + )); + } + + public function testCanCastle_long_captureable3() { + $king = new King( + new Position(4, 0), + Side::WHITE + ); + + $rook = new Rook( + new Position(0, 0), + Side::WHITE, + ); + + $maximumOccupiedMap = + FieldBitMap::full()->intersect((new FieldBitMap([ + new Position(1, 0), + new Position(2, 0), + new Position(3, 0), + ]))->invert()); + $maximumCaptureableMap = + FieldBitMap::full()->intersect((new FieldBitMap([ + new Position(1, 0), + new Position(2, 0), + ]))->invert()); + $maximumThreatenedMap = + FieldBitMap::full()->intersect((new FieldBitMap([ + new Position(2, 0), + new Position(3, 0), + ]))->invert()); + + $this->assertFalse($king->canCastle( + $maximumOccupiedMap, + $maximumCaptureableMap, + $maximumThreatenedMap, + $rook, + )); + } + + public function testCanCastle_long_threatened2() { + $king = new King( + new Position(4, 0), + Side::WHITE + ); + + $rook = new Rook( + new Position(0, 0), + Side::WHITE, + ); + + $maximumOccupiedMap = + FieldBitMap::full()->intersect((new FieldBitMap([ + new Position(1, 0), + new Position(2, 0), + new Position(3, 0), + ]))->invert()); + $maximumCaptureableMap = + FieldBitMap::full()->intersect((new FieldBitMap([ + new Position(1, 0), + new Position(2, 0), + new Position(3, 0), + ]))->invert()); + $maximumThreatenedMap = + FieldBitMap::full()->intersect((new FieldBitMap([ + new Position(3, 0), + ]))->invert()); + + $this->assertFalse($king->canCastle( + $maximumOccupiedMap, + $maximumCaptureableMap, + $maximumThreatenedMap, + $rook, + )); + } + + public function testCanCastle_long_threatened3() { + $king = new King( + new Position(4, 0), + Side::WHITE + ); + + $rook = new Rook( + new Position(0, 0), + Side::WHITE, + ); + + $maximumOccupiedMap = + FieldBitMap::full()->intersect((new FieldBitMap([ + new Position(1, 0), + new Position(2, 0), + new Position(3, 0), + ]))->invert()); + $maximumCaptureableMap = + FieldBitMap::full()->intersect((new FieldBitMap([ + new Position(1, 0), + new Position(2, 0), + new Position(3, 0), + ]))->invert()); + $maximumThreatenedMap = + FieldBitMap::full()->intersect((new FieldBitMap([ + new Position(2, 0), + ]))->invert()); + + $this->assertFalse($king->canCastle( + $maximumOccupiedMap, + $maximumCaptureableMap, + $maximumThreatenedMap, + $rook, + )); + } + + public function testCanCastle_long_kingMoved() { + $king = new King( + new Position(4, 0), + Side::WHITE, + true + ); + + $rook = new Rook( + new Position(0, 0), + Side::WHITE, + ); + + $maximumOccupiedMap = + FieldBitMap::full()->intersect((new FieldBitMap([ + new Position(1, 0), + new Position(2, 0), + new Position(3, 0), + ]))->invert()); + $maximumCaptureableMap = + FieldBitMap::full()->intersect((new FieldBitMap([ + new Position(1, 0), + new Position(2, 0), + new Position(3, 0), + ]))->invert()); + $maximumThreatenedMap = + FieldBitMap::full()->intersect((new FieldBitMap([ + new Position(2, 0), + new Position(3, 0), + ]))->invert()); + + $this->assertFalse($king->canCastle( + $maximumOccupiedMap, + $maximumCaptureableMap, + $maximumThreatenedMap, + $rook, + )); + } + + public function testCanCastle_long_rookMoved() { + $king = new King( + new Position(4, 0), + Side::WHITE, + ); + $king->move(new Position(4, 0)); + + $rook = new Rook( + new Position(0, 0), + Side::WHITE, + true, + ); + + $maximumOccupiedMap = + FieldBitMap::full()->intersect((new FieldBitMap([ + new Position(1, 0), + new Position(2, 0), + new Position(3, 0), + ]))->invert()); + $maximumCaptureableMap = + FieldBitMap::full()->intersect((new FieldBitMap([ + new Position(1, 0), + new Position(2, 0), + new Position(3, 0), + ]))->invert()); + $maximumThreatenedMap = + FieldBitMap::full()->intersect((new FieldBitMap([ + new Position(2, 0), + new Position(3, 0), + ]))->invert()); + + $this->assertFalse($king->canCastle( + $maximumOccupiedMap, + $maximumCaptureableMap, + $maximumThreatenedMap, + $rook, + )); + } } \ No newline at end of file