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

Source for file Chess.php

Documentation is available at Chess.php

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

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