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

Documentation generated on Mon, 11 Mar 2019 15:05:01 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.