Skip to content

Commit

Permalink
Issue/696 convert an entire game into a chess signal (#701)
Browse files Browse the repository at this point in the history
* Implemented Chess\SanSignal

* Implemented Chess\SanTrait

* Refactored Chess\SanHeuristics

* Refactored Chess\SanSignal

* Refactored Chess\SanHeuristics

* Refactored Chess\SanTrait

* Wording

* Refactored Chess\SanSignal

* Wording

* Updated Chess\SanSignal

* Added test

* Updated tests
  • Loading branch information
programarivm authored Dec 20, 2024
1 parent af9daa2 commit 7e250b1
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 118 deletions.
127 changes: 9 additions & 118 deletions src/SanHeuristics.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

namespace Chess;

use Chess\Eval\AbstractEval;
use Chess\Eval\InverseEvalInterface;
use Chess\Function\AbstractFunction;
use Chess\Play\SanPlay;
use Chess\Variant\AbstractBoard;
Expand All @@ -17,33 +15,7 @@
*/
class SanHeuristics extends SanPlay
{
/**
* Function.
*
* @var \Chess\Function\AbstractFunction
*/
public AbstractFunction $function;

/**
* The name of the evaluation feature.
*
* @var string
*/
public string $name;

/**
* Continuous oscillations.
*
* @var array
*/
public array $result = [];

/**
* The balanced normalized result.
*
* @var array
*/
public array $balance = [];
use SanTrait;

/**
* @param \Chess\Function\AbstractFunction $function
Expand All @@ -59,109 +31,28 @@ public function __construct(
) {
parent::__construct($movetext, $board);

$this->function = $function;
$this->name = $name;

$this->calc()->balance()->normalize(-1, 1);
}

/**
* Calculates the result.
*
* @return \Chess\SanHeuristics
*/
protected function calc(): SanHeuristics
{
$this->result[] = $this->item(EvalFactory::create(
$this->function,
$this->name,
$function,
$name,
$this->board
));

foreach ($this->sanMovetext->moves as $move) {
if ($move !== Move::ELLIPSIS) {
if ($this->board->play($this->board->turn, $move)) {
foreach ($this->sanMovetext->moves as $val) {
if ($val !== Move::ELLIPSIS) {
if ($this->board->play($this->board->turn, $val)) {
$this->result[] = $this->item(EvalFactory::create(
$this->function,
$this->name,
$function,
$name,
$this->board
));
}
}
}

return $this;
}

/**
* Balances the result.
*
* @return \Chess\SanHeuristics
*/
protected function balance(): SanHeuristics
{
foreach ($this->result as $result) {
$this->balance[] = $result[Color::W] - $result[Color::B];
}

return $this;
}

/**
* Normalizes the balance.
*
* @param int $newMin
* @param int $newMax
*/
protected function normalize(int $newMin, int $newMax): void
{
$min = min($this->balance);
$max = max($this->balance);

foreach ($this->balance as $key => $val) {
if ($val > 0) {
$this->balance[$key] = round($this->balance[$key] * $newMax / $max, 2);
} elseif ($val < 0) {
$this->balance[$key] = round($this->balance[$key] * $newMin / $min, 2);
} else {
$this->balance[$key] = 0;
}
}
}

/**
* Calculates an item.
*
* @param \Chess\Eval\AbstractEval $eval
* @return array
*/
protected function item(AbstractEval $eval): array
{
$result = $eval->result;

if (is_array($result[Color::W])) {
if ($eval instanceof InverseEvalInterface) {
$item = [
Color::W => count($result[Color::B]),
Color::B => count($result[Color::W]),
];
} else {
$item = [
Color::W => count($result[Color::W]),
Color::B => count($result[Color::B]),
];
}
} else {
if ($eval instanceof InverseEvalInterface) {
$item = [
Color::W => $result[Color::B],
Color::B => $result[Color::W],
];
} else {
$item = $result;
}
}

return $item;
$this->balance = $this->normalize(-1, 1, $this->balance);
}
}
67 changes: 67 additions & 0 deletions src/SanSignal.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

namespace Chess;

use Chess\Function\AbstractFunction;
use Chess\Play\SanPlay;
use Chess\Variant\AbstractBoard;
use Chess\Variant\Classical\PGN\Move;
use Chess\Variant\Classical\PGN\AN\Color;

/**
* SAN Signal
*
* A signal encoding the continuous oscillations of a chessboard.
*/
class SanSignal extends SanPlay
{
use SanTrait;

/**
* The signal.
*
* @var array
*/
public array $signal = [];

/**
* @param \Chess\Function\AbstractFunction $function
* @param string $movetext
* @param \Chess\Variant\AbstractBoard $board
*/
public function __construct(
AbstractFunction $function,
string $movetext,
AbstractBoard $board
) {
parent::__construct($movetext, $board);

$this->result[] = array_fill(0, count($function->names()), 0);
$component = [];

foreach ($this->sanMovetext->moves as $val) {
if ($val !== Move::ELLIPSIS) {
if ($this->board->play($this->board->turn, $val)) {
foreach ($function->names() as $val) {
$item = $this->item(EvalFactory::create(
$function,
$val,
$this->board
));
$component[] = $item[Color::W] - $item[Color::B];
}
$this->result[] = $component;
$component = [];
}
}
}

for ($i = 0; $i < count($this->result[0]); $i++) {
$this->balance[$i] = $this->normalize(-1, 1, array_column($this->result, $i));
}

for ($i = 0; $i < count($this->result[0]); $i++) {
$this->signal[$i] = round(array_sum(array_column($this->balance, $i)), 2);
}
}
}
86 changes: 86 additions & 0 deletions src/SanTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php

namespace Chess;

use Chess\Eval\AbstractEval;
use Chess\Eval\InverseEvalInterface;
use Chess\Variant\Classical\PGN\AN\Color;

trait SanTrait
{
/**
* Continuous oscillations.
*
* @var array
*/
public array $result = [];

/**
* The balanced normalized result.
*
* @var array
*/
public array $balance = [];

/**
* Calculates an item.
*
* @param \Chess\Eval\AbstractEval $eval
* @return array
*/
protected function item(AbstractEval $eval): array
{
$result = $eval->result;

if (is_array($result[Color::W])) {
if ($eval instanceof InverseEvalInterface) {
$item = [
Color::W => count($result[Color::B]),
Color::B => count($result[Color::W]),
];
} else {
$item = [
Color::W => count($result[Color::W]),
Color::B => count($result[Color::B]),
];
}
} else {
if ($eval instanceof InverseEvalInterface) {
$item = [
Color::W => $result[Color::B],
Color::B => $result[Color::W],
];
} else {
$item = $result;
}
}

return $item;
}

/**
* Normalizes an array of values.
*
* @param int $newMin
* @param int $newMax
* @param array $values
* @return array
*/
protected function normalize(int $newMin, int $newMax, array $values): array
{
$min = min($values);
$max = max($values);

foreach ($values as $key => $val) {
if ($val > 0) {
$values[$key] = round($values[$key] * $newMax / $max, 2);
} elseif ($val < 0) {
$values[$key] = round($values[$key] * $newMin / $min, 2);
} else {
$values[$key] = 0;
}
}

return $values;
}
}
49 changes: 49 additions & 0 deletions tests/unit/SanSignalTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

namespace Chess\Tests\Unit;

use Chess\SanSignal;
use Chess\Function\FastFunction;
use Chess\Play\SanPlay;
use Chess\Tests\AbstractUnitTestCase;
use Chess\Variant\Classical\Board;

class SanSignalTest extends AbstractUnitTestCase
{
static private FastFunction $function;

public static function setUpBeforeClass(): void
{
self::$function = new FastFunction();
}

/**
* @test
*/
public function e4_d5_exd5_Qxd5()
{
$expectedBalance = [ 0, 1.0, 0.25, 0.50, -1.0 ];
$expectedSignal = [ 0.0, 2.0, -1.67, -0.16, -5.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.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ];

$movetext = '1.e4 d5 2.exd5 Qxd5';

$sanSignal = new SanSignal(self::$function, $movetext, new Board());

$this->assertEquals($expectedBalance, $sanSignal->balance[3]);
$this->assertEquals($expectedSignal, $sanSignal->signal);
}

/**
* @test
*/
public function A59()
{
$expectedSignal = [ 0.0, 2.5, 0.77, 1.01, 1.31, 1.4, 1.69, 3.52, 3.03, 2.77, 0.0, 1.85, 0.17, 0.88, -2.13, 3.42, 2.07, 2.24, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ];

$A59 = file_get_contents(self::DATA_FOLDER.'/sample/A59.pgn');

$sanSignal = new SanSignal(self::$function, $A59, new Board());

$this->assertEquals($expectedSignal, $sanSignal->signal);
}
}

0 comments on commit 7e250b1

Please sign in to comment.