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

Documentation generated on Mon, 11 Mar 2019 10:14:58 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.