Skip to content

Commit

Permalink
Issue/528 tdd the chess eval combined attack eval class (#541)
Browse files Browse the repository at this point in the history
* Refactored Chess\Eval\ThreatEval

* Refactored Chess\Eval\ThreatEval

* Refactored Chess\Eval\ThreatEval

* TDDed the Chess\Eval\ThreatEval class

* Updated ThreatEvalTest

* Fixed Chess\Eval\SqCount

* Fixed Chess\Eval\ThreatEval

* Updated tests

* Refactored Chess\Eval\ThreatEval

* Documented the Chess\Eval\ThreatEval class
  • Loading branch information
programarivm authored May 24, 2024
1 parent 0c5af2f commit 393740d
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 49 deletions.
5 changes: 5 additions & 0 deletions src/Eval/SqCount.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ private function free(): array
*/
private function used(): object
{
$used = [
Color::W => [],
Color::B => [],
];

foreach ($this->board->getPieces() as $piece) {
$used[$piece->getColor()][] = $piece->getSq();
}
Expand Down
96 changes: 58 additions & 38 deletions src/Eval/ThreatEval.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,18 @@
namespace Chess\Eval;

use Chess\Piece\AbstractPiece;
use Chess\Tutor\PiecePhrase;
use Chess\Variant\Classical\Board;

use Chess\Variant\Classical\PGN\AN\Color;
use Chess\Variant\Classical\PGN\AN\Piece;

/**
* Threat evaluation.
*
* Total piece value obtained from the squares under threat of being attacked.
*
* @author Jordi Bassagaña
* @license MIT
*/
class ThreatEval extends AbstractEval implements
ElaborateEvalInterface,
ExplainEvalInterface
Expand All @@ -15,11 +24,16 @@ class ThreatEval extends AbstractEval implements

const NAME = 'Threat';

/**
* Constructor.
*
* @param \Chess\Variant\Classical\Board $board
*/
public function __construct(Board $board)
{
$this->board = $board;

$this->range = [1, 5];
$this->range = [0.8, 5];

$this->subject = [
'White',
Expand All @@ -32,49 +46,55 @@ public function __construct(Board $board)
"has a total threat advantage",
];

foreach ($this->board->getPieces() as $piece) {
$countAttacking = count($piece->attackingPieces());
$countDefending = count($piece->defendingPieces());
$diff = $countAttacking - $countDefending;
if ($diff > 0 && $countDefending > 0) {
$valueDefending = $this->valueDefending($piece->defendingPieces());
$valueAttacking = $this->valueAttacking($piece->attackingPieces());
if (($valueDefending + self::$value[$piece->getId()]) >= $valueAttacking) {
$this->result[$piece->oppColor()] += $diff;
$this->elaborate($piece);
if (!$this->board->isMate()) {
foreach ($this->board->getPieces() as $piece) {
if ($piece->getId() !== Piece::K) {
$clone = unserialize(serialize($this->board));
$clone->setTurn($piece->oppColor());
$threat = [
Color::W => 0,
Color::B => 0,
];
$attackingPiece = current($piece->attackingPieces());
while ($attackingPiece) {
$capturedPiece = $clone->getPieceBySq($piece->getSq());
if ($clone->playLan($clone->getTurn(), $attackingPiece->getSq() . $piece->getSq())) {
$threat[$attackingPiece->getColor()] += self::$value[$capturedPiece->getId()];
if ($defendingPiece = current($piece->defendingPieces())) {
$capturedPiece = $clone->getPieceBySq($piece->getSq());
if ($clone->playLan($clone->getTurn(), $defendingPiece->getSq() . $piece->getSq())) {
$threat[$defendingPiece->getColor()] += self::$value[$capturedPiece->getId()];
}
}
$attackingPiece = current($clone->getPieceBySq($piece->getSq())->attackingPieces());
}
}
$diff = $threat[Color::W] - $threat[Color::B];
if ($piece->oppColor() === Color::W) {
if ($diff > 0) {
$this->result[Color::W] += $diff;
$this->elaborate($piece);
}
} else {
if ($diff < 0) {
$this->result[Color::B] += abs($diff);
$this->elaborate($piece);
}
}
}
}
}

$this->explain($this->result);
}

private function valueDefending(array $pieces)
{
$sum = 0;
foreach ($pieces as $piece) {
$sum += self::$value[$piece->getId()];
}

return $sum;
}

private function valueAttacking(array $pieces)
{
$values = [];
foreach ($pieces as $piece) {
$values[] = self::$value[$piece->getId()];
}
sort($values);
array_pop($values);

return array_sum($values);
}

/**
* Elaborate on the result.
*
* @param \Chess\Piece\AbstractPiece $piece
*/
private function elaborate(AbstractPiece $piece): void
{
$phrase = PiecePhrase::create($piece);

$this->elaboration[] = ucfirst("$phrase is being threatened and may be lost if not defended properly.");
$this->elaboration[] = "The {$piece->getSq()}-square is under threat of being attacked.";
}
}
2 changes: 1 addition & 1 deletion src/Variant/Classical/AbstractPgnParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
*
* The root class in the hierarchy of chess boards defines the getter and the
* setter methods, in addition to implementing the internal methods required to
* convert a PGN move in text format into a data structure.
* convert a PGN move in text format into a data structure.
*
* @author Jordi Bassagaña
* @license MIT
Expand Down
57 changes: 52 additions & 5 deletions tests/unit/Eval/ThreatEvalTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,32 @@ class ThreatEvalTest extends AbstractUnitTestCase
* @test
*/
public function B21()
{
$expectedResult = [
'w' => 0,
'b' => 2.33,
];

$expectedExplanation = [
"Black has a slight threat advantage.",
];

$expectedElaboration = [
"The c4-square is under threat of being attacked.",
];

$board = (new StrToBoard('r1bqkbnr/5ppp/p1npp3/1p6/2B1P3/2N2N2/PP2QPPP/R1B2RK1 w kq b6'))->create();
$threatEval = new ThreatEval($board);

$this->assertSame($expectedResult, $threatEval->getResult());
$this->assertSame($expectedExplanation, $threatEval->getExplanation());
$this->assertSame($expectedElaboration, $threatEval->getElaboration());
}

/**
* @test
*/
public function B21_Bb3()
{
$expectedResult = [
'w' => 0,
Expand All @@ -22,7 +48,7 @@ public function B21()

$expectedElaboration = [];

$board = (new StrToBoard('r1bqkbnr/5ppp/p1npp3/1p6/2B1P3/2N2N2/PP2QPPP/R1B2RK1 w kq b6'))->create();
$board = (new StrToBoard('r1bqkbnr/5ppp/p1npp3/1p6/4P3/1BN2N2/PP2QPPP/R1B2RK1 b kq -'))->create();
$threatEval = new ThreatEval($board);

$this->assertSame($expectedResult, $threatEval->getResult());
Expand All @@ -36,7 +62,7 @@ public function B21()
public function middlegame()
{
$expectedResult = [
'w' => 1,
'w' => 0.8700000000000001,
'b' => 0,
];

Expand All @@ -45,7 +71,7 @@ public function middlegame()
];

$expectedElaboration = [
"The knight on b5 is being threatened and may be lost if not defended properly.",
"The b5-square is under threat of being attacked.",
];

$board = (new StrToBoard('r1bqkbnr/5ppp/p1npp3/1n6/2B1P3/2N2N2/PP2QPPP/R1B2RK1 w kq b6'))->create();
Expand All @@ -63,15 +89,15 @@ public function endgame()
{
$expectedResult = [
'w' => 0,
'b' => 1,
'b' => 1.0,
];

$expectedExplanation = [
"Black has a slight threat advantage.",
];

$expectedElaboration = [
"The pawn on d4 is being threatened and may be lost if not defended properly.",
"The d4-square is under threat of being attacked.",
];

$board = (new StrToBoard('6k1/6p1/2n2b2/8/3P4/5N2/2K5/8 w - -'))->create();
Expand All @@ -81,4 +107,25 @@ public function endgame()
$this->assertSame($expectedExplanation, $threatEval->getExplanation());
$this->assertSame($expectedElaboration, $threatEval->getElaboration());
}

/**
* @test
*/
public function w_N_c2()
{
$expectedResult = [
'w' => 0,
'b' => 3.2,
];

$expectedExplanation = [
"Black has a moderate threat advantage.",
];

$board = (new StrToBoard('2r3k1/8/8/2q5/8/8/2N5/1K6 w - -'))->create();
$threatEval = new ThreatEval($board);

$this->assertSame($expectedResult, $threatEval->getResult());
$this->assertSame($expectedExplanation, $threatEval->getExplanation());
}
}
6 changes: 3 additions & 3 deletions tests/unit/Heuristics/SanHeuristicsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public function get_balance_e4_e6_d4_d5()
[ 0, 0.86, -0.8, 1.0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
[ 0, 0.21, -0.4, 0.13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
[ 0, 1.0, -1.0, 0.88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
[ 0, 0.64, -0.6, 0.63, 0, 0, -1.0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
[ 0, 0.64, -0.6, 0.63, 0, 0, -1.0, -1.0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
];

$this->assertEquals($expected, $balance);
Expand All @@ -113,7 +113,7 @@ public function get_balance_e4_e6_d4_d5_Nd2_Nf6()
[ 0, 0.86, -0.8, 1.0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
[ 0, 0.21, -0.4, 0.13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
[ 0, 1.0, -1.0, 0.88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
[ 0, 0.64, -0.6, 0.63, 0, 0, -1.0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
[ 0, 0.64, -0.6, 0.63, 0, 0, -1.0, -0.31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
[ 0, 0.59, 1.0, 0.25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
[ 0, 0.27, -0.8, 0.25, -1.0, 0, 0, -1.0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
];
Expand Down Expand Up @@ -161,7 +161,7 @@ public function get_balance_scholar_checkmate()
[ 0, 0.56, -0.07, 0.13, 0.25, 0.25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
[ 0, 0.19, -0.36, 0, 0.25, 0.25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
[ 0, 0.36, -0.64, 0.5, 1.0, 0.25, 0, 1.0, 0, 0, 0, 0, 0, 0, 0, 0, 1.0, 0.0, 1.0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
[ 0, -1.0, -1.0, 0.5, 0.5, 0.25, -1.0, 1.0, -1.0, 0, 0, 0, 0, 0, 0, 0, 1.0, 0.0, 1.0, 0, 0, -1.0, 0, 0, 0, 0, 0, 0, 0 ],
[ 0, -1.0, -1.0, 0.5, 0.5, 0.25, -1.0, -1.0, -1.0, 0, 0, 0, 0, 0, 0, 0, 1.0, 0, 1.0, 0, 0, -1.0, 0, 0, 0, 0, 0, 0, 0 ],
[ 1.0, 0.47, -0.86, 0.25, 0.75, 1.0, -0.1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.2, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1.0, 0, 0 ],
];

Expand Down
8 changes: 6 additions & 2 deletions tests/unit/Tutor/PgnEvaluationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ public function A08()
$expected = [
"Black has a slight space advantage.",
"White has a slight protection advantage.",
"White has a slight threat advantage.",
"The pawn on c5 is unprotected.",
"Overall, 2 heuristic evaluation features are favoring White while 2 are favoring Black.",
"The c5-square is under threat of being attacked.",
"Overall, 3 heuristic evaluation features are favoring White while 2 are favoring Black.",
];

$A08 = file_get_contents(self::DATA_FOLDER.'/sample/A08.pgn');
Expand All @@ -39,8 +41,10 @@ public function endgame()
"White has a total space advantage.",
"The white pieces are timidly approaching the other side's king.",
"Black has a decisive protection advantage.",
"Black has a moderate threat advantage.",
"The bishop on e6 is unprotected.",
"Overall, 6 heuristic evaluation features are favoring White while 1 is favoring Black.",
"The e6-square is under threat of being attacked.",
"Overall, 6 heuristic evaluation features are favoring White while 2 are favoring Black.",
];

$board = FenToBoardFactory::create('8/5k2/4n3/8/8/1BK5/1B6/8 w - - 0 1');
Expand Down

0 comments on commit 393740d

Please sign in to comment.