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 first or back rank is attempted
  226.  */
  227. define('GAMES_CHESS_ERROR_CANT_PLACE_18'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][2-7])$/'$move$match)) {
  940.             $res = array(
  941.                 'piece' => $match[1],
  942.                 'square' => $match[2],
  943.             );
  944.             return array(GAMES_CHESS_PIECEPLACEMENT => $res);
  945.         // error
  946.         elseif (preg_match('/^([P])@([a-h][18])$/'$move$match)) {
  947.             return $this->raiseError(GAMES_CHESS_ERROR_CANT_PLACE_18array('san' => $move));
  948.         // error
  949.         else {
  950.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_SAN,
  951.                 array('pgn' => $move));
  952.         }
  953.     }
  954.     
  955.     
  956.     /**
  957.      * Set up the board with the starting position
  958.      *
  959.      * Must be overridden in child classes
  960.      * @abstract
  961.      * @access protected
  962.      */
  963.     function _setupStartingPosition()
  964.     {
  965.         trigger_error("Error: do not use abstract Games_Chess class"E_USER_ERROR);
  966.     }
  967.     
  968.     /**
  969.      * Parse a Farnsworth-Edwards Notation (FEN) chessboard position string, and
  970.      * set up the chessboard with this position
  971.      * @param string 
  972.      * @access private
  973.      */
  974.     function _parseFen($fen)
  975.     {
  976.         $splitfen explode(' '$fen);
  977.         if (count($splitfen!= 6{
  978.             return $this->raiseError(GAMES_CHESS_ERROR_FEN_COUNT,
  979.                 array('fen' => $fen'sections' => count($splitfen)));
  980.         }
  981.  
  982.         foreach($splitfen as $index => $test{
  983.             if ($test == ''{
  984.                 return $this->raiseError(GAMES_CHESS_ERROR_EMPTY_FEN,
  985.                     array('fen' => $fen'section' => $index));
  986.             }
  987.         }
  988.  
  989.         $this->blankBoard();
  990.         $loc 'a8';
  991.         $idx = 0;
  992.         $FEN $splitfen[0];
  993.  
  994.         // parse position section
  995.         while ($idx strlen($FEN)) {
  996.             $c $FEN{$idx};
  997.             switch ($c{
  998.                 case "K" :
  999.                 case "Q" :
  1000.                 case "R" :
  1001.                 case "B" :
  1002.                 case "N" :
  1003.                 case "P" :
  1004.                     $err $this->addPiece('W'$c$loc);
  1005.                     if ($this->isError($err)) {
  1006.                         if ($err->getCode(== GAMES_CHESS_ERROR_MULTIPIECE{
  1007.                             return $this->raiseError(GAMES_CHESS_ERROR_FEN_MULTIPIECE,
  1008.                             array('fen' => $fen'color' => 'W''piece' => $c));
  1009.                         else {
  1010.                             return $err;
  1011.                         }
  1012.                     }
  1013.                 break;
  1014.                 case "k" :
  1015.                 case "q" :
  1016.                 case "r" :
  1017.                 case "b" :
  1018.                 case "n" :
  1019.                 case "p" :
  1020.                     $err $this->addPiece('B'strtoupper($c)$loc);
  1021.                     if ($this->isError($err)) {
  1022.                         if ($err->getCode(== GAMES_CHESS_ERROR_MULTIPIECE{
  1023.                             return $this->raiseError(GAMES_CHESS_ERROR_FEN_MULTIPIECE,
  1024.                             array('fen' => $fen'color' => 'B''piece' => $c));
  1025.                         else {
  1026.                             return $err;
  1027.                         }
  1028.                     }
  1029.                 break;
  1030.  
  1031.                 case "1" :
  1032.                 case "2" :
  1033.                 case "3" :
  1034.                 case "4" :
  1035.                 case "5" :
  1036.                 case "6" :
  1037.                 case "7" :
  1038.                 case "8" :
  1039.                     $loc{0chr(ord($loc{0}($c - 1));
  1040.                 break;
  1041.                 case "/" :
  1042.                     $loc{1$loc{1- 1;
  1043.                     $loc{0'a';
  1044.                     $idx++;
  1045.                     continue 2;
  1046.                 break;
  1047.                 default :
  1048.                     return $this->raiseError(GAMES_CHESS_ERROR_FEN_INVALIDPIECE,
  1049.                         array('fen' => $fen'fenchar' => $c));
  1050.                 break;
  1051.             }
  1052.             $idx++;
  1053.             $loc{0chr(ord($loc{0}+ 1);
  1054.             if (ord($loc{0}ord('h')) {
  1055.                 if (strlen($FEN$idx && $FEN{$idx!= '/'{
  1056.                     return $this->raiseError(GAMES_CHESS_ERROR_FEN_TOOMUCH,
  1057.                         array('fen' => $fen));
  1058.                 }
  1059.             }
  1060.         }
  1061.         if ($loc != 'i1'{
  1062.             return $this->raiseError(GAMES_CHESS_ERROR_FEN_TOOLITTLE,
  1063.                 array('fen' => $fen));
  1064.         }
  1065.  
  1066.         // parse who's to move
  1067.         if (!in_array($splitfen[1]array('w''b''W''B'))) {
  1068.             return $this->raiseError(GAMES_CHESS_ERROR_FEN_TOMOVEWRONG,
  1069.                 array('fen' => $fen'tomove' => $splitfen[1]));
  1070.         }
  1071.         $this->_move = strtoupper($splitfen[1]);
  1072.  
  1073.         // parse castling rights
  1074.         if (strlen($splitfen[2]> 4{
  1075.             return $this->raiseError(GAMES_CHESS_ERROR_FEN_CASTLETOOLONG,
  1076.                 array('fen' => $fen'castle' => $splitfen[2]));
  1077.         }
  1078.         $this->_WCastleQ = false;
  1079.         $this->_WCastleK = false;
  1080.         $this->_BCastleQ = false;
  1081.         $this->_BCastleK = false;
  1082.         if ($splitfen[2!= '-'{
  1083.             for ($i = 0; $i < 4; $i++{
  1084.                 if ($i >= strlen($splitfen[2])) {
  1085.                     continue;
  1086.                 }
  1087.                 switch ($splitfen[2]{$i}{
  1088.                     case 'K' :
  1089.                         $this->_WCastleK = true;
  1090.                     break;
  1091.                     case 'Q' :
  1092.                         $this->_WCastleQ = true;
  1093.                     break;
  1094.                     case 'k' :
  1095.                         $this->_BCastleK = true;
  1096.                     break;
  1097.                     case 'q' :
  1098.                         $this->_BCastleQ = true;
  1099.                     break;
  1100.                     default:
  1101.                         return $this->raiseError(GAMES_CHESS_ERROR_FEN_CASTLEWRONG,
  1102.                             array('fen' => $fen'castle' => $splitfen[2]{$i}));
  1103.                     break;
  1104.                 }
  1105.             }
  1106.         }
  1107.  
  1108.         // parse en passant square
  1109.         $this->_enPassantSquare = '-';
  1110.         if ($splitfen[3!= '-'{
  1111.             if (!preg_match('/^[a-h][36]$/'$splitfen[3])) {
  1112.                 return $this->raiseError(GAMES_CHESS_ERROR_FEN_INVALID_EP,
  1113.                     array('fen' => $fen'enpassant' => $splitfen[3]));
  1114.             }
  1115.             $this->_enPassantSquare = $splitfen[3];
  1116.         }
  1117.  
  1118.         // parse half moves since last pawn move or capture
  1119.         if (!is_numeric($splitfen[4])) {
  1120.             return $this->raiseError(GAMES_CHESS_ERROR_FEN_INVALID_PLY,
  1121.                 array('fen' => $fen'ply' => $splitfen[4]));
  1122.         }
  1123.         $this->_halfMoves = $splitfen[4];
  1124.  
  1125.         // parse move number
  1126.         if (!is_numeric($splitfen[5])) {
  1127.             return $this->raiseError(GAMES_CHESS_ERROR_FEN_INVALID_MOVENUMBER,
  1128.                 array('fen' => $fen'movenumber' => $splitfen[5]));
  1129.         }
  1130.         $this->_moveNumber = $splitfen[5];
  1131.         return true;
  1132.     }
  1133.     
  1134.     /**
  1135.      * Validate a move
  1136.      * @param array parsed move array from {@link _parsedMove()}
  1137.      * @return true|PEAR_Error
  1138.      * @throws GAMES_CHESS_ERROR_IN_CHECK
  1139.      * @throws GAMES_CHESS_ERROR_CANT_CK
  1140.      * @throws GAMES_CHESS_ERROR_CK_PIECES_IN_WAY
  1141.      * @throws GAMES_CHESS_ERROR_CANT_CQ
  1142.      * @throws GAMES_CHESS_ERROR_CQ_PIECES_IN_WAY
  1143.      * @throws GAMES_CHESS_ERROR_CASTLE_WOULD_CHECK
  1144.      * @throws GAMES_CHESS_ERROR_CANT_CAPTURE_OWN
  1145.      * @throws GAMES_CHESS_ERROR_STILL_IN_CHECK
  1146.      * @throws GAMES_CHESS_ERROR_MOVE_WOULD_CHECK
  1147.      * @access protected
  1148.      */
  1149.     function _validMove($move)
  1150.     {
  1151.         list($type$infoeach($move);
  1152.         $this->startTransaction();
  1153.         $valid = false;
  1154.         switch ($type{
  1155.             case GAMES_CHESS_CASTLE :
  1156.                 if ($this->inCheck($this->_move)) {
  1157.                     $this->rollbackTransaction();
  1158.                     return $this->raiseError(GAMES_CHESS_ERROR_IN_CHECK);
  1159.                 }
  1160.                 if ($info == 'K'{
  1161.                     if ($this->_move == 'W'{
  1162.                         if (!$this->_WCastleK{
  1163.                             $this->rollbackTransaction();
  1164.                             return $this->raiseError(GAMES_CHESS_ERROR_CANT_CK);
  1165.                         }
  1166.                         if ($this->_board['f1'!= 'f1' || $this->_board['g1'!= 'g1'{
  1167.                             $this->rollbackTransaction();
  1168.                             return $this->raiseError(GAMES_CHESS_ERROR_CK_PIECES_IN_WAY);
  1169.                         }
  1170.                         $kingsquares = array('f1''g1');
  1171.                         $on 'e1';
  1172.                     else {
  1173.                         if (!$this->_BCastleK{
  1174.                             $this->rollbackTransaction();
  1175.                             return $this->raiseError(GAMES_CHESS_ERROR_CANT_CK);
  1176.                         }
  1177.                         if ($this->_board['f8'!= 'f8' || $this->_board['g8'!= 'g8'{
  1178.                             $this->rollbackTransaction();
  1179.                             return $this->raiseError(GAMES_CHESS_ERROR_CK_PIECES_IN_WAY);
  1180.                         }
  1181.                         $kingsquares = array('f8''g8');
  1182.                         $on 'e8';
  1183.                     }
  1184.                 else {
  1185.                     if ($this->_move == 'W'{
  1186.                         if (!$this->_WCastleQ{
  1187.                             $this->rollbackTransaction();
  1188.                             return $this->raiseError(GAMES_CHESS_ERROR_CANT_CQ);
  1189.                         }
  1190.                         if ($this->_board['d1'!= 'd1' ||
  1191.                               $this->_board['c1'!= 'c1' ||
  1192.                               $this->_board['b1'!= 'b1'{
  1193.                             $this->rollbackTransaction();
  1194.                             return $this->raiseError(GAMES_CHESS_ERROR_CQ_PIECES_IN_WAY);
  1195.                         }
  1196.                         $kingsquares = array('d1''c1');
  1197.                         $on 'e1';
  1198.                     else {
  1199.                         if (!$this->_BCastleQ{
  1200.                             $this->rollbackTransaction();
  1201.                             return $this->raiseError(GAMES_CHESS_ERROR_CANT_CQ);
  1202.                         }
  1203.                         if ($this->_board['d8'!= 'd8' ||
  1204.                               $this->_board['c8'!= 'c8' ||
  1205.                               $this->_board['b8'!= 'b8'{
  1206.                             $this->rollbackTransaction();
  1207.                             return $this->raiseError(GAMES_CHESS_ERROR_CQ_PIECES_IN_WAY);
  1208.                         }
  1209.                         $kingsquares = array('d8''c8');
  1210.                         $on 'e8';
  1211.                     }
  1212.                 }
  1213.                 
  1214.                 // check every square the king could move to and make sure
  1215.                 // we wouldn't be in check
  1216.                 foreach ($kingsquares as $square{
  1217.                     $this->_moveAlgebraic($on$square);
  1218.                     if ($this->inCheck($this->_move)) {
  1219.                         $this->rollbackTransaction();
  1220.                         return $this->raiseError(GAMES_CHESS_ERROR_CASTLE_WOULD_CHECK);
  1221.                     }
  1222.                     $on $square;
  1223.                 }
  1224.                 $valid = true;
  1225.             break;
  1226.             case GAMES_CHESS_PIECEMOVE :
  1227.             case GAMES_CHESS_PAWNMOVE :
  1228.                 if (!$this->isError($piecesq $this->_getSquareFromParsedMove($info))) {
  1229.                     $wasinCheck $this->inCheck($this->_move);
  1230.                     $piece $this->_board[$info['square']];
  1231.                     if ($info['takes'&& $this->_board[$info['square']] ==
  1232.                           $info['square']{
  1233.                         if (!($info['square'== $this->_enPassantSquare &&
  1234.                               $info['piece'== 'P')) {
  1235.                             return $this->raiseError(GAMES_CHESS_ERROR_NO_PIECE,
  1236.                                 array('square' => $info['square']));
  1237.                         }
  1238.                     }
  1239.                     $this->_moveAlgebraic($piecesq$info['square']);
  1240.                     $valid !$this->inCheck($this->_move);
  1241.                     if ($wasinCheck && !$valid{
  1242.                         $this->rollbackTransaction();
  1243.                         return $this->raiseError(GAMES_CHESS_ERROR_STILL_IN_CHECK);
  1244.                     elseif (!$valid{
  1245.                         $this->rollbackTransaction();
  1246.                         return $this->raiseError(GAMES_CHESS_ERROR_MOVE_WOULD_CHECK);
  1247.                     }
  1248.                 else {
  1249.                     $this->rollbackTransaction();
  1250.                     return $piecesq;
  1251.                 }
  1252.             break;
  1253.         }
  1254.         $this->rollbackTransaction();
  1255.         return $valid;
  1256.     }
  1257.     
  1258.     /**
  1259.      * Convert a starting and ending algebraic square into SAN
  1260.      * @access protected
  1261.      * @param string [a-h][1-8] square piece is on
  1262.      * @param string [a-h][1-8] square piece moves to
  1263.      * @param string Q|R|B|N
  1264.      * @return string|PEAR_Error
  1265.      * @throws GAMES_CHESS_ERROR_INVALID_PROMOTE
  1266.      * @throws GAMES_CHESS_ERROR_INVALID_SQUARE
  1267.      * @throws GAMES_CHESS_ERROR_NO_PIECE
  1268.      * @throws GAMES_CHESS_ERROR_WRONG_COLOR
  1269.      * @throws GAMES_CHESS_ERROR_CANT_MOVE_THAT_WAY
  1270.      */
  1271.     function _convertSquareToSAN($from$to$promote '')
  1272.     {
  1273.         if ($promote == ''{
  1274.             $promote 'Q';
  1275.         }
  1276.         $promote strtoupper($promote);
  1277.         if (!in_array($promotearray('Q''B''N''R'))) {
  1278.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_PROMOTE,
  1279.                 array('piece' => $promote));
  1280.         }
  1281.         $SAN '';
  1282.         if (!preg_match('/^[a-h][1-8]$/'$from)) {
  1283.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_SQUARE,
  1284.                 array('square' => $from));
  1285.         }
  1286.         if (!preg_match('/^[a-h][1-8]$/'$to)) {
  1287.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_SQUARE,
  1288.                 array('square' => $to));
  1289.         }
  1290.         $piece $this->_squareToPiece($from);
  1291.         if (!$piece{
  1292.             return $this->raiseError(GAMES_CHESS_ERROR_NO_PIECE,
  1293.                 array('square' => $from));
  1294.         }
  1295.         if ($piece['color'!= $this->_move{
  1296.             return $this->raiseError(GAMES_CHESS_ERROR_WRONG_COLOR,
  1297.                 array('square' => $from));
  1298.         }
  1299.         $moves $this->getPossibleMoves($piece['piece']$from$piece['color']);
  1300.         if (!in_array($to$moves)) {
  1301.             return $this->raiseError(GAMES_CHESS_ERROR_CANT_MOVE_THAT_WAY,
  1302.                 array('from' => $from'to' => $to));
  1303.         }
  1304.         if ($piece['piece'== 'K' && !in_array($to$this->_getKingSquares($from))) {
  1305.             // this is a castling attempt
  1306.             if ($to{0== 'g'{
  1307.                 return 'O-O';
  1308.             else {
  1309.                 return 'O-O-O';
  1310.             }
  1311.         }
  1312.         $others = array();
  1313.         if ($piece['piece'!= 'K' && $piece['piece'!= 'P'{
  1314.             $others $this->_getAllPieceSquares($piece['piece'],
  1315.                                                  $piece['color']$from);
  1316.         }
  1317.         $disambiguate '';
  1318.         $ambiguous = array();
  1319.         if (count($others)) {
  1320.             foreach ($others as $square{
  1321.                 if (in_array($to$this->getPossibleMoves($piece['piece']$square,
  1322.                                                           $piece['color']))) {
  1323.                     // other pieces can move to this square - need to disambiguate
  1324.                     $ambiguous[$square;
  1325.                 }
  1326.             }
  1327.         }
  1328.         if (count($ambiguous== 1{
  1329.             if ($ambiguous[0]{0!= $from{0}{
  1330.                 $disambiguate $from{0};
  1331.             elseif ($ambiguous[0]{1!= $from{1}{
  1332.                 $disambiguate $from{1};
  1333.             else {
  1334.                 $disambiguate $from;
  1335.             }
  1336.         elseif (count($ambiguous)) {
  1337.             $disambiguate $from;
  1338.         }
  1339.         if ($piece['piece'== 'P'{
  1340.             if ($from{0!= $to{0}{
  1341.                 $SAN $from{0};
  1342.             }
  1343.         else {
  1344.             $SAN $piece['piece'];
  1345.         }
  1346.         $SAN .= $disambiguate;
  1347.         if ($this->_board[$to!= $to{
  1348.             $SAN .= 'x';
  1349.         else {
  1350.             if ($piece['piece'== 'P' && $to == $this->_enPassantSquare{
  1351.                 $SAN .= 'x';
  1352.             }
  1353.         }
  1354.         $SAN .= $to;
  1355.         if ($piece['piece'== 'P' && ($to{1== '1' || $to{1== '8')) {
  1356.             $SAN .= '=' $promote;
  1357.         }
  1358.         return $SAN;
  1359.     }
  1360.     
  1361.     /**
  1362.      * Get a list of all possible theoretical squares a piece of this nature
  1363.      * and color could move to with the current board and game setup.
  1364.      *
  1365.      * This method will return all valid moves without determining the presence
  1366.      * of check
  1367.      * @param K|P|Q|R|B|NPiece name
  1368.      * @param string [a-h][1-8] algebraic location of the piece
  1369.      * @param B|Wcolor of the piece
  1370.      * @param boolean Whether to return shortcut king moves for castling
  1371.      * @return array|PEAR_Error
  1372.      * @throws GAMES_CHESS_ERROR_INVALID_COLOR
  1373.      * @throws GAMES_CHESS_ERROR_INVALID_SQUARE
  1374.      * @throws GAMES_CHESS_ERROR_INVALID_PIECE
  1375.      */
  1376.     function getPossibleMoves($piece$square$color = null$returnCastleMoves = true)
  1377.     {
  1378.         if (is_null($color)) {
  1379.             $color $this->_move;
  1380.         }
  1381.         $color strtoupper($color);
  1382.         if (!in_array($colorarray('W''B'))) {
  1383.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_COLOR,
  1384.                 array('color' => $color));
  1385.         }
  1386.         if (!preg_match('/^[a-h][1-8]$/'$square)) {
  1387.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_SQUARE,
  1388.                 array('square' => $square));
  1389.         }
  1390.         $piece strtoupper($piece);
  1391.         if (!in_array($piecearray('K''Q''B''N''R''P'))) {
  1392.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_PIECE,
  1393.                 array('piece' => $piece));
  1394.         }
  1395.         switch ($piece{
  1396.             case 'K' :
  1397.                 return $this->getPossibleKingMoves($square$color$returnCastleMoves);
  1398.             break;
  1399.             case 'Q' :
  1400.                 return $this->getPossibleQueenMoves($square$color);
  1401.             break;
  1402.             case 'B' :
  1403.                 return $this->getPossibleBishopMoves($square$color);
  1404.             break;
  1405.             case 'N' :
  1406.                 return $this->getPossibleKnightMoves($square$color);
  1407.             break;
  1408.             case 'R' :
  1409.                 return $this->getPossibleRookMoves($square$color);
  1410.             break;
  1411.             case 'P' :
  1412.                 return $this->getPossiblePawnMoves($square$color);
  1413.             break;
  1414.         }
  1415.     }
  1416.     
  1417.     /**
  1418.      * Get the set of squares that are diagonals from this square on an empty board.
  1419.      *
  1420.      * WARNING: assumes valid input
  1421.      * @param string [a-h][1-8]
  1422.      * @param boolean if true, simply returns an array of all squares
  1423.      * @return array Format:
  1424.      *
  1425.      *  <pre>
  1426.      *  array(
  1427.      *    'NE' => array(square, square),
  1428.      *    'NW' => array(square, square),
  1429.      *    'SE' => array(square, square),
  1430.      *    'SW' => array(square, square)
  1431.      *  )
  1432.      *  </pre>
  1433.      *
  1434.      *  Think of the diagonal directions as on a map.  squares are listed with
  1435.      *  closer squares first
  1436.      */
  1437.     function _getDiagonals($square$returnFlatArray = false)
  1438.     {
  1439.         $nw ($square{0!= 'a'&& ($square{1!= '8');
  1440.         $ne ($square{0!= 'h'&& ($square{1!= '8');
  1441.         $sw ($square{0!= 'a'&& ($square{1!= '1');
  1442.         $se ($square{0!= 'h'&& ($square{1!= '1');
  1443.         if ($nw{
  1444.             $nw = array();
  1445.             $i $square;
  1446.             while(ord($i{0}ord('a'&& ord($i{1}ord('8')) {
  1447.                 $i{0chr(ord($i{0}- 1);
  1448.                 $i{1chr(ord($i{1}+ 1);
  1449.                 $nw[$i;
  1450.             }
  1451.         }
  1452.         if ($ne{
  1453.             $ne = array();
  1454.             $i $square;
  1455.             while(ord($i{0}ord('h'&& ord($i{1}ord('8')) {
  1456.                 $i{0chr(ord($i{0}+ 1);
  1457.                 $i{1chr(ord($i{1}+ 1);
  1458.                 $ne[$i;
  1459.             }
  1460.         }
  1461.         if ($sw{
  1462.             $sw = array();
  1463.             $i $square;
  1464.             while(ord($i{0}ord('a'&& ord($i{1}ord('1')) {
  1465.                 $i{0chr(ord($i{0}- 1);
  1466.                 $i{1chr(ord($i{1}- 1);
  1467.                 $sw[$i;
  1468.             }
  1469.         }
  1470.         if ($se{
  1471.             $se = array();
  1472.             $i $square;
  1473.             while(ord($i{0}ord('h'&& ord($i{1}ord('1')) {
  1474.                 $i{0chr(ord($i{0}+ 1);
  1475.                 $i{1chr(ord($i{1}- 1);
  1476.                 $se[$i;
  1477.             }
  1478.         }
  1479.         if ($returnFlatArray{
  1480.             if (!$nw{
  1481.                 $nw = array();
  1482.             }
  1483.             if (!$sw{
  1484.                 $sw = array();
  1485.             }
  1486.             if (!$ne{
  1487.                 $ne = array();
  1488.             }
  1489.             if (!$se{
  1490.                 $se = array();
  1491.             }
  1492.             return array_merge($nearray_merge($nwarray_merge($se$sw)));
  1493.         }
  1494.         return array('NE' => $ne'NW' => $nw'SE' => $se'SW' => $sw);
  1495.     }
  1496.     
  1497.     /**
  1498.      * Get the set of squares that are diagonals from this square on an empty board.
  1499.      *
  1500.      * WARNING: assumes valid input
  1501.      * @param string [a-h][1-8]
  1502.      * @param boolean if true, simply returns an array of all squares
  1503.      * @return array Format:
  1504.      *
  1505.      *  <pre>
  1506.      *  array(
  1507.      *    'N' => array(square, square),
  1508.      *    'E' => array(square, square),
  1509.      *    'S' => array(square, square),
  1510.      *    'W' => array(square, square)
  1511.      *  )
  1512.      *  </pre>
  1513.      *
  1514.      *  Think of the horizontal directions as on a map.  squares are listed with
  1515.      *  closer squares first
  1516.      * @access protected
  1517.      */
  1518.     function _getRookSquares($square$returnFlatArray = false)
  1519.     {
  1520.         $n ($square{1!= '8');
  1521.         $e ($square{0!= 'h');
  1522.         $s ($square{1!= '1');
  1523.         $w ($square{0!= 'a');
  1524.         if ($n{
  1525.             $n = array();
  1526.             $i $square;
  1527.             while(ord($i{1}ord('8')) {
  1528.                 $i{1chr(ord($i{1}+ 1);
  1529.                 $n[$i;
  1530.             }
  1531.         }
  1532.         if ($e{
  1533.             $e = array();
  1534.             $i $square;
  1535.             while(ord($i{0}ord('h')) {
  1536.                 $i{0chr(ord($i{0}+ 1);
  1537.                 $e[$i;
  1538.             }
  1539.         }
  1540.         if ($s{
  1541.             $s = array();
  1542.             $i $square;
  1543.             while(ord($i{1}ord('1')) {
  1544.                 $i{1chr(ord($i{1}- 1);
  1545.                 $s[$i;
  1546.             }
  1547.         }
  1548.         if ($w{
  1549.             $w = array();
  1550.             $i $square;
  1551.             while(ord($i{0}ord('a')) {
  1552.                 $i{0chr(ord($i{0}- 1);
  1553.                 $w[$i;
  1554.             }
  1555.         }
  1556.         if ($returnFlatArray{
  1557.             if (!$n{
  1558.                 $n = array();
  1559.             }
  1560.             if (!$s{
  1561.                 $s = array();
  1562.             }
  1563.             if (!$e{
  1564.                 $e = array();
  1565.             }
  1566.             if (!$w{
  1567.                 $w = array();
  1568.             }
  1569.             return array_merge($narray_merge($sarray_merge($e$w)));
  1570.         }
  1571.         return array('N' => $n'E' => $e'S' => $s'W' => $w);
  1572.     }
  1573.     
  1574.     /**
  1575.      * Get all the squares a queen could go to on a blank board
  1576.      *
  1577.      * WARNING: assumes valid input
  1578.      * @return array combines contents of {@link _getRookSquares()} and
  1579.      *                {@link _getDiagonals()}
  1580.      * @param string [a-h][1-8]
  1581.      * @param boolean if true, simply returns an array of all squares
  1582.      * @access protected
  1583.      */
  1584.     function _getQueenSquares($square$returnFlatArray = false)
  1585.     {
  1586.         return array_merge($this->_getRookSquares($square$returnFlatArray),
  1587.                            $this->_getDiagonals($square$returnFlatArray));
  1588.     }
  1589.     
  1590.     /**
  1591.      * Get all the squares a knight could move to on an empty board
  1592.      *
  1593.      * WARNING: assumes valid input
  1594.      * @param string [a-h][1-8]
  1595.      * @param boolean if true, simply returns an array of all squares
  1596.      * @return array Returns an array of all the squares organized by compass
  1597.      *                point, that a knight can go to.  These squares may be indexed
  1598.      *                by any of WNW, NNW, NNE, ENE, ESE, SSE, SSW or WSW, unless
  1599.      *                $returnFlatArray is true, in which case an array of squares
  1600.      *                is returned
  1601.      * @access protected
  1602.      */
  1603.     function _getKnightSquares($square$returnFlatArray = false)
  1604.     {
  1605.         $squares = array();
  1606.         // west-northwest square
  1607.         if (ord($square{0}ord('b'&& $square{1< 8{
  1608.             $squares['WNW'chr(ord($square{0}- 2($square{1+ 1);
  1609.         }
  1610.         // north-northwest square
  1611.         if (ord($square{0}ord('a'&& $square{1< 7{
  1612.             $squares['NNW'chr(ord($square{0}- 1($square{1+ 2);
  1613.         }
  1614.         // north-northeast square
  1615.         if (ord($square{0}ord('h'&& $square{1< 7{
  1616.             $squares['NNE'chr(ord($square{0}+ 1($square{1+ 2);
  1617.         }
  1618.         // east-northeast square
  1619.         if (ord($square{0}ord('g'&& $square{1< 8{
  1620.             $squares['ENE'chr(ord($square{0}+ 2($square{1+ 1);
  1621.         }
  1622.         // east-southeast square
  1623.         if (ord($square{0}ord('g'&& $square{1> 1{
  1624.             $squares['ESE'chr(ord($square{0}+ 2($square{1- 1);
  1625.         }
  1626.         // south-southeast square
  1627.         if (ord($square{0}ord('h'&& $square{1> 2{
  1628.             $squares['SSE'chr(ord($square{0}+ 1($square{1- 2);
  1629.         }
  1630.         // south-southwest square
  1631.         if (ord($square{0}ord('a'&& $square{1> 2{
  1632.             $squares['SSW'chr(ord($square{0}- 1($square{1- 2);
  1633.         }
  1634.         // west-southwest square
  1635.         if (ord($square{0}ord('b'&& $square{1> 1{
  1636.             $squares['WSW'chr(ord($square{0}- 2($square{1- 1);
  1637.         }
  1638.         if ($returnFlatArray{
  1639.             return array_values($squares);
  1640.         }
  1641.         return $squares;
  1642.     }
  1643.     
  1644.     /**
  1645.      * Get a list of all the squares a king could castle to on an empty board
  1646.      *
  1647.      * WARNING: assumes valid input
  1648.      * @param string [a-h][1-8]
  1649.      * @return array 
  1650.      * @access protected
  1651.      * @since 0.7alpha
  1652.      */
  1653.     function _getCastleSquares($square)
  1654.     {
  1655.         $ret = array();
  1656.         if ($this->_move == 'W'{
  1657.             if ($square == 'e1' && $this->_WCastleK{
  1658.                 $ret['g1';
  1659.             }
  1660.             if ($square == 'e1' && $this->_WCastleQ{
  1661.                 $ret['c1';
  1662.             }
  1663.  
  1664.         else {
  1665.             if ($square == 'e8' && $this->_BCastleK{
  1666.                 $ret['g8';
  1667.             }
  1668.             if ($square == 'e8' && $this->_BCastleQ{
  1669.                 $ret['c8';
  1670.             }
  1671.         }
  1672.         return $ret;
  1673.     }
  1674.     
  1675.     /**
  1676.      * Get a list of all the squares a king could move to on an empty board
  1677.      *
  1678.      * WARNING: assumes valid input
  1679.      * @param string [a-h][1-8]
  1680.      * @return array 
  1681.      * @access protected
  1682.      */
  1683.     function _getKingSquares($square)
  1684.     {
  1685.         $squares = array();
  1686.         if (ord($square{0}ord('a')) {
  1687.             $squares[chr(ord($square{0}- 1$square{1};
  1688.             if ($square{1< 8{
  1689.                 $squares[chr(ord($square{0}- 1($square{1+ 1);
  1690.             }
  1691.             if ($square{1> 1{
  1692.                 $squares[chr(ord($square{0}- 1($square{1- 1);
  1693.             }
  1694.         }
  1695.         if (ord($square{0}ord('h')) {
  1696.             $squares[chr(ord($square{0}+ 1$square{1};
  1697.             if ($square{1< 8{
  1698.                 $squares[chr(ord($square{0}+ 1($square{1+ 1);
  1699.             }
  1700.             if ($square{1> 1{
  1701.                 $squares[chr(ord($square{0}+ 1($square{1- 1);
  1702.             }
  1703.         }
  1704.         if ($square{1> 1{
  1705.             $squares[$square{0($square{1- 1);
  1706.         }
  1707.         if ($square{1< 8{
  1708.             $squares[$square{0($square{1+ 1);
  1709.         }
  1710.         return $squares;
  1711.     }
  1712.     
  1713.     /**
  1714.      * Get the location of all pieces on the board of a certain color
  1715.      *
  1716.      * Default is the color that is about to move
  1717.      * @param W|B
  1718.      * @return array|PEAR_Error
  1719.      * @throws GAMES_CHESS_ERROR_INVALID_COLOR
  1720.      */
  1721.     function getPieceLocations($color = null)
  1722.     {
  1723.         if (is_null($color)) {
  1724.             $color $this->_move;
  1725.         }
  1726.         $color strtoupper($color);
  1727.         if (!in_array($colorarray('W''B'))) {
  1728.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_COLOR,
  1729.                 array('color' => $color));
  1730.         }
  1731.         return $this->_getAllPieceLocations($color);
  1732.     }
  1733.  
  1734.     /**
  1735.      * Get the location of every piece on the board of color $color
  1736.      * @param W|Bcolor of pieces to check
  1737.      * @return array 
  1738.      * @abstract
  1739.      * @access protected
  1740.      */
  1741.     function _getAllPieceLocations($color)
  1742.     {
  1743.         trigger_error('Error: do not use abstract Games_Chess class'E_USER_ERROR);
  1744.     }
  1745.     
  1746.     /**
  1747.      * Get all legal Knight moves (checking of the king is not taken into account)
  1748.      * @param string [a-h][1-8] Location of piece
  1749.      * @param W|Bcolor of piece, or null to use current piece to move
  1750.      * @return array 
  1751.      */
  1752.     function getPossibleKnightMoves($square$color = null)
  1753.     {
  1754.         if (is_null($color)) {
  1755.             $color $this->_move;
  1756.         }
  1757.         $color strtoupper($color);
  1758.         if (!in_array($colorarray('W''B'))) {
  1759.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_COLOR,
  1760.                 array('color' => $color));
  1761.         }
  1762.         if (!preg_match('/^[a-h][1-8]$/'$square)) {
  1763.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_SQUARE,
  1764.                 array('square' => $square));
  1765.         }
  1766.         $allmoves $this->_getKnightSquares($square);
  1767.         $mypieces $this->getPieceLocations($color);
  1768.         return array_values(array_diff($allmoves$mypieces));
  1769.     }
  1770.     
  1771.     /**
  1772.      * Get all legal Bishop moves (checking of the king is not taken into account)
  1773.      * @param string [a-h][1-8] Location of piece
  1774.      * @param W|Bcolor of piece, or null to use current piece to move
  1775.      * @return array 
  1776.      */
  1777.     function getPossibleBishopMoves($square$color = null)
  1778.     {
  1779.         if (is_null($color)) {
  1780.             $color $this->_move;
  1781.         }
  1782.         $color strtoupper($color);
  1783.         if (!in_array($colorarray('W''B'))) {
  1784.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_COLOR,
  1785.                 array('color' => $color));
  1786.         }
  1787.         if (!preg_match('/^[a-h][1-8]$/'$square)) {
  1788.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_SQUARE,
  1789.                 array('square' => $square));
  1790.         }
  1791.         $allmoves $this->_getDiagonals($square);
  1792.         $mypieces $this->getPieceLocations($color);
  1793.         foreach($mypieces as $loc{
  1794.             // go through the diagonals, and remove squares behind our own pieces
  1795.             // and also remove the piece's square
  1796.             // as bishops cannot pass through any pieces.
  1797.             if (is_array($allmoves['NW']&& in_array($loc$allmoves['NW'])) {
  1798.                 $pos array_search($loc$allmoves['NW']);
  1799.                 $allmoves['NW'array_slice($allmoves['NW']0$pos);
  1800.             }
  1801.             if (is_array($allmoves['NE']&& in_array($loc$allmoves['NE'])) {
  1802.                 $pos array_search($loc$allmoves['NE']);
  1803.                 $allmoves['NE'array_slice($allmoves['NE']0$pos);
  1804.             }
  1805.             if (is_array($allmoves['SE']&& in_array($loc$allmoves['SE'])) {
  1806.                 $pos array_search($loc$allmoves['SE']);
  1807.                 $allmoves['SE'array_slice($allmoves['SE']0$pos);
  1808.             }
  1809.             if (is_array($allmoves['SW']&& in_array($loc$allmoves['SW'])) {
  1810.                 $pos array_search($loc$allmoves['SW']);
  1811.                 $allmoves['SW'array_slice($allmoves['SW']0$pos);
  1812.             }
  1813.         }
  1814.         $enemypieces $this->getPieceLocations($color == 'W' 'B' 'W');
  1815.         foreach($enemypieces as $loc{
  1816.             // go through the diagonals, and remove squares behind enemy pieces
  1817.             // and include the piece's square, since we can capture it
  1818.             // but bishops cannot pass through any pieces.
  1819.             if (is_array($allmoves['NW']&& in_array($loc$allmoves['NW'])) {
  1820.                 $pos array_search($loc$allmoves['NW']);
  1821.                 $allmoves['NW'array_slice($allmoves['NW']0$pos + 1);
  1822.             }
  1823.             if (is_array($allmoves['NE']&& in_array($loc$allmoves['NE'])) {
  1824.                 $pos array_search($loc$allmoves['NE']);
  1825.                 $allmoves['NE'array_slice($allmoves['NE']0$pos + 1);
  1826.             }
  1827.             if (is_array($allmoves['SE']&& in_array($loc$allmoves['SE'])) {
  1828.                 $pos array_search($loc$allmoves['SE']);
  1829.                 $allmoves['SE'array_slice($allmoves['SE']0$pos + 1);
  1830.             }
  1831.             if (is_array($allmoves['SW']&& in_array($loc$allmoves['SW'])) {
  1832.                 $pos array_search($loc$allmoves['SW']);
  1833.                 $allmoves['SW'array_slice($allmoves['SW']0$pos + 1);
  1834.             }
  1835.         }
  1836.         $newmoves = array();
  1837.         foreach($allmoves as $key => $value{
  1838.             if (!$value{
  1839.                 continue;
  1840.             }
  1841.             $newmoves array_merge($newmoves$value);
  1842.         }
  1843.         return array_values(array_diff($newmoves$mypieces));
  1844.     }
  1845.  
  1846.     /**
  1847.      * Get all legal Rook moves (checking of the king is not taken into account)
  1848.      * @param string [a-h][1-8] Location of piece
  1849.      * @param W|Bcolor of piece, or null to use current piece to move
  1850.      * @return array 
  1851.      */
  1852.     function getPossibleRookMoves($square$color = null)
  1853.     {
  1854.         if (is_null($color)) {
  1855.             $color $this->_move;
  1856.         }
  1857.         $color strtoupper($color);
  1858.         if (!in_array($colorarray('W''B'))) {
  1859.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_COLOR,
  1860.                 array('color' => $color));
  1861.         }
  1862.         if (!preg_match('/^[a-h][1-8]$/'$square)) {
  1863.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_SQUARE,
  1864.                 array('square' => $square));
  1865.         }
  1866.         $allmoves $this->_getRookSquares($square);
  1867.         $mypieces $this->getPieceLocations($color);
  1868.         foreach($mypieces as $loc{
  1869.             // go through the rook squares, and remove squares behind our own pieces
  1870.             // and also remove the piece's square
  1871.             // as rooks cannot pass through any pieces.
  1872.             if (is_array($allmoves['N']&& in_array($loc$allmoves['N'])) {
  1873.                 $pos array_search($loc$allmoves['N']);
  1874.                 $allmoves['N'array_slice($allmoves['N']0$pos);
  1875.             }
  1876.             if (is_array($allmoves['E']&& in_array($loc$allmoves['E'])) {
  1877.                 $pos array_search($loc$allmoves['E']);
  1878.                 $allmoves['E'array_slice($allmoves['E']0$pos);
  1879.             }
  1880.             if (is_array($allmoves['S']&& in_array($loc$allmoves['S'])) {
  1881.                 $pos array_search($loc$allmoves['S']);
  1882.                 $allmoves['S'array_slice($allmoves['S']0$pos);
  1883.             }
  1884.             if (is_array($allmoves['W']&& in_array($loc$allmoves['W'])) {
  1885.                 $pos array_search($loc$allmoves['W']);
  1886.                 $allmoves['W'array_slice($allmoves['W']0$pos);
  1887.             }
  1888.         }
  1889.         $enemypieces $this->getPieceLocations($color == 'W' 'B' 'W');
  1890.         foreach($enemypieces as $loc{
  1891.             // go through the rook squares, and remove squares behind enemy pieces
  1892.             // and include the piece's square, since we can capture it
  1893.             // but rooks cannot pass through any pieces.
  1894.             if (is_array($allmoves['N']&& in_array($loc$allmoves['N'])) {
  1895.                 $pos array_search($loc$allmoves['N']);
  1896.                 $allmoves['N'array_slice($allmoves['N']0$pos + 1);
  1897.             }
  1898.             if (is_array($allmoves['E']&& in_array($loc$allmoves['E'])) {
  1899.                 $pos array_search($loc$allmoves['E']);
  1900.                 $allmoves['E'array_slice($allmoves['E']0$pos + 1);
  1901.             }
  1902.             if (is_array($allmoves['S']&& in_array($loc$allmoves['S'])) {
  1903.                 $pos array_search($loc$allmoves['S']);
  1904.                 $allmoves['S'array_slice($allmoves['S']0$pos + 1);
  1905.             }
  1906.             if (is_array($allmoves['W']&& in_array($loc$allmoves['W'])) {
  1907.                 $pos array_search($loc$allmoves['W']);
  1908.                 $allmoves['W'array_slice($allmoves['W']0$pos + 1);
  1909.             }
  1910.         }
  1911.         $newmoves = array();
  1912.         foreach($allmoves as $key => $value{
  1913.             if (!$value{
  1914.                 continue;
  1915.             }
  1916.             $newmoves array_merge($newmoves$value);
  1917.         }
  1918.         return array_values(array_diff($newmoves$mypieces));
  1919.     }
  1920.     
  1921.     /**
  1922.      * Get all legal Queen moves (checking of the king is not taken into account)
  1923.      * @param string [a-h][1-8] Location of piece
  1924.      * @param W|Bcolor of piece, or null to use current piece to move
  1925.      * @return array 
  1926.      */
  1927.     function getPossibleQueenMoves($square$color = null)
  1928.     {
  1929.         $a $this->getPossibleRookMoves($square$color);
  1930.         $b $this->getPossibleBishopMoves($square$color);
  1931.         if ($this->isError($a)) {
  1932.             return $a;
  1933.         }
  1934.         if ($this->isError($b)) {
  1935.             return $b;
  1936.         }
  1937.         return array_merge($a$b);
  1938.     }
  1939.     
  1940.     /**
  1941.      * Get all legal Pawn moves (checking of the king is not taken into account)
  1942.      * @param string [a-h][1-8] Location of piece
  1943.      * @param W|Bcolor of piece, or null to use current piece to move
  1944.      * @return array 
  1945.      */
  1946.     function getPossiblePawnMoves($square$color = null$enpassant = null)
  1947.     {
  1948.         if (is_null($color)) {
  1949.             $color $this->_move;
  1950.         }
  1951.         $color strtoupper($color);
  1952.         if (!in_array($colorarray('W''B'))) {
  1953.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_COLOR,
  1954.                 array('color' => $color));
  1955.         }
  1956.         if (!preg_match('/^[a-h][1-8]$/'$square)) {
  1957.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_SQUARE,
  1958.                 array('square' => $square));
  1959.         }
  1960.         if (is_null($enpassant)) {
  1961.             $enpassant $this->_enPassantSquare;
  1962.         }
  1963.         $mypieces $this->getPieceLocations($color);
  1964.         $enemypieces $this->getPieceLocations($color == 'W' 'B' 'W');
  1965.         $allmoves = array();
  1966.         if ($color == 'W'{
  1967.             $dbl '2';
  1968.             $direction = 1;
  1969.             // en passant calculation
  1970.             if ($square{1== '5' && in_array(ord($enpassant{0}ord($square{0}),
  1971.                                               array(1-1))) {
  1972.                 if (in_array(chr(ord($square{0}- 1. 5,
  1973.                              $enemypieces)) {
  1974.                     $allmoves[chr(ord($square{0}- 1. 6;
  1975.                 }
  1976.                 if (in_array(chr(ord($square{0}+ 1. 5,
  1977.                              $enemypieces)) {
  1978.                     $allmoves[chr(ord($square{0}+ 1. 6;
  1979.                 }
  1980.             }
  1981.         else {
  1982.             $dbl '7';
  1983.             $direction = -1;
  1984.             // en passant calculation
  1985.             if ($square{1== '4' && in_array(ord($enpassant{0}ord($square{0}),
  1986.                                               array(1-1))) {
  1987.                 if (in_array(chr(ord($square{0}- 1. 4,
  1988.                              $enemypieces)) {
  1989.                     $allmoves[chr(ord($square{0}- 1. 3;
  1990.                 }
  1991.                 if (in_array(chr(ord($square{0}+ 1. 4,
  1992.                              $enemypieces)) {
  1993.                     $allmoves[chr(ord($square{0}+ 1. 3;
  1994.                 }
  1995.             }
  1996.         }
  1997.         if (!in_array($square{0($square{1$direction)$mypieces&&
  1998.             !in_array($square{0($square{1$direction)$enemypieces))
  1999.         {
  2000.             $allmoves[$square{0($square{1$direction);
  2001.         }
  2002.         if (count($allmoves&& $square{1== $dbl{
  2003.             if (!in_array($square{0($square{1+ 2 * $direction)$mypieces&&
  2004.                 !in_array($square{0($square{1+ 2 * $direction)$enemypieces))
  2005.             {
  2006.                 $allmoves[$square{0($square{1+ 2 * $direction);
  2007.             }
  2008.         }
  2009.         if (in_array(chr(ord($square{0}- 1($square{1$direction),
  2010.                      $enemypieces)) {
  2011.             $allmoves[chr(ord($square{0}- 1($square{1$direction);
  2012.         }
  2013.         if (in_array(chr(ord($square{0}+ 1($square{1$direction),
  2014.                      $enemypieces)) {
  2015.             $allmoves[chr(ord($square{0}+ 1($square{1$direction);
  2016.         }
  2017.         return $allmoves;
  2018.     }
  2019.     
  2020.     /**
  2021.      * Get all legal King moves (checking of the king is not taken into account)
  2022.      * @param string [a-h][1-8] Location of piece
  2023.      * @param W|Bcolor of piece, or null to use current piece to move
  2024.      * @return array 
  2025.      * @since 0.7alpha castling is possible by moving the king to the destination square
  2026.      */
  2027.     function getPossibleKingMoves($square$color = null$returnCastleMoves = true)
  2028.     {
  2029.         if (is_null($color)) {
  2030.             $color $this->_move;
  2031.         }
  2032.         $color strtoupper($color);
  2033.         if (!in_array($colorarray('W''B'))) {
  2034.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_COLOR,
  2035.                 array('color' => $color));
  2036.         }
  2037.         if (!preg_match('/^[a-h][1-8]$/'$square)) {
  2038.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_SQUARE,
  2039.                 array('square' => $square));
  2040.         }
  2041.         $newret $castleret = array();
  2042.         $ret $this->_getKingSquares($square);
  2043.         if ($returnCastleMoves{
  2044.             $castleret $this->_getCastleSquares($square);
  2045.         }
  2046.         $mypieces $this->getPieceLocations($color);
  2047.         foreach ($ret as $square{
  2048.             if (!in_array($square$mypieces)) {
  2049.                 $newret[$square;
  2050.             }
  2051.         }
  2052.         return array_merge($newret$castleret);
  2053.     }
  2054.     
  2055.     /**
  2056.      * Return the color of a square (black or white)
  2057.      * @param string [a-h][1-8]
  2058.      * @access protected
  2059.      * @return B|W
  2060.      */
  2061.     function _getDiagonalColor($square)
  2062.     {
  2063.         $map = array('a' => 1'b' => 2'c' => 3'd' => 4'e' => 5'f' => 6,
  2064.             'g' => 7'h' => 8);
  2065.         $rank $map[$square{0}];
  2066.         $file $square{1};
  2067.         $color ($rank $file% 2;
  2068.         return $color 'W' 'B';
  2069.     }
  2070.     
  2071.     function getDiagonalColor($square)
  2072.     {
  2073.         if (!preg_match('/^[a-h][1-8]$/'$square)) {
  2074.             return $this->raiseError(GAMES_CHESS_ERROR_INVALID_SQUARE,
  2075.                 array('square' => $square));
  2076.         }
  2077.         return $this->_getDiagonalColor($square);
  2078.     }
  2079.     
  2080.     /**
  2081.      * Get all the squares between an attacker and the king where another
  2082.      * piece can interpose, or capture the checking piece
  2083.      *
  2084.      * @param string algebraic square of the checking piece
  2085.      * @param string algebraic square of the king
  2086.      */
  2087.     function _getPathToKing($checkee$king)
  2088.     {
  2089.         if ($this->_isKnight($this->_board[$checkee])) {
  2090.             return array($checkee);
  2091.         else {
  2092.             $path = array();
  2093.             // get all the paths 
  2094.             $kingpaths $this->_getQueenSquares($king);
  2095.             foreach ($kingpaths as $subpath{
  2096.                 if (!$subpath{
  2097.                     continue;
  2098.                 }
  2099.                 if (in_array($checkee$subpath)) {
  2100.                     foreach ($subpath as $square{
  2101.                         $path[$square;
  2102.                         if ($square == $checkee{
  2103.                             return $path;
  2104.                         }
  2105.                     }
  2106.                 }
  2107.             }
  2108.         }
  2109.     }
  2110.     
  2111.     /**
  2112.      * @param integer error code from {@link Chess.php}
  2113.      * @param array associative array of additional error message data
  2114.      * @uses PEAR::raiseError()
  2115.      * @return PEAR_Error 
  2116.      */
  2117.     function raiseError($code$extra = array())
  2118.     {
  2119.         require_once 'PEAR.php';
  2120.         return PEAR::raiseError($this->getMessage($code$extra)$code,
  2121.             nullnull$extra);
  2122.     }
  2123.     
  2124.     /**
  2125.      * Get an error message from the code
  2126.      *
  2127.      * Future versions of this method will be multi-language
  2128.      * @return string 
  2129.      * @param integer Error code
  2130.      * @param array extra information to pass for error message creation
  2131.      */
  2132.     function getMessage($code$extra)
  2133.     {
  2134.         $messages = array(
  2135.             GAMES_CHESS_ERROR_INVALID_SAN =>
  2136.                 '"%pgn%" is not a valid algebraic move',
  2137.             GAMES_CHESS_ERROR_FEN_COUNT =>
  2138.                 'Invalid FEN - "%fen%" has %sections% fields, 6 is required',
  2139.             GAMES_CHESS_ERROR_EMPTY_FEN => 
  2140.                 'Invalid FEN - "%fen%" has an empty field at index %section%',
  2141.             GAMES_CHESS_ERROR_FEN_TOOMUCH =>
  2142.                 'Invalid FEN - "%fen%" has too many pieces for a chessboard',
  2143.             GAMES_CHESS_ERROR_FEN_TOMOVEWRONG =>
  2144.                 'Invalid FEN - "%fen%" has invalid to-move indicator, must be "w" or "b"',
  2145.             GAMES_CHESS_ERROR_FEN_CASTLETOOLONG =>
  2146.                 'Invalid FEN - "%fen%" the castling indicator (KQkq) is too long',
  2147.             GAMES_CHESS_ERROR_FEN_CASTLEWRONG =>
  2148.                 'Invalid FEN - "%fen%" the castling indicator "%castle%" is invalid',
  2149.             GAMES_CHESS_ERROR_FEN_INVALID_EP =>
  2150.                 'Invalid FEN - "%fen%" the en passant square indicator "%enpassant%" is invalid',
  2151.             GAMES_CHESS_ERROR_FEN_INVALID_PLY =>
  2152.                 'Invalid FEN - "%fen%" the half-move ply count "%ply%" is not a number',
  2153.             GAMES_CHESS_ERROR_FEN_INVALID_MOVENUMBER =>
  2154.                 'Invalid FEN - "%fen%" the move number "%movenumber%" is not a number',
  2155.             GAMES_CHESS_ERROR_IN_CHECK =>
  2156.                 'The king is in check and that move does not prevent check',
  2157.             GAMES_CHESS_ERROR_CANT_CK =>
  2158.                 'Can\'t castle kingside, either the king or rook has moved',
  2159.             GAMES_CHESS_ERROR_CK_PIECES_IN_WAY =>
  2160.                 'Can\'t castle kingside, pieces are in the way',
  2161.             GAMES_CHESS_ERROR_CANT_CQ =>
  2162.                 'Can\'t castle queenside, either the king or rook has moved',
  2163.             GAMES_CHESS_ERROR_CQ_PIECES_IN_WAY =>
  2164.                 'Can\'t castle queenside, pieces are in the way',
  2165.             GAMES_CHESS_ERROR_CASTLE_WOULD_CHECK =>
  2166.                 'Can\'t castle, it would put the king in check',
  2167.             GAMES_CHESS_ERROR_MOVE_WOULD_CHECK =>
  2168.                 'That move would put the king in check',
  2169.             GAMES_CHESS_ERROR_STILL_IN_CHECK =>
  2170.                 'The move does not remove the check on the king',
  2171.             GAMES_CHESS_ERROR_CANT_CAPTURE_OWN =>
  2172.                 'Cannot capture your own pieces',
  2173.             GAMES_CHESS_ERROR_NO_PIECE =>
  2174.                 'There is no piece on square %square%',
  2175.             GAMES_CHESS_ERROR_WRONG_COLOR =>
  2176.                 'The piece on %square% is not your piece',
  2177.             GAMES_CHESS_ERROR_CANT_MOVE_THAT_WAY =>
  2178.                 'The piece on %from% cannot move to %to%',
  2179.             GAMES_CHESS_ERROR_MULTIPIECE =>
  2180.                 'Too many %color% %piece%s',
  2181.             GAMES_CHESS_ERROR_FEN_MULTIPIECE =>
  2182.                 'Invalid FEN - "%fen%" Too many %color% %piece%s',
  2183.             GAMES_CHESS_ERROR_DUPESQUARE =>
  2184.                 '%dpiece% already occupies square %square%, cannot be replaced by %piece%',
  2185.             GAMES_CHESS_ERROR_FEN_INVALIDPIECE =>
  2186.                 'Invalid FEN - "%fen%" the character "%fenchar%" is not a valid piece, separator or number',
  2187.             GAMES_CHESS_ERROR_FEN_TOOLITTLE =>
  2188.                 'Invalid FEN - "%fen%" has too few pieces for a chessboard',
  2189.             GAMES_CHESS_ERROR_INVALID_COLOR =>
  2190.                 '"%color%" is not a valid piece color, try W or B',
  2191.             GAMES_CHESS_ERROR_INVALID_SQUARE =>
  2192.                 '"%square%" is not a valid square, must be between a1 and h8',
  2193.             GAMES_CHESS_ERROR_INVALID_PIECE =>
  2194.                 '"%piece%" is not a valid piece, must be P, Q, R, N, K or B',
  2195.             GAMES_CHESS_ERROR_INVALID_PROMOTE =>
  2196.                 '"%piece%" is not a valid promotion piece, must be Q, R, N or B',
  2197.             GAMES_CHESS_ERROR_TOO_AMBIGUOUS =>
  2198.                 '"%san%" does not resolve ambiguity between %piece%s on %squares%',
  2199.             GAMES_CHESS_ERROR_NOPIECE_CANDOTHAT =>
  2200.                 'There are no %color% pieces on the board that can do "%san%"',
  2201.             GAMES_CHESS_ERROR_MOVE_MUST_CAPTURE =>
  2202.                 'Capture is possible, "%san%" does not capture',
  2203.             GAMES_CHESS_ERROR_NOPIECES_TOPLACE =>
  2204.                 'There are no captured %color% %piece%s available to place',
  2205.             GAMES_CHESS_ERROR_PIECEINTHEWAY =>
  2206.                 'There is already a piece on %square%, cannot place another there',
  2207.             GAMES_CHESS_ERROR_CANT_PLACE_18 =>
  2208.                 'Placing a piece on the first or back rank is illegal (%san%)',
  2209.         );
  2210.         $message $messages[$code];
  2211.         foreach ($extra as $key => $value{
  2212.             if (strpos($key'piece'!== false{
  2213.                 switch(strtoupper($value)) {
  2214.                     case 'R' :
  2215.                         $value 'Rook';
  2216.                     break;
  2217.                     case 'Q' :
  2218.                         $value 'Queen';
  2219.                     break;
  2220.                     case 'P' :
  2221.                         $value 'Pawn';
  2222.                     break;
  2223.                     case 'B' :
  2224.                         $value 'Bishop';
  2225.                     break;
  2226.                     case 'K' :
  2227.                         $value 'King';
  2228.                     break;
  2229.                     case 'N' :
  2230.                         $value 'Knight';
  2231.                     break;
  2232.                 }
  2233.             }
  2234.             if ($key == 'color'{
  2235.                 switch($value{
  2236.                     case 'W' :
  2237.                         $value 'White';
  2238.                     break;
  2239.                     case 'B' :
  2240.                         $value 'Black';
  2241.                     break;
  2242.                 }
  2243.             }
  2244.             $message str_replace('%'.$key.'%'$value$message);
  2245.         }
  2246.         return $message;
  2247.     }
  2248.     
  2249.     /**
  2250.      * Determines whether the data returned from a method is a PEAR-related
  2251.      * error class
  2252.      * @param mixed 
  2253.      * @return boolean 
  2254.      */
  2255.     function isError($err)
  2256.     {
  2257.         return is_a($err'PEAR_Error');
  2258.     }
  2259.     
  2260.     /**
  2261.      * Begin a chess piece transaction
  2262.      *
  2263.      * Transactions are used to attempt moves that may be revoked later, especially
  2264.      * in methods like {@link inCheckMate()}
  2265.      */
  2266.     function startTransaction()
  2267.     {
  2268.         $state get_object_vars($this);
  2269.         unset($state['_saveState']);
  2270.         if (!is_array($this->_saveState)) {
  2271.             $this->_saveState = array();
  2272.         }
  2273.         array_push($this->_saveState$state);
  2274.     }
  2275.     
  2276.     /**
  2277.      * Remove any possibility of undo.
  2278.      */
  2279.     function commitTransaction()
  2280.     {
  2281.         array_pop($this->_saveState);
  2282.     }
  2283.     
  2284.     /**
  2285.      * Undo any changes to state since {@link startTransaction()} was first used
  2286.      */
  2287.     function rollbackTransaction()
  2288.     {
  2289.         $vars array_pop($this->_saveState);
  2290.         foreach($vars as $name => $value{
  2291.             $this->$name $value;
  2292.         }
  2293.     }
  2294. }
  2295. ?>

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