diff --git a/FenParser0x88.php b/FenParser0x88.php index 6cffe95..bf333b5 100644 --- a/FenParser0x88.php +++ b/FenParser0x88.php @@ -75,7 +75,7 @@ class FenParser0x88 $ret = $type . $fromSquare . $separator . $toSquare; if (isset($move['promoteTo'])) { - $ret .= '=' + $move['promoteTo']; + $ret .= '=' . $move['promoteTo']; } return $ret; } @@ -131,6 +131,37 @@ class FenParser0x88 } } + /** + * Returns piece on given square + * @param string $square + * @return array|null + */ + public function getPieceOnSquareBoardCoordinate($square){ + return $this->getPieceOnSquare(Board0x88Config::$mapping[$square]); + } + + /** + * Returns piece on a square. + * Example: + * $fenBishopOnB3CheckingKingOnG7 = '6k1/6pp/8/8/8/1B6/8/6K1 b - - 0 1'; + * $parser = new FenParser0x88($fenBishopOnB3CheckingKingOnG7); + * $bishop = $parser->getPieceOnSquare(Board0x88Config::$mapping['b3']); + * var_dump($bishop). + * + * Returns an array + * { + * "square" : "b3", + * "s" : 33, + * "t" : 5, + * "type" : "bishop", + * "color": "white", + * "sliding" : 4 + * } + * + * sliding is greater than 0 for bishop, rook and queen. + * @param int $square + * @return array|null + */ public function getPieceOnSquare($square) { $piece = $this->cache['board'][$square]; @@ -167,16 +198,69 @@ class FenParser0x88 return false; } + /** + * Return square of white king, example: "g1" + * Example: + * $parser = new FenParser0x88('6k1/6pp/8/8/8/1B6/8/6K1 b - - 0 1'); + * $whiteKing = $parser->getWhiteKingSquare(); // returns g1 + * @return string + */ + public function getWhiteKingSquare(){ + return $this->getKingSquareBoardCoordinates("white"); + } + + /** + * Returns square of black king, example "g8" + * Example: + * $parser = new FenParser0x88('6k1/6pp/8/8/8/1B6/8/6K1 b - - 0 1'); + * $whiteKing = $parser->getBlackKingSquare(); // returns g8 + * @return string + */ + public function getBlackKingSquare(){ + return $this->getKingSquareBoardCoordinates("black"); + } + + public function getKingSquareBoardCoordinates($color){ + $king = $this->getKing($color); + return Board0x88Config::$numberToSquareMapping[$king["s"]]; + + } + + /** + * Returns king square in numeric format. + * Example: + * $fenBishopOnB3CheckingKingOnG7 = '6k1/6pp/8/8/8/1B6/8/6K1 b - - 0 1'; + * $parser = new FenParser0x88($fenBishopOnB3CheckingKingOnG7); + * $king = $parser->getKing("black"); + * + * returns array("t" : 11, "s" : 128). + * + * where "t" is type, and "s" is square. Square can be converted to board coordinates using + * Board0x88Config::$numberToSquareMapping[$array["s"]] + * @param string $color + * @return array + */ function getKing($color) { return $this->cache['king' . $color]; } + /** + * Returns pieces in given color, + * example: + * + * @param $color + * @return array + */ function getPiecesOfAColor($color) { return $this->cache[$color]; } + /** + * Returns en passant square or null + * @return string|null + */ function getEnPassantSquare() { return ($this->fenParts['enPassant'] != '-') ? $this->fenParts['enPassant'] : null; @@ -189,25 +273,71 @@ class FenParser0x88 function getSlidingPieces($color) { - return $this->cache[$color + 'Sliding']; + return $this->cache[$color . 'Sliding']; } + /** + * Returns count half moves made(1. e4 e5 2 Nf3 Nf6 counts as 4 half moves) + * @return int + */ function getHalfMoves() { return $this->fenParts['halfMoves']; } + /** + * Returns count full moves made(1. e4 e5 2 Nf3 Nf6 counts as 2 full moves); + * @return int + */ function getFullMoves() { return $this->fenParts['fullMoves']; } - function canCastleKingSide($color) - { - $code = $color === 'white' ? Board0x88Config::$castle['K'] : Board0x88Config::$castle['k']; - return $this->fenParts['castleCode'] & $code; + /** + * Returns true if white can castle king side + * Example: + * $fen = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'; + * $parser = new FenParser0x88($fen); + * $whiteCanCastle = $parser->canWhiteCastleKingSide(); + * @return bool + */ + public function canWhiteCastleKingSide(){ + return $this->canCastleKingSide("white"); } + /** + * Returns true if black can castle king side + * Example: + * $fen = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'; + * $parser = new FenParser0x88($fen); + * $whiteCanCastle = $parser->canBlackCastleKingSide(); + * @return bool + */ + public function canBlackCastleKingSide(){ + return $this->canCastleKingSide("black"); + } + + /** + * Return boolean true if king side castling is possible + * Example: + * $fen = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'; + * $parser = new FenParser0x88($fen); + * $whiteCanCastle = $parser->canCastleKingSide("white"); + * $blackCanCastle = $parser->canCastleKingSide("black"); + * @param string $color + * @return bool + */ + public function canCastleKingSide($color) + { + $code = $color === 'white' ? Board0x88Config::$castle['K'] : Board0x88Config::$castle['k']; + return ($this->fenParts['castleCode'] & $code) ? true : false; + } + + /** + * Return color to move, "white" or "black" + * @return string + */ function getColor() { return Board0x88Config::$colorAbbreviations[$this->fenParts['color']]; @@ -223,10 +353,31 @@ class FenParser0x88 return $this->fenParts['color']; } + /** + * Returns true if white can castle queen side(from current fen) + * @return bool + */ + public function canWhiteCastleQueenSide(){ + return $this->canCastleQueenSide("white"); + } + + /** + * Returns true if black can castle queen side (from current fen) + * @return bool + */ + public function canBlackCastleQueenSide(){ + return $this->canCastleQueenSide("black"); + } + + /** + * Returns true if queen side castle for given color is possible(based on fen only, i.e. no checks or obstructions is checked). + * @param string $color + * @return bool + */ function canCastleQueenSide($color) { $code = $color === 'white' ? Board0x88Config::$castle['Q'] : Board0x88Config::$castle['q']; - return $this->fenParts['castleCode'] & $code; + return ($this->fenParts['castleCode'] & $code) ? true : false; } function isOnSameRank($square1, $square2) @@ -239,6 +390,55 @@ class FenParser0x88 return ($square1 & 15) === ($square2 & 15); } + /** + * Returns array of valid moves for given color in real board coordinates. + * @param string|null $color + * @return array + * + * Example: + * $parser = new FenParser0x88('6k1/6p1/4n3/8/8/8/B7/6K1 b - - 0 1'); + * $validBlackMoves = $parser->getValidMovesBoardCoordinates("black"); + * + * returns {"g8":["f7","h7","f8","h8"],"g7":["g6","g5"],"e6":[]} + * + * where the array key(example "g8") is from square and ["f7","h7","f8","h8"] are valid square for + * the piece on "g8" + * + */ + public function getValidMovesBoardCoordinates($color = null){ + $movesAndResult = $this->getValidMovesAndResult($color); + $moves = $movesAndResult["moves"]; + + $ret = array(); + foreach($moves as $from => $toSquares){ + $fromSquare = Board0x88Config::$numberToSquareMapping[$from]; + + $squares = array(); + foreach($toSquares as $square){ + $squares[] = Board0x88Config::$numberToSquareMapping[$square]; + } + + $ret[$fromSquare] = $squares; + + } + + return $ret; + } + + /** + * Returns result(0 = undecided, 0.5 = draw, 1 = white wins, -1 = black wins) + * @return int + */ + public function getResult(){ + $movesAndResult = $this->getValidMovesAndResult(); + return $movesAndResult["result"]; + } + + /** + * Returns valid moves in 0x88 numeric format and result + * @param null $color + * @return array|null + */ function getValidMovesAndResult($color = null) { if (!$color) { @@ -560,6 +760,49 @@ class FenParser0x88 return $ret; } + /** + * Returns array of pinned pieces in standard chess coordinate system. + * Example: + * $parser = new FenParser0x88('5k2/8/8/8/8/8/r5PK/8 w - - 0 1'); // pawn on g2 pinned by rook on a2 + * $pinned = $parser->getPinnedBoardCoordinates('white'); // find pinned white pieces + * var_dump($pinned); + * + * returns + * array(1) { + * [0]=> + * array(3) { + * ["square"]=> + * string(2) "g2" + * ["pinnedBy"]=> + * string(2) "a2" + * ["direction"]=> + * int(1) + * } + * } + * + * @param string $color + * @return array + */ + public function getPinnedBoardCoordinates($color){ + $pinned = $this->getPinned($color); + + $ret = array(); + foreach($pinned as $square=>$by){ + $ret[] = array( + "square" => Board0x88Config::$numberToSquareMapping[$square], + "pinnedBy" => Board0x88Config::$numberToSquareMapping[$by["by"]], + "direction" => $by["direction"] + ); + } + + return $ret; + + } + /** + * Return numeric squares(0x88) of pinned pieces + * @param $color + * @return array|null + */ function getPinned($color) { $ret = array(); @@ -603,9 +846,29 @@ class FenParser0x88 return $this->cache['board']; } + + /** + * returns getValidSquaresOnCheck($color) in chess coordinates system(example: array("g2","g3","g4","g5","g6",g7","g8") + * $color is either "white" or "black" + * @param string $color + * @return array + */ + public function getValidSquaresOnCheckBoardCoordinates($color){ + $squares = $this->getValidSquaresOnCheck($color); + + $ret = array(); + foreach($squares as $square){ + $ret[] = Board0x88Config::$numberToSquareMapping[$square]; + } + return $ret; + } + /** * Return valid squares for other pieces than king to move to when in check, i.e. squares - * which avoids the check + * which avoids the check. + * Example: if white king on g1 is checked by rook on g8, then valid squares for other pieces + * are the squares g2,g3,g4,g5,g6,g7,g8. + * Squares are returned in numeric format * @method getValidSquaresOnCheck * @param $color * @return array|null @@ -724,6 +987,11 @@ class FenParser0x88 return null; } + /** + * @param $kingColor + * @param $moves + * @return int + */ function getCountChecks($kingColor, $moves) { $king = $this->cache['king' . $kingColor]; @@ -1072,6 +1340,10 @@ class FenParser0x88 return Board0x88Config::$files[$notation]; } + /** + * @param $notation + * @return int + */ function getToSquareByNotation($notation) { $notation = preg_replace("/.*([a-h][1-8]).*/s", '$1', $notation); diff --git a/README.md b/README.md index 3fc046b..fdd8673 100644 --- a/README.md +++ b/README.md @@ -298,4 +298,4 @@ $parser->newGame(); $parser->move("g1f3"); $notation = $parser->getNotation(); // returns Nf3 $fen = $parser->getFen(); -// returns fen: rnbqkbnr/pppppppp/8/8/8/5N2/PPPPPPPP/RNBQKB1R b KQkq - 1 1 +// $fen = rnbqkbnr/pppppppp/8/8/8/5N2/PPPPPPPP/RNBQKB1R b KQkq - 1 1 diff --git a/test/ParserTest.php b/test/ParserTest.php index ccd2abd..7036bdf 100644 --- a/test/ParserTest.php +++ b/test/ParserTest.php @@ -93,7 +93,7 @@ Qe7 28. e4 Nh7 29. h5 Nf8 30. Qb8 g5 31. Qc8 Ne6 32. Bxe6 Qxe6 33. Qxe6 fxe6 $pgnParser = new PgnParser(); $pgnParser->setPgnContent($fen); - $game = $pgnParser->getFirstGame(); + $pgnParser->getFirstGame(); $parser = new FenParser0x88(); $parser->setFen('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'); @@ -180,12 +180,7 @@ Rc8 Ne6+ 72. Kf6 d2 73. c5+ Kd7 0-1'; 1. a3+'; $pgnParser = new PgnParser(); $pgnParser->setPgnContent($game); - $game = $pgnParser->getFirstGame(); - - var_dump($game); - - - + $pgnParser->getFirstGame(); } @@ -278,7 +273,7 @@ Rc8 Ne6+ 72. Kf6 d2 73. c5+ Kd7 0-1'; // given $fen = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'; // when - $parser = $this->getParser($fen); + $parser = new FenParser0x88($fen); // then @@ -395,17 +390,17 @@ Rc8 Ne6+ 72. Kf6 d2 73. c5+ Kd7 0-1'; private function assertHasSquares($expectedSquares, $moves) { - $originalMoves = $moves; + if(is_array($moves))$moves = implode(",",$moves); - if (strstr($moves, ',')) { - $newMoves = explode(",", $moves); - $moves = array(); - foreach($newMoves as $move){ - if(isset($move) && strlen($move)){ - $moves[] = $move; - } + + $newMoves = explode(",", $moves); + $moves = array(); + foreach($newMoves as $move){ + if(isset($move) && strlen($move)){ + $moves[] = $move; } } + for ($i = 0; $i < count($expectedSquares); $i++) { $this->assertTrue($this->isSquareInPaths($expectedSquares[$i], $moves), $expectedSquares[$i] . ' is not in path(' . $this->getReadableSquares($moves)."), expected squares: ". implode(",", $expectedSquares)); } @@ -868,6 +863,9 @@ Rc8 Ne6+ 72. Kf6 d2 73. c5+ Kd7 0-1'; $this->assertEquals(2, $parser->getCountChecks('black', $moves)); } + /** + * @test + */ public function OnlyKingShouldbeAbleToMoveOnDoubleCheck() { @@ -876,12 +874,14 @@ Rc8 Ne6+ 72. Kf6 d2 73. c5+ Kd7 0-1'; $parser = $this->getParser($fen); // when $validMoves = $parser->getValidMovesAndResult('black'); + $pLegal = $validMoves['moves']; - $pawnMoves = $this->getValidMovesForSquare($pLegal, 'g8'); + $kingMoves = $this->getValidMovesForSquare($pLegal, 'g8'); + $expectedSquares = array('h7'); // then - $this->assertHasSquares($expectedSquares, $pawnMoves); + $this->assertHasSquares($expectedSquares, $kingMoves); } @@ -985,7 +985,31 @@ Rc8 Ne6+ 72. Kf6 d2 73. c5+ Kd7 0-1'; } - public function KnightShouldNotbeableToMoveWhenPinned() + /** + * @test + */ + public function shouldGetValidMovesInBoardCoordinates(){ + // given + $parser = new FenParser0x88('6k1/6p1/4n3/8/8/8/B7/6K1 b - - 0 1'); + + // when + $validBlackMoves = $parser->getValidMovesBoardCoordinates("black"); + $validKingMoves = $validBlackMoves["g8"]; + // then + + $expectedSquares = array("f7","h7","f8","h8"); + + $this->assertEquals(count($expectedSquares), count($validKingMoves)); + foreach($validKingMoves as $move){ + $this->assertTrue(in_array($move, $expectedSquares)); + } + + } + + /** + * @test + */ + public function knightShouldNotbeableToMoveWhenPinned() { // given $fen = '6k1/6p1/4n3/8/8/8/B7/6K1 b - - 0 1'; @@ -993,6 +1017,7 @@ Rc8 Ne6+ 72. Kf6 d2 73. c5+ Kd7 0-1'; // when $validMoves = $parser->getValidMovesAndResult('black'); + var_dump($validMoves); $pLegal = $validMoves['moves']; $knightMoves = $this->getValidMovesForSquare($pLegal, 'e6'); $expectedSquares = array(); @@ -1001,19 +1026,23 @@ Rc8 Ne6+ 72. Kf6 d2 73. c5+ Kd7 0-1'; } + /** + * @test + */ public function PawnShouldNotbeAbleToMoveWhenPinnedByRook() { // given $fenPawnOnG2KingOnH2BlackRookOnA2 = '5k2/8/8/8/8/8/r5PK/8 w - - 0 1'; $parser = $this->getParser($fenPawnOnG2KingOnH2BlackRookOnA2); $pinned = $parser->getPinned('white'); - + $pinned2 = $parser->getPinnedBoardCoordinates('white'); // then $this->assertSquareIsPinnedBy('g2', 'a2', $pinned); // when $validMoves = $parser->getValidMovesAndResult('white'); $pLegal = $validMoves['moves']; $pawnMoves = $this->getValidMovesForSquare($pLegal, 'g2'); + var_dump($pawnMoves); $expectedSquares = array(); // then $this->assertHasSquares($expectedSquares, $pawnMoves); @@ -1051,6 +1080,9 @@ Rc8 Ne6+ 72. Kf6 d2 73. c5+ Kd7 0-1'; $this->assertHasSquares($expectedSquares, $pawnMoves); } + /** + * @test + */ public function PinnedBishopSlidingPiecesShouldOnlybeAbleToBetweenPinningAndKing() { // given @@ -1066,6 +1098,9 @@ Rc8 Ne6+ 72. Kf6 d2 73. c5+ Kd7 0-1'; } + /** + * @test + */ public function PinnedRookSlidingPiecesShouldOnlybeAbleToBetweenPinningAndKing() { // given @@ -1103,10 +1138,10 @@ Rc8 Ne6+ 72. Kf6 d2 73. c5+ Kd7 0-1'; { // given $fenPawnOng2CheckingKingOng1 = '5k2/8/8/8/8/8/5p2/6K1 w - - 0 1'; - $parser = $this->getParser($fenPawnOng2CheckingKingOng1); + $parser = new FenParser0x88($fenPawnOng2CheckingKingOng1); // when $checks = $parser->getValidSquaresOnCheck('white'); - + var_dump($checks); // then $this->assertEquals(1, count($checks)); $this->assertEquals($this->getNumericSquare('f2'), $checks[0]); @@ -1129,6 +1164,26 @@ Rc8 Ne6+ 72. Kf6 d2 73. c5+ Kd7 0-1'; } + /** + * @test + */ + public function shouldFindKingSquare(){ + // King on g1(white) and g8(black) + $parser = new FenParser0x88('6k1/6pp/8/8/8/1B6/8/6K1 b - - 0 1'); + + // when + $blackKing = $parser->getBlackKingSquare(); + // then + $this->assertEquals("g8", $blackKing); + + + // when + $whiteKing = $parser->getWhiteKingSquare(); + // then + $this->assertEquals("g1", $whiteKing); + + } + /** * @test */ @@ -1136,12 +1191,15 @@ Rc8 Ne6+ 72. Kf6 d2 73. c5+ Kd7 0-1'; { // given $fenBishopOnB3CheckingKingOnG7 = '6k1/6pp/8/8/8/1B6/8/6K1 b - - 0 1'; - $parser = $this->getParser($fenBishopOnB3CheckingKingOnG7); + $parser = new FenParser0x88($fenBishopOnB3CheckingKingOnG7); $blackKing = $parser->getKing('black'); + var_dump($blackKing); $this->assertEquals(Board0x88Config::$mapping['g8'], $blackKing['s']); $sq = Board0x88Config::$mapping['b3']; $bishop = $parser->getPieceOnSquare($sq); + + $bishopCheckPaths = $parser->getBishopCheckPath($bishop, $blackKing);