mirror of
https://github.com/sigmasternchen/php-chess
synced 2025-03-15 07:58:54 +00:00
feat: Add pawns to game system
This commit is contained in:
parent
85fa12c08f
commit
26f1a295a7
7 changed files with 397 additions and 5 deletions
|
@ -20,7 +20,7 @@ class FieldBitMap {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getBitForPosition(Position $position): int {
|
public function getBitForPosition(Position $position): int {
|
||||||
return $position->file * 8 + $position->rank;
|
return $position->rank * 8 + $position->file;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function add(Position $position): void {
|
public function add(Position $position): void {
|
||||||
|
@ -50,7 +50,7 @@ class FieldBitMap {
|
||||||
|
|
||||||
for ($i = 0; $i < 64; $i++) {
|
for ($i = 0; $i < 64; $i++) {
|
||||||
if ($this->map & (1 << $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;
|
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 {
|
public static function full(): FieldBitMap {
|
||||||
return new FieldBitMap(-1);
|
return new FieldBitMap(-1);
|
||||||
}
|
}
|
||||||
|
|
71
src/Game/Pawn.php
Normal file
71
src/Game/Pawn.php
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Game;
|
||||||
|
|
||||||
|
class Pawn extends Piece {
|
||||||
|
public function getName(): string {
|
||||||
|
return "Pawn";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getShort(): string {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMoveCandidateMap(FieldBitMap $occupied, FieldBitMap $captureable, FieldBitMap $threatened): FieldBitMap {
|
||||||
|
$result = FieldBitMap::empty();
|
||||||
|
|
||||||
|
$direction = $this->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;
|
||||||
|
}
|
||||||
|
}
|
39
src/Game/Piece.php
Normal file
39
src/Game/Piece.php
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Game;
|
||||||
|
|
||||||
|
abstract class Piece {
|
||||||
|
protected Side $side;
|
||||||
|
protected Position $position;
|
||||||
|
protected bool $hasMoved = false;
|
||||||
|
protected bool $wasMovedLast = false;
|
||||||
|
protected ?Position $oldPosition = null;
|
||||||
|
|
||||||
|
public function __construct(Position $position, Side $side) {
|
||||||
|
$this->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]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
8
src/Game/Side.php
Normal file
8
src/Game/Side.php
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Game;
|
||||||
|
|
||||||
|
enum Side {
|
||||||
|
case WHITE;
|
||||||
|
case BLACK;
|
||||||
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Game\FieldBitMap;
|
namespace Game;
|
||||||
use Game\Position;
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
final class FieldBitMapTest extends TestCase {
|
final class FieldBitMapTest extends TestCase {
|
||||||
|
@ -34,4 +34,35 @@ final class FieldBitMapTest extends TestCase {
|
||||||
$this->assertEquals($positions, $subject->getPositions());
|
$this->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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
230
tests/Game/PawnTest.php
Normal file
230
tests/Game/PawnTest.php
Normal file
|
@ -0,0 +1,230 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Game;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
final class PawnTest extends TestCase {
|
||||||
|
|
||||||
|
public function testMoves_initialWhiteUnobstructed() {
|
||||||
|
$subject = new Pawn(new Position(
|
||||||
|
3, 1
|
||||||
|
), Side::WHITE);
|
||||||
|
|
||||||
|
$result = $subject->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)
|
||||||
|
]))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Game\Position;
|
namespace Game;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
final class PositionTest extends TestCase {
|
final class PositionTest extends TestCase {
|
||||||
|
|
Loading…
Reference in a new issue