Games_Chess
[ class tree: Games_Chess ] [ index: Games_Chess ] [ all elements ]

Source for file Chess.php

Documentation is available at Chess.php

  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4: */
  3. // +----------------------------------------------------------------------+
  4. // | PHP version 4                                                        |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 2003 The PHP Group                                     |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 3.0 of the PHP license,       |
  9. // | that is bundled with this package in the file LICENSE, and is        |
  10. // | available through the world-wide-web at                              |
  11. // | http://www.php.net/license/3_0.txt.                                  |
  12. // | If you did not receive a copy of the PHP license and are unable to   |
  13. // | obtain it through the world-wide-web, please send a note to          |
  14. // | license@php.net so we can mail you a copy immediately.               |
  15. // +----------------------------------------------------------------------+
  16. // | Authors: Gregory Beaver <cellog@php.net>                             |
  17. // +----------------------------------------------------------------------+
  18. //
  19. // $Id: Chess.php,v 1.6 2004/04/09 06:54:28 cellog Exp $
  20. /**
  21.  * The Games_Chess Package
  22.  *
  23.  * The logic of handling a chessboard and parsing standard
  24.  * FEN (Farnsworth-Edwards Notation) for describing a position as well as SAN
  25.  * (Standard Algebraic Notation) for describing individual moves is handled.  This
  26.  * class can be used as a backend driver for playing chess, or for validating
  27.  * and/or creating PGN files using the File_ChessPGN package.
  28. rn *
  29.  * Although this package is alpha, it is fully unit-tested.  The code works, but
  30.  * the API is fluid, and may change dramatically as it is put into use and better
  31.  * ways are found to use it.  When the API stabilizes, the stability will increase.
  32.  *
  33.  * To learn how to play chess, there are many sites online, try searching for
  34.  * "chess."  To play online, I use the Internet Chess Club at
  35.  * {@link http://www.chessclub.com} as CelloJi, look me up sometime :).  Don't
  36.  * worry, I'm not very good.
  37.  * @todo implement special class Games_Chess_Chess960 for Fischer Random Chess
  38.  * @todo implement special class Games_Chess_Wild23 for ICC Wild variant 23
  39.  * @author Gregory Beaver <cellog@php.net>
  40.  * @copyright 2003
  41.  * @license http://www.php.net/license/3_0.txt PHP License 3.0
  42.  * @version @VER@
  43.  */
  44. /**#@+
  45.  * Move constants
  46.  */
  47. /**
  48.  * Castling move (O-O or O-O-O)
  49.  */
  50. define('GAMES_CHESS_CASTLE'1);
  51. /**
  52.  * Pawn move (e4, e8=Q, exd5)
  53.  */
  54. define('GAMES_CHESS_PAWNMOVE'2);
  55. /**
  56.  * Piece move (Qa4, Nfe6, Bxe5, Re2xe6)
  57.  */
  58. define('GAMES_CHESS_PIECEMOVE'3);
  59. /**
  60.  * Special move type used in Wild23 like P@a4 (place a pawn at a4)
  61.  */
  62. define('GAMES_CHESS_PIECEPLACEMENT'4);
  63. /**#@-*/
  64.  
  65. /**#@+
  66.  * Error Constants
  67.  */
  68. /**
  69.  * Invalid Standard Algebraic Notation was used
  70.  */
  71. define('GAMES_CHESS_ERROR_INVALID_SAN'1);
  72. /**
  73.  * The number of space-separated fields in a FEN passed to {@internal 
  74.  * {@link _parseFen()} through }} {@link resetGame()} was incorrect, should be 6
  75.  */
  76. define('GAMES_CHESS_ERROR_FEN_COUNT'2);
  77. /**
  78.  * A FEN containing multiple spaces in a row was parsed {@internal by
  79.  * {@link _parseFen()}}}}
  80.  */
  81. define('GAMES_CHESS_ERROR_EMPTY_FEN'3);
  82. /**
  83.  * Too many pieces were passed in for the chessboard to fit them in a FEN
  84.  * {@internal passed to {@link _parseFen()}  
  85.  */
  86. define('GAMES_CHESS_ERROR_FEN_TOOMUCH'4);
  87. /**
  88.  * The indicator of which side to move in a FEN was neither "w" nor "b"
  89.  */
  90. define('GAMES_CHESS_ERROR_FEN_TOMOVEWRONG'5);
  91. /**
  92.  * The list of castling indicators was too long (longest is KQkq) of a FEN
  93.  */
  94. define('GAMES_CHESS_ERROR_FEN_CASTLETOOLONG'6);
  95. /**
  96.  * Something other than K, Q, k or q was in the castling indicators of a FEN
  97.  */
  98. define('GAMES_CHESS_ERROR_FEN_CASTLEWRONG'7);
  99. /**
  100.  * The en passant square was neither "-" nor an algebraic square in a FEN
  101.  */
  102. define('GAMES_CHESS_ERROR_FEN_INVALID_EP'8);
  103. /**
  104.  * The ply count (number of half-moves) was not a number in a FEN
  105.  */
  106. define('GAMES_CHESS_ERROR_FEN_INVALID_PLY'9);
  107. /**
  108.  * The move count (pairs of white/black moves) was not a number in a FEN
  109.  */
  110. define('GAMES_CHESS_ERROR_FEN_INVALID_MOVENUMBER'10);
  111. /**
  112.  * An illegal move was attempted, the king is in check
  113.  */
  114. define('GAMES_CHESS_ERROR_IN_CHECK'11);
  115. /**
  116.  * Can't castle kingside, either king or rook has moved
  117.  */
  118. define('GAMES_CHESS_ERROR_CANT_CK'12);
  119. /**
  120.  * Can't castle kingside, pieces are in the way on the f and/or g files
  121.  */
  122. define('GAMES_CHESS_ERROR_CK_PIECES_IN_WAY'13);
  123. /**
  124.  * Can't castle kingside, either king or rook has moved
  125.  */
  126. define('GAMES_CHESS_ERROR_CANT_CQ'14);
  127. /**
  128.  * Can't castle queenside, pieces are in the way on the d, c and/or b files
  129.  */
  130. define('GAMES_CHESS_ERROR_CQ_PIECES_IN_WAY'15);
  131. /**
  132.  * Castling would place the king in check, which is illegal
  133.  */
  134. define('GAMES_CHESS_ERROR_CASTLE_WOULD_CHECK'16);
  135. /**
  136.  * Performing a requested move would place the king in check
  137.  */
  138. define('GAMES_CHESS_ERROR_MOVE_WOULD_CHECK'17);
  139. /**
  140.  * The requested move does not remove a check on the king
  141.  */
  142. define('GAMES_CHESS_ERROR_STILL_IN_CHECK'18);
  143. /**
  144.  * An attempt (however misguided) was made to capture one's own piece, illegal
  145.  */
  146. define('GAMES_CHESS_ERROR_CANT_CAPTURE_OWN'19);
  147. /**
  148.  * An attempt was made to capture a piece on a square that does not contain a piece
  149.  */
  150. define('GAMES_CHESS_ERROR_NO_PIECE'20);
  151. /**
  152.  * A attempt to move an opponent's piece was made, illegal
  153.  */
  154. define('GAMES_CHESS_ERROR_WRONG_COLOR'21);
  155. /**
  156.  * A request was made to move a piece from one square to another, but it can't
  157.  * move to that square legally
  158.  */
  159. define('GAMES_CHESS_ERROR_CANT_MOVE_THAT_WAY'22);
  160. /**
  161.  * An attempt was made to add a piece to the chessboard, but there are too many
  162.  * pieces of that type already on the chessboard
  163.  */
  164. define('GAMES_CHESS_ERROR_MULTIPIECE'23);
  165. /**
  166.  * An attempt was made to add a piece to the chessboard through the parsing of
  167.  * a FEN, but there are too many pieces of that type already on the chessboard
  168.  */
  169. define('GAMES_CHESS_ERROR_FEN_MULTIPIECE'24);
  170. /**
  171.  * An attempt was made to add a piece to the chessboard on top of an existing piece
  172.  */
  173. define('GAMES_CHESS_ERROR_DUPESQUARE'25);
  174. /**
  175.  * An invalid piece indicator was used in a FEN
  176.  */
  177. define('GAMES_CHESS_ERROR_FEN_INVALIDPIECE'26);
  178. /**
  179.  * Not enough piece data was passed into the FEN to explain every square on the board
  180.  */
  181. define('GAMES_CHESS_ERROR_FEN_TOOLITTLE'27);
  182. /**
  183.  * Something other than "W" or "B" was passed to a method needing a color
  184.  */
  185. define('GAMES_CHESS_ERROR_INVALID_COLOR'28);
  186. /**
  187.  * Something that isn't SAN ([a-h][1-8]) was passed to a function requiring a
  188.  * square location
  189.  */
  190. define('GAMES_CHESS_ERROR_INVALID_SQUARE'29);
  191. /**
  192.  * Something other than "P", "Q", "R", "B", "N" or "K" was passed to a method
  193.  * needing a piece type
  194.  */
  195. define('GAMES_CHESS_ERROR_INVALID_PIECE'30);
  196. /**
  197.  * Something other than "Q", "R", "B", or "N" was passed to a method
  198.  * needing a piece type for pawn promotion
  199.  */
  200. define('GAMES_CHESS_ERROR_INVALID_PROMOTE'31);
  201. /**
  202.  * SAN was passed in that is too ambiguous - multiple pieces could execute
  203.  * the move, and no disambiguation (like Naf3 or Bf3xe4) was used
  204.  */
  205. define('GAMES_CHESS_ERROR_TOO_AMBIGUOUS'32);
  206. /**
  207.  * No piece of the current color can execute the SAN (as in, if Na3 is passed
  208.  * in, but there are no knights that can reach a3
  209.  */
  210. define('GAMES_CHESS_ERROR_NOPIECE_CANDOTHAT'33);
  211. /**
  212.  * In loser's chess, and the current move does not capture a piece although
  213.  * capture is possible.
  214.  */
  215. define('GAMES_CHESS_ERROR_MOVE_MUST_CAPTURE'34);
  216. /**
  217.  * When piece placement is attempted, but no pieces exist to be placed
  218.  */
  219. define('GAMES_CHESS_ERROR_NOPIECES_TOPLACE'35);
  220. /**
  221.  * When piece placement is attempted, but there is a piece on the desired square already
  222.  */
  223. define('GAMES_CHESS_ERROR_PIECEINTHEWAY'36);
  224. /**
  225.  * When a pawn placement on the back rank does not include a promotion, this error is used
  226.  */
  227. define('GAMES_CHESS_ERROR_MUSTPROMOTE'37);
  228. /**
  229.  * ABSTRACT parent class - use {@link Games_Chess_Standard} for a typical
  230.  * chess game
  231.  *
  232.  * This class contains a few public methods that are the only thing most
  233.  * users of the package will ever need.  Protected methods are available
  234.  * for usage by child classes, and it is expected that all child classes
  235.  * will implement certain protected methods used by the utility methods in
  236.  * this class.
  237.  *
  238.  * Public API methods used are:
  239.  *
  240.  * Game-related methods
  241.  *
  242.  * - {@link resetGame()}: in order to start a new game (pass a FEN for a starting
  243.  *   position)
  244.  * - {@link blankBoard()}: in order to start with an empty chessboard
  245.  * - {@link addPiece()}: Use to add pieces one at a time to the board
  246.  * - {@link moveSAN()}: Use to move pieces based on their SAN (Qa3, exd5, etc.)
  247.  * - {@link moveSquare()}: Use to move pieces based on their square (a2 -> a3
  248.  *   for Qa3, e4 -> d5 for exd5, etc.)
  249.  *
  250.  * Game state methods:
  251.  *
  252.  * - {@link inCheck()}: Use to determine the presence of check
  253.  * - {@link inCheckMate()}: Use to determine a won game
  254.  * - {@link inStaleMate()}: Use to determine presence of stalemate draw
  255.  * - {@link in50MoveDraw()}: Use to determine presence of 50-move rule draw
  256.  * - {@link inRepetitionDraw()}: Use to determine presence of a draw by repetition
  257.  * - {@link inStaleMate()}: Use to determine presence of stalemate draw
  258.  * - {@link inDraw()}: Use to determine if any forced draw condition exists
  259.  *
  260.  * Game data methods:
  261.  *
  262.  * - {@link renderFen()}: Use to retrieve a FEN representation of the
  263.  *   current chessboard position, in order to transfer to another chess program
  264.  * - {@link toArray()}: Use to retrieve a literal representation of the
  265.  *   current chessboard position, in order to display as HTML or some other
  266.  *   format for the user
  267.  * - {@link getMoveList()}: Use to retrieve the list of SAN moves for this game
  268.  * @package Games_Chess
  269.  */
  270. class Games_Chess {
  271.     /**
  272.      * Used for transactions
  273.      * @var array 
  274.      * @access private
  275.      */
  276.     var $_saveState = array();
  277.     /**
  278.      * @var array 
  279.      * @access private
  280.      */
  281.     var $_board;
  282.     /**
  283.      * @var string 
  284.      * @access private
  285.      */
  286.     var $_move 'W';
  287.     /**
  288.      * @var integer 
  289.      * @access private
  290.      */
  291.     var $_moveNumber = 1;
  292.     /**
  293.      * Half-moves since last pawn move or capture
  294.      * @var integer 
  295.      * @access private
  296.      */
  297.     var $_halfMoves = 1;
  298.     /**
  299.      * Square that an en passant can happen, or "-"
  300.      * @var string 
  301.      * @access private
  302.      */
  303.     var $_enPassantSquare '-';
  304.     /**
  305.      * Moves in SAN format for easy write-out to a PGN file
  306.      *
  307.      * The format is:
  308.      * <pre>
  309.      * array(
  310.      *  movenumber => array(White move, Black move),
  311.      *  movenumber => array(White move, Black move),
  312.      * )
  313.      * </pre>
  314.      * @var array 
  315.      * @access private
  316.      */
  317.     var $_moves = array();
  318.     /**
  319.      * Moves in SAN format for easy write-out to a PGN file, with check/checkmate annotations appended
  320.      *
  321.      * The format is:
  322.      * <pre>
  323.      * array(
  324.      *  movenumber => array(White move, Black move),
  325.      *  movenumber => array(White move, Black move),
  326.      * )
  327.      * </pre>
  328.      * @var array 
  329.      * @access private
  330.      */
  331.     var $_movesWithCheck = array();
  332.     /**
  333.      * Store every position from the game, used to determine draw by repetition
  334.      *
  335.      * If the exact same position is encountered three times, then it is a draw
  336.      * @var array 
  337.      * @access private
  338.      */
  339.     var $_allFENs = array();
  340.     /**#@+
  341.      * Castling rights
  342.      * @var boolean
  343.      * @access private
  344.      */
  345.     var $_WCastleQ = true;
  346.     var $_WCastleK = false;
  347.     var $_BCastleQ = true;
  348.     var $_BCastleK = false;
  349.     /**#@-*/
  350.     /**
  351.      * Contents of the last move returned from {@link _parseMove()}, used to
  352.      * process en passant.
  353.      * @var false|array
  354.      * @access private
  355.      */
  356.     var $_lastMove = false;
  357.     
  358.     /**
  359.      * Create a blank chessboard with no pieces on it
  360.      */
  361.     function blankBoard()
  362.     {
  363.         $this->_board = array();
  364.         for ($j = 8; $j >= 1; $j--{
  365.             for ($i ord('a')$i <= ord('h')$i++{
  366.                 $this->_board[chr($i$jchr($i$j;
  367.             }
  368.         }
  369.     }
  370.  
  371.     /**
  372.      * Create a new game with the starting position, or from the position
  373.      * specified by $fen
  374.      *
  375.      * @param false|string
  376.      * @return PEAR_Error|truereturns any errors thrown by {@link _parseFen()}
  377.      */
  378.     function resetGame($fen = false)
  379.     {
  380.         $this->_saveState = array();
  381.         if (!$fen{
  382.             $this->_setupStartingPosition();
  383.         else {
  384.             return $this->_parseFen($fen);
  385.         }
  386.         return true;
  387.     }
  388.     
  389.     /**
  390.      * Make a move from a Standard Algebraic Notation (SAN) format
  391.      *
  392.      * SAN is just a normal chess move like Na4, instead of the English Notation,
  393.      * like NR4
  394.      * @param string 
  395.      * @return true|PEAR_Error
  396.      */
  397.     function moveSAN($move)
  398.     {
  399.         if (!is_array($this->_board)) {
  400.             $this->resetGame();
  401.         }
  402.         if (!$this->isError($parsedMove $this->_parseMove($move))) {
  403.             if (!$this->isError($err $this->_validMove($parsedMove))) {
  404.                 list($key$parsedMoveeach($parsedMove);
  405.                 $this->_moves[$this->_moveNumber][($this->_move == 'W'? 0 : 1$move;
  406.                 $oldMoveNumber $this->_moveNumber;
  407.                 $this->_moveNumber += ($this->_move == 'W'? 0 : 1;
  408.                 $this->_halfMoves++;
  409.                 if ($key == GAMES_CHESS_CASTLE{
  410.                     $a ($parsedMove == 'Q''K' 'Q';
  411.                     // clear castling rights
  412.                     $this->{'_' $this->_move 'Castle' $parsedMove= false;
  413.                     $this->{'_' $this->_move 'Castle' $a= false;
  414.                     $row ($this->_move == 'W'? 1 : 8;
  415.                     switch ($parsedMove{
  416.                         case 'K' :
  417.                             $this->_moveAlgebraic("e$row""g$row");
  418.                             $this->_moveAlgebraic("h$row""f$row");
  419.                         break;
  420.                         case 'Q' :
  421.                             $this->_moveAlgebraic("e$row""c$row");
  422.                             $this->_moveAlgebraic("a$row""d$row");
  423.                         break;
  424.                     }
  425.                     $this->_enPassantSquare '-';
  426.                 else {
  427.                     $movedfrom $this->_getSquareFromParsedMove($parsedMove);
  428.                     $promote = isset($parsedMove['promote']?
  429.                         $parsedMove['promote''';
  430.                     $this->_moveAlgebraic($movedfrom$parsedMove['square']$promote);
  431.                     if ($parsedMove['takes']{
  432.                         $this->_halfMoves = 1;
  433.                     }
  434.                     if ($parsedMove['piece'== 'P'{
  435.                         $this->_halfMoves = 1;
  436.                         $this->_enPassantSquare '-';
  437.                         if (in_array($movedfrom{1$parsedMove['square']{1},
  438.                               array(2-2))) {
  439.                             $direction ($this->_move == 'W' ? 1 : -1);
  440.                             $this->_enPassantSquare $parsedMove['square']{0.
  441.                                 ($parsedMove['square']{1$direction);
  442.                         }
  443.                     else {
  444.                         $this->_enPassantSquare '-';
  445.                     }
  446.                     if ($parsedMove['piece'== 'K'{
  447.                         $this->{'_' $this->_move 'CastleQ'= false;
  448.                         $this->{'_' $this->_move 'CastleK'= false;
  449.                     }
  450.                     if ($parsedMove['piece'== 'R'{
  451.                         if ($movedfrom{0== 'a'{
  452.                         $this->{'_' $this->_move . 'CastleQ'= false;
  453.                         }
  454.                         if ($movedfrom{0== 'h'{
  455.                         $this->{'_' $this->_move . 'CastleK'= false;
  456.                         }
  457.                     }
  458.                 }
  459.                 $moveWithCheck $move;
  460.                 if ($this->inCheckMate(($this->_move == 'W''B' 'W')) {
  461.                     $moveWithCheck .= '#';
  462.                 elseif ($this->inCheck(($this->_move == 'W''B' 'W')) {
  463.                     $moveWithCheck .= '+';
  464.                 }
  465.                 $this->_movesWithCheck[$oldMoveNumber][($this->_move == 'W'? 0 : 1$moveWithCheck;
  466.                 $this->_move = ($this->_move == 'W' 'B' 'W');
  467.                 
  468.                 // increment the position counter for this position
  469.                 $x $this->renderFen(false);
  470.                 if (!isset($this->_allFENs[$x])) {
  471.                     $this->_allFENs[$x= 0;
  472.                 }
  473.                 $this->_allFENs[$x]++;
  474.                 return true;
  475.             else {
  476.                 return $err;
  477.             }
  478.         else {
  479.             return $parsedMove;
  480.         }
  481.     }
  482.     
  483.     /**
  484.      * Move a piece from one square to another, and mark the old square as empty
  485.      *
  486.      * @param string [a-h][1-8] square to move from
  487.      * @param string [a-h][1-8] square to move to
  488.      * @param string piece to promote to, if this is a promotion move
  489.      * @return true|PEAR_Error
  490.      */
  491.     function moveSquare($from$to$promote '')
  492.     {
  493.         $move $this->_convertSquareToSAN($from$to$promote);
  494.         if ($this->isError($move)) {
  495.             return $move;
  496.         else {
  497.             return $this->moveSAN($move);
  498.         }
  499.     }
  500.     
  501.     /**
  502.      * Get the list of moves in Standard Algebraic Notation
  503.      *
  504.      * Can be used to populate a PGN file.
  505.      * @param boolean If true, then moves that check will be postfixed with "+" and checkmate with "#"
  506.      *                 as in Nf3+ or Qxg7#
  507.      * @return array 
  508.      */
  509.     function getMoveList($withChecks = false)
  510.     {
  511.         if ($withChecks{
  512.             return $this->_movesWithCheck;
  513.         }
  514.         return $this->_moves;
  515.     }
  516.     
  517.     /**
  518.      * @return W|B|D|falsewinner of game, or draw, or false if still going
  519.      */
  520.     function gameOver()
  521.     {
  522.         $opposite $this->_move == 'W' 'B' 'W';
  523.         if ($this->inCheckmate()) {
  524.             return $opposite;
  525.         }
  526.         if ($this->inDraw()) {
  527.             return 'D';
  528.         }
  529.         return false;
  530.     }
  531.     
  532.     /**
  533.      * Determine whether a side is in checkmate
  534.      * @param W|Bcolor of side to check, defaults to the current side
  535.      * @return boolean 
  536.      * @throws GAMES_CHESS_ERROR_INVALID_COLOR
  537.      */
  538.     function inCheckMate($color = null)
  539.     {
  540.         if (is_null($color)) {
  541.             $color $this->_move;
  542.         }
  543.         $color strtoupper($color);
  544.         if (!in_array($colorarray('W''B'))) {
  545.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_COLOR,
  546.                 array('color' => $color));
  547.         }
  548.         if (!($checking $this->inCheck($color))) {
  549.             return false;
  550.         }
  551.         $moves $this->getPossibleKingMoves($king $this->_getKing($color)$color);
  552.         foreach ($moves as $escape{
  553.             $this->startTransaction();
  554.             $this->_move = $color;
  555.             $this->moveSquare($king$escape);
  556.             $this->_move = $color;
  557.             $stillchecked $this->inCheck($color);
  558.             $this->rollbackTransaction();
  559.             if (!$stillchecked{
  560.                 return false;
  561.             }
  562.         }
  563.         // if we're in double check, and the king can't move, that's checkmate
  564.         if (is_array($checking&& count($checking> 1{
  565.             return true;
  566.         }
  567.         $squares $this->_getPathToKing($checking$king);
  568.         if ($this->_interposeOrCapture($squares$color)) {
  569.             return false;
  570.         }
  571.         return true;
  572.     }
  573.     
  574.     /**
  575.      * Determine whether a side is in stalemate
  576.      * @param W|Bcolor of the side to look at, defaults to the current side
  577.      * @return boolean 
  578.      * @throws GAMES_CHESS_ERROR_INVALID_COLOR
  579.      */
  580.     function inStaleMate($color = null)
  581.     {
  582.         if (is_null($color)) {
  583.             $color $this->_move;
  584.         }
  585.         $color strtoupper($color);
  586.         if (!in_array($colorarray('W''B'))) {
  587.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_COLOR,
  588.                 array('color' => $color));
  589.         }
  590.         if ($this->inCheck($color)) {
  591.             return false;
  592.         }
  593.         $moves $this->_getPossibleChecks($color);
  594.         foreach($moves as $name => $canmove{
  595.             if (count($canmove)) {
  596.                 $a $this->_getPiece($name);
  597.                 foreach($canmove as $move{
  598.                     $this->startTransaction();
  599.                     $this->_move = $color;
  600.                     $err $this->moveSquare($a$move);
  601.                     $this->rollbackTransaction();
  602.                     if (!is_object($err)) {
  603.                         return false;
  604.                     }
  605.                 }
  606.             }
  607.         }
  608.         return true;
  609.     }
  610.     
  611.     /**
  612.      * Determines the presence of a forced draw
  613.      * @param W|B
  614.      * @return boolean 
  615.      */
  616.     function inDraw($color = null)
  617.     {
  618.         return $this->inStaleMate($color||
  619.                $this->inRepetitionDraw(||
  620.                $this->in50MoveDraw(||
  621.                $this->inBasicDraw();
  622.     }
  623.     
  624.     /**
  625.      * Determine whether draw by repetition has happened
  626.      *
  627.      * From FIDE rules:
  628.      * <pre>
  629.      * 10.10
  630.      *
  631.      * The game is drawn, upon a claim by the player having the move, when the
  632.      * same position, for the third time:
  633.      * (a) is about to appear, if he first writes the move on his
  634.      *     scoresheet and declares to the arbiter his intention of making
  635.      *     this move; or
  636.      * (b) has just appeared, the same player having the move each time.
  637.      *
  638.      * The position is considered the same if pieces of the same kind and
  639.      * colour occupy the same squares, and if all the possible moves of
  640.      * all the pieces are the same, including the rights to castle [at
  641.      * some future time] or to capture a pawn "en passant".
  642.      * </pre>
  643.      *
  644.      * This class determines draw by comparing FENs rendered after every move
  645.      * @return boolean 
  646.      */
  647.     function inRepetitionDraw()
  648.     {
  649.         $fen $this->renderFen(false);
  650.         if (isset($this->_allFENs[$fen]&& $this->_allFENs[$fen== 3{
  651.             return true;
  652.         }
  653.         return false;
  654.     }
  655.     
  656.     /**
  657.      * Determine whether any pawn move or capture has occurred in the past 50 moves
  658.      * @return boolean 
  659.      */
  660.     function in50MoveDraw()
  661.     {
  662.         return $this->_halfMoves >= 50;
  663.     }
  664.     
  665.     /**
  666.      * Determine the presence of a basic draw as defined by FIDE rules
  667.      *
  668.      * The rule states:
  669.      * <pre>
  670.      * 10.4
  671.      *
  672.      * The game is drawn when one of the following endings arises:
  673.      * (a) king against king;
  674.      * (b) king against king with only bishop or knight;
  675.      * (c) king and bishop against king and bishop, with both bishops
  676.      *     on diagonals of the same colour.
  677.      * </pre>
  678.      * @return boolean 
  679.      */
  680.     function inBasicDraw()
  681.     {
  682.         $pieces $this->_getPieceTypes();
  683.         $blackpieces array_keys($pieces['B']);
  684.         $whitepieces array_keys($pieces['W']);
  685.         if (count($blackpieces> 2 || count($whitepieces> 2{
  686.             return false;
  687.         }
  688.         if (count($blackpieces== 1{
  689.             if (count($whitepieces== 1{
  690.                 return true;
  691.             }
  692.             if ($whitepieces[0== 'K'{
  693.                 if (in_array($whitepieces[1]array('N''B'))) {
  694.                     return true;
  695.                 else {
  696.                     return false;
  697.                 }
  698.             else {
  699.                 if (in_array($whitepieces[0]array('N''B'))) {
  700.                     return true;
  701.                 else {
  702.                     return false;
  703.                 }
  704.             }
  705.         }
  706.  
  707.         if (count($whitepieces== 1{
  708.             if (count($blackpieces== 1{
  709.                 return true;
  710.             }
  711.             if ($blackpieces[0== 'K'{
  712.                 if (in_array($blackpieces[1]array('N''B'))) {
  713.                     return true;
  714.                 else {
  715.                     return false;
  716.                 }
  717.             else {
  718.                 if (in_array($blackpieces[0]array('N''B'))) {
  719.                     return true;
  720.                 else {
  721.                     return false;
  722.                 }
  723.             }
  724.         }
  725.         $wpindex ($whitepieces[0== 'K'? 1 : 0;
  726.         $bpindex ($blackpieces[0== 'K'? 1 : 0;
  727.         if ($whitepieces[$wpindex== 'B' && $blackpieces[$bpindex== 'B'{
  728.             // bishops of same color?
  729.             if ($pieces['B']['B'][0== $pieces['W']['B'][0]{
  730.                 return true;
  731.             }
  732.         }
  733.         return false;
  734.     }
  735.     
  736.     /**
  737.      * render the FEN notation for the current board
  738.      * @param boolean private parameter, used to determine whether to include
  739.      *                 move number/ply count - this is used to keep track of
  740.      *                 positions for draw detection
  741.      * @return string 
  742.      */
  743.     function renderFen($include_moves = true)
  744.     {
  745.         $fen $this->_renderFen(' ';
  746.         
  747.         // render who's to move
  748.         $fen .= strtolower($this->_move' ';
  749.         
  750.         // render castling rights
  751.         if (!$this->_WCastleQ && !$this->_WCastleK && !$this->_BCastleQ
  752.               && !$this->_BCastleK{
  753.             $fen .= '- ';
  754.         else {
  755.             if ($this->_WCastleK{
  756.                 $fen .= 'K';
  757.             }
  758.             if ($this->_WCastleQ{
  759.                 $fen .= 'Q';
  760.             }
  761.             if ($this->_BCastleK{
  762.                 $fen .= 'k';
  763.             }
  764.             if ($this->_BCastleQ{
  765.                 $fen .= 'q';
  766.             }
  767.             $fen .= ' ';
  768.         }
  769.         
  770.         // render en passant square
  771.         $fen .= $this->_enPassantSquare;
  772.  
  773.         if (!$include_moves{
  774.             return $fen;
  775.         }
  776.  
  777.         // render half moves since last pawn move or capture
  778.         $fen .=  ' ' $this->_halfMoves . ' ';
  779.         
  780.         // render move number
  781.         $fen .= $this->_moveNumber;
  782.         return $fen;
  783.     }
  784.     
  785.     /**
  786.      * Add a piece to the chessboard
  787.      *
  788.      * Must be overridden in child classes
  789.      * @abstract
  790.      * @param W|BColor of piece
  791.      * @param P|N|K|Q|R|BPiece type
  792.      * @param string algebraic location of piece
  793.      */
  794.     function addPiece($color$type$square)
  795.     {
  796.         trigger_error("Error: do not use abstract Games_Chess class"E_USER_ERROR);
  797.     }
  798.     
  799.     /**
  800.      * Generate a representation of the chess board and pieces for use as a
  801.      * direct translation to a visual chess board
  802.      *
  803.      * Must be overridden in child classes
  804.      * @return array 
  805.      * @abstract
  806.      */
  807.     function toArray()
  808.     {
  809.         trigger_error("Error: do not use abstract Games_Chess class"E_USER_ERROR);
  810.     }
  811.     
  812.     /**
  813.      * Determine whether moving a piece from one square to another requires
  814.      * a pawn promotion
  815.      * @param string [a-h][1-8] location of the piece to move
  816.      * @param string [a-h][1-8] place to move the piece to
  817.      * @return boolean true if the move represented by moving from $from to $to
  818.      *                  is a pawn promotion move
  819.      */
  820.     function isPromoteMove($from$to)
  821.     {
  822.         $test $this->_convertSquareToSAN($from$to);
  823.         if ($this->isError($test)) {
  824.             return false;
  825.         }
  826.         if (strpos($test'=Q'!== false{
  827.             return true;
  828.         }
  829.         return false;
  830.     }
  831.     
  832.     /**
  833.      * @return W|Breturn the color of the side to move (white or black)
  834.      */
  835.     function toMove()
  836.     {
  837.         return $this->_move;
  838.     }
  839.     
  840.     /**
  841.      * Determine legality of kingside castling
  842.      * @return boolean 
  843.      */
  844.     function canCastleKingside()
  845.     {
  846.         return $this->{'_' $this->_move . 'CastleK'};
  847.     }
  848.     
  849.     
  850.     /**
  851.      * Determine legality of queenside castling
  852.      * @return boolean 
  853.      */
  854.     function canCastleQueenside()
  855.     {
  856.         return $this->{'_' $this->_move . 'CastleQ'};
  857.     }
  858.     
  859.     /**
  860.      * Move a piece from one square to another, and mark the old square as empty
  861.      *
  862.      * NO validation is performed, use {@link moveSquare()} for validation.
  863.      *
  864.      * @param string [a-h][1-8] square to move from
  865.      * @param string [a-h][1-8] square to move to
  866.      * @param string piece to promote to, if this is a promotion move
  867.      * @access protected
  868.      */
  869.     function _moveAlgebraic($from$to$promote '')
  870.     {
  871.         if ($to == $this->_enPassantSquare && $this->isPawn($this->_board[$from])) {
  872.             $rank ($to{1== '3''4' '5';
  873.             // this piece was just taken
  874.             $this->_takePiece($to{0$rank);
  875.             $this->_board[$to{0$rank$to{0$rank;
  876.         }
  877.         if ($this->_board[$to!= $to{
  878.             // this piece was just taken
  879.             $this->_takePiece($to);
  880.         }
  881.         // mark the piece as moved
  882.         $this->_movePiece($from$to$promote);
  883.         $this->_board[$to$this->_board[$from];
  884.         $this->_board[$from$from;
  885.     }
  886.     
  887.     /**
  888.      * Parse out the segments of a move (minus any annotations)
  889.      * @param string 
  890.      * @return array 
  891.      * @access protected
  892.      */
  893.     function _parseMove($move)
  894.     {
  895.         if ($move == 'O-O'{
  896.             return array(GAMES_CHESS_CASTLE => 'K');
  897.         }
  898.         if ($move == 'O-O-O'{
  899.             return array(GAMES_CHESS_CASTLE => 'Q');
  900.         }
  901.         // pawn moves
  902.         if (preg_match('/^P?(([a-h])([1-8])?(x))?([a-h][1-8])(=([QRNB]))?$/'$move$match)) {
  903.             if ($match[2]{
  904.                 $takesfrom $match[2]{0};
  905.             else {
  906.                 $takesfrom '';
  907.             }
  908.             $res = array(
  909.                 'takesfrom' => $takesfrom,
  910.                 'takes' => $match[4],
  911.                 'disambiguate' => '',
  912.                 'square' => $match[5],
  913.                 'promote' => '',
  914.                 'piece' => 'P',
  915.             );
  916.             if (isset($match[7])) {
  917.                 $res['promote'$match[7];
  918.             }
  919.             return array(GAMES_CHESS_PAWNMOVE => $res);
  920.         // piece moves
  921.         elseif (preg_match('/^(K)(x)?([a-h][1-8])$/'$move$match)) {
  922.             $res = array(
  923.                 'takesfrom' => false,
  924.                 'piece' => $match[1],
  925.                 'disambiguate' => '',
  926.                 'takes' => $match[2],
  927.                 'square' => $match[3],
  928.             );
  929.             return array(GAMES_CHESS_PIECEMOVE => $res);
  930.         elseif (preg_match('/^([QRBN])([a-h]|[1-8]|[a-h][1-8])?(x)?([a-h][1-8])$/'$move$match)) {
  931.             $res = array(
  932.                 'takesfrom' => false,
  933.                 'piece' => $match[1],
  934.                 'disambiguate' => $match[2],
  935.                 'takes' => $match[3],
  936.                 'square' => $match[4],
  937.             );
  938.             return array(GAMES_CHESS_PIECEMOVE => $res);
  939.         elseif (preg_match('/^([QRBNP])@([a-h][1-8])$/'$move$match)) {
  940.             $res = array(
  941.                 'piece' => $match[1],
  942.                 'square' => $match[2],
  943.                 'promote' => false,
  944.             );
  945.             return array(GAMES_CHESS_PIECEPLACEMENT => $res);
  946.         // error
  947.         elseif (preg_match('/^([P])@([a-h][18])=([QRBN])$/'$move$match)) {
  948.             $res = array(
  949.                 'piece' => $match[1],
  950.                 'square' => $match[2],
  951.                 'promote' => $match[3]
  952.             );
  953.             return array(GAMES_CHESS_PIECEPLACEMENT => $res);
  954.         // error
  955.         else {
  956.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_SAN,
  957.                 array('pgn' => $move));
  958.         }
  959.     }
  960.     
  961.     
  962.     /**
  963.      * Set up the board with the starting position
  964.      *
  965.      * Must be overridden in child classes
  966.      * @abstract
  967.      * @access protected
  968.      */
  969.     function _setupStartingPosition()
  970.     {
  971.         trigger_error("Error: do not use abstract Games_Chess class"E_USER_ERROR);
  972.     }
  973.     
  974.     /**
  975.      * Parse a Farnsworth-Edwards Notation (FEN) chessboard position string, and
  976.      * set up the chessboard with this position
  977.      * @param string 
  978.      * @access private
  979.      */
  980.     function _parseFen($fen)
  981.     {
  982.         $splitfen explode(' '$fen);
  983.         if (count($splitfen!= 6{
  984.             return $this->raiseError(GAMES_CHESS_ERROR_FEN_COUNT,
  985.                 array('fen' => $fen'sections' => count($splitfen)));
  986.         }
  987.  
  988.         foreach($splitfen as $index => $test{
  989.             if ($test == ''{
  990.                 return $this->raiseError(GAMES_CHESS_ERROR_EMPTY_FEN,
  991.                     array('fen' => $fen'section' => $index));
  992.             }
  993.         }
  994.  
  995.         $this->blankBoard();
  996.         $loc 'a8';
  997.         $idx = 0;
  998.         $FEN $splitfen[0];
  999.  
  1000.         // parse position section
  1001.         while ($idx strlen($FEN)) {
  1002.             $c $FEN{$idx};
  1003.             switch ($c{
  1004.                 case "K" :
  1005.                 case "Q" :
  1006.                 case "R" :
  1007.                 case "B" :
  1008.                 case "N" :
  1009.                 case "P" :
  1010.                     $err $this->addPiece('W'$c$loc);
  1011.                     if ($this->isError($err)) {
  1012.                         if ($err->getCode(== GAMES_CHESS_ERROR_MULTIPIECE{
  1013.                             return $this->raiseError(GAMES_CHESS_ERROR_FEN_MULTIPIECE,
  1014.                             array('fen' => $fen'color' => 'W''piece' => $c));
  1015.                         else {
  1016.                             return $err;
  1017.                         }
  1018.                     }
  1019.                 break;
  1020.                 case "k" :
  1021.                 case "q" :
  1022.                 case "r" :
  1023.                 case "b" :
  1024.                 case "n" :
  1025.                 case "p" :
  1026.                     $err $this->addPiece('B'strtoupper($c)$loc);
  1027.                     if ($this->isError($err)) {
  1028.                         if ($err->getCode(== GAMES_CHESS_ERROR_MULTIPIECE{
  1029.                             return $this->raiseError(GAMES_CHESS_ERROR_FEN_MULTIPIECE,
  1030.                             array('fen' => $fen'color' => 'B''piece' => $c));
  1031.                         else {
  1032.                             return $err;
  1033.                         }
  1034.                     }
  1035.                 break;
  1036.  
  1037.                 case "1" :
  1038.                 case "2" :
  1039.                 case "3" :
  1040.                 case "4" :
  1041.                 case "5" :
  1042.                 case "6" :
  1043.                 case "7" :
  1044.                 case "8" :
  1045.                     $loc{0chr(ord($loc{0}($c - 1));
  1046.                 break;
  1047.                 case "/" :
  1048.                     $loc{1$loc{1- 1;
  1049.                     $loc{0'a';
  1050.                     $idx++;
  1051.                     continue 2;
  1052.                 break;
  1053.                 default :
  1054.                     return $this->raiseError(GAMES_CHESS_ERROR_FEN_INVALIDPIECE,
  1055.                         array('fen' => $fen'fenchar' => $c));
  1056.                 break;
  1057.             }
  1058.             $idx++;
  1059.             $loc{0chr(ord($loc{0}+ 1);
  1060.             if (ord($loc{0}ord('h')) {
  1061.                 if (strlen($FEN$idx && $FEN{$idx!= '/'{
  1062.                     return $this->raiseError(GAMES_CHESS_ERROR_FEN_TOOMUCH,
  1063.                         array('fen' => $fen));
  1064.                 }
  1065.             }
  1066.         }
  1067.         if ($loc != 'i1'{
  1068.             return $this->raiseError(GAMES_CHESS_ERROR_FEN_TOOLITTLE,
  1069.                 array('fen' => $fen));
  1070.         }
  1071.  
  1072.         // parse who's to move
  1073.         if (!in_array($splitfen[1]array('w''b''W''B'))) {
  1074.             return $this->raiseError(GAMES_CHESS_ERROR_FEN_TOMOVEWRONG,
  1075.                 array('fen' => $fen'tomove' => $splitfen[1]));
  1076.         }
  1077.         $this->_move = strtoupper($splitfen[1]);
  1078.  
  1079.         // parse castling rights
  1080.         if (strlen($splitfen[2]> 4{
  1081.             return $this->raiseError(GAMES_CHESS_ERROR_FEN_CASTLETOOLONG,
  1082.                 array('fen' => $fen'castle' => $splitfen[2]));
  1083.         }
  1084.         $this->_WCastleQ = false;
  1085.         $this->_WCastleK = false;
  1086.         $this->_BCastleQ = false;
  1087.         $this->_BCastleK = false;
  1088.         if ($splitfen[2!= '-'{
  1089.             for ($i = 0; $i < 4; $i++{
  1090.                 if ($i >= strlen($splitfen[2])) {
  1091.                     continue;
  1092.                 }
  1093.                 switch ($splitfen[2]{$i}{
  1094.                     case 'K' :
  1095.                         $this->_WCastleK = true;
  1096.                     break;
  1097.                     case 'Q' :
  1098.                         $this->_WCastleQ = true;
  1099.                     break;
  1100.                     case 'k' :
  1101.                         $this->_BCastleK = true;
  1102.                     break;
  1103.                     case 'q' :
  1104.                         $this->_BCastleQ = true;
  1105.                     break;
  1106.                     default:
  1107.                         return $this->raiseError(GAMES_CHESS_ERROR_FEN_CASTLEWRONG,
  1108.                             array('fen' => $fen'castle' => $splitfen[2]{$i}));
  1109.                     break;
  1110.                 }
  1111.             }
  1112.         }
  1113.  
  1114.         // parse en passant square
  1115.         $this->_enPassantSquare = '-';
  1116.         if ($splitfen[3!= '-'{
  1117.             if (!preg_match('/^[a-h][36]$/'$splitfen[3])) {
  1118.                 return $this->raiseError(GAMES_CHESS_ERROR_FEN_INVALID_EP,
  1119.                     array('fen' => $fen'enpassant' => $splitfen[3]));
  1120.             }
  1121.             $this->_enPassantSquare = $splitfen[3];
  1122.         }
  1123.  
  1124.         // parse half moves since last pawn move or capture
  1125.         if (!is_numeric($splitfen[4])) {
  1126.             return $this->raiseError(GAMES_CHESS_ERROR_FEN_INVALID_PLY,
  1127.                 array('fen' => $fen'ply' => $splitfen[4]));
  1128.         }
  1129.         $this->_halfMoves = $splitfen[4];
  1130.  
  1131.         // parse move number
  1132.         if (!is_numeric($splitfen[5])) {
  1133.             return $this->raiseError(GAMES_CHESS_ERROR_FEN_INVALID_MOVENUMBER,
  1134.                 array('fen' => $fen'movenumber' => $splitfen[5]));
  1135.         }
  1136.         $this->_moveNumber = $splitfen[5];
  1137.         return true;
  1138.     }
  1139.     
  1140.     /**
  1141.      * Validate a move
  1142.      * @param array parsed move array from {@link _parsedMove()}
  1143.      * @return true|PEAR_Error
  1144.      * @throws GAMES_CHESS_ERROR_IN_CHECK
  1145.      * @throws GAMES_CHESS_ERROR_CANT_CK
  1146.      * @throws GAMES_CHESS_ERROR_CK_PIECES_IN_WAY
  1147.      * @throws GAMES_CHESS_ERROR_CANT_CQ
  1148.      * @throws GAMES_CHESS_ERROR_CQ_PIECES_IN_WAY
  1149.      * @throws GAMES_CHESS_ERROR_CASTLE_WOULD_CHECK
  1150.      * @throws GAMES_CHESS_ERROR_CANT_CAPTURE_OWN
  1151.      * @throws GAMES_CHESS_ERROR_STILL_IN_CHECK
  1152.      * @throws GAMES_CHESS_ERROR_MOVE_WOULD_CHECK
  1153.      * @access protected
  1154.      */
  1155.     function _validMove($move)
  1156.     {
  1157.         list($type$infoeach($move);
  1158.         $this->startTransaction();
  1159.         $valid = false;
  1160.         switch ($type{
  1161.             case GAMES_CHESS_CASTLE :
  1162.                 if ($this->inCheck($this->_move)) {
  1163.                     $this->rollbackTransaction();
  1164.                     return $this->raiseError(GAMES_CHESS_ERROR_IN_CHECK);
  1165.                 }
  1166.                 if ($info == 'K'{
  1167.                     if ($this->_move == 'W'{
  1168.                         if (!$this->_WCastleK{
  1169.                             $this->rollbackTransaction();
  1170.                             return $this->raiseError(GAMES_CHESS_ERROR_CANT_CK);
  1171.                         }
  1172.                         if ($this->_board['f1'!= 'f1' || $this->_board['g1'!= 'g1'{
  1173.                             $this->rollbackTransaction();
  1174.                             return $this->raiseError(GAMES_CHESS_ERROR_CK_PIECES_IN_WAY);
  1175.                         }
  1176.                         $kingsquares = array('f1''g1');
  1177.                         $on 'e1';
  1178.                     else {
  1179.                         if (!$this->_BCastleK{
  1180.                             $this->rollbackTransaction();
  1181.                             return $this->raiseError(GAMES_CHESS_ERROR_CANT_CK);
  1182.                         }
  1183.                         if ($this->_board['f8'!= 'f8' || $this->_board['g8'!= 'g8'{
  1184.                             $this->rollbackTransaction();
  1185.                             return $this->raiseError(GAMES_CHESS_ERROR_CK_PIECES_IN_WAY);
  1186.                         }
  1187.                         $kingsquares = array('f8''g8');
  1188.                         $on 'e8';
  1189.                     }
  1190.                 else {
  1191.                     if ($this->_move == 'W'{
  1192.                         if (!$this->_WCastleQ{
  1193.                             $this->rollbackTransaction();
  1194.                             return $this->raiseError(GAMES_CHESS_ERROR_CANT_CQ);
  1195.                         }
  1196.                         if ($this->_board['d1'!= 'd1' ||
  1197.                               $this->_board['c1'!= 'c1' ||
  1198.                               $this->_board['b1'!= 'b1'{
  1199.                             $this->rollbackTransaction();
  1200.                             return $this->raiseError(GAMES_CHESS_ERROR_CQ_PIECES_IN_WAY);
  1201.                         }
  1202.                         $kingsquares = array('d1''c1');
  1203.                         $on 'e1';
  1204.                     else {
  1205.                         if (!$this->_BCastleQ{
  1206.                             $this->rollbackTransaction();
  1207.                             return $this->raiseError(GAMES_CHESS_ERROR_CANT_CQ);
  1208.                         }
  1209.                         if ($this->_board['d8'!= 'd8' ||
  1210.                               $this->_board['c8'!= 'c8' ||
  1211.                               $this->_board['b8'!= 'b8'{
  1212.                             $this->rollbackTransaction();
  1213.                             return $this->raiseError(GAMES_CHESS_ERROR_CQ_PIECES_IN_WAY);
  1214.                         }
  1215.                         $kingsquares = array('d8''c8');
  1216.                         $on 'e8';
  1217.                     }
  1218.                 }
  1219.                 
  1220.                 // check every square the king could move to and make sure
  1221.                 // we wouldn't be in check
  1222.                 foreach ($kingsquares as $square{
  1223.                     $this->_moveAlgebraic($on$square);
  1224.                     if ($this->inCheck($this->_move)) {
  1225.                         $this->rollbackTransaction();
  1226.                         return $this->raiseError(GAMES_CHESS_ERROR_CASTLE_WOULD_CHECK);
  1227.                     }
  1228.                     $on $square;
  1229.                 }
  1230.                 $valid = true;
  1231.             break;
  1232.             case GAMES_CHESS_PIECEMOVE :
  1233.             case GAMES_CHESS_PAWNMOVE :
  1234.                 if (!$this->isError($piecesq $this->_getSquareFromParsedMove($info))) {
  1235.                     $wasinCheck $this->inCheck($this->_move);
  1236.                     $piece $this->_board[$info['square']];
  1237.                     if ($info['takes'&& $this->_board[$info['square']] ==
  1238.                           $info['square']{
  1239.                         if (!($info['square'== $this->_enPassantSquare &&
  1240.                               $info['piece'== 'P')) {
  1241.                             return $this->raiseError(GAMES_CHESS_ERROR_NO_PIECE,
  1242.                                 array('square' => $info['square']));
  1243.                         }
  1244.                     }
  1245.                     $this->_moveAlgebraic($piecesq$info['square']);
  1246.                     $valid !$this->inCheck($this->_move);
  1247.                     if ($wasinCheck && !$valid{
  1248.                         $this->rollbackTransaction();
  1249.                         return $this->raiseError(GAMES_CHESS_ERROR_STILL_IN_CHECK);
  1250.                     elseif (!$valid{
  1251.                         $this->rollbackTransaction();
  1252.                         return $this->raiseError(GAMES_CHESS_ERROR_MOVE_WOULD_CHECK);
  1253.                     }
  1254.                 else {
  1255.                     $this->rollbackTransaction();
  1256.                     return $piecesq;
  1257.                 }
  1258.             break;
  1259.         }
  1260.         $this->rollbackTransaction();
  1261.         return $valid;
  1262.     }
  1263.     
  1264.     /**
  1265.      * Convert a starting and ending algebraic square into SAN
  1266.      * @access protected
  1267.      * @param string [a-h][1-8] square piece is on
  1268.      * @param string [a-h][1-8] square piece moves to
  1269.      * @param string Q|R|B|N
  1270.      * @return string|PEAR_Error
  1271.      * @throws GAMES_CHESS_ERROR_INVALID_PROMOTE
  1272.      * @throws GAMES_CHESS_ERROR_INVALID_SQUARE
  1273.      * @throws GAMES_CHESS_ERROR_NO_PIECE
  1274.      * @throws GAMES_CHESS_ERROR_WRONG_COLOR
  1275.      * @throws GAMES_CHESS_ERROR_CANT_MOVE_THAT_WAY
  1276.      */
  1277.     function _convertSquareToSAN($from$to$promote '')
  1278.     {
  1279.         if ($promote == ''{
  1280.             $promote 'Q';
  1281.         }
  1282.         $promote strtoupper($promote);
  1283.         if (!in_array($promotearray('Q''B''N''R'))) {
  1284.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_PROMOTE,
  1285.                 array('piece' => $promote));
  1286.         }
  1287.         $SAN '';
  1288.         if (!preg_match('/^[a-h][1-8]$/'$from)) {
  1289.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_SQUARE,
  1290.                 array('square' => $from));
  1291.         }
  1292.         if (!preg_match('/^[a-h][1-8]$/'$to)) {
  1293.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_SQUARE,
  1294.                 array('square' => $to));
  1295.         }
  1296.         $piece $this->_squareToPiece($from);
  1297.         if (!$piece{
  1298.             return $this->raiseError(GAMES_CHESS_ERROR_NO_PIECE,
  1299.                 array('square' => $from));
  1300.         }
  1301.         if ($piece['color'!= $this->_move{
  1302.             return $this->raiseError(GAMES_CHESS_ERROR_WRONG_COLOR,
  1303.                 array('square' => $from));
  1304.         }
  1305.         $moves $this->getPossibleMoves($piece['piece']$from$piece['color']);
  1306.         if (!in_array($to$moves)) {
  1307.             return $this->raiseError(GAMES_CHESS_ERROR_CANT_MOVE_THAT_WAY,
  1308.                 array('from' => $from'to' => $to));
  1309.         }
  1310.         if ($piece['piece'== 'K' && !in_array($to$this->_getKingSquares($from))) {
  1311.             // this is a castling attempt
  1312.             if ($to{0== 'g'{
  1313.                 return 'O-O';
  1314.             else {
  1315.                 return 'O-O-O';
  1316.             }
  1317.         }
  1318.         $others = array();
  1319.         if ($piece['piece'!= 'K' && $piece['piece'!= 'P'{
  1320.             $others $this->_getAllPieceSquares($piece['piece'],
  1321.                                                  $piece['color']$from);
  1322.         }
  1323.         $disambiguate '';
  1324.         $ambiguous = array();
  1325.         if (count($others)) {
  1326.             foreach ($others as $square{
  1327.                 if (in_array($to$this->getPossibleMoves($piece['piece']$square,
  1328.                                                           $piece['color']))) {
  1329.                     // other pieces can move to this square - need to disambiguate
  1330.                     $ambiguous[$square;
  1331.                 }
  1332.             }
  1333.         }
  1334.         if (count($ambiguous== 1{
  1335.             if ($ambiguous[0]{0!= $from{0}{
  1336.                 $disambiguate $from{0};
  1337.             elseif ($ambiguous[0]{1!= $from{1}{
  1338.                 $disambiguate $from{1};
  1339.             else {
  1340.                 $disambiguate $from;
  1341.             }
  1342.         elseif (count($ambiguous)) {
  1343.             $disambiguate $from;
  1344.         }
  1345.         if ($piece['piece'== 'P'{
  1346.             if ($from{0!= $to{0}{
  1347.                 $SAN $from{0};
  1348.             }
  1349.         else {
  1350.             $SAN $piece['piece'];
  1351.         }
  1352.         $SAN .= $disambiguate;
  1353.         if ($this->_board[$to!= $to{
  1354.             $SAN .= 'x';
  1355.         else {
  1356.             if ($piece['piece'== 'P' && $to == $this->_enPassantSquare{
  1357.                 $SAN .= 'x';
  1358.             }
  1359.         }
  1360.         $SAN .= $to;
  1361.         if ($piece['piece'== 'P' && ($to{1== '1' || $to{1== '8')) {
  1362.             $SAN .= '=' $promote;
  1363.         }
  1364.         return $SAN;
  1365.     }
  1366.     
  1367.     /**
  1368.      * Get a list of all possible theoretical squares a piece of this nature
  1369.      * and color could move to with the current board and game setup.
  1370.      *
  1371.      * This method will return all valid moves without determining the presence
  1372.      * of check
  1373.      * @param K|P|Q|R|B|NPiece name
  1374.      * @param string [a-h][1-8] algebraic location of the piece
  1375.      * @param B|Wcolor of the piece
  1376.      * @param boolean Whether to return shortcut king moves for castling
  1377.      * @return array|PEAR_Error
  1378.      * @throws GAMES_CHESS_ERROR_INVALID_COLOR
  1379.      * @throws GAMES_CHESS_ERROR_INVALID_SQUARE
  1380.      * @throws GAMES_CHESS_ERROR_INVALID_PIECE
  1381.      */
  1382.     function getPossibleMoves($piece$square$color = null$returnCastleMoves = true)
  1383.     {
  1384.         if (is_null($color)) {
  1385.             $color $this->_move;
  1386.         }
  1387.         $color strtoupper($color);
  1388.         if (!in_array($colorarray('W''B'))) {
  1389.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_COLOR,
  1390.                 array('color' => $color));
  1391.         }
  1392.         if (!preg_match('/^[a-h][1-8]$/'$square)) {
  1393.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_SQUARE,
  1394.                 array('square' => $square));
  1395.         }
  1396.         $piece strtoupper($piece);
  1397.         if (!in_array($piecearray('K''Q''B''N''R''P'))) {
  1398.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_PIECE,
  1399.                 array('piece' => $piece));
  1400.         }
  1401.         switch ($piece{
  1402.             case 'K' :
  1403.                 return $this->getPossibleKingMoves($square$color$returnCastleMoves);
  1404.             break;
  1405.             case 'Q' :
  1406.                 return $this->getPossibleQueenMoves($square$color);
  1407.             break;
  1408.             case 'B' :
  1409.                 return $this->getPossibleBishopMoves($square$color);
  1410.             break;
  1411.             case 'N' :
  1412.                 return $this->getPossibleKnightMoves($square$color);
  1413.             break;
  1414.             case 'R' :
  1415.                 return $this->getPossibleRookMoves($square$color);
  1416.             break;
  1417.             case 'P' :
  1418.                 return $this->getPossiblePawnMoves($square$color);
  1419.             break;
  1420.         }
  1421.     }
  1422.     
  1423.     /**
  1424.      * Get the set of squares that are diagonals from this square on an empty board.
  1425.      *
  1426.      * WARNING: assumes valid input
  1427.      * @param string [a-h][1-8]
  1428.      * @param boolean if true, simply returns an array of all squares
  1429.      * @return array Format:
  1430.      *
  1431.      *  <pre>
  1432.      *  array(
  1433.      *    'NE' => array(square, square),
  1434.      *    'NW' => array(square, square),
  1435.      *    'SE' => array(square, square),
  1436.      *    'SW' => array(square, square)
  1437.      *  )
  1438.      *  </pre>
  1439.      *
  1440.      *  Think of the diagonal directions as on a map.  squares are listed with
  1441.      *  closer squares first
  1442.      */
  1443.     function _getDiagonals($square$returnFlatArray = false)
  1444.     {
  1445.         $nw ($square{0!= 'a'&& ($square{1!= '8');
  1446.         $ne ($square{0!= 'h'&& ($square{1!= '8');
  1447.         $sw ($square{0!= 'a'&& ($square{1!= '1');
  1448.         $se ($square{0!= 'h'&& ($square{1!= '1');
  1449.         if ($nw{
  1450.             $nw = array();
  1451.             $i $square;
  1452.             while(ord($i{0}ord('a'&& ord($i{1}ord('8')) {
  1453.                 $i{0chr(ord($i{0}- 1);
  1454.                 $i{1chr(ord($i{1}+ 1);
  1455.                 $nw[$i;
  1456.             }
  1457.         }
  1458.         if ($ne{
  1459.             $ne = array();
  1460.             $i $square;
  1461.             while(ord($i{0}ord('h'&& ord($i{1}ord('8')) {
  1462.                 $i{0chr(ord($i{0}+ 1);
  1463.                 $i{1chr(ord($i{1}+ 1);
  1464.                 $ne[$i;
  1465.             }
  1466.         }
  1467.         if ($sw{
  1468.             $sw = array();
  1469.             $i $square;
  1470.             while(ord($i{0}ord('a'&& ord($i{1}ord('1')) {
  1471.                 $i{0chr(ord($i{0}- 1);
  1472.                 $i{1chr(ord($i{1}- 1);
  1473.                 $sw[$i;
  1474.             }
  1475.         }
  1476.         if ($se{
  1477.             $se = array();
  1478.             $i $square;
  1479.             while(ord($i{0}ord('h'&& ord($i{1}ord('1')) {
  1480.                 $i{0chr(ord($i{0}+ 1);
  1481.                 $i{1chr(ord($i{1}- 1);
  1482.                 $se[$i;
  1483.             }
  1484.         }
  1485.         if ($returnFlatArray{
  1486.             if (!$nw{
  1487.                 $nw = array();
  1488.             }
  1489.             if (!$sw{
  1490.                 $sw = array();
  1491.             }
  1492.             if (!$ne{
  1493.                 $ne = array();
  1494.             }
  1495.             if (!$se{
  1496.                 $se = array();
  1497.             }
  1498.             return array_merge($nearray_merge($nwarray_merge($se$sw)));
  1499.         }
  1500.         return array('NE' => $ne'NW' => $nw'SE' => $se'SW' => $sw);
  1501.     }
  1502.     
  1503.     /**
  1504.      * Get the set of squares that are diagonals from this square on an empty board.
  1505.      *
  1506.      * WARNING: assumes valid input
  1507.      * @param string [a-h][1-8]
  1508.      * @param boolean if true, simply returns an array of all squares
  1509.      * @return array Format:
  1510.      *
  1511.      *  <pre>
  1512.      *  array(
  1513.      *    'N' => array(square, square),
  1514.      *    'E' => array(square, square),
  1515.      *    'S' => array(square, square),
  1516.      *    'W' => array(square, square)
  1517.      *  )
  1518.      *  </pre>
  1519.      *
  1520.      *  Think of the horizontal directions as on a map.  squares are listed with
  1521.      *  closer squares first
  1522.      * @access protected
  1523.      */
  1524.     function _getRookSquares($square$returnFlatArray = false)
  1525.     {
  1526.         $n ($square{1!= '8');
  1527.         $e ($square{0!= 'h');
  1528.         $s ($square{1!= '1');
  1529.         $w ($square{0!= 'a');
  1530.         if ($n{
  1531.             $n = array();
  1532.             $i $square;
  1533.             while(ord($i{1}ord('8')) {
  1534.                 $i{1chr(ord($i{1}+ 1);
  1535.                 $n[$i;
  1536.             }
  1537.         }
  1538.         if ($e{
  1539.             $e = array();
  1540.             $i $square;
  1541.             while(ord($i{0}ord('h')) {
  1542.                 $i{0chr(ord($i{0}+ 1);
  1543.                 $e[$i;
  1544.             }
  1545.         }
  1546.         if ($s{
  1547.             $s = array();
  1548.             $i $square;
  1549.             while(ord($i{1}ord('1')) {
  1550.                 $i{1chr(ord($i{1}- 1);
  1551.                 $s[$i;
  1552.             }
  1553.         }
  1554.         if ($w{
  1555.             $w = array();
  1556.             $i $square;
  1557.             while(ord($i{0}ord('a')) {
  1558.                 $i{0chr(ord($i{0}- 1);
  1559.                 $w[$i;
  1560.             }
  1561.         }
  1562.         if ($returnFlatArray{
  1563.             if (!$n{
  1564.                 $n = array();
  1565.             }
  1566.             if (!$s{
  1567.                 $s = array();
  1568.             }
  1569.             if (!$e{
  1570.                 $e = array();
  1571.             }
  1572.             if (!$w{
  1573.                 $w = array();
  1574.             }
  1575.             return array_merge($narray_merge($sarray_merge($e$w)));
  1576.         }
  1577.         return array('N' => $n'E' => $e'S' => $s'W' => $w);
  1578.     }
  1579.     
  1580.     /**
  1581.      * Get all the squares a queen could go to on a blank board
  1582.      *
  1583.      * WARNING: assumes valid input
  1584.      * @return array combines contents of {@link _getRookSquares()} and
  1585.      *                {@link _getDiagonals()}
  1586.      * @param string [a-h][1-8]
  1587.      * @param boolean if true, simply returns an array of all squares
  1588.      * @access protected
  1589.      */
  1590.     function _getQueenSquares($square$returnFlatArray = false)
  1591.     {
  1592.         return array_merge($this->_getRookSquares($square$returnFlatArray),
  1593.                            $this->_getDiagonals($square$returnFlatArray));
  1594.     }
  1595.     
  1596.     /**
  1597.      * Get all the squares a knight could move to on an empty board
  1598.      *
  1599.      * WARNING: assumes valid input
  1600.      * @param string [a-h][1-8]
  1601.      * @param boolean if true, simply returns an array of all squares
  1602.      * @return array Returns an array of all the squares organized by compass
  1603.      *                point, that a knight can go to.  These squares may be indexed
  1604.      *                by any of WNW, NNW, NNE, ENE, ESE, SSE, SSW or WSW, unless
  1605.      *                $returnFlatArray is true, in which case an array of squares
  1606.      *                is returned
  1607.      * @access protected
  1608.      */
  1609.     function _getKnightSquares($square$returnFlatArray = false)
  1610.     {
  1611.         $squares = array();
  1612.         // west-northwest square
  1613.         if (ord($square{0}ord('b'&& $square{1< 8{
  1614.             $squares['WNW'chr(ord($square{0}- 2($square{1+ 1);
  1615.         }
  1616.         // north-northwest square
  1617.         if (ord($square{0}ord('a'&& $square{1< 7{
  1618.             $squares['NNW'chr(ord($square{0}- 1($square{1+ 2);
  1619.         }
  1620.         // north-northeast square
  1621.         if (ord($square{0}ord('h'&& $square{1< 7{
  1622.             $squares['NNE'chr(ord($square{0}+ 1($square{1+ 2);
  1623.         }
  1624.         // east-northeast square
  1625.         if (ord($square{0}ord('g'&& $square{1< 8{
  1626.             $squares['ENE'chr(ord($square{0}+ 2($square{1+ 1);
  1627.         }
  1628.         // east-southeast square
  1629.         if (ord($square{0}ord('g'&& $square{1> 1{
  1630.             $squares['ESE'chr(ord($square{0}+ 2($square{1- 1);
  1631.         }
  1632.         // south-southeast square
  1633.         if (ord($square{0}ord('h'&& $square{1> 2{
  1634.             $squares['SSE'chr(ord($square{0}+ 1($square{1- 2);
  1635.         }
  1636.         // south-southwest square
  1637.         if (ord($square{0}ord('a'&& $square{1> 2{
  1638.             $squares['SSW'chr(ord($square{0}- 1($square{1- 2);
  1639.         }
  1640.         // west-southwest square
  1641.         if (ord($square{0}ord('b'&& $square{1> 1{
  1642.             $squares['WSW'chr(ord($square{0}- 2($square{1- 1);
  1643.         }
  1644.         if ($returnFlatArray{
  1645.             return array_values($squares);
  1646.         }
  1647.         return $squares;
  1648.     }
  1649.     
  1650.     /**
  1651.      * Get a list of all the squares a king could castle to on an empty board
  1652.      *
  1653.      * WARNING: assumes valid input
  1654.      * @param string [a-h][1-8]
  1655.      * @return array 
  1656.      * @access protected
  1657.      * @since 0.7alpha
  1658.      */
  1659.     function _getCastleSquares($square)
  1660.     {
  1661.         $ret = array();
  1662.         if ($this->_move == 'W'{
  1663.             if ($square == 'e1' && $this->_WCastleK{
  1664.                 $ret['g1';
  1665.             }
  1666.             if ($square == 'e1' && $this->_WCastleQ{
  1667.                 $ret['c1';
  1668.             }
  1669.  
  1670.         else {
  1671.             if ($square == 'e8' && $this->_BCastleK{
  1672.                 $ret['g8';
  1673.             }
  1674.             if ($square == 'e8' && $this->_BCastleQ{
  1675.                 $ret['c8';
  1676.             }
  1677.         }
  1678.         return $ret;
  1679.     }
  1680.     
  1681.     /**
  1682.      * Get a list of all the squares a king could move to on an empty board
  1683.      *
  1684.      * WARNING: assumes valid input
  1685.      * @param string [a-h][1-8]
  1686.      * @return array 
  1687.      * @access protected
  1688.      */
  1689.     function _getKingSquares($square)
  1690.     {
  1691.         $squares = array();
  1692.         if (ord($square{0}ord('a')) {
  1693.             $squares[chr(ord($square{0}- 1$square{1};
  1694.             if ($square{1< 8{
  1695.                 $squares[chr(ord($square{0}- 1($square{1+ 1);
  1696.             }
  1697.             if ($square{1> 1{
  1698.                 $squares[chr(ord($square{0}- 1($square{1- 1);
  1699.             }
  1700.         }
  1701.         if (ord($square{0}ord('h')) {
  1702.             $squares[chr(ord($square{0}+ 1$square{1};
  1703.             if ($square{1< 8{
  1704.                 $squares[chr(ord($square{0}+ 1($square{1+ 1);
  1705.             }
  1706.             if ($square{1> 1{
  1707.                 $squares[chr(ord($square{0}+ 1($square{1- 1);
  1708.             }
  1709.         }
  1710.         if ($square{1> 1{
  1711.             $squares[$square{0($square{1- 1);
  1712.         }
  1713.         if ($square{1< 8{
  1714.             $squares[$square{0($square{1+ 1);
  1715.         }
  1716.         return $squares;
  1717.     }
  1718.     
  1719.     /**
  1720.      * Get the location of all pieces on the board of a certain color
  1721.      *
  1722.      * Default is the color that is about to move
  1723.      * @param W|B
  1724.      * @return array|PEAR_Error
  1725.      * @throws GAMES_CHESS_ERROR_INVALID_COLOR
  1726.      */
  1727.     function getPieceLocations($color = null)
  1728.     {
  1729.         if (is_null($color)) {
  1730.             $color $this->_move;
  1731.         }
  1732.         $color strtoupper($color);
  1733.         if (!in_array($colorarray('W''B'))) {
  1734.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_COLOR,
  1735.                 array('color' => $color));
  1736.         }
  1737.         return $this->_getAllPieceLocations($color);
  1738.     }
  1739.  
  1740.     /**
  1741.      * Get the location of every piece on the board of color $color
  1742.      * @param W|Bcolor of pieces to check
  1743.      * @return array 
  1744.      * @abstract
  1745.      * @access protected
  1746.      */
  1747.     function _getAllPieceLocations($color)
  1748.     {
  1749.         trigger_error('Error: do not use abstract Games_Chess class'E_USER_ERROR);
  1750.     }
  1751.     
  1752.     /**
  1753.      * Get all legal Knight moves (checking of the king is not taken into account)
  1754.      * @param string [a-h][1-8] Location of piece
  1755.      * @param W|Bcolor of piece, or null to use current piece to move
  1756.      * @return array 
  1757.      */
  1758.     function getPossibleKnightMoves($square$color = null)
  1759.     {
  1760.         if (is_null($color)) {
  1761.             $color $this->_move;
  1762.         }
  1763.         $color strtoupper($color);
  1764.         if (!in_array($colorarray('W''B'))) {
  1765.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_COLOR,
  1766.                 array('color' => $color));
  1767.         }
  1768.         if (!preg_match('/^[a-h][1-8]$/'$square)) {
  1769.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_SQUARE,
  1770.                 array('square' => $square));
  1771.         }
  1772.         $allmoves $this->_getKnightSquares($square);
  1773.         $mypieces $this->getPieceLocations($color);
  1774.         return array_values(array_diff($allmoves$mypieces));
  1775.     }
  1776.     
  1777.     /**
  1778.      * Get all legal Bishop moves (checking of the king is not taken into account)
  1779.      * @param string [a-h][1-8] Location of piece
  1780.      * @param W|Bcolor of piece, or null to use current piece to move
  1781.      * @return array 
  1782.      */
  1783.     function getPossibleBishopMoves($square$color = null)
  1784.     {
  1785.         if (is_null($color)) {
  1786.             $color $this->_move;
  1787.         }
  1788.         $color strtoupper($color);
  1789.         if (!in_array($colorarray('W''B'))) {
  1790.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_COLOR,
  1791.                 array('color' => $color));
  1792.         }
  1793.         if (!preg_match('/^[a-h][1-8]$/'$square)) {
  1794.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_SQUARE,
  1795.                 array('square' => $square));
  1796.         }
  1797.         $allmoves $this->_getDiagonals($square);
  1798.         $mypieces $this->getPieceLocations($color);
  1799.         foreach($mypieces as $loc{
  1800.             // go through the diagonals, and remove squares behind our own pieces
  1801.             // and also remove the piece's square
  1802.             // as bishops cannot pass through any pieces.
  1803.             if (is_array($allmoves['NW']&& in_array($loc$allmoves['NW'])) {
  1804.                 $pos array_search($loc$allmoves['NW']);
  1805.                 $allmoves['NW'array_slice($allmoves['NW']0$pos);
  1806.             }
  1807.             if (is_array($allmoves['NE']&& in_array($loc$allmoves['NE'])) {
  1808.                 $pos array_search($loc$allmoves['NE']);
  1809.                 $allmoves['NE'array_slice($allmoves['NE']0$pos);
  1810.             }
  1811.             if (is_array($allmoves['SE']&& in_array($loc$allmoves['SE'])) {
  1812.                 $pos array_search($loc$allmoves['SE']);
  1813.                 $allmoves['SE'array_slice($allmoves['SE']0$pos);
  1814.             }
  1815.             if (is_array($allmoves['SW']&& in_array($loc$allmoves['SW'])) {
  1816.                 $pos array_search($loc$allmoves['SW']);
  1817.                 $allmoves['SW'array_slice($allmoves['SW']0$pos);
  1818.             }
  1819.         }
  1820.         $enemypieces $this->getPieceLocations($color == 'W' 'B' 'W');
  1821.         foreach($enemypieces as $loc{
  1822.             // go through the diagonals, and remove squares behind enemy pieces
  1823.             // and include the piece's square, since we can capture it
  1824.             // but bishops cannot pass through any pieces.
  1825.             if (is_array($allmoves['NW']&& in_array($loc$allmoves['NW'])) {
  1826.                 $pos array_search($loc$allmoves['NW']);
  1827.                 $allmoves['NW'array_slice($allmoves['NW']0$pos + 1);
  1828.             }
  1829.             if (is_array($allmoves['NE']&& in_array($loc$allmoves['NE'])) {
  1830.                 $pos array_search($loc$allmoves['NE']);
  1831.                 $allmoves['NE'array_slice($allmoves['NE']0$pos + 1);
  1832.             }
  1833.             if (is_array($allmoves['SE']&& in_array($loc$allmoves['SE'])) {
  1834.                 $pos array_search($loc$allmoves['SE']);
  1835.                 $allmoves['SE'array_slice($allmoves['SE']0$pos + 1);
  1836.             }
  1837.             if (is_array($allmoves['SW']&& in_array($loc$allmoves['SW'])) {
  1838.                 $pos array_search($loc$allmoves['SW']);
  1839.                 $allmoves['SW'array_slice($allmoves['SW']0$pos + 1);
  1840.             }
  1841.         }
  1842.         $newmoves = array();
  1843.         foreach($allmoves as $key => $value{
  1844.             if (!$value{
  1845.                 continue;
  1846.             }
  1847.             $newmoves array_merge($newmoves$value);
  1848.         }
  1849.         return array_values(array_diff($newmoves$mypieces));
  1850.     }
  1851.  
  1852.     /**
  1853.      * Get all legal Rook moves (checking of the king is not taken into account)
  1854.      * @param string [a-h][1-8] Location of piece
  1855.      * @param W|Bcolor of piece, or null to use current piece to move
  1856.      * @return array 
  1857.      */
  1858.     function getPossibleRookMoves($square$color = null)
  1859.     {
  1860.         if (is_null($color)) {
  1861.             $color $this->_move;
  1862.         }
  1863.         $color strtoupper($color);
  1864.         if (!in_array($colorarray('W''B'))) {
  1865.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_COLOR,
  1866.                 array('color' => $color));
  1867.         }
  1868.         if (!preg_match('/^[a-h][1-8]$/'$square)) {
  1869.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_SQUARE,
  1870.                 array('square' => $square));
  1871.         }
  1872.         $allmoves $this->_getRookSquares($square);
  1873.         $mypieces $this->getPieceLocations($color);
  1874.         foreach($mypieces as $loc{
  1875.             // go through the rook squares, and remove squares behind our own pieces
  1876.             // and also remove the piece's square
  1877.             // as rooks cannot pass through any pieces.
  1878.             if (is_array($allmoves['N']&& in_array($loc$allmoves['N'])) {
  1879.                 $pos array_search($loc$allmoves['N']);
  1880.                 $allmoves['N'array_slice($allmoves['N']0$pos);
  1881.             }
  1882.             if (is_array($allmoves['E']&& in_array($loc$allmoves['E'])) {
  1883.                 $pos array_search($loc$allmoves['E']);
  1884.                 $allmoves['E'array_slice($allmoves['E']0$pos);
  1885.             }
  1886.             if (is_array($allmoves['S']&& in_array($loc$allmoves['S'])) {
  1887.                 $pos array_search($loc$allmoves['S']);
  1888.                 $allmoves['S'array_slice($allmoves['S']0$pos);
  1889.             }
  1890.             if (is_array($allmoves['W']&& in_array($loc$allmoves['W'])) {
  1891.                 $pos array_search($loc$allmoves['W']);
  1892.                 $allmoves['W'array_slice($allmoves['W']0$pos);
  1893.             }
  1894.         }
  1895.         $enemypieces $this->getPieceLocations($color == 'W' 'B' 'W');
  1896.         foreach($enemypieces as $loc{
  1897.             // go through the rook squares, and remove squares behind enemy pieces
  1898.             // and include the piece's square, since we can capture it
  1899.             // but rooks cannot pass through any pieces.
  1900.             if (is_array($allmoves['N']&& in_array($loc$allmoves['N'])) {
  1901.                 $pos array_search($loc$allmoves['N']);
  1902.                 $allmoves['N'array_slice($allmoves['N']0$pos + 1);
  1903.             }
  1904.             if (is_array($allmoves['E']&& in_array($loc$allmoves['E'])) {
  1905.                 $pos array_search($loc$allmoves['E']);
  1906.                 $allmoves['E'array_slice($allmoves['E']0$pos + 1);
  1907.             }
  1908.             if (is_array($allmoves['S']&& in_array($loc$allmoves['S'])) {
  1909.                 $pos array_search($loc$allmoves['S']);
  1910.                 $allmoves['S'array_slice($allmoves['S']0$pos + 1);
  1911.             }
  1912.             if (is_array($allmoves['W']&& in_array($loc$allmoves['W'])) {
  1913.                 $pos array_search($loc$allmoves['W']);
  1914.                 $allmoves['W'array_slice($allmoves['W']0$pos + 1);
  1915.             }
  1916.         }
  1917.         $newmoves = array();
  1918.         foreach($allmoves as $key => $value{
  1919.             if (!$value{
  1920.                 continue;
  1921.             }
  1922.             $newmoves array_merge($newmoves$value);
  1923.         }
  1924.         return array_values(array_diff($newmoves$mypieces));
  1925.     }
  1926.     
  1927.     /**
  1928.      * Get all legal Queen moves (checking of the king is not taken into account)
  1929.      * @param string [a-h][1-8] Location of piece
  1930.      * @param W|Bcolor of piece, or null to use current piece to move
  1931.      * @return array 
  1932.      */
  1933.     function getPossibleQueenMoves($square$color = null)
  1934.     {
  1935.         $a $this->getPossibleRookMoves($square$color);
  1936.         $b $this->getPossibleBishopMoves($square$color);
  1937.         if ($this->isError($a)) {
  1938.             return $a;
  1939.         }
  1940.         if ($this->isError($b)) {
  1941.             return $b;
  1942.         }
  1943.         return array_merge($a$b);
  1944.     }
  1945.     
  1946.     /**
  1947.      * Get all legal Pawn moves (checking of the king is not taken into account)
  1948.      * @param string [a-h][1-8] Location of piece
  1949.      * @param W|Bcolor of piece, or null to use current piece to move
  1950.      * @return array 
  1951.      */
  1952.     function getPossiblePawnMoves($square$color = null$enpassant = null)
  1953.     {
  1954.         if (is_null($color)) {
  1955.             $color $this->_move;
  1956.         }
  1957.         $color strtoupper($color);
  1958.         if (!in_array($colorarray('W''B'))) {
  1959.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_COLOR,
  1960.                 array('color' => $color));
  1961.         }
  1962.         if (!preg_match('/^[a-h][1-8]$/'$square)) {
  1963.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_SQUARE,
  1964.                 array('square' => $square));
  1965.         }
  1966.         if (is_null($enpassant)) {
  1967.             $enpassant $this->_enPassantSquare;
  1968.         }
  1969.         $mypieces $this->getPieceLocations($color);
  1970.         $enemypieces $this->getPieceLocations($color == 'W' 'B' 'W');
  1971.         $allmoves = array();
  1972.         if ($color == 'W'{
  1973.             $dbl '2';
  1974.             $direction = 1;
  1975.             // en passant calculation
  1976.             if ($square{1== '5' && in_array(ord($enpassant{0}ord($square{0}),
  1977.                                               array(1-1))) {
  1978.                 if (in_array(chr(ord($square{0}- 1. 5,
  1979.                              $enemypieces)) {
  1980.                     $allmoves[chr(ord($square{0}- 1. 6;
  1981.                 }
  1982.                 if (in_array(chr(ord($square{0}+ 1. 5,
  1983.                              $enemypieces)) {
  1984.                     $allmoves[chr(ord($square{0}+ 1. 6;
  1985.                 }
  1986.             }
  1987.         else {
  1988.             $dbl '7';
  1989.             $direction = -1;
  1990.             // en passant calculation
  1991.             if ($square{1== '4' && in_array(ord($enpassant{0}ord($square{0}),
  1992.                                               array(1-1))) {
  1993.                 if (in_array(chr(ord($square{0}- 1. 4,
  1994.                              $enemypieces)) {
  1995.                     $allmoves[chr(ord($square{0}- 1. 3;
  1996.                 }
  1997.                 if (in_array(chr(ord($square{0}+ 1. 4,
  1998.                              $enemypieces)) {
  1999.                     $allmoves[chr(ord($square{0}+ 1. 3;
  2000.                 }
  2001.             }
  2002.         }
  2003.         if (!in_array($square{0($square{1$direction)$mypieces&&
  2004.             !in_array($square{0($square{1$direction)$enemypieces))
  2005.         {
  2006.             $allmoves[$square{0($square{1$direction);
  2007.         }
  2008.         if (count($allmoves&& $square{1== $dbl{
  2009.             if (!in_array($square{0($square{1+ 2 * $direction)$mypieces&&
  2010.                 !in_array($square{0($square{1+ 2 * $direction)$enemypieces))
  2011.             {
  2012.                 $allmoves[$square{0($square{1+ 2 * $direction);
  2013.             }
  2014.         }
  2015.         if (in_array(chr(ord($square{0}- 1($square{1$direction),
  2016.                      $enemypieces)) {
  2017.             $allmoves[chr(ord($square{0}- 1($square{1$direction);
  2018.         }
  2019.         if (in_array(chr(ord($square{0}+ 1($square{1$direction),
  2020.                      $enemypieces)) {
  2021.             $allmoves[chr(ord($square{0}+ 1($square{1$direction);
  2022.         }
  2023.         return $allmoves;
  2024.     }
  2025.     
  2026.     /**
  2027.      * Get all legal King moves (checking of the king is not taken into account)
  2028.      * @param string [a-h][1-8] Location of piece
  2029.      * @param W|Bcolor of piece, or null to use current piece to move
  2030.      * @return array 
  2031.      * @since 0.7alpha castling is possible by moving the king to the destination square
  2032.      */
  2033.     function getPossibleKingMoves($square$color = null$returnCastleMoves = true)
  2034.     {
  2035.         if (is_null($color)) {
  2036.             $color $this->_move;
  2037.         }
  2038.         $color strtoupper($color);
  2039.         if (!in_array($colorarray('W''B'))) {
  2040.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_COLOR,
  2041.                 array('color' => $color));
  2042.         }
  2043.         if (!preg_match('/^[a-h][1-8]$/'$square)) {
  2044.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_SQUARE,
  2045.                 array('square' => $square));
  2046.         }
  2047.         $newret $castleret = array();
  2048.         $ret $this->_getKingSquares($square);
  2049.         if ($returnCastleMoves{
  2050.             $castleret $this->_getCastleSquares($square);
  2051.         }
  2052.         $mypieces $this->getPieceLocations($color);
  2053.         foreach ($ret as $square{
  2054.             if (!in_array($square$mypieces)) {
  2055.                 $newret[$square;
  2056.             }
  2057.         }
  2058.         return array_merge($newret$castleret);
  2059.     }
  2060.     
  2061.     /**
  2062.      * Return the color of a square (black or white)
  2063.      * @param string [a-h][1-8]
  2064.      * @access protected
  2065.      * @return B|W
  2066.      */
  2067.     function _getDiagonalColor($square)
  2068.     {
  2069.         $map = array('a' => 1'b' => 2'c' => 3'd' => 4'e' => 5'f' => 6,
  2070.             'g' => 7'h' => 8);
  2071.         $rank $map[$square{0}];
  2072.         $file $square{1};
  2073.         $color ($rank $file% 2;
  2074.         return $color 'W' 'B';
  2075.     }
  2076.     
  2077.     function getDiagonalColor($square)
  2078.     {
  2079.         if (!preg_match('/^[a-h][1-8]$/'$square)) {
  2080.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_SQUARE,
  2081.                 array('square' => $square));
  2082.         }
  2083.         return $this->_getDiagonalColor($square);
  2084.     }
  2085.     
  2086.     /**
  2087.      * Get all the squares between an attacker and the king where another
  2088.      * piece can interpose, or capture the checking piece
  2089.      *
  2090.      * @param string algebraic square of the checking piece
  2091.      * @param string algebraic square of the king
  2092.      */
  2093.     function _getPathToKing($checkee$king)
  2094.     {
  2095.         if ($this->_isKnight($this->_board[$checkee])) {
  2096.             return array($checkee);
  2097.         else {
  2098.             $path = array();
  2099.             // get all the paths 
  2100.             $kingpaths $this->_getQueenSquares($king);
  2101.             foreach ($kingpaths as $subpath{
  2102.                 if (!$subpath{
  2103.                     continue;
  2104.                 }
  2105.                 if (in_array($checkee$subpath)) {
  2106.                     foreach ($subpath as $square{
  2107.                         $path[$square;
  2108.                         if ($square == $checkee{
  2109.                             return $path;
  2110.                         }
  2111.                     }
  2112.                 }
  2113.             }
  2114.         }
  2115.     }
  2116.     
  2117.     /**
  2118.      * @param integer error code from {@link Chess.php}
  2119.      * @param array associative array of additional error message data
  2120.      * @uses PEAR::raiseError()
  2121.      * @return PEAR_Error 
  2122.      */
  2123.     function raiseError($code$extra = array())
  2124.     {
  2125.         require_once 'PEAR.php';
  2126.         return PEAR::raiseError($this->getMessage($code$extra)$code,
  2127.             nullnull$extra);
  2128.     }
  2129.     
  2130.     /**
  2131.      * Get an error message from the code
  2132.      *
  2133.      * Future versions of this method will be multi-language
  2134.      * @return string 
  2135.      * @param integer Error code
  2136.      * @param array extra information to pass for error message creation
  2137.      */
  2138.     function getMessage($code$extra)
  2139.     {
  2140.         $messages = array(
  2141.             GAMES_CHESS_ERROR_INVALID_SAN =>
  2142.                 '"%pgn%" is not a valid algebraic move',
  2143.             GAMES_CHESS_ERROR_FEN_COUNT =>
  2144.                 'Invalid FEN - "%fen%" has %sections% fields, 6 is required',
  2145.             GAMES_CHESS_ERROR_EMPTY_FEN => 
  2146.                 'Invalid FEN - "%fen%" has an empty field at index %section%',
  2147.             GAMES_CHESS_ERROR_FEN_TOOMUCH =>
  2148.                 'Invalid FEN - "%fen%" has too many pieces for a chessboard',
  2149.             GAMES_CHESS_ERROR_FEN_TOMOVEWRONG =>
  2150.                 'Invalid FEN - "%fen%" has invalid to-move indicator, must be "w" or "b"',
  2151.             GAMES_CHESS_ERROR_FEN_CASTLETOOLONG =>
  2152.                 'Invalid FEN - "%fen%" the castling indicator (KQkq) is too long',
  2153.             GAMES_CHESS_ERROR_FEN_CASTLEWRONG =>
  2154.                 'Invalid FEN - "%fen%" the castling indicator "%castle%" is invalid',
  2155.             GAMES_CHESS_ERROR_FEN_INVALID_EP =>
  2156.                 'Invalid FEN - "%fen%" the en passant square indicator "%enpassant%" is invalid',
  2157.             GAMES_CHESS_ERROR_FEN_INVALID_PLY =>
  2158.                 'Invalid FEN - "%fen%" the half-move ply count "%ply%" is not a number',
  2159.             GAMES_CHESS_ERROR_FEN_INVALID_MOVENUMBER =>
  2160.                 'Invalid FEN - "%fen%" the move number "%movenumber%" is not a number',
  2161.             GAMES_CHESS_ERROR_IN_CHECK =>
  2162.                 'The king is in check and that move does not prevent check',
  2163.             GAMES_CHESS_ERROR_CANT_CK =>
  2164.                 'Can\'t castle kingside, either the king or rook has moved',
  2165.             GAMES_CHESS_ERROR_CK_PIECES_IN_WAY =>
  2166.                 'Can\'t castle kingside, pieces are in the way',
  2167.             GAMES_CHESS_ERROR_CANT_CQ =>
  2168.                 'Can\'t castle queenside, either the king or rook has moved',
  2169.             GAMES_CHESS_ERROR_CQ_PIECES_IN_WAY =>
  2170.                 'Can\'t castle queenside, pieces are in the way',
  2171.             GAMES_CHESS_ERROR_CASTLE_WOULD_CHECK =>
  2172.                 'Can\'t castle, it would put the king in check',
  2173.             GAMES_CHESS_ERROR_MOVE_WOULD_CHECK =>
  2174.                 'That move would put the king in check',
  2175.             GAMES_CHESS_ERROR_STILL_IN_CHECK =>
  2176.                 'The move does not remove the check on the king',
  2177.             GAMES_CHESS_ERROR_CANT_CAPTURE_OWN =>
  2178.                 'Cannot capture your own pieces',
  2179.             GAMES_CHESS_ERROR_NO_PIECE =>
  2180.                 'There is no piece on square %square%',
  2181.             GAMES_CHESS_ERROR_WRONG_COLOR =>
  2182.                 'The piece on %square% is not your piece',
  2183.             GAMES_CHESS_ERROR_CANT_MOVE_THAT_WAY =>
  2184.                 'The piece on %from% cannot move to %to%',
  2185.             GAMES_CHESS_ERROR_MULTIPIECE =>
  2186.                 'Too many %color% %piece%s',
  2187.             GAMES_CHESS_ERROR_FEN_MULTIPIECE =>
  2188.                 'Invalid FEN - "%fen%" Too many %color% %piece%s',
  2189.             GAMES_CHESS_ERROR_DUPESQUARE =>
  2190.                 '%dpiece% already occupies square %square%, cannot be replaced by %piece%',
  2191.             GAMES_CHESS_ERROR_FEN_INVALIDPIECE =>
  2192.                 'Invalid FEN - "%fen%" the character "%fenchar%" is not a valid piece, separator or number',
  2193.             GAMES_CHESS_ERROR_FEN_TOOLITTLE =>
  2194.                 'Invalid FEN - "%fen%" has too few pieces for a chessboard',
  2195.             GAMES_CHESS_ERROR_INVALID_COLOR =>
  2196.                 '"%color%" is not a valid piece color, try W or B',
  2197.             GAMES_CHESS_ERROR_INVALID_SQUARE =>
  2198.                 '"%square%" is not a valid square, must be between a1 and h8',
  2199.             GAMES_CHESS_ERROR_INVALID_PIECE =>
  2200.                 '"%piece%" is not a valid piece, must be P, Q, R, N, K or B',
  2201.             GAMES_CHESS_ERROR_INVALID_PROMOTE =>
  2202.                 '"%piece%" is not a valid promotion piece, must be Q, R, N or B',
  2203.             GAMES_CHESS_ERROR_TOO_AMBIGUOUS =>
  2204.                 '"%san%" does not resolve ambiguity between %piece%s on %squares%',
  2205.             GAMES_CHESS_ERROR_NOPIECE_CANDOTHAT =>
  2206.                 'There are no %color% pieces on the board that can do "%san%"',
  2207.             GAMES_CHESS_ERROR_MOVE_MUST_CAPTURE =>
  2208.                 'Capture is possible, "%san%" does not capture',
  2209.             GAMES_CHESS_ERROR_NOPIECES_TOPLACE =>
  2210.                 'There are no captured %color% %piece%s available to place',
  2211.             GAMES_CHESS_ERROR_PIECEINTHEWAY =>
  2212.                 'There is already a piece on %square%, cannot place another there',
  2213.             GAMES_CHESS_ERROR_MUSTPROMOTE =>
  2214.                 'Placing a piece on the back rank requires promotion, use %san%=Q or any of R, N, B, Q',
  2215.         );
  2216.         $message $messages[$code];
  2217.         foreach ($extra as $key => $value{
  2218.             if (strpos($key'piece'!== false{
  2219.                 switch(strtoupper($value)) {
  2220.                     case 'R' :
  2221.                         $value 'Rook';
  2222.                     break;
  2223.                     case 'Q' :
  2224.                         $value 'Queen';
  2225.                     break;
  2226.                     case 'P' :
  2227.                         $value 'Pawn';
  2228.                     break;
  2229.                     case 'B' :
  2230.                         $value 'Bishop';
  2231.                     break;
  2232.                     case 'K' :
  2233.                         $value 'King';
  2234.                     break;
  2235.                     case 'N' :
  2236.                         $value 'Knight';
  2237.                     break;
  2238.                 }
  2239.             }
  2240.             if ($key == 'color'{
  2241.                 switch($value{
  2242.                     case 'W' :
  2243.                         $value 'White';
  2244.                     break;
  2245.                     case 'B' :
  2246.                         $value 'Black';
  2247.                     break;
  2248.                 }
  2249.             }
  2250.             $message str_replace('%'.$key.'%'$value$message);
  2251.         }
  2252.         return $message;
  2253.     }
  2254.     
  2255.     /**
  2256.      * Determines whether the data returned from a method is a PEAR-related
  2257.      * error class
  2258.      * @param mixed 
  2259.      * @return boolean 
  2260.      */
  2261.     function isError($err)
  2262.     {
  2263.         return is_a($err'PEAR_Error');
  2264.     }
  2265.     
  2266.     /**
  2267.      * Begin a chess piece transaction
  2268.      *
  2269.      * Transactions are used to attempt moves that may be revoked later, especially
  2270.      * in methods like {@link inCheckMate()}
  2271.      */
  2272.     function startTransaction()
  2273.     {
  2274.         $state get_object_vars($this);
  2275.         unset($state['_saveState']);
  2276.         if (!is_array($this->_saveState)) {
  2277.             $this->_saveState = array();
  2278.         }
  2279.         array_push($this->_saveState$state);
  2280.     }
  2281.     
  2282.     /**
  2283.      * Remove any possibility of undo.
  2284.      */
  2285.     function commitTransaction()
  2286.     {
  2287.         array_pop($this->_saveState);
  2288.     }
  2289.     
  2290.     /**
  2291.      * Undo any changes to state since {@link startTransaction()} was first used
  2292.      */
  2293.     function rollbackTransaction()
  2294.     {
  2295.         $vars array_pop($this->_saveState);
  2296.         foreach($vars as $name => $value{
  2297.             $this->$name $value;
  2298.         }
  2299.     }
  2300. }
  2301. ?>

Documentation generated on Mon, 11 Mar 2019 13:52:42 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.