diff --git a/html/index.php b/html/index.php
index 3ab3363..d67531e 100644
--- a/html/index.php
+++ b/html/index.php
@@ -4,6 +4,7 @@ require_once '../src/core.php';
use Engine\GameOutcome;
use Engine\MinimaxDF;
+use Engine\PeSTO;
use Engine\PieceValues;
use Engine\WeightedHeuristics;
use Game\Game;
@@ -18,7 +19,8 @@ if (isset($_SESSION["game"])) {
$game = Game::fromStartPosition();
$engine = new MinimaxDF(1, new WeightedHeuristics([
[new GameOutcome(), 1.0],
- [new PieceValues(), 1.0]
+ [new PieceValues(), 1.0],
+ [new PeSTO(), 1.0],
]));
$_SESSION["game"] = $game;
diff --git a/src/Engine/PeSTO.php b/src/Engine/PeSTO.php
new file mode 100644
index 0000000..9e767c0
--- /dev/null
+++ b/src/Engine/PeSTO.php
@@ -0,0 +1,260 @@
+middlegameTable = [
+ PieceType::PAWN->getShort() => array_map(
+ fn($value) => $value + self::middlegamePieceValues[0],
+ self::middlegamePawnTable
+ ),
+ PieceType::BISHOP->getShort() => array_map(
+ fn($value) => $value + self::middlegamePieceValues[2],
+ self::middlegameBishopTable
+ ),
+ PieceType::KNIGHT->getShort() => array_map(
+ fn($value) => $value + self::middlegamePieceValues[1],
+ self::middlegameKnightTable
+ ),
+ PieceType::ROOK->getShort() => array_map(
+ fn($value) => $value + self::middlegamePieceValues[3],
+ self::middlegameRookTable
+ ),
+ PieceType::QUEEN->getShort() => array_map(
+ fn($value) => $value + self::middlegamePieceValues[4],
+ self::middlegameQueenTable
+ ),
+ PieceType::KING->getShort() => array_map(
+ fn($value) => $value + self::middlegamePieceValues[5],
+ self::middlegameKingTable
+ ),
+ ];
+ $this->endgameTable = [
+ PieceType::PAWN->getShort() => array_map(
+ fn($value) => $value + self::endgamePieceValues[0],
+ self::endgamePawnTable
+ ),
+ PieceType::BISHOP->getShort() => array_map(
+ fn($value) => $value + self::endgamePieceValues[2],
+ self::endgameBishopTable
+ ),
+ PieceType::KNIGHT->getShort() => array_map(
+ fn($value) => $value + self::endgamePieceValues[1],
+ self::endgameKnightTable
+ ),
+ PieceType::ROOK->getShort() => array_map(
+ fn($value) => $value + self::endgamePieceValues[3],
+ self::endgameRookTable
+ ),
+ PieceType::QUEEN->getShort() => array_map(
+ fn($value) => $value + self::endgamePieceValues[4],
+ self::endgameQueenTable
+ ),
+ PieceType::KING->getShort() => array_map(
+ fn($value) => $value + self::endgamePieceValues[5],
+ self::endgameKingTable
+ ),
+ ];
+ }
+
+ private function lookupEndgameCounterValue(PieceType $pieceType): float {
+ return match ($pieceType) {
+ PieceType::PAWN => 0,
+ PieceType::BISHOP => 1,
+ PieceType::KNIGHT => 1,
+ PieceType::ROOK => 2,
+ PieceType::QUEEN => 4,
+ default => 0,
+ };
+ }
+
+ private function lookupPieceValues(Piece $piece): array {
+ $rank = $piece->getPosition()->rank;
+ $file = $piece->getPosition()->file;
+
+ $index = $rank * 8 + $file;
+ if ($piece->getSide() == Side::BLACK) {
+ $index = 64 - $index;
+ }
+
+ return [
+ $this->middlegameTable[$piece->getType()->getShort()][$index],
+ $this->endgameTable[$piece->getType()->getShort()][$index],
+ $this->lookupEndgameCounterValue($piece->getType()),
+ ];
+ }
+
+ public function ratePosition(Game $game): float {
+ $ownPieces = $game->getPieces($game->getCurrentSide());
+ $opponentPieces = $game->getPieces($game->getCurrentSide()->getNext());
+
+ $ownValues = array_map([$this, "lookupPieceValues"], $ownPieces);
+ $opponentValues = array_map([$this, "lookupPieceValues"], $opponentPieces);
+
+ $middlegameScore = array_sum(
+ array_map(fn($values) => $values[0], $ownValues)
+ ) - array_sum(
+ array_map(fn($values) => $values[0], $opponentValues)
+ );
+
+ $endgameScore = array_sum(
+ array_map(fn($values) => $values[1], $ownValues)
+ ) - array_sum(
+ array_map(fn($values) => $values[1], $opponentValues)
+ );
+
+ $phase = array_sum(array_map(fn($values) => $values[2], array_merge($ownValues, $opponentValues)));
+ $phase = max($phase, 24);
+ $phase /= 24.0;
+
+ return ($middlegameScore * $phase + $endgameScore * (1 - $phase)) * self::normalizationFactor;
+ }
+}
\ No newline at end of file