commit 5396570fd604de1e8d245ad16986b20bdf8f464a Author: DHTMLGoodies Date: Thu Jan 17 15:02:46 2013 +0100 Added files 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(); + +*/ +