'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]; } }