feat: Add pawns to game system

This commit is contained in:
overflowerror 2023-12-29 23:40:38 +01:00
parent 85fa12c08f
commit 26f1a295a7
7 changed files with 397 additions and 5 deletions

View file

@ -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);
}

71
src/Game/Pawn.php Normal file
View 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
View 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
View file

@ -0,0 +1,8 @@
<?php
namespace Game;
enum Side {
case WHITE;
case BLACK;
}

View file

@ -1,8 +1,8 @@
<?php
declare(strict_types=1);
use Game\FieldBitMap;
use Game\Position;
namespace Game;
use PHPUnit\Framework\TestCase;
final class FieldBitMapTest extends TestCase {
@ -34,4 +34,35 @@ final class FieldBitMapTest extends TestCase {
$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
View 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)
]))
);
}
}

View file

@ -1,7 +1,8 @@
<?php
declare(strict_types=1);
use Game\Position;
namespace Game;
use PHPUnit\Framework\TestCase;
final class PositionTest extends TestCase {