diff --git a/src/Game/FieldBitMap.php b/src/Game/FieldBitMap.php index 647507d..014ac13 100644 --- a/src/Game/FieldBitMap.php +++ b/src/Game/FieldBitMap.php @@ -20,7 +20,7 @@ class FieldBitMap { } public function getBitForPosition(Position $position): int { - return $position->file * 8 + $position->rank; + return $position->rank * 8 + $position->file; } public function add(Position $position): void { @@ -50,7 +50,7 @@ class FieldBitMap { for ($i = 0; $i < 64; $i++) { if ($this->map & (1 << $i)) { - $result[] = new Position(floor($i / 8), $i % 8); + $result[] = new Position($i % 8, floor($i / 8)); } } @@ -61,6 +61,18 @@ class FieldBitMap { return $this->map; } + public function equals(FieldBitMap $map): bool { + return $this->map == $map->map; + } + + public function visualize(): string { + $result = ""; + for ($rank = 7; $rank >= 0; $rank--) { + $result .= strrev(sprintf("\n%08b", ($this->map >> ($rank * 8)) & 0b11111111)); + } + return $result; + } + public static function full(): FieldBitMap { return new FieldBitMap(-1); } diff --git a/src/Game/Pawn.php b/src/Game/Pawn.php new file mode 100644 index 0000000..cffa858 --- /dev/null +++ b/src/Game/Pawn.php @@ -0,0 +1,71 @@ +side == Side::WHITE ? 1 : -1; + + $rank = $this->position->rank; + $file = $this->position->file; + + $regular = new Position($file, $rank + 1 * $direction); + $capture1 = new Position($file - 1, $rank + 1 * $direction); + $capture2 = new Position($file + 1, $rank + 1 * $direction); + + $initial = new Position($file, $rank + 2 * $direction); + + if (!$occupied->has($regular)) { + $result->add($regular); + + if (!$this->hasMoved && !$occupied->has($initial)) { + $result->add($initial); + } + } + if ($captureable->has($capture1)) { + $result->add($capture1); + } + if ($captureable->has($capture2)) { + $result->add($capture2); + } + + return $result; + } + + public function getCaptureMap(FieldBitMap $occupied): FieldBitMap { + $direction = $this->side == Side::WHITE ? 1 : -1; + + $rank = $this->position->rank; + $file = $this->position->file; + + return new FieldBitMap([ + new Position($file - 1, $rank + 1 * $direction), + new Position($file + 1, $rank + 1 * $direction), + ]); + } + + public function getCaptureableMap(bool $forPawn): FieldBitMap { + $result = new FieldBitMap([ + $this->position, + ]); + + if ($forPawn && $this->wasMovedLast && abs($this->oldPosition->rank - $this->position->rank) == 2) { + $result->add(new Position( + $this->position->file, + ($this->position->rank + $this->oldPosition->rank) / 2 + )); + } + + return $result; + } +} \ No newline at end of file diff --git a/src/Game/Piece.php b/src/Game/Piece.php new file mode 100644 index 0000000..d410d70 --- /dev/null +++ b/src/Game/Piece.php @@ -0,0 +1,39 @@ +position = $position; + $this->side = $side; + } + + public function tick() { + $this->wasMovedLast = false; + } + + public function move(Position $position) { + $this->oldPosition = $this->position; + $this->position = $position; + $this->hasMoved = true; + $this->wasMovedLast = true; + } + + abstract public function getName(): string; + abstract public function getShort(): string; + abstract public function getMoveCandidateMap(FieldBitMap $occupied, FieldBitMap $captureable, FieldBitMap $threatened): FieldBitMap; + public function getCaptureMap(FieldBitMap $occupied): FieldBitMap { + return $this->getMoveCandidateMap($occupied, FieldBitMap::empty(), FieldBitMap::empty()); + } + public function getCaptureableMap(bool $forPawn): FieldBitMap { + return new FieldBitMap([$this->position]); + } + + +} \ No newline at end of file diff --git a/src/Game/Side.php b/src/Game/Side.php new file mode 100644 index 0000000..e5cee8d --- /dev/null +++ b/src/Game/Side.php @@ -0,0 +1,8 @@ +assertEquals($positions, $subject->getPositions()); } + public function testVisualize() { + $subject = new FieldBitMap([ + new Position(0, 0), + new Position(1, 1), + new Position(2,2), + new Position(3, 3), + new Position(4, 3), + new Position(5, 3), + new Position(6, 3), + new Position(7, 3), + new Position(7, 4), + new Position(7, 5), + new Position(7, 6), + new Position(7, 7), + ]); + + $result = $subject->visualize(); + $this->assertEquals( + "" . + "00000001\n" . + "00000001\n" . + "00000001\n" . + "00000001\n" . + "00011111\n" . + "00100000\n" . + "01000000\n" . + "10000000\n", + $result, + ); + } + } \ No newline at end of file diff --git a/tests/Game/PawnTest.php b/tests/Game/PawnTest.php new file mode 100644 index 0000000..c7d27a1 --- /dev/null +++ b/tests/Game/PawnTest.php @@ -0,0 +1,230 @@ +getMoveCandidateMap(FieldBitMap::empty(), FieldBitMap::empty(), FieldBitMap::empty()); + + $this->assertTrue( + $result->equals(new FieldBitMap([ + new Position(3, 2), + new Position(3, 3) + ])) + ); + } + + public function testMoves_initialWhitePartlyObstructed() { + $subject = new Pawn(new Position( + 3, 1 + ), Side::WHITE); + + $result = $subject->getMoveCandidateMap( + new FieldBitMap([ + new Position(3, 3), + ]), + FieldBitMap::empty(), + FieldBitMap::empty() + ); + + $this->assertTrue( + $result->equals(new FieldBitMap([ + new Position(3, 2), + ])) + ); + } + + public function testMoves_whiteDefault() { + $subject = new Pawn(new Position( + 4, 3 + ), Side::WHITE + ); + $subject->move(new Position(4, 3)); + + $result = $subject->getMoveCandidateMap(FieldBitMap::empty(), FieldBitMap::empty(), FieldBitMap::empty()); + $this->assertTrue( + $result->equals(new FieldBitMap([ + new Position(4, 4) + ])) + ); + } + + public function testMoves_whiteObstructedCapturesPossible() { + $subject = new Pawn(new Position( + 3, 4 + ), Side::WHITE); + $subject->move(new Position(3, 4)); + + $result = $subject->getMoveCandidateMap( + new FieldBitMap([ + new Position(2, 5), + new Position(3, 5) + ]), + new FieldBitMap([ + new Position(4, 5) + ]), + FieldBitMap::empty() + ); + + $this->assertTrue( + $result->equals(new FieldBitMap([ + new Position(4, 5), + ])) + ); + } + + public function testMoves_initialBlackUnobstructed() { + $subject = new Pawn(new Position( + 3, 6 + ), Side::BLACK); + + $result = $subject->getMoveCandidateMap(FieldBitMap::empty(), FieldBitMap::empty(), FieldBitMap::empty()); + + $this->assertTrue( + $result->equals(new FieldBitMap([ + new Position(3, 5), + new Position(3, 4) + ])) + ); + } + + public function testMoves_initialBlackPartlyObstructed() { + $subject = new Pawn(new Position( + 3, 6 + ), Side::BLACK); + + $result = $subject->getMoveCandidateMap( + new FieldBitMap([ + new Position(3, 4), + ]), + FieldBitMap::empty(), + FieldBitMap::empty() + ); + + $this->assertTrue( + $result->equals(new FieldBitMap([ + new Position(3, 5), + ])) + ); + } + + public function testMoves_blackDefault() { + $subject = new Pawn(new Position( + 4, 3 + ), Side::BLACK + ); + $subject->move(new Position(4, 3)); + + $result = $subject->getMoveCandidateMap(FieldBitMap::empty(), FieldBitMap::empty(), FieldBitMap::empty()); + $this->assertTrue( + $result->equals(new FieldBitMap([ + new Position(4, 2) + ])) + ); + } + + public function testMoves_blackObstructedCapturesPossible() { + $subject = new Pawn(new Position( + 3, 5 + ), Side::BLACK); + $subject->move(new Position(3, 5)); + + $result = $subject->getMoveCandidateMap( + new FieldBitMap([ + new Position(2, 4), + new Position(3, 4) + ]), + new FieldBitMap([ + new Position(4, 4) + ]), + FieldBitMap::empty() + ); + + $this->assertTrue( + $result->equals(new FieldBitMap([ + new Position(4, 4), + ])) + ); + } + + public function testCaptureable_default() { + $subject = new Pawn( + new Position(3, 4), + Side::WHITE, + ); + $subject->move(new Position(3, 4)); + $subject->tick(); + + $this->assertTrue( + $subject->getCaptureableMap(true)->equals(new FieldBitMap([ + new Position(3, 4) + ])) + ); + } + + public function testCaptureable_whiteInitial() { + $subject = new Pawn( + new Position(3, 1), + Side::WHITE, + ); + $subject->move(new Position(3, 3)); + + $this->assertTrue( + $subject->getCaptureableMap(true)->equals(new FieldBitMap([ + new Position(3, 2), + new Position(3, 3) + ])) + ); + } + + public function testCaptureable_blackInitial() { + $subject = new Pawn( + new Position(3, 6), + Side::BLACK, + ); + $subject->move(new Position(3, 4)); + + $this->assertTrue( + $subject->getCaptureableMap(true)->equals(new FieldBitMap([ + new Position(3, 5), + new Position(3, 4) + ])) + ); + } + + public function testCaptureable_initialForNonPawns() { + $subject = new Pawn( + new Position(3, 1), + Side::WHITE, + ); + $subject->move(new Position(3, 3)); + + $this->assertTrue( + $subject->getCaptureableMap(false)->equals(new FieldBitMap([ + new Position(3, 3) + ])) + ); + } + + public function testCaptureMap() { + $subject = new Pawn( + new Position(3, 4), + Side::WHITE, + ); + + $this->assertTrue( + $subject->getCaptureMap(FieldBitMap::empty())->equals(new FieldBitMap([ + new Position(2, 5), + new Position(4, 5) + ])) + ); + } +} \ No newline at end of file diff --git a/tests/Game/PositionTest.php b/tests/Game/PositionTest.php index 603c0f4..b790499 100644 --- a/tests/Game/PositionTest.php +++ b/tests/Game/PositionTest.php @@ -1,7 +1,8 @@