From 5396570fd604de1e8d245ad16986b20bdf8f464a Mon Sep 17 00:00:00 2001 From: DHTMLGoodies Date: Thu, 17 Jan 2013 15:02:46 +0100 Subject: [PATCH] Added files --- Board0x88Config.php | 212 +++++++ DGTGameParser.php | 131 ++++ FenParser0x88.php | 1392 +++++++++++++++++++++++++++++++++++++++++++ GameParser.php | 59 ++ MoveBuilder.php | 67 +++ MoveParser.php | 492 +++++++++++++++ PgnGameParser.php | 126 ++++ PgnParser.php | 128 ++++ 8 files changed, 2607 insertions(+) create mode 100644 Board0x88Config.php create mode 100644 DGTGameParser.php create mode 100644 FenParser0x88.php create mode 100644 GameParser.php create mode 100644 MoveBuilder.php create mode 100644 MoveParser.php create mode 100644 PgnGameParser.php create mode 100644 PgnParser.php diff --git a/Board0x88Config.php b/Board0x88Config.php new file mode 100644 index 0000000..adf588f --- /dev/null +++ b/Board0x88Config.php @@ -0,0 +1,212 @@ + 'blackPawns', 'b' => 'blackBishops', 'n' => 'blackKnights', 'r' => 'blackRooks', 'q' => 'blackQueens', 'k' => 'blackKing', + 'P' => 'whitePawns', 'B' => 'whiteBishops', 'N' => 'whiteKnights', 'R' => 'whiteRooks', 'Q' => 'whiteQueens', 'K' => 'whiteKing' + ); + + public static $colorAbbreviations = array('w' => 'white', 'b' => 'black'); + public static $oppositeColors = array('white' => 'black', 'black' => 'white'); + public static $mapping = array( + 'a1' => 0, 'b1' => 1, 'c1' => 2, 'd1' => 3, 'e1' => 4, 'f1' => 5, 'g1' => 6, 'h1' => 7, + 'a2' => 16, 'b2' => 17, 'c2' => 18, 'd2' => 19, 'e2' => 20, 'f2' => 21, 'g2' => 22, 'h2' => 23, + 'a3' => 32, 'b3' => 33, 'c3' => 34, 'd3' => 35, 'e3' => 36, 'f3' => 37, 'g3' => 38, 'h3' => 39, + 'a4' => 48, 'b4' => 49, 'c4' => 50, 'd4' => 51, 'e4' => 52, 'f4' => 53, 'g4' => 54, 'h4' => 55, + 'a5' => 64, 'b5' => 65, 'c5' => 66, 'd5' => 67, 'e5' => 68, 'f5' => 69, 'g5' => 70, 'h5' => 71, + 'a6' => 80, 'b6' => 81, 'c6' => 82, 'd6' => 83, 'e6' => 84, 'f6' => 85, 'g6' => 86, 'h6' => 87, + 'a7' => 96, 'b7' => 97, 'c7' => 98, 'd7' => 99, 'e7' => 100, 'f7' => 101, 'g7' => 102, 'h7' => 103, + 'a8' => 112, 'b8' => 113, 'c8' => 114, 'd8' => 115, 'e8' => 116, 'f8' => 117, 'g8' => 118, 'h8' => 119 + ); + + public static $pieceAbbr = array( + 'Q' => 'queen', + 'R' => 'rook', + 'N' => 'knight', + 'B' => 'bishop' + ); + + public static $numberToSquareMapping = array( + '0' => 'a1', '1' => 'b1', '2' => 'c1', '3' => 'd1', '4' => 'e1', '5' => 'f1', '6' => 'g1', '7' => 'h1', + '16' => 'a2', '17' => 'b2', '18' => 'c2', '19' => 'd2', '20' => 'e2', '21' => 'f2', '22' => 'g2', '23' => 'h2', + '32' => 'a3', '33' => 'b3', '34' => 'c3', '35' => 'd3', '36' => 'e3', '37' => 'f3', '38' => 'g3', '39' => 'h3', + '48' => 'a4', '49' => 'b4', '50' => 'c4', '51' => 'd4', '52' => 'e4', '53' => 'f4', '54' => 'g4', '55' => 'h4', + '64' => 'a5', '65' => 'b5', '66' => 'c5', '67' => 'd5', '68' => 'e5', '69' => 'f5', '70' => 'g5', '71' => 'h5', + '80' => 'a6', '81' => 'b6', '82' => 'c6', '83' => 'd6', '84' => 'e6', '85' => 'f6', '86' => 'g6', '87' => 'h6', + '96' => 'a7', '97' => 'b7', '98' => 'c7', '99' => 'd7', '100' => 'e7', '101' => 'f7', '102' => 'g7', '103' => 'h7', + '112' => 'a8', '113' => 'b8', '114' => 'c8', '115' => 'd8', '116' => 'e8', '117' => 'f8', '118' => 'g8', '119' => 'h8' + ); + + public static $numericMapping = array( + '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, '6' => 6, '7' => 7, + '8' => 16, '9' => 17, '10' => 18, '11' => 19, '12' => 20, '13' => 21, '14' => 22, '15' => 23, + '16' => 32, '17' => 33, '18' => 34, '19' => 35, '20' => 36, '21' => 37, '22' => 38, '23' => 39, + '24' => 48, '25' => 49, '26' => 50, '27' => 51, '28' => 52, '29' => 53, '30' => 54, '31' => 55, + '32' => 64, '33' => 65, '34' => 66, '35' => 67, '36' => 68, '37' => 69, '38' => 70, '39' => 71, + '40' => 80, '41' => 81, '42' => 82, '43' => 83, '44' => 84, '45' => 85, '46' => 86, '47' => 87, + '48' => 96, '49' => 97, '50' => 98, '51' => 99, '52' => 100, '53' => 101, '54' => 102, '55' => 103, + '56' => 112, '57' => 113, '58' => 114, '59' => 115, '60' => 116, '61' => 117, '62' => 118, '63' => 119 + ); + public static $keySquares = array(',0,', ',1,', ',2,', ',3,', ',4,', ',5,', ',6,', ',7,', ',8,', ',9,', ',10,', ',11,', ',12,', ',13,', ',14,', ',15,', + ',16,', ',17,', ',18,', ',19,', ',20,', ',21,', ',22,', ',23,', ',24,', ',25,', ',26,', ',27,', ',28,', ',29,', ',30,', ',31,', ',32,', ',33,', ',34,', ',35,', ',36,', ',37,', ',38,', ',39,', ',40,', ',41,', ',42,', ',43,', ',44,', ',45,', ',46,', ',47,', ',48,', ',49,', ',50,', ',51,', ',52,', ',53,', ',54,', ',55,', ',56,', ',57,', ',58,', ',59,', ',60,', ',61,', ',62,', ',63,', ',64,', ',65,', ',66,', ',67,', ',68,', ',69,', ',70,', ',71,', ',72,', ',73,', ',74,', ',75,', ',76,', ',77,', ',78,', ',79,', ',80,', ',81,', ',82,', ',83,', ',84,', ',85,', ',86,', ',87,', ',88,', ',89,', ',90,', ',91,', ',92,', ',93,', ',94,', ',95,', ',96,', ',97,', ',98,', ',99,', ',100,', ',101,', ',102,', ',103,', ',104,', ',105,', ',106,', ',107,', ',108,', ',109,', ',110,', ',111,', ',112,', ',113,', ',114,', ',115,', ',116,', ',117,', ',118,', ',119,' + ); + public static $pieces = array( + 'P' => 0x01, + 'N' => 0x02, + 'K' => 0x03, + 'B' => 0x05, + 'R' => 0x06, + 'Q' => 0x07, + 'p' => 0x09, + 'n' => 0x0A, + 'k' => 0x0B, + 'b' => 0x0D, + 'r' => 0x0E, + 'q' => 0x0F + ); + public static $pieceMapping = array( + 0x01 => 'P', + 0x02 => 'N', + 0x03 => 'K', + 0x05 => 'B', + 0x06 => 'R', + 0x07 => 'Q', + 0x09 => 'p', + 0x0A => 'n', + 0x0B => 'k', + 0x0D => 'b', + 0x0E => 'r', + 0x0F => 'q' + ); + + public static $typeMapping = array( + 0x01 => 'pawn', + 0x02 => 'knight', + 0x03 => 'king', + 0x05 => 'bishop', + 0x06 => 'rook', + 0x07 => 'queen', + 0x09 => 'pawn', + 0x0A => 'knight', + 0x0B => 'king', + 0x0D => 'bishop', + 0x0E => 'rook', + 0x0F => 'queen' + ); + + public static $notationMapping = array( + 0x01 => '', + 0x02 => 'N', + 0x03 => 'K', + 0x05 => 'B', + 0x06 => 'R', + 0x07 => 'Q', + 0x09 => '', + 0x0A => 'N', + 0x0B => 'K', + 0x0D => 'B', + 0x0E => 'R', + 0x0F => 'Q' + ); + + public static $typeToNumberMapping = array( + 'pawn' => 0x01, + 'knight' => 0x02, + 'king' => 0x03, + 'bishop' => 0x05, + 'rook' => 0x06, + 'queen' => 0x07 + + ); + + public static $colorMapping = array( + 'p' => 'black', 'n' => 'black', 'b' => 'black', 'r' => 'black', 'q' => 'black', 'k' => 'black', + 'P' => 'white', 'N' => 'white', 'B' => 'white', 'R' => 'white', 'Q' => 'white', 'K' => 'white' + ); + + public static $castle = array( + '-' => 0, + 'K' => 8, + 'Q' => 4, + 'k' => 2, + 'q' => 1 + ); + + public static $numbers = array( + '0' => 1, '1' => 1, '2' => 1, '3' => 1, '4' => 1, '5' => 1, '6' => 1, '7' => 1, '8' => 1, '9' => 0 + ); + public static $movePatterns = array( + 0X01 => array(16, 32, 15, 17), + 0X09 => array(-16, -32, -15, -17), + 0x05 => array(-15, -17, 15, 17), + 0x0D => array(-15, -17, 15, 17), + 0x06 => array(-1, 1, -16, 16), + 0x0E => array(-1, 1, -16, 16), + 0x07 => array(-15, -17, 15, 17, -1, 1, -16, 16), + 0x0F => array(-15, -17, 15, 17, -1, 1, -16, 16), + 0X02 => array(-33, -31, -18, -14, 14, 18, 31, 33), + 0x0A => array(-33, -31, -18, -14, 14, 18, 31, 33), + 0X03 => array(-17, -16, -15, -1, 1, 15, 16, 17), + 0X0B => array(-17, -16, -15, -1, 1, 15, 16, 17) + ); + + public static $distances = array('241' => 1, '242' => 2, '243' => 3, '244' => 4, '245' => 5, '246' => 6, '247' => 7, '272' => 1, + '273' => 1, '274' => 2, '275' => 3, '276' => 4, '277' => 5, '278' => 6, '279' => 7, '304' => 2, '305' => 2, + '306' => 2, '307' => 3, '308' => 4, '309' => 5, '310' => 6, '311' => 7, '336' => 3, '337' => 3, '338' => 3, + '339' => 3, '340' => 4, '341' => 5, '342' => 6, '343' => 7, '368' => 4, '369' => 4, '370' => 4, '371' => 4, + '372' => 4, '373' => 5, '374' => 6, '375' => 7, '400' => 5, '401' => 5, '402' => 5, '403' => 5, '404' => 5, + '405' => 5, '406' => 6, '407' => 7, '432' => 6, '433' => 6, '434' => 6, '435' => 6, '436' => 6, '437' => 6, + '438' => 6, '439' => 7, '464' => 7, '465' => 7, '466' => 7, '467' => 7, '468' => 7, '469' => 7, '470' => 7, + '471' => 7, '239' => 1, '271' => 1, '303' => 2, '335' => 3, '367' => 4, '399' => 5, '431' => 6, '463' => 7, + '238' => 2, '270' => 2, '302' => 2, '334' => 3, '366' => 4, '398' => 5, '430' => 6, '462' => 7, '237' => 3, + '269' => 3, '301' => 3, '333' => 3, '365' => 4, '397' => 5, '429' => 6, '461' => 7, '236' => 4, '268' => 4, + '300' => 4, '332' => 4, '364' => 4, '396' => 5, '428' => 6, '460' => 7, '235' => 5, '267' => 5, '299' => 5, + '331' => 5, '363' => 5, '395' => 5, '427' => 6, '459' => 7, '234' => 6, '266' => 6, '298' => 6, '330' => 6, + '362' => 6, '394' => 6, '426' => 6, '458' => 7, '233' => 7, '265' => 7, '297' => 7, '329' => 7, '361' => 7, + '393' => 7, '425' => 7, '457' => 7, '208' => 1, '209' => 1, '210' => 2, '211' => 3, '212' => 4, '213' => 5, + '214' => 6, '215' => 7, '207' => 1, '206' => 2, '205' => 3, '204' => 4, '203' => 5, '202' => 6, '201' => 7, + '176' => 2, '177' => 2, '178' => 2, '179' => 3, '180' => 4, '181' => 5, '182' => 6, '183' => 7, '175' => 2, + '174' => 2, '173' => 3, '172' => 4, '171' => 5, '170' => 6, '169' => 7, '144' => 3, '145' => 3, '146' => 3, + '147' => 3, '148' => 4, '149' => 5, '150' => 6, '151' => 7, '143' => 3, '142' => 3, '141' => 3, '140' => 4, + '139' => 5, '138' => 6, '137' => 7, '112' => 4, '113' => 4, '114' => 4, '115' => 4, '116' => 4, '117' => 5, + '118' => 6, '119' => 7, '111' => 4, '110' => 4, '109' => 4, '108' => 4, '107' => 5, '106' => 6, '105' => 7, + '80' => 5, '81' => 5, '82' => 5, '83' => 5, '84' => 5, '85' => 5, '86' => 6, '87' => 7, '79' => 5, '78' => 5, + '77' => 5, '76' => 5, '75' => 5, '74' => 6, '73' => 7, '48' => 6, '49' => 6, '50' => 6, '51' => 6, '52' => 6, + '53' => 6, '54' => 6, '55' => 7, '47' => 6, '46' => 6, '45' => 6, '44' => 6, '43' => 6, '42' => 6, '41' => 7, + '16' => 7, '17' => 7, '18' => 7, '19' => 7, '20' => 7, '21' => 7, '22' => 7, '23' => 7, '15' => 7, '14' => 7, + '13' => 7, '12' => 7, '11' => 7, '10' => 7, '9' => 7 + ); + + public static $defaultBoard = array(0=> 0,1=> 0,2=> 0,3=> 0,4=> 0,5=> 0,6=> 0,7=> 0,8=> 0,9=> 0,10=> 0,11=> 0,12=> 0,13=> 0,14=> 0,15=> 0,16=> 0,17=> 0,18=> 0,19=> 0,20=> 0,21=> 0,22=> 0,23=> 0,24=> 0,25=> 0,26=> 0,27=> 0,28=> 0,29=> 0,30=> 0,31=> 0,32=> 0,33=> 0,34=> 0,35=> 0,36=> 0,37=> 0,38=> 0,39=> 0,40=> 0,41=> 0,42=> 0,43=> 0,44=> 0,45=> 0,46=> 0,47=> 0,48=> 0,49=> 0,50=> 0,51=> 0,52=> 0,53=> 0,54=> 0,55=> 0,56=> 0,57=> 0,58=> 0,59=> 0,60=> 0,61=> 0,62=> 0,63=> 0,64=> 0,65=> 0,66=> 0,67=> 0,68=> 0,69=> 0,70=> 0,71=> 0,72=> 0,73=> 0,74=> 0,75=> 0,76=> 0,77=> 0,78=> 0,79=> 0,80=> 0,81=> 0,82=> 0,83=> 0,84=> 0,85=> 0,86=> 0,87=> 0,88=> 0,89=> 0,90=> 0,91=> 0,92=> 0,93=> 0,94=> 0,95=> 0,96=> 0,97=> 0,98=> 0,99=> 0,100=> 0,101=> 0,102=> 0,103=> 0,104=> 0,105=> 0,106=> 0,107=> 0,108=> 0,109=> 0,110=> 0,111=> 0,112=> 0,113=> 0,114=> 0,115=> 0,116=> 0,117=> 0,118=> 0,119=> 0); + + public static $fileMapping = array('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'); + public static $rankMapping = array(0 => 1, 16 => 2, 32 => 3, 48 => 4, 64 => 5, 80 => 6, 96 => 7, 112 => 8); + public static $files = array('a' => 0, 'b' => 1, 'c' => 2, 'd' => 3, 'e' => 4, 'f' => 5, 'g' => 6, 'h' => 7); +} \ No newline at end of file diff --git a/DGTGameParser.php b/DGTGameParser.php new file mode 100644 index 0000000..de5b6a8 --- /dev/null +++ b/DGTGameParser.php @@ -0,0 +1,131 @@ +remoteUrl = $this->getCorrectUrl($remoteUrl); + $gameIds = $this->getGameIds(); + + if(!count($gameIds)){ + return array( + 'success' => false, + 'message' => 'Unable to load data from url ' . $this->remoteUrl + ); + } + + $contents = ''; + foreach($gameIds as $gameId){ + $urlPropertyData = $this->remoteUrl . 'game' . $gameId . '.txt'; + if(!$dgtGameData = $this->readRemoteFile($urlPropertyData)){ + return false; + } + $urlPositionData = $this->remoteUrl . 'pos' . $gameId . '.txt'; + if(!$dgtMoveData = $this->readRemoteFile($urlPositionData)){ + return false; + } + $contents .= $this->toPgn($dgtGameData, $dgtMoveData); + } + $ret['finished_round'] = false; + return $contents; + } + private function getCorrectUrl($url){ + $posQueryString = strpos($url, '?'); + if($posQueryString >0){ + $url = substr($url, 0,$posQueryString); + } + return $url."/"; + } + + private function getGameIds(){ + $ret = array(); + $i = 1; + $content = $this->readRemoteFile($this->remoteUrl . 'tocks.txt'); + preg_match_all("/<(.*?)>/s", $content,$matches, PREG_SET_ORDER); + for($i=0,$count = count($matches);$i<$count; $i+=2){ + if($matches[$i][1]!='.'){ + $ret[] = $matches[$i][1]; + }else{ + return $ret; + } + } + return $ret; + } + + private function readRemoteFile($url) { + $contents = RemoteFileReader::getFromUrl($url); + if(preg_match("//si", $contents) || preg_match("//si", $contents)){ + return ''; + } + return $contents; + } + + private function toPgn($gameData, $moveData){ + return ( + $this->getGameProperties($gameData) . + $this->getFenProperty($moveData). + "\n". + $this->getMoves($moveData)). + "\n\n"; + } + + private function getGameProperties($dgtData){ + $ret = ''; + $mappingKeys = array( + 'u' => 'Event', + 'w' => 'White', + 'b' => 'Black', + 'm' => 'LastMoves' + ); + $indexKeys = array( + array('index' => 4, 'property' => 'Result'), + array('index' => 5, 'property' => 'ClockWhite'), + array('index' => 6, 'property' => 'ClockBlack'), + ); + foreach($mappingKeys as $key=>$value){ + $property = preg_replace("/.*?<".$key . ">(.*?)<.*/si", "$1", $dgtData); + if($property){ + $ret.= '['. $value . '" '.$property.'"]' . "\n"; + } + } + + $items = explode("<", $dgtData); + foreach($indexKeys as $indexKey) { + $ret.= '['. $indexKey['property'] . ' "'.$this->removeTags($items[$indexKey['index']]).'"]' . "\n"; + } + return $ret; + } + + private function getFenProperty($moveData){ + $items = explode("<", $moveData); + return '[FEN "'.$this->removeTags($items[2]).'"]' . "\n"; + } + + + private function removeTags($content){ + return preg_replace("/[<>]/", "", $content); + } + + private function getMoves($dgtData){ + $ret = ''; + preg_match_all("/<([a-hO0RQKBN][^\.]{1,4}|[RNBQK][0-8a-h][^\.]{1,4})>/s", $dgtData,$matches); + + $moves = $matches[1]; + for($i=0, $countMoves = count($moves);$i<$countMoves; $i++){ + if($i % 2 == 0){ + $ret.= ceil(($i+1) / 2) .". "; + } + $ret.=$moves[$i]." "; + } + + return trim($ret); + } + +} diff --git a/FenParser0x88.php b/FenParser0x88.php new file mode 100644 index 0000000..1d1fceb --- /dev/null +++ b/FenParser0x88.php @@ -0,0 +1,1392 @@ +setFen($fen); + } + } + + public function newGame($fen = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'){ + $this->validMoves = null; + $this->setFen($fen); + } + + private $fenParts = array(); + + function setFen($fen) + { + $this->cache = array( + 'board' => array(), + 'white' => array(), + 'black' => array(), + 'whiteSliding' => array(), + 'blackSliding' => array(), + 'king' => array('white' => null, 'black' => null) + ); + if($this->fen){ + $this->previousFen = $this->fen; + } + $this->fen = $fen; + $this->updateFenArray(); + $this->parseFen(); + } + + private $longNotation; + + public function getLongNotation(){ + return $this->longNotation; + } + + /** + * Return long notation for a move + * @method getLongNotationForAMove + * @param {Object} move + * @param {String} shortNotation + * @return {String} long notation + */ + public function getLongNotationForAMove($move, $shortNotation) { + if (strstr($shortNotation, 'O-')) { + return $shortNotation; + } + $fromSquare = $move['from']; + $toSquare = $move['to']; + + + $type = $this->cache['board'][Board0x88Config::$mapping[$move['from']]]; + $type = Board0x88Config::$typeMapping[$type]; + $separator = strstr($shortNotation, 'x') >= 0 ? 'x' : '-'; + + $ret = $type . $fromSquare .$separator .$toSquare; + + if (isset($move['promoteTo'])) { + $ret .= '=' + $move['promoteTo']; + } + return $ret; + } + + private function updateFenArray() + { + $fenParts = explode(" ", $this->fen); + $castleCode = 0; + for ($i = 0, $count = strlen($fenParts[2]); $i < $count; $i++) { + $castleCode += Board0x88Config::$castle[substr($fenParts[2], $i, 1)]; + } + + $this->fenParts = array( + 'pieces' => $fenParts[0], + 'color' => $fenParts[1], + 'castle' => $fenParts[2], + 'castleCode' => $castleCode, + 'enPassant' => $fenParts[3], + 'halfMoves' => $fenParts[4], + 'fullMoves' => $fenParts[5] + ); + } + + private function parseFen() + { + $pos = 0; + $this->cache['board'] = Board0x88Config::$defaultBoard; + $squares = Board0x88Config::$fenSquares; + for ($i = 0, $len = strlen($this->fenParts['pieces']); $i < $len; $i++) { + $token = $this->fenParts['pieces'][$i]; + + if (isset(Board0x88Config::$fenPieces[$token])) { + $index = Board0x88Config::$mapping[$squares[$pos]]; + $type = Board0x88Config::$pieces[$token]; + $piece = array( + 't' => $type, + 's' => $index + ); + // Board array + $this->cache['board'][$index] = $type; + + // White and black array + $this->cache[Board0x88Config::$colorMapping[$token]][] = $piece; + + // King array + if (Board0x88Config::$typeMapping[$type] == 'king') { + $this->cache['king' . ($piece['t'] & 0x8 ? 'black' : 'white')] = $piece; + } + $pos++; + } else if ($i < $len - 1 && isset(Board0x88Config::$numbers[$token])) { + $token2 = $this->fenParts['pieces'][$i + 1]; + if (preg_match("/[0-9]/", $token2)) { + $token = $token . $token2; + } + $pos += intval($token); + } + } + + } + + public function getPieceOnSquare($square) { + $piece = $this->cache['board'][$square]; + if (isset($piece)) { + return array( + 'square' => Board0x88Config::$numberToSquareMapping[$square], + 's' => $square, + 't' => $piece, + 'type' => Board0x88Config::$typeMapping[$piece], + 'color' => $piece & 0x8 ? 'black' : 'white', + 'sliding' => $piece & 0x4 + ); + } + return null; + } + + public function isValid($move, $fen){ + $this->setFen($fen); + if(!isset($move['from'])){ + $fromAndTo = $this->getFromAndToByNotation($move[CHESS_JSON::MOVE_NOTATION]); + $move['from'] = $fromAndTo['from']; + $move['to'] = $fromAndTo['to']; + + } + $from = Board0x88Config::$mapping[$move['from']]; + $to = Board0x88Config::$mapping[$move['to']]; + + $obj = $this->getValidMovesAndResult(); + $moves = $obj['moves']; + if(isset($moves[$from]) && in_array($to, $moves[$from])){ + return true; + } + return false; + } + + function getKing($color) + { + return $this->cache['king' . $color]; + } + + function getPiecesOfAColor($color) + { + return $this->cache[$color]; + } + + function getEnPassantSquare() + { + $enPassant = $this->fenParts['enPassant']; + if ($enPassant != '-') { + return $enPassant; + } + return null; + } + + private function setEnPassantSquare($square) + { + $this->fenParts['enPassant'] = $square; + } + + function getSlidingPieces($color) + { + return $this->cache[$color + 'Sliding']; + } + + function getHalfMoves() + { + return $this->fenParts['halfMoves']; + } + + function getFullMoves() + { + return $this->fenParts['fullMoves']; + } + + function canCastleKingSide($color) + { + $code = $color === 'white' ? Board0x88Config::$castle['K'] : Board0x88Config::$castle['k']; + return $this->fenParts['castleCode'] & $code; + } + + function getColor() + { + return Board0x88Config::$colorAbbreviations[$this->fenParts['color']]; + } + + function switchColor() { + $this->fenParts['color'] = $this->fenParts['color'] == 'w' ? 'b' : 'w'; + } + + function getColorCode() + { + return $this->fenParts['color']; + } + + function canCastleQueenSide($color) + { + $code = $color === 'white' ? Board0x88Config::$castle['Q'] : Board0x88Config::$castle['q']; + return $this->fenParts['castleCode'] & $code; + } + + function isOnSameRank($square1, $square2) + { + return ($square1 & 240) === ($square2 & 240); + } + + function isOnSameFile($square1, $square2) + { + return ($square1 & 15) === ($square2 & 15); + } + + function getValidMovesAndResult($color = null) + { + if (!$color) { + $color = $this->getColor(); + } + + $ret = array(); + $enPassantSquare = $this->getEnPassantSquare(); + if ($enPassantSquare) { + $enPassantSquare = Board0x88Config::$mapping[$enPassantSquare]; + } + + $kingSideCastle = $this->canCastleKingSide($color); + $queenSideCastle = $this->canCastleQueenSide($color); + $oppositeColor = $color === 'white' ? 'black' : 'white'; + + $WHITE = $color === 'white' ? true : false; + + $protectiveMoves = $this->getCaptureAndProtectiveMoves($oppositeColor); + + $checks = $this->getCountChecks($color, $protectiveMoves); + $validSquares = null; + $pinned = array(); + if ($checks === 2) { + $pieces = array($this->getKing($color)); + } else { + $pieces = $this->cache[$color]; + $pinned = $this->getPinned($color); + if ($checks === 1) { + $validSquares = $this->getValidSquaresOnCheck($color); + } + } + + $totalCountMoves = 0; + for ($i = 0, $count = count($pieces); $i < $count; $i++) { + $piece = $pieces[$i]; + $paths = array(); + + switch ($piece['t']) { + // pawns + case 0x01: + if (!isset($pinned[$piece['s']]) || ($pinned[$piece['s']] && $this->isOnSameFile($piece['s'], $pinned[$piece['s']]['by']))) { + if (!$this->cache['board'][$piece['s'] + 16]) { + $paths[] = $piece['s'] + 16; + if ($piece['s'] < 32) { + if (!$this->cache['board'][$piece['s'] + 32]) { + $paths[] = $piece['s'] + 32; + } + } + } + } + if (!isset($pinned[$piece['s']]) || ($pinned[$piece['s']] && $pinned[$piece['s']]['by'] === $piece['s'] + 15)) { + if ($enPassantSquare == $piece['s'] + 15 || $this->cache['board'][$piece['s'] + 15] & 0x8) { + $paths[] = $piece['s'] + 15; + } + } + if (!isset($pinned[$piece['s']]) || ($pinned[$piece['s']] && $pinned[$piece['s']]['by'] === $piece['s'] + 17)) { + if ($enPassantSquare == $piece['s'] + 17 || ($this->cache['board'][$piece['s'] + 17]) && $this->cache['board'][$piece['s'] + 17] & 0x8) { + $paths[] = $piece['s'] + 17; + } + } + break; + case 0x09: + if (!isset($pinned[$piece['s']]) || ($pinned[$piece['s']] && $this->isOnSameFile($piece['s'], $pinned[$piece['s']]['by']))) { + if (!$this->cache['board'][$piece['s'] - 16]) { + $paths[] = $piece['s'] - 16; + if ($piece['s'] > 87) { + if (!$this->cache['board'][$piece['s'] - 32]) { + $paths[] = $piece['s'] - 32; + } + } + } + } + if (!isset($pinned[$piece['s']]) || ($pinned[$piece['s']] && $pinned[$piece['s']]['by'] === $piece['s'] - 15)) { + if ($enPassantSquare == $piece['s'] - 15 || ($this->cache['board'][$piece['s'] - 15]) && !($this->cache['board'][$piece['s'] - 15] & 0x8)) { + $paths[] = $piece['s'] - 15; + } + } + if($piece['s'] - 17 >= 0){ + if (!isset($pinned[$piece['s']]) || ($pinned[$piece['s']] && $pinned[$piece['s']]['by'] === $piece['s'] - 17)) { + if ($enPassantSquare == $piece['s'] - 17 || ($this->cache['board'][$piece['s'] - 17]) && !($this->cache['board'][$piece['s'] - 17] & 0x8)) { + $paths[] = $piece['s'] - 17; + } + } + } + break; + // Sliding pieces + case 0x05: + case 0x07: + case 0x06: + case 0x0D: + case 0x0E: + case 0x0F: + $directions = Board0x88Config::$movePatterns[$piece['t']]; + if (isset($pinned[$piece['s']])) { + if (array_search($pinned[$piece['s']]['direction'], $directions) !== FALSE) { + $directions = array($pinned[$piece['s']]['direction'], $pinned[$piece['s']]['direction'] * -1); + } else { + $directions = array(); + } + } + for ($a = 0, $len = count($directions); $a < $len; $a++) { + $square = $piece['s'] + $directions[$a]; + while (($square & 0x88) === 0) { + if ($this->cache['board'][$square]) { + if (($WHITE && $this->cache['board'][$square] & 0x8) || (!$WHITE && !($this->cache['board'][$square] & 0x8))) { + $paths[] = $square; + } + break; + } + $paths[] = $square; + $square += $directions[$a]; + } + } + break; + // Knight + case 0x02: + case 0x0A: + if (isset($pinned[$piece['s']])) { + break; + } + $directions = Board0x88Config::$movePatterns[$piece['t']]; + for ($a = 0, $lenD = count($directions); $a < $lenD; $a++) { + $square = $piece['s'] + $directions[$a]; + + if (($square & 0x88) === 0) { + if ($this->cache['board'][$square]) { + if (($WHITE && $this->cache['board'][$square] & 0x8) || (!$WHITE && !($this->cache['board'][$square] & 0x8))) { + $paths[] = $square; + } + } else { + $paths[] = $square; + } + } + } + break; + // White king + // Black king + case 0X03: + case 0X0B: + $directions = Board0x88Config::$movePatterns[$piece['t']]; + for ($a = 0, $lenD = count($directions); $a < $lenD; $a++) { + $square = $piece['s'] + $directions[$a]; + if (($square & 0x88) === 0) { + if (!strstr($protectiveMoves, Board0x88Config::$keySquares[$square])) { + #if ($protectiveMoves.indexOf(Board0x88Config::$keySquares[$square]) == -1) { + if ($this->cache['board'][$square]) { + if (($WHITE && $this->cache['board'][$square] & 0x8) || (!$WHITE && !($this->cache['board'][$square] & 0x8))) { + $paths[] = $square; + } + } else { + $paths[] = $square; + } + } + } + } + + if ($kingSideCastle + && !strstr($protectiveMoves, Board0x88Config::$keySquares[$piece['s']]) + && ($piece['s'] < 118 && !strstr($protectiveMoves, Board0x88Config::$keySquares[$piece['s'] + 1]) ) + && ($piece['s'] < 117 && !strstr($protectiveMoves, Board0x88Config::$keySquares[$piece['s'] + 2]))) { + $paths[] = $piece['s'] + 2; + + } + + if ($queenSideCastle && $piece['s'] - 2 != -1 && !strstr($protectiveMoves, Board0x88Config::$keySquares[$piece['s']]) && !strstr($protectiveMoves, Board0x88Config::$keySquares[$piece['s'] - 1]) && !strstr($protectiveMoves, Board0x88Config::$keySquares[$piece['s'] - 2])) { + $paths[] = $piece['s'] - 2; + } + break; + } + if ($validSquares && $piece['t'] != 0x03 && $piece['t'] != 0x0B) { + $paths = $this->excludeInvalidSquares($paths, $validSquares); + } + $ret[$piece['s']] = $paths; + $totalCountMoves += count($paths); + } + $result = 0; + if ($checks && !$totalCountMoves) { + $result = $color === 'black' ? 1 : -1; + } + else if (!$checks && !$totalCountMoves) { + $result = .5; + } + $this->validMoves = array('moves' => $ret, 'result' => $result, 'check' => $checks); + return $this->validMoves; + } + + function excludeInvalidSquares($squares, $validSquares) + { + $ret = array(); + for ($i = 0, $len = count($squares); $i < $len; $i++) { + if (in_array($squares[$i], $validSquares)) { + $ret[] = $squares[$i]; + } + } + return $ret; + } + + /* This method returns a commaseparated string of moves since it's faster to work with than arrays*/ + function getCaptureAndProtectiveMoves($color) + { + $ret = array(); + + $pieces = $this->cache[$color]; + + $oppositeKing = $this->getKing($color === 'white' ? 'black' : 'white'); + $oppositeKingSquare = $oppositeKing['s']; + for ($i = 0, $len = count($pieces); $i < $len; $i++) { + $piece = $pieces[$i]; + switch ($piece['t']) { + // pawns + case 0x01: + if ((($piece['s'] + 15) & 0x88) === 0) $ret[] = $piece['s'] + 15; + if ((($piece['s'] + 17) & 0x88) === 0) $ret[] = $piece['s'] + 17; + break; + case 0x09: + if ((($piece['s'] - 15) & 0x88) === 0) $ret[] = $piece['s'] - 15; + if ((($piece['s'] - 17) & 0x88) === 0) $ret[] = $piece['s'] - 17; + break; + // Sliding pieces + case 0x05: + case 0x07: + case 0x06: + case 0x0D: + case 0x0E: + case 0x0F: + $directions = Board0x88Config::$movePatterns[$piece['t']]; + for ($a = 0, $lenA = count($directions); $a < $lenA; $a++) { + $square = $piece['s'] + $directions[$a]; + while (($square & 0x88) === 0) { + if ($this->cache['board'][$square] && $square !== $oppositeKingSquare) { + $ret[] = $square; + break; + } + $ret[] = $square; + $square += $directions[$a]; + } + } + break; + // knight + case 0x02: + case 0x0A: + // White knight + $directions = Board0x88Config::$movePatterns[$piece['t']]; + for ($a = 0, $lenA = count($directions); $a < $lenA; $a++) { + $square = $piece['s'] + $directions[$a]; + + if (($square & 0x88) === 0) { + $ret[] = $square; + } + } + break; + // king + case 0X03: + case 0X0B: + $directions = Board0x88Config::$movePatterns[$piece['t']]; + for ($a = 0, $lenD = count($directions); $a < $lenD; $a++) { + $square = $piece['s'] + $directions[$a]; + if (($square & 0x88) === 0) { + $ret[] = $square; + } + } + break; + } + + } + return ',' . implode(",", $ret) . ','; + } + + function getSlidingPiecesAttackingKing($color) + { + $ret = array(); + $king = $this->cache['king' . ($color === 'white' ? 'black' : 'white')]; + $pieces = $this->cache[$color]; + for ($i = 0, $len = count($pieces); $i < $len; $i++) { + $piece = $pieces[$i]; + if ($piece['t'] & 0x4) { + $numericDistance = $king['s'] - $piece['s']; + $boardDistance = ($king['s'] - $piece['s']) / $this->getDistance($king['s'], $piece['s']); + + switch ($piece['t']) { + // Bishop + case 0x05: + case 0x0D: + if ($numericDistance % 15 === 0 || $numericDistance % 17 === 0) { + $ret[] = (array('s' => $piece['s'], 'p' => $boardDistance)); + } + break; + // Rook + case 0x06: + case 0x0E: + if ($numericDistance % 16 === 0) { + $ret[] = array('s' => $piece['s'], 'p' => $boardDistance); + } + else if (($piece['s'] & 240) == ($king['s'] & 240)) { + $ret[] = array('s' => $piece['s'], 'p' => $numericDistance > 0 ? 1 : -1); + } + break; + // Queen + case 0x07: + case 0x0F: + if ($numericDistance % 15 === 0 || $numericDistance % 17 === 0 || $numericDistance % 16 === 0) { + $ret[] = array('s' => $piece['s'], 'p' => $boardDistance); + } + else if (($piece['s'] & 240) == ($king['s'] & 240)) { + $ret[] = (array('s' => $piece['s'], 'p' => $numericDistance > 0 ? 1 : -1)); + } + break; + } + } + } + return $ret; + } + + function getPinned($color) + { + $ret = array(); + $pieces = $this->getSlidingPiecesAttackingKing(($color === 'white' ? 'black' : 'white')); + $WHITE = $color === 'white' ? true : false; + $king = $this->cache['king' . $color]; + $i = 0; + $countPieces = count($pieces); + while ($i < $countPieces) { + $piece = $pieces[$i]; + $square = $piece['s'] + $piece['p']; + $countOpposite = 0; + + $squares = array($piece['s']); + $pinning = ''; + while ($square !== $king['s'] && $countOpposite < 2) { + $squares[] = $square; + + if ($this->cache['board'][$square]) { + $countOpposite++; + if ((!$WHITE && $this->cache['board'][$square] & 0x8) || ($WHITE && !($this->cache['board'][$square] & 0x8))) { + + $pinning = $square; + } else { + break; + } + } + $square += $piece['p']; + } + if ($countOpposite === 1) { + $ret[$pinning] = array('by' => $piece['s'], 'direction' => $piece['p']); + } + $i++; + } + if (count($ret) === 0) { + return null; + } + return $ret; + } + + public function getBoardCache(){ + return $this->cache['board']; + } + /** + * Return valid squares for other pieces than king to move to when in check, i.e. squares + * which avoids the check + * @method getValidSquaresOnCheck + * @param $color + * @return array|null + */ + function getValidSquaresOnCheck($color) + { + $king = $this->cache['king' . $color]; + $pieces = $this->cache[$color === 'white' ? 'black' : 'white']; + + for ($i = 0, $len = count($pieces); $i < $len; $i++) { + $piece = $pieces[$i]; + + switch ($piece['t']) { + case 0x01: + if ($king['s'] === $piece['s'] + 15 || $king['s'] === $piece['s'] + 17) { + return array($piece['s']); + } + break; + case 0x09: + if ($king['s'] === $piece['s'] - 15 || $king['s'] === $piece['s'] - 17) { + return array($piece['s']); + } + break; + // knight + case 0x02: + case 0x0A: + if ($this->getDistance($piece['s'], $king['s']) === 2) { + $directions = Board0x88Config::$movePatterns[$piece['t']]; + for ($a = 0, $lenD = count($directions); $a < $lenD; $a++) { + $square = $piece['s'] + $directions[$a]; + if ($square === $king['s']) { + return array($piece['s']); + } + } + } + break; + // Bishop + case 0x05: + case 0x0D: + $checks = $this->getBishopCheckPath($piece, $king); + if (isset($checks) && is_array($checks)) { + return $checks; + } + break; + // Rook + case 0x06: + case 0x0E: + $checks = $this->getRookCheckPath($piece, $king); + if (isset($checks) && is_array($checks)) { + return $checks; + } + break; + case 0x07: + case 0x0F: + $checks = $this->getRookCheckPath($piece, $king); + if (isset($checks) && is_array($checks)) { + return $checks; + } + $checks = $this->getBishopCheckPath($piece, $king); + if (isset($checks) && is_array($checks)) { + return $checks; + } + break; + } + + } + + return null; + } + + public function getBishopCheckPath($piece, $king) + { + if (($king['s'] - $piece['s']) % 15 === 0 || ($king['s'] - $piece['s']) % 17 === 0) { + $direction = ($king['s'] - $piece['s']) / $this->getDistance($piece['s'], $king['s']); + $square = $piece['s'] + $direction; + $pieceFound = false; + $squares = array($piece['s']); + while ($square !== $king['s'] && !$pieceFound) { + $squares[] = $square; + if (isset($this->cache['board'][$square]) && $this->cache['board'][$square]) { + $pieceFound = true; + } + $square += $direction; + } + if (!$pieceFound) { + return $squares; + } + } + return null; + } + + function getRookCheckPath($piece, $king) + { + $direction = null; + if ($this->isOnSameFile($piece['s'], $king['s'])) { + $direction = ($king['s'] - $piece['s']) / $this->getDistance($piece['s'], $king['s']); + } else if ($this->isOnSameRank($piece['s'], $king['s'])) { + $direction = $king['s'] > $piece['s'] ? 1 : -1; + } + + if ($direction) { + $square = $piece['s'] + $direction; + $pieceFound = false; + $squares = array($piece['s']); + while ($square !== $king['s'] && !$pieceFound) { + $squares[] = $square; + if ($this->cache['board'][$square]) { + $pieceFound = true; + } + $square += $direction; + } + if (!$pieceFound) { + return $squares; + } + } + return null; + } + + function getCountChecks($kingColor, $moves) + { + $king = $this->cache['king' . $kingColor]; + $index = strpos($moves, Board0x88Config::$keySquares[$king['s']]); + if ($index > 0) { + if (strpos($moves, Board0x88Config::$keySquares[$king['s']], $index + 1) > 0) { + return 2; + } + return 1; + } + return 0; + } + + function getDistance($sq1, $sq2) + { + return Board0x88Config::$distances[$sq2 - $sq1 + ($sq2 | 7) - ($sq1 | 7) + 240]; + } + + function getPiecesInvolvedInMove($move) + { + $ret = array( + array('from' => $move['from'], 'to' => $move['to']) + ); + $move = array( + 'from' => Board0x88Config::$mapping[$move['from']], + 'to' => Board0x88Config::$mapping[$move['to']], + 'promoteTo' => isset($move['promoteTo']) ? $move['promoteTo'] : null + ); + + $color = ($this->cache['board'][$move['from']] & 0x8) ? 'black' : 'white'; + + if ($this->isEnPassantMove($move)) { + if ($color == 'black') { + $square = $move['to'] + 16; + + } else { + $square = $move['to'] - 16; + } + $ret[] = array('capture' => Board0x88Config::$numberToSquareMapping[$square]); + } + + if ($this->isCastleMove($move)) { + if (($move['from'] & 15) < ($move['to'] & 15)) { + $ret[] = (array( + 'from' => 'h' . ($color == 'white' ? 1 : 8), + 'to' => 'f' . ($color == 'white' ? 1 : 8) + )); + } else { + $ret[] = (array( + 'from' => 'a' . ($color == 'white' ? 1 : 8), + 'to' => 'd' . ($color == 'white' ? 1 : 8) + )); + } + } + + if ($move['promoteTo']) { + $ret[] = (array( + 'promoteTo' => $move['promoteTo'], 'square' => Board0x88Config::$numberToSquareMapping[$move['to']] + )); + } + return $ret; + } + + function isEnPassantMove($move) + { + if (($this->cache['board'][$move['from']] === 0x01 || $this->cache['board'][$move['from']] == 0x09)) { + if ( + !$this->cache['board'][$move['to']] && + (($move['from'] - $move['to']) % 17 === 0 || ($move['from'] - $move['to']) % 15 === 0) + ) { + return true; + } + } + return false; + } + + function isCastleMove($move) + { + if (($this->cache['board'][$move['from']] === 0x03 || $this->cache['board'][$move['from']] == 0x0B)) { + if ($this->getDistance($move['from'], $move['to']) === 2) { + return true; + } + } + return false; + } + + function makeMoveByNotation($notation) + { + $this->makeMove($this->getFromAndToByNotation($notation)); + } + + function makeMove($move) + { + $this->updateBoardData($move); + $this->fen = null; + } + + + public function getParsed($move){ + if($move['m'] == '--'){ + $this->fen = null; + $this->switchColor(); + return array( + 'm' => $move['m'] , + 'fen' => $this->getFen() + ); + } + $fromAndTo = $this->getFromAndToByNotation($move['m']); + $this->makeMove($fromAndTo); + $newProperties = array( + 'from' => $fromAndTo['from'], + 'to' => $fromAndTo['to'], + 'fen' => $this->getFen() + ); + return array_merge($move, $newProperties); + } + + + function getFromAndToByNotation($notation) + { + + $ret = array('promoteTo' => $this->getPromoteByNotation($notation)); + $color = $this->getColor(); + $offset = 0; + if ($color === 'black') { + $offset = 112; + } + + $foundPieces = array(); + $fromRank = $this->getFromRankByNotation($notation); + $fromFile = $this->getFromFileByNotation($notation); + + if (strlen($notation) === 2) { + $square = Board0x88Config::$mapping[$notation]; + $ret['to'] = Board0x88Config::$mapping[$notation]; + $direction = $color === 'white' ? -16 : 16; + if ($this->cache['board'][$square + $direction]) { + $foundPieces[] = $square + $direction; + } else { + $foundPieces[] = $square + ($direction * 2); + } + + } else { + $notation = preg_replace("/=[QRBN]/", "", $notation); + $notation = preg_replace("/[\+#!\?]/s", "", $notation); + $notation = preg_replace("/^(.*?)[QRBN]$/s", "$1", $notation); + $pieceType = $this->getPieceTypeByNotation($notation, $color); + + $ret['to'] = $this->getToSquareByNotation($notation); + switch ($pieceType) { + + case 0x01: + case 0x09: + if ($color === 'black') { + $offsets = array(15, 17,16); + if($ret['to'] >= 64){ + $offsets[] = 32; + } + } else { + $offsets = array(-15, -17,-16); + if($ret['to'] < 64){ + $offsets[] = -32; + } + } + + for ($i = 0, $lenO = count($offsets); $i < $lenO; $i++) { + $sq = $ret['to'] + $offsets[$i]; + if ($this->cache['board'][$sq] && $this->cache['board'][$sq] === $pieceType) { + $foundPieces[] = ($sq); + } + } + break; + case 0x03: + case 0x0B: + + if ($notation === 'O-O') { + $foundPieces[] = ($offset + 4); + $ret['to'] = $offset + 6; + } else if ($notation === 'O-O-O') { + $foundPieces[] = ($offset + 4); + $ret['to'] = $offset + 2; + } else { + $k = $this->getKing($color); + $foundPieces[] = $k['s']; + } + break; + case 0x02: + case 0x0A: + + $pattern = Board0x88Config::$movePatterns[$pieceType]; + for ($i = 0, $len = count($pattern); $i < $len; $i++) { + $sq = $ret['to'] + $pattern[$i]; + if (!($sq & 0x88)) { + if ($this->cache['board'][$sq] && $this->cache['board'][$sq] === $pieceType) { + $foundPieces[] = ($sq); + } + } + } + break; + // Sliding pieces + default: + $patterns = Board0x88Config::$movePatterns[$pieceType]; + for ($i = 0, $len = count($patterns); $i < $len; $i++) { + $sq = $ret['to'] + $patterns[$i]; + while (!($sq & 0x88)) { + if ($this->cache['board'][$sq] && $this->cache['board'][$sq] === $pieceType) { + $foundPieces[] = ($sq); + } + if($this->cache['board'][$sq]){ + break; + } + $sq += $patterns[$i]; + } + } + break; + } + } + + if (count($foundPieces) === 1) { + $ret['from'] = $foundPieces[0]; + } else { + + if ($fromRank!==null && $fromRank >= 0) { + for ($i = 0, $len = count($foundPieces); $i < $len; $i++) { + if ($this->isOnSameRank($foundPieces[$i], $fromRank)) { + $ret['from'] = $foundPieces[$i]; + break; + } + } + } + else if ($fromFile !== null && $fromFile >= 0) { + for ($i = 0, $len = count($foundPieces); $i < $len; $i++) { + if ($this->isOnSameFile($foundPieces[$i], $fromFile)) { + $ret['from'] = $foundPieces[$i]; + break; + } + } + } + if(!isset($ret['from'])){ + $config = $this->getValidMovesAndResult(); + $moves = $config['moves']; + foreach($foundPieces as $piece){ + if(in_array($ret['to'], $moves[$piece])){ + $ret['from'] = $piece; + break; + } + } + } + } + + if(!isset($ret['from'])){ + $msg = $this->fen .", ". $notation.", Rank:". $fromRank. ", File:". $fromFile.",". count($foundPieces).", ". $foundPieces[0]; + throw new Exception($msg); + } + $ret['from'] = Board0x88Config::$numberToSquareMapping[$ret['from']]; + $ret['to'] = Board0x88Config::$numberToSquareMapping[$ret['to']]; + + return $ret; + } + + public function hasThreeFoldRepetition($fens = array()){ + if(!count($fens))return false; + $shortenedFens = array(); + foreach($fens as $fen){ + $fen = array_slice(explode(" ", $fen), 0, 3); + $fen = implode(" ", $fen); + $shortenedFens[] = $fen; + + } + $lastFen = $shortenedFens[count($shortenedFens)-1]; + $count = array_count_values($shortenedFens); + return $count[$lastFen] >= 2; + } + + public function getPromoteByNotation($notation){ + if(strstr($notation,'=')){ + $piece = preg_replace("/^.*?=([QRBN]).*$/",'$1', $notation); + return Board0x88Config::$pieceAbbr[$piece]; + } + + if(preg_match("/[a-h][18][NBRQ]/", $notation)){ + $notation = preg_replace("/[^a-h18NBRQ]/s", "", $notation); + return Board0x88Config::$pieceAbbr[substr($notation, strlen($notation)-1, 1)]; + } + return ''; + } + + function getFromRankByNotation($notation) + { + $notation = preg_replace("/^.+([0-9]).+[0-9].*$/s", '$1', $notation); + if (strlen($notation) > 1) { + return null; + } + return ($notation - 1) * 16; + } + + function getFromFileByNotation($notation) + { + $notation = preg_replace("/^.*([a-h]).*[a-h].*$/s", '$1', $notation); + if (strlen($notation) > 1) { + return null; + } + return Board0x88Config::$files[$notation]; + } + + function getToSquareByNotation($notation) + { + $notation = preg_replace("/.*([a-h][1-8]).*/s", '$1', $notation); + if(isset(Board0x88Config::$mapping[$notation])){ + return Board0x88Config::$mapping[$notation]; + } + return ''; + } + + function getPieceTypeByNotation($notation, $color = null) + { + $notation = preg_replace("/\=[QRNB]/s", "", $notation); + if ($notation === 'O-O-O' || $notation === 'O-O') { + $notation = 'K'; + } else { + $notation = preg_replace("/.*?([NRBQK]).*/s", '$1', $notation); + if (!$notation || strlen($notation) > 1) { + $notation = 'P'; + } + } + + $notation = Board0x88Config::$pieces[$notation]; + if ($color === 'black') { + $notation += 8; + } + + return $notation; + + } + + private $piecesInvolved; + private $notation; + private $validMoves = null; + function move($move) + { + $this->fen = null; + $this->validMoves = null; + $this->piecesInvolved = $this->getPiecesInvolvedInMove($move); + $this->notation = $this->getNotationForAMove($move); + $this->updateBoardData($move); + + $config = $this->getValidMovesAndResult(); + + if ($config['result'] === 1 || $config['result'] === -1) { + $this->notation .= '#'; + } else { + if ($config['check'] > 0) { + $this->notation .= '+'; + } + } + + } + + function setNewColor() + { + $this->fenParts['color'] = ($this->fenParts['color'] == 'w') ? 'b' : 'w'; + + } + + function setCastle($castle) + { + if (!$castle) { + $castle = '-'; + } + $this->fenParts['castle'] = $castle; + + + $castleCode = 0; + for ($i = 0, $count = strlen($castle); $i < $count; $i++) { + $castleCode += Board0x88Config::$castle[substr($castle, $i, 1)]; + } + $this->fenParts['castleCode'] = $castleCode; + } + + function getCastle() + { + return $this->fenParts['castle']; + } + + function getCastleCode(){ + return $this->fenParts['castleCode']; + } + + function updateBoardData($move) + { + + $move = array( + 'from' => Board0x88Config::$mapping[$move['from']], + 'to' => Board0x88Config::$mapping[$move['to']], + 'promoteTo' => isset($move['promoteTo']) ? $move['promoteTo'] : '' + ); + $movedPiece = $this->cache['board'][$move['from']]; + $color = ($movedPiece & 0x8) ? 'black' : 'white'; + $enPassant = '-'; + + if ($this->cache['board'][$move['to']]) { + $incrementHalfMoves = false; + } else { + $incrementHalfMoves = true; + } + if (($this->cache['board'][$move['from']] === 0x01 || $this->cache['board'][$move['from']] == 0x09)) { + $incrementHalfMoves = false; + if ($this->isEnPassantMove($move)) { + if ($color == 'black') { + $this->cache['board'][$move['to'] + 16] = null; + } else { + $this->cache['board'][$move['to'] - 16] = null; + } + } + + if (($move['from'] & 15) == ($move['to'] & 15) && $this->getDistance($move['from'], $move['to']) == 2) { + if ($this->cache['board'][$move['to'] - 1] || $this->cache['board'][$move['to'] + 1]) { + if ($color === 'white') { + $enPassant = Board0x88Config::$numberToSquareMapping[$move['from'] + 16]; + } else { + $enPassant = Board0x88Config::$numberToSquareMapping[$move['from'] - 16]; + } + } + } + } + + $this->setEnPassantSquare($enPassant); + + if ($this->isCastleMove(array('from' => $move['from'], 'to' => $move['to']))) { + $castle = $this->getCastle(); + if ($color == 'white') { + $castleNotation = '/[KQ]/s'; + $pieceType = 0x06; + $offset = 0; + } else { + $castleNotation = '/[kq]/s'; + $pieceType = 0x0E; + $offset = 112; + } + + if ($move['from'] < $move['to']) { + $this->cache['board'][7 + $offset] = null; + $this->cache['board'][5 + $offset] = $pieceType; + + } else { + $this->cache['board'][0 + $offset] = null; + $this->cache['board'][3 + $offset] = $pieceType; + } + $castle = preg_replace($castleNotation, '', $castle); + $this->setCastle($castle); + } else { + $this->updateCastleForMove($movedPiece, $move['from']); + } + + if ($color === 'black') { + $this->incrementFullMoves(); + } + if ($incrementHalfMoves) { + $this->incrementHalfMoves(); + } else { + $this->resetHalfMoves(); + } + $this->cache['board'][$move['to']] = $this->cache['board'][$move['from']]; + $this->cache['board'][$move['from']] = null; + if ($move['promoteTo']) { + $this->cache['board'][$move['to']] = Board0x88Config::$typeToNumberMapping[$move['promoteTo']]; + if ($color === 'black') { + $this->cache['board'][$move['to']] += 8; + } + } + $this->setNewColor(); + $this->updatePieces(); + + + } + + function updateCastleForMove($movedPiece, $from) + { + switch ($movedPiece) { + case 0x03: + $this->setCastle(preg_replace("/[KQ]/s", "", $this->getCastle())); + break; + case 0x0B: + $this->setCastle(preg_replace("/[kq]/s", "", $this->getCastle())); + break; + case 0x06: + if ($from === 0) { + $this->setCastle(preg_replace("/[Q]/s", "", $this->getCastle())); + } + if ($from === 7) { + $this->setCastle(preg_replace("/[K]/s", "", $this->getCastle())); + } + break; + case 0x0E: + if ($from === 112) { + $this->setCastle(preg_replace("/[q]/s", "", $this->getCastle())); + } + if ($from === 119) { + $this->setCastle(preg_replace("/[k]/s", "", $this->getCastle())); + } + break; + } + + + } + + function updatePieces() + { + $this->cache['white'] = array(); + $this->cache['black'] = array(); + $piece = null; + for ($i = 0; $i < 120; $i++) { + if ($i & 0x88) { + $i += 8; + } + if ($piece = $this->cache['board'][$i]) { + $color = $piece & 0x8 ? 'black' : 'white'; + $obj = array( + 't' => $piece, + 's' => $i + ); + $this->cache[$color][] = $obj; + + if ($piece == 0x03 || $piece == 0x0B) { + $this->cache['king' . $color] = $obj; + } + } + } + } + + function incrementFullMoves() + { + $this->fenParts['fullMoves']++; + } + + function incrementHalfMoves() + { + $this->fenParts['halfMoves']++; + } + + function resetHalfMoves() + { + $this->fenParts['halfMoves'] = 0; + } + + function getPiecesInvolvedInLastMove() + { + return $this->piecesInvolved; + } + + function getNotation() + { + return $this->notation; + } + + function getFen() + { + if (!$this->fen) { + $this->fen = $this->setNewFen(); + } + return $this->fen; + } + + function getNotationForAMove($move) + { + $move['from'] = Board0x88Config::$mapping[$move['from']]; + $move['to'] = Board0x88Config::$mapping[$move['to']]; + $type = $this->cache['board'][$move['from']]; + + $ret = Board0x88Config::$notationMapping[$this->cache['board'][$move['from']]]; + + switch ($type) { + case 0x01: + case 0x09: + if ($this->isEnPassantMove($move) || $this->cache['board'][$move['to']]) { + $ret += Board0x88Config::$fileMapping[$move['from'] & 15] + 'x'; + } + + $ret += Board0x88Config::$fileMapping[$move['to'] & 15] + '' + Board0x88Config::$rankMapping[$move['to'] & 240]; + + if (isset($move['promoteTo']) && $move['promoteTo']) { + $numType = Board0x88Config::$typeToNumberMapping[$move['promoteTo']]; + $ret .= '=' . Board0x88Config::$notationMapping[$numType]; + } + break; + case 0x02: + case 0x05: + case 0x06: + case 0x07: + case 0x0A: + case 0x0D: + case 0x0E: + case 0x0F: + $config = $this->getValidMovesAndResult(); + $configMoves = $config['moves']; + foreach ($configMoves as $square => $moves) { + if ($square != $move['from'] && $this->cache['board'][$square] === $type) { + if (array_search($move['to'], $moves) >= 0) { + if (($square & 15) != ($move['from'] & 15)) { + $ret += Board0x88Config::$fileMapping[$move['from'] & 15]; + } + else if (($square & 240) != ($move['from'] & 240)) { + $ret += Board0x88Config::$rankMapping[$move['from'] & 240]; + } + } + } + } + + if ($this->cache['board'][$move['to']]) { + $ret .= 'x'; + } + $ret .= Board0x88Config::$fileMapping[$move['to'] & 15] . '' . Board0x88Config::$rankMapping[$move['to'] & 240]; + break; + case 0x03: + case 0x0B: + if ($this->isCastleMove($move)) { + if ($move['to'] > $move['from']) { + $ret = 'O-O'; + } else { + $ret = 'O-O-O'; + } + } else { + if ($this->cache['board'][$move['to']]) { + $ret .= 'x'; + } + $ret .= Board0x88Config::$fileMapping[$move['to'] & 15] + '' + Board0x88Config::$rankMapping[$move['to'] & 240]; + } + break; + + } + return $ret; + + } + + function setNewFen() + { + + $board = $this->cache['board']; + $fen = ''; + $emptyCounter = 0; + + for ($rank = 7; $rank >= 0; $rank--) { + + for ($file = 0; $file < 8; $file++) { + $index = ($rank * 8) + $file; + + if ($board[Board0x88Config::$numericMapping[$index]]) { + if ($emptyCounter) { + $fen .= $emptyCounter; + } + $fen .= Board0x88Config::$pieceMapping[$board[Board0x88Config::$numericMapping[$index]]]; + $emptyCounter = 0; + } else { + $emptyCounter++; + } + } + if ($rank) { + if ($emptyCounter) { + $fen .= $emptyCounter; + } + $fen .= '/'; + $emptyCounter = 0; + } + } + + if ($emptyCounter) { + $fen .= $emptyCounter; + } + + return implode(" ", array($fen, $this->getColorCode(), $this->getCastle(), $this->fenParts['enPassant'], $this->getHalfMoves(), $this->getFullMoves())); + } + +} diff --git a/GameParser.php b/GameParser.php new file mode 100644 index 0000000..662eec7 --- /dev/null +++ b/GameParser.php @@ -0,0 +1,59 @@ +moveParser = new FenParser0x88(); + } + + public function getParsedGame($game){ + $this->game = $game; + $this->fen = $this->getStartFen(); + + $this->moveParser->newGame($this->fen); + + $this->parseMoves($this->game[CHESS_JSON::MOVE_MOVES]); + $this->addParsedProperty(); + return $this->game; + } + + private function addParsedProperty(){ + $this->game[CHESS_JSON::GAME_METADATA][CHESS_JSON::MOVE_PARSED] = 1; + } + + private function parseMoves(&$moves){ + foreach($moves as &$move){ + $this->parseAMove($move); + } + } + + private function parseAMove(&$move){ + if(!isset($move[CHESS_JSON::MOVE_NOTATION]) || (isset($move[CHESS_JSON::FEN]) && isset($move[CHESS_JSON::MOVE_FROM]) && isset($move[CHESS_JSON::MOVE_TO]))){ + return; + } + + if(isset($move[CHESS_JSON::MOVE_VARIATIONS])){ + $fen = $this->moveParser->getFen(); + $this->parseVariations($move[CHESS_JSON::MOVE_VARIATIONS]); + $this->moveParser->setFen($fen); + } + $move = $this->moveParser->getParsed($move); + } + + private function parseVariations(&$variations){ + foreach($variations as &$variation){ + $fen = $this->moveParser->getFen(); + $this->parseMoves($variation); + $this->moveParser->setFen($fen); + } + } + + private function getStartFen(){ + return $this->game[CHESS_JSON::GAME_METADATA][CHESS_JSON::FEN]; + } +} \ No newline at end of file diff --git a/MoveBuilder.php b/MoveBuilder.php new file mode 100644 index 0000000..f4a6ea6 --- /dev/null +++ b/MoveBuilder.php @@ -0,0 +1,67 @@ +moveReferences[0] =& $this->moves; + + } + + public function addMoves($moveString){ + $moves = explode(" ", $moveString); + foreach($moves as $move){ + $this->addMove($move); + } + } + + private function addMove($move){ + if(!$move || $move == '..' || $move == '*' || strstr($move, '1-') || strstr($move, '-1') || strstr($move, '1/2')){ + return; + } + $this->moveReferences[$this->pointer][] = array(CHESS_JSON::MOVE_NOTATION => $move); + $this->currentIndex ++; + } + + public function addCommentBeforeFirstMove($comment){ + $comment = trim($comment); + if(!strlen($comment)){ + return; + } + $this->moveReferences[$this->pointer][] = array(); + $this->addComment($comment); + } + + public function addComment($comment){ + $comment = trim($comment); + if(!strlen($comment)){ + return; + } + $index = count($this->moveReferences[$this->pointer])-1; + $this->moveReferences[$this->pointer][$index][CHESS_JSON::MOVE_COMMENT] = $comment; + } + + public function startVariation(){ + $index = count($this->moveReferences[$this->pointer])-1; + if(!isset($this->moveReferences[$this->pointer][$index][CHESS_JSON::MOVE_VARIATIONS])){ + $this->moveReferences[$this->pointer][$index][CHESS_JSON::MOVE_VARIATIONS] = array(); + } + $countVariations = count($this->moveReferences[$this->pointer][$index][CHESS_JSON::MOVE_VARIATIONS]); + $this->moveReferences[$this->pointer][$index][CHESS_JSON::MOVE_VARIATIONS][$countVariations] = array(); + $this->moveReferences[] =& $this->moveReferences[$this->pointer][$index][CHESS_JSON::MOVE_VARIATIONS][$countVariations]; + $this->pointer++; + } + + public function endVariation(){ + array_pop($this->moveReferences); + $this->pointer--; + } + + public function getMoves(){ + return $this->moves; + } +} diff --git a/MoveParser.php b/MoveParser.php new file mode 100644 index 0000000..33e29c2 --- /dev/null +++ b/MoveParser.php @@ -0,0 +1,492 @@ + 'king', + 'Q' => 'queen', + 'R' => 'rook', + 'B' => 'bishop', + 'N' => 'knight' + ); + + public function __construct(){ + $this->fenParser = new FenParser(); + $this->fenBuilder = new FenBuilder(); + $this->squareParser = new SquareParser(); + } + + public function isCheckMate(){ + $positionParser = new PositionParser($this->getFen()); + return $positionParser->isCheckMate($this->fenParser->getWhoToMove()); + + } + + public function getFen(){ + return $this->fenParser->getFen(); + } + + public function newGame($fen){ + $this->emptyCache(); + $this->setFen($fen); + + } + + public function setFen($fen){ + $this->fenParser->setFen($fen); + $this->fenBuilder->setFen($fen); + } + + public function getParsed($move){ + + $moveProperties = $this->getMoveProperties($move); + + $move[CHESS_JSON::MOVE_FROM] = $moveProperties[CHESS_JSON::MOVE_FROM]; + $move[CHESS_JSON::MOVE_TO] = $moveProperties[CHESS_JSON::MOVE_TO]; + + $this->fenBuilder->move($moveProperties); + $move[CHESS_JSON::FEN] = $this->fenBuilder->getFen(); + + $this->fenParser->setFen($move[CHESS_JSON::FEN]); + return $move; + } + + public function isValid($move){ + if(!is_array($move)){ + $move = array( CHESS_JSON::MOVE_NOTATION => $move); + } + $notation = $this->getNotation($move[CHESS_JSON::MOVE_NOTATION]); + $success = $this->getMovedPiece($notation, self::STRICT_VALIDATION); + + return $success ? true : false; + } + + public function emptyCache(){ + $this->fenParser->emptyCache(); + } + + private function getMoveProperties($move){ + $notation = $this->getNotation($move[CHESS_JSON::MOVE_NOTATION]); + + if(isset($move[CHESS_JSON::MOVE_FROM])){ + $movedPiece = $move[CHESS_JSON::MOVE_FROM]; + }else{ + $movedPiece = $this->getMovedPiece($notation); + } + + if(isset($move[CHESS_JSON::MOVE_TO])){ + $destinationSquare = $move[CHESS_JSON::MOVE_TO]; + }else{ + $destinationSquare = $this->getDestinationSquare($notation); + } + $ret = array( + CHESS_JSON::MOVE_FROM => $movedPiece['square'], + CHESS_JSON::MOVE_TO => $destinationSquare, + CHESS_JSON::MOVE_NOTATION => $move[CHESS_JSON::MOVE_NOTATION], + CHESS_JSON::MOVE_CAPTURE => $this->getCaptureSquare($notation), + CHESS_JSON::MOVE_PROMOTE_TO => $this->getPromoteTo($notation), + CHESS_JSON::MOVE_CASTLE => $this->getCastle($notation) + ); + return $ret; + } + + private function getNotation($notation){ + return preg_replace("/[!\?+\s]/s", "", $notation); + } + + private $castleMoves = array('O-O' => 'O-O', 'O-O-O' => 'O-O-O'); + private function getCastle($notation){ + if(isset($this->castleMoves[$notation])){ + return $this->castleMoves[$notation]; + } + return false; + } + + private function getCaptureSquare($notation){ + if(strstr($notation, 'x')){ + $square = $this->getDestinationSquare($notation); + + $piece = $this->fenParser->getPieceOnSquare($square); + if($piece){ + return $square; + } + if($square === $this->fenParser->getEnPassantSquare()){ + $ret = substr($square, 0, 1); + $rank = substr($square, 1,1); + if($this->fenParser->getWhoToMove() === 'white'){ + $ret.= $rank-1; + }else{ + $ret.= $rank+1; + } + return $ret; + } + } + return ''; + } + + private function getPromoteTo($notation){ + if(preg_match("/=[QRBN]/", $notation)){ + $pos = strpos($notation, '='); + return substr($notation, $pos+1,1); + } + return ''; + } + + private function isCaptureMove($notation){ + return strstr($notation, 'x') ? true : false; + } + + private function getMovedPiece($notation, $strictValidation = false){ + $availablePieces = $this->getAvailablePiecesForMove($notation); + + if($strictValidation){ + $availablePieces = $this->getPiecesAfterStrictValidation($availablePieces, $notation); + } + + if(count($availablePieces) === 1){ + return $availablePieces[0]; + } + + if(count($availablePieces) > 1){ + $availablePieces = $this->getAvailablePiecesAfterStrippingPaths($availablePieces, $notation); + + if(count($availablePieces) === 1){ + return $availablePieces[0]; + } + + $availablePieces = $this->removePinnedPieces($availablePieces); + if(count($availablePieces) === 1){ + return $availablePieces[0]; + } + + if(self::$debugMode){ + $pieceType = $this->getPieceTypeFromMove($notation); + + $fromFile = $this->getFromFile($notation); + $fromRank = $this->getFromRank($notation); + + echo "

To many pieces : $notation - $fromFile | $fromRank

"; + echo $this->fenParser->getFen()."
";; + + echo "From file: ". $fromFile."
"; + echo "From rank: ". $fromRank."
"; + echo "Piece type: ". $this->getPieceTypeFromMove($notation)."
"; + + + outputTime(); + die(); + } + + return false; + } + + if(count($availablePieces) === 0){ + if(self::$debugMode){ + $fromFile = $this->getFromFile($notation); + $fromRank = $this->getFromRank($notation); + echo "

No pieces for move - $notation - ". $this->fenParser->getWhoToMove() . "

"; + echo $this->fenParser->getFen()."
";; + echo "Destination square: ". $this->getDestinationSquare($notation)."
"; + echo "Piece type: ". $this->getPieceTypeFromMove($notation)."
"; + echo "From file: ". $fromFile."
"; + echo "From rank: ". $fromRank."
"; + echo "Capture: ". $this->isCaptureMove($notation)."
"; + + die(); + } + return false; + } + } + + private function getPiecesAfterStrictValidation($availablePieces, $notation){ + $availablePieces = $this->getAvailablePiecesAfterStrippingPaths($availablePieces, $notation); + $availablePieces = $this->removePinnedPieces($availablePieces); + $availablePieces = $this->removeInvalidPawnPieces($availablePieces, $notation); + $availablePieces = $this->removeInvalidKingMoves($availablePieces, $notation); + return $availablePieces; + } + + private function removeInvalidPawnPieces($availablePieces, $notation){ + $toSquare = $this->getDestinationSquare($notation); + $pieceOnSquare = $this->fenParser->getPieceOnSquare($toSquare); + if(!$pieceOnSquare){ + return $availablePieces; + + } + $ret = array(); + $file = $toSquare[0]; + foreach($availablePieces as $piece){ + if($piece['type'] === 'pawn'){ + if($piece['square'][0] !== $file){ + $ret[] = $piece; + } + } + } + return $ret; + } + + private function removeInvalidKingMoves($availablePieces, $notation){ + if(count($availablePieces)==0 || $availablePieces[0]['type']!='king'){ + return $availablePieces; + } + $toSquare = $this->getDestinationSquare($notation); + $piece = $availablePieces[0]; + + + $fenBuilder = new FenBuilder(); + $fenBuilder->setFen($this->getFen()); + $fenBuilder->move(array( + 'from' => $piece['square'], + 'to' => $toSquare, + 'capture' => false, + 'castle' => null, + 'promoteTo' => null, + )); + + $pieces = $fenBuilder->getPiecesOfAColor($fenBuilder->getWhoToMove()); + $king = $fenBuilder->getKing($this->fenParser->getWhoToMove()); + + $moveParser = new MoveParser(); + $moveParser->setFen($fenBuilder->getFen()); + + + $countPieces = 0; + + foreach($pieces as $piece){ + $not = $this->getShortPieceType($piece) . $piece['square'][0] . $piece['square'][1] . 'x' . $king['square']; + + $fromFile = $this->getFromFile($not); + $fromRank = $this->getFromRank($not); + $move = array( + 'm' => $not, + 'from' => $piece['square'], + 'to' => $king['square'] + ); + + $tmpPieces = $moveParser->getAvailablePiecesAfterStrippingPaths(array($piece), $not, $king['square']); + if(count($tmpPieces)){ + return array(); + } + $countPieces+= count($tmpPieces); + $isValid = $moveParser->isValid($move); + + } + + return $availablePieces; + } + + private $pieceTypesShort = array( + 'king' => 'K', + 'queen' => 'Q', + 'rook' => 'R', + 'bishop' => 'B', + 'knight' => 'N', + 'pawn' => '' + ); + private function getShortPieceType($piece){ + return $this->pieceTypesShort[$piece['type']]; + } + + private function removePinnedPieces($pieces){ + + $ret = array(); + foreach($pieces as $piece){ + if(!$this->isPinned($piece)){ + $ret[] = $piece; + } + } + return $ret; + } + + private function isPinned($piece){ + + $topParser = new FenParser("0" . $this->fenParser->getFen()); + $piece = $topParser->getPieceOnSquare($piece['square']); + + $fen = $topParser->getFenAfterRemovingPiece($piece); + + + $parser = new FenParser($fen); + $king = $parser->getKing($piece['color']); + + + $otherColor = $parser->getPassiveColor(); + + $pieces = $parser->getPiecesOfAColor($otherColor); + $piecesToCheck = array(); + foreach($pieces as $piece){ + $squares = $this->squareParser->getAllSquares($piece); + if(in_array($king['square'], $squares)){ + $piecesToCheck[] = $piece; + } + } + + foreach($piecesToCheck as $piece){ + // Pawn on same file is not a threat + if($piece['type'] === 'pawn' && $piece['square'][0] == $king['square'][0]){ + continue; + } + $squares = $this->getSquaresOfStrippedPaths($piece, $parser); + + if(in_array($king['square'], $squares)){ + return true; + } + } + + return false; + } + + private function getAvailablePiecesAfterStrippingPaths($pieces, $notation, $toSquare = null){ + if(!$toSquare){ + $toSquare = $this->getDestinationSquare($notation); + } + $ret = array(); + + $fromFile = $this->getFromFile($notation); + $fromRank = $this->getFromRank($notation); + + foreach($pieces as $piece){ + if($fromFile && $piece['square'][0] !== $fromFile){ + continue; + } + if($fromRank && $piece['square'][1] !== $fromRank){ + continue; + } + + $squares = $this->getSquaresOfStrippedPaths($piece); + if(in_array($toSquare, $squares)){ + $ret[] = $piece; + } + } + return $ret; + } + + private function getAvailablePiecesForMove($notation){ + $ret = array(); + + $pieceType = $this->getPieceTypeFromMove($notation); + + $whoToMove = $this->fenParser->getWhoToMove(); + + + + if($pieceType === 'king'){ + return array($this->fenParser->getKing($whoToMove)); + } + + $toSquare = $this->getDestinationSquare($notation); + + $isCapture = $this->isCaptureMove($notation); + + $pieces = $this->fenParser->getColoredPiecesOfAType($whoToMove, $pieceType); + + + foreach($pieces as $piece){ + if($piece['type'] === 'pawn' && !$isCapture){ + $squares = $this->squareParser->getSquaresInFirstPath($piece); + }else{ + $squares = $this->squareParser->getAllSquares($piece); + } + + if(in_array($toSquare, $squares)){ + $ret[] = $piece; + } + } + return $ret; + } + + private function getSquaresOfStrippedPaths($piece, $fenParser = null){ + $ret = array(); + $paths = $this->squareParser->getAllPaths($piece); + + if(!$fenParser){ + $fenParser = $this->fenParser; + } + foreach($paths as $path){ + for($i=0, $count = count($path); $i<$count; $i++){ + $pieceOnSquare = $fenParser->getPieceOnSquare($path[$i]); + $ret[] = $path[$i]; + if($pieceOnSquare){ + break; + } + } + } + return $ret; + } + + private static $fromFileCache = array(); + + private function getFromFile($notation){ + if(!isset(self::$fromFileCache[$notation])){ + if(preg_match("/.*?[a-h].*[a-h]/s", $notation)){ + self::$fromFileCache[$notation] = preg_replace("/^.*?([a-h]).+?$/s", '$1', $notation); + }else{ + self::$fromFileCache[$notation] = ''; + } + } + return self::$fromFileCache[$notation]; + } + + private static $fromRankCache = array(); + private function getFromRank($notation){ + if(!isset(self::$fromRankCache[$notation])){ + if(preg_match("/.*?[1-8].*[1-8]/s", $notation)){ + self::$fromRankCache[$notation] = preg_replace("/^.*?([1-8]).*?$/s", '$1', $notation); + }else{ + self::$fromRankCache[$notation] = ''; + } + } + return self::$fromRankCache[$notation]; + } + + private function getStartFen(){ + return $this->game['metadata'][CHESS_JSON::FEN]; + } + + private static $pieceTypeCache = array(); + private function getPieceTypeFromMove($move){ + if(!isset(self::$pieceTypeCache[$move])){ + if(strstr($move, 'O-O')){ + self::$pieceTypeCache[$move] = 'king'; + }else{ + $firstChar = substr($move, 0, 1); + if(isset($this->pieceTypes[$firstChar])){ + self::$pieceTypeCache[$move] = $this->pieceTypes[$firstChar]; + }else{ + self::$pieceTypeCache[$move] = 'pawn'; + } + } + } + return self::$pieceTypeCache[$move]; + } + + private static $destinationSquareCache = array(); + + private function getDestinationSquare($move){ + $key = $move."_". $this->fenParser->getWhoToMove(); + if(!isset(self::$destinationSquareCache[$key])){ + if(strlen($move) === 2){ + self::$destinationSquareCache[$key] = $move; + } + else if($move === 'O-O'){ + self::$destinationSquareCache[$key] = 'g' . ($this->fenParser->getWhoToMove() === 'white' ? '1' : '8'); + } + else if($move === 'O-O-O'){ + self::$destinationSquareCache[$key] = 'c' . ($this->fenParser->getWhoToMove() === 'white' ? '1' : '8'); + }else{ + self::$destinationSquareCache[$key] = preg_replace("/^.*?([a-h][1-8]).*?$/s", "$1", $move); + } + } + + return self::$destinationSquareCache[$key]; + } + +} \ No newline at end of file diff --git a/PgnGameParser.php b/PgnGameParser.php new file mode 100644 index 0000000..8436ec0 --- /dev/null +++ b/PgnGameParser.php @@ -0,0 +1,126 @@ +pgnGame = trim($pgnGame); + #echo $this->pgnGame."
"; + $this->moveBuilder = new MoveBuilder(); + } + + public function getParsedData(){ + $this->gameData = array(); + $this->gameData[CHESS_JSON::GAME_METADATA] = $this->getMetadata(); + $this->gameData[CHESS_JSON::MOVE_MOVES] = $this->getMoves(); + return $this->gameData; + } + + private function getMetadata(){ + $ret = array(); + $lines = explode("\n", $this->pgnGame); + foreach($lines as $line){ + $line = trim($line); + if(substr($line, 0, 1) === '[' && substr($line, strlen($line)-1, 1) === ']'){ + $metadata = $this->getMetadataKeyAndValue($line); + $ret[$metadata['key']] = $metadata['value']; + } + } + if(!isset($ret[CHESS_JSON::FEN])) { + $ret[CHESS_JSON::FEN] = $this->defaultFen; + } + + return $ret; + } + + private function getMetadataKeyAndValue($metadataString){ + $metadataString = preg_replace("/[\[\]]/s", "", $metadataString); + $metadataString = str_replace('"', '', $metadataString); + $tokens = explode(" ", $metadataString); + + $key = $tokens[0]; + $value = implode(" ", array_slice($tokens, 1)); + $ret = array('key' => $this->getValidKey($key), 'value' => $value ); + return $ret; + } + + private function getValidKey($key){ + $key = strtolower($key); + return $key; + } + + private function getMoves(){ + $parts = $this->getMovesAndComments(); + for($i=0, $count = count($parts); $i<$count; $i++){ + $move = trim($parts[$i]); + + switch($move){ + case '{': + if($i==0){ + $this->moveBuilder->addCommentBeforeFirstMove($parts[$i+1]); + }else{ + + $this->moveBuilder->addComment($parts[$i+1]); + } + $i+=2; + break; + default: + $moves = $this->getMovesAndVariationFromString($move); + foreach($moves as $move){ + switch($move){ + case '(': + $this->moveBuilder->startVariation(); + break; + case ')': + $this->moveBuilder->endVariation(); + break; + default: + $this->moveBuilder->addMoves($move); + } + } + } + } + + + return $this->moveBuilder->getMoves(); + } + + private function addGameComment($comment){ + $this->gameData[CHESS_JSON::GAME_METADATA][CHESS_JSON::MOVE_COMMENT] = $comment; + } + + private function getMovesAndComments(){ + $ret = preg_split("/({|})/s", $this->getMoveString(), 0, PREG_SPLIT_DELIM_CAPTURE); + if(!$ret[0]){ + $ret = array_slice($ret, 1); + } + return $ret; + } + + private function getMovesAndVariationFromString($string){ + $string = " ". $string; + + $string = preg_replace("/[0-9]+?\./s", "", $string); + $string = str_replace(" ..", "", $string); + $string = str_replace(" ", " ", $string); + $string = trim($string); + + return preg_split("/(\(|\))/s", $string, 0, PREG_SPLIT_DELIM_CAPTURE); + } + + private function getMoveString() { + $tokens = preg_split("/\n\n/s", $this->pgnGame); + if(count($tokens) < 2){ + return ""; + } + $gameData = $tokens[1]; + $gameData = str_replace("\n", " ", $gameData); + $gameData = preg_replace("/(\s+)/", " ", $gameData); + return trim($gameData); + } +} \ No newline at end of file diff --git a/PgnParser.php b/PgnParser.php new file mode 100644 index 0000000..950aca5 --- /dev/null +++ b/PgnParser.php @@ -0,0 +1,128 @@ +pgnFile = $pgnFile; + } + $this->gameParser = new GameParser(); + } + + public function setPgnContent($content){ + $this->pgnContent = $content; + } + + private function readPgn(){ + $fh = fopen($this->pgnFile, 'r'); + $this->pgnContent = fread($fh, filesize($this->pgnFile)); + fclose($fh); + } + + private function cleanPgn(){ + $c = $this->pgnContent; + $c = preg_replace("/\\$[0-9]+/s", "", $c); + $c = preg_replace("/{(.*?)\[(.*?)}/s", '{$1-SB-$2}', $c); + $c = preg_replace("/\r/s", "", $c); + $c = preg_replace("/\t/s", "", $c); + $c = preg_replace("/\]\s+\[/s", "]\n[", $c); + $c = str_replace(" [", "[", $c); + $c = preg_replace("/([^\]])(\n+)\[/si", "$1\n\n[", $c); + $c = preg_replace("/\n{3,}/s", "\n\n", $c); + $c = str_replace("-SB-", "[", $c); + $this->pgnContent = $c; + } + + public static function getArrayOfGames($pgn) { + return self::getPgnGamesAsArray($pgn); + } + private function splitPgnIntoGames(){ + $this->pgnGames = $this->getPgnGamesAsArray($this->pgnContent); + } + + private function getPgnGamesAsArray($pgn){ + $ret = array(); + $content = "\n\n" . $pgn; + $games = preg_split("/\n\n\[/s", $content, -1, PREG_SPLIT_DELIM_CAPTURE); + for($i=1, $count = count($games); $i<$count; $i++){ + array_push($ret, trim("[". $games[$i])); + } + return $ret; + } + + public function getGamesAsJSON(){ + return json_encode($this->getGames()); + } + + private function isLazy(){ + return false; + } + + public function getUnparsedGames() { + if($this->pgnFile){ + $this->readPgn(); + } + $this->cleanPgn(); + $this->splitPgnIntoGames(); + return $this->pgnGames; + } + + public function getFirstGame(){ + $games = $this->getGames(); + if(count($games)){ + return $games[0]; + } + return null; + } + + public function getGames(){ + if($this->pgnFile){ + $this->readPgn(); + } + $this->cleanPgn(); + $this->splitPgnIntoGames(); + + for($i=0, $count = count($this->pgnGames);$i<$count; $i++){ + $gameParser = new PgnGameParser($this->pgnGames[$i]); + $this->games[$i] = $gameParser->getParsedData(); + + + if(!$this->isLazy()){ + $this->games[$i] = $this->gameParser->getParsedGame($this->games[$i]); + } + } + return $this->games; + } +} + +/* +error_reporting(E_ALL); +ini_set('display_errors','on'); + +require_once("GameParser.php"); +require_once("FenParser0x88.php"); +require_once("PgnGameParser.php"); +require_once("MoveBuilder.php"); +require_once("Board0x88Config.php"); +require_once("../CHESS_JSON.php"); + +echo "

"; + +#$parser = new PgnParser('ChessUserRoles/winning-chess-sacrifices-and-combinations.pgn'); + +$parser = new PgnParser('../ChessUserRoles/alf2.pgn'); + +echo "

"; +$games = $parser->getGames(); +echo json_encode($games[0]); +echo "

"; +#outputTime(); + +*/ +