Source for file Chess.php
Documentation is available at Chess.php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
// +----------------------------------------------------------------------+
// +----------------------------------------------------------------------+
// | Copyright (c) 2003 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available through the world-wide-web at |
// | http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Authors: Gregory Beaver <cellog@php.net> |
// +----------------------------------------------------------------------+
// $Id: Chess.php,v 1.21 2007/06/17 05:46:43 cellog Exp $
* The Games_Chess Package
* The logic of handling a chessboard and parsing standard
* FEN (Forsyth-Edwards Notation) for describing a position as well as SAN
* (Standard Algebraic Notation) for describing individual moves is handled. This
* class can be used as a backend driver for playing chess, or for validating
* and/or creating PGN files using the File_ChessPGN package.
* Although this package is alpha, it is fully unit-tested. The code works, but
* the API is fluid, and may change dramatically as it is put into use and better
* ways are found to use it. When the API stabilizes, the stability will increase.
* To learn how to play chess, there are many sites online, try searching for
* "chess." To play online, I use the Internet Chess Club at
* {@link http://www.chessclub.com} as CelloJi, look me up sometime :). Don't
* worry, I'm not very good.
* @todo implement special class Games_Chess_Chess960 for Fischer Random Chess
* @todo implement special class Games_Chess_Wild23 for ICC Wild variant 23
* @author Gregory Beaver <cellog@php.net>
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* Castling move (O-O or O-O-O)
define('GAMES_CHESS_CASTLE', 1 );
* Pawn move (e4, e8=Q, exd5)
define('GAMES_CHESS_PAWNMOVE', 2 );
* Piece move (Qa4, Nfe6, Bxe5, Re2xe6)
define('GAMES_CHESS_PIECEMOVE', 3 );
* Special move type used in Wild23 like P@a4 (place a pawn at a4)
define('GAMES_CHESS_PIECEPLACEMENT', 4 );
* Invalid Standard Algebraic Notation was used
define('GAMES_CHESS_ERROR_INVALID_SAN', 1 );
* The number of space-separated fields in a FEN passed to {@internal
* {@link _parseFen()} through }} } {@link resetGame()} was incorrect, should be 6
define('GAMES_CHESS_ERROR_FEN_COUNT', 2 );
* A FEN containing multiple spaces in a row was parsed {@internal by
define('GAMES_CHESS_ERROR_EMPTY_FEN', 3 );
* Too many pieces were passed in for the chessboard to fit them in a FEN
* {@internal passed to {@link _parseFen()}
define('GAMES_CHESS_ERROR_FEN_TOOMUCH', 4 );
* The indicator of which side to move in a FEN was neither "w" nor "b"
define('GAMES_CHESS_ERROR_FEN_TOMOVEWRONG', 5 );
* The list of castling indicators was too long (longest is KQkq) of a FEN
define('GAMES_CHESS_ERROR_FEN_CASTLETOOLONG', 6 );
* Something other than K, Q, k or q was in the castling indicators of a FEN
define('GAMES_CHESS_ERROR_FEN_CASTLEWRONG', 7 );
* The en passant square was neither "-" nor an algebraic square in a FEN
define('GAMES_CHESS_ERROR_FEN_INVALID_EP', 8 );
* The ply count (number of half-moves) was not a number in a FEN
define('GAMES_CHESS_ERROR_FEN_INVALID_PLY', 9 );
* The move count (pairs of white/black moves) was not a number in a FEN
define('GAMES_CHESS_ERROR_FEN_INVALID_MOVENUMBER', 10 );
* An illegal move was attempted, the king is in check
define('GAMES_CHESS_ERROR_IN_CHECK', 11 );
* Can't castle kingside, either king or rook has moved
define('GAMES_CHESS_ERROR_CANT_CK', 12 );
* Can't castle kingside, pieces are in the way on the f and/or g files
define('GAMES_CHESS_ERROR_CK_PIECES_IN_WAY', 13 );
* Can't castle kingside, either king or rook has moved
define('GAMES_CHESS_ERROR_CANT_CQ', 14 );
* Can't castle queenside, pieces are in the way on the d, c and/or b files
define('GAMES_CHESS_ERROR_CQ_PIECES_IN_WAY', 15 );
* Castling would place the king in check, which is illegal
define('GAMES_CHESS_ERROR_CASTLE_WOULD_CHECK', 16 );
* Performing a requested move would place the king in check
define('GAMES_CHESS_ERROR_MOVE_WOULD_CHECK', 17 );
* The requested move does not remove a check on the king
define('GAMES_CHESS_ERROR_STILL_IN_CHECK', 18 );
* An attempt (however misguided) was made to capture one's own piece, illegal
define('GAMES_CHESS_ERROR_CANT_CAPTURE_OWN', 19 );
* An attempt was made to capture a piece on a square that does not contain a piece
define('GAMES_CHESS_ERROR_NO_PIECE', 20 );
* A attempt to move an opponent's piece was made, illegal
define('GAMES_CHESS_ERROR_WRONG_COLOR', 21 );
* A request was made to move a piece from one square to another, but it can't
* move to that square legally
define('GAMES_CHESS_ERROR_CANT_MOVE_THAT_WAY', 22 );
* An attempt was made to add a piece to the chessboard, but there are too many
* pieces of that type already on the chessboard
define('GAMES_CHESS_ERROR_MULTIPIECE', 23 );
* An attempt was made to add a piece to the chessboard through the parsing of
* a FEN, but there are too many pieces of that type already on the chessboard
define('GAMES_CHESS_ERROR_FEN_MULTIPIECE', 24 );
* An attempt was made to add a piece to the chessboard on top of an existing piece
define('GAMES_CHESS_ERROR_DUPESQUARE', 25 );
* An invalid piece indicator was used in a FEN
define('GAMES_CHESS_ERROR_FEN_INVALIDPIECE', 26 );
* Not enough piece data was passed into the FEN to explain every square on the board
define('GAMES_CHESS_ERROR_FEN_TOOLITTLE', 27 );
* Something other than "W" or "B" was passed to a method needing a color
define('GAMES_CHESS_ERROR_INVALID_COLOR', 28 );
* Something that isn't SAN ([a-h][1-8]) was passed to a function requiring a
define('GAMES_CHESS_ERROR_INVALID_SQUARE', 29 );
* Something other than "P", "Q", "R", "B", "N" or "K" was passed to a method
define('GAMES_CHESS_ERROR_INVALID_PIECE', 30 );
* Something other than "Q", "R", "B", or "N" was passed to a method
* needing a piece type for pawn promotion
define('GAMES_CHESS_ERROR_INVALID_PROMOTE', 31 );
* SAN was passed in that is too ambiguous - multiple pieces could execute
* the move, and no disambiguation (like Naf3 or Bf3xe4) was used
define('GAMES_CHESS_ERROR_TOO_AMBIGUOUS', 32 );
* No piece of the current color can execute the SAN (as in, if Na3 is passed
* in, but there are no knights that can reach a3
define('GAMES_CHESS_ERROR_NOPIECE_CANDOTHAT', 33 );
* In loser's chess, and the current move does not capture a piece although
define('GAMES_CHESS_ERROR_MOVE_MUST_CAPTURE', 34 );
* When piece placement is attempted, but no pieces exist to be placed
define('GAMES_CHESS_ERROR_NOPIECES_TOPLACE', 35 );
* When piece placement is attempted, but there is a piece on the desired square already
define('GAMES_CHESS_ERROR_PIECEINTHEWAY', 36 );
* When a pawn placement on the first or back rank is attempted
define('GAMES_CHESS_ERROR_CANT_PLACE_18', 37 );
* ABSTRACT parent class - use {@link Games_Chess_Standard} for a typical
* This class contains a few public methods that are the only thing most
* users of the package will ever need. Protected methods are available
* for usage by child classes, and it is expected that all child classes
* will implement certain protected methods used by the utility methods in
* Public API methods used are:
* - {@link resetGame()}: in order to start a new game (pass a FEN for a starting
* - {@link blankBoard()}: in order to start with an empty chessboard
* - {@link addPiece()}: Use to add pieces one at a time to the board
* - {@link moveSAN()}: Use to move pieces based on their SAN (Qa3, exd5, etc.)
* - {@link moveSquare()}: Use to move pieces based on their square (a2 -> a3
* for Qa3, e4 -> d5 for exd5, etc.)
* - {@link inCheck()}: Use to determine the presence of check
* - {@link inCheckMate()}: Use to determine a won game
* - {@link inStaleMate()}: Use to determine presence of stalemate draw
* - {@link in50MoveDraw()}: Use to determine presence of 50-move rule draw
* - {@link inRepetitionDraw()}: Use to determine presence of a draw by repetition
* - {@link inStaleMate()}: Use to determine presence of stalemate draw
* - {@link inDraw()}: Use to determine if any forced draw condition exists
* - {@link renderFen()}: Use to retrieve a FEN representation of the
* current chessboard position, in order to transfer to another chess program
* - {@link toArray()}: Use to retrieve a literal representation of the
* current chessboard position, in order to display as HTML or some other
* - {@link getMoveList()}: Use to retrieve the list of SAN moves for this game
var $_saveState = array ();
* Half-moves since last pawn move or capture
* Square that an en passant can happen, or "-"
var $_enPassantSquare = '-';
* Moves in SAN format for easy write-out to a PGN file
* movenumber => array(White move, Black move),
* movenumber => array(White move, Black move),
* Moves in SAN format for easy write-out to a PGN file, with check/checkmate annotations appended
* movenumber => array(White move, Black move),
* movenumber => array(White move, Black move),
var $_movesWithCheck = array ();
* Store every position from the game, used to determine draw by repetition
* If the exact same position is encountered three times, then it is a draw
* Contents of the last move returned from {@link _parseMove()}, used to
function &factory($type = 'Standard')
$type = " Games_Chess_$type";
* Create a blank chessboard with no pieces on it
for ($j = 8; $j >= 1; $j-- ) {
for ($i = ord('a'); $i <= ord('h'); $i++ ) {
$this->_board[chr($i) . $j] = chr($i) . $j;
* Create a new game with the starting position, or from the position
* @return PEAR_Error|truereturns any errors thrown by {@link _parseFen()}
$this->_saveState = array ();
return $this->_parseFen ($fen);
* Make a move from a Standard Algebraic Notation (SAN) format
* SAN is just a normal chess move like Na4, instead of the English Notation,
* @return true|PEAR_Error
list ($key, $parsedMove) = each($parsedMove);
$this->_moves[$this->_moveNumber][($this->_move == 'W') ? 0 : 1 ] = $move;
$oldMoveNumber = $this->_moveNumber;
$this->_moveNumber += ($this->_move == 'W') ? 0 : 1;
$a = ($parsedMove == 'Q') ? 'K' : 'Q';
$this->{'_' . $this->_move . 'Castle' . $parsedMove} = false;
$this->{'_' . $this->_move . 'Castle' . $a} = false;
$row = ($this->_move == 'W') ? 1 : 8;
$this->_enPassantSquare = '-';
$movedfrom = $this->_getSquareFromParsedMove ($parsedMove);
$promote = isset ($parsedMove['promote']) ?
$parsedMove['promote'] : '';
if ($parsedMove['takes']) {
if ($parsedMove['piece'] == 'P') {
$this->_enPassantSquare = '-';
if (in_array($movedfrom{1 } - $parsedMove['square']{1 },
$direction = ($this->_move == 'W' ? 1 : -1 );
$this->_enPassantSquare = $parsedMove['square']{0 } .
($parsedMove['square']{1 } - $direction);
$this->_enPassantSquare = '-';
if ($parsedMove['piece'] == 'K') {
$this->{'_' . $this->_move . 'CastleQ'} = false;
$this->{'_' . $this->_move . 'CastleK'} = false;
if ($parsedMove['piece'] == 'R') {
if ($movedfrom{0 } == 'a') {
$this->{'_' . $this->_move . 'CastleQ'} = false;
if ($movedfrom{0 } == 'h') {
$this->{'_' . $this->_move . 'CastleK'} = false;
if ($this->inCheckMate(($this->_move == 'W') ? 'B' : 'W')) {
} elseif ($this->inCheck (($this->_move == 'W') ? 'B' : 'W')) {
$this->_movesWithCheck [$oldMoveNumber][($this->_move == 'W') ? 0 : 1 ] = $moveWithCheck;
$this->_move = ($this->_move == 'W' ? 'B' : 'W');
// increment the position counter for this position
if (!isset ($this->_allFENs [$x])) {
* Move a piece from one square to another, and mark the old square as empty
* @param string [a-h][1-8] square to move from
* @param string [a-h][1-8] square to move to
* @param string piece to promote to, if this is a promotion move
* @return true|PEAR_Error
* Get the list of moves in Standard Algebraic Notation
* Can be used to populate a PGN file.
* @param boolean If true, then moves that check will be postfixed with "+" and checkmate with "#"
return $this->_movesWithCheck;
* @return W|B|D|falsewinner of game, or draw, or false if still going
$opposite = $this->_move == 'W' ? 'B' : 'W';
if ($this->inCheckmate ()) {
* Determine whether a side is in checkmate
* @param W|Bcolor of side to check, defaults to the current side
* @throws GAMES_CHESS_ERROR_INVALID_COLOR
if (!in_array($color, array ('W', 'B'))) {
array ('color' => $color));
if (!($checking = $this->inCheck ($color))) {
foreach ($moves as $escape) {
PEAR ::pushErrorHandling (PEAR_ERROR_RETURN );
PEAR ::popErrorHandling ();
$stillchecked = $this->inCheck ($color);
// if we're in double check, and the king can't move, that's checkmate
$squares = $this->_getPathToKing ($checking, $king);
if ($this->_interposeOrCapture ($squares, $color)) {
* Determine whether a side is in stalemate
* @param W|Bcolor of the side to look at, defaults to the current side
* @throws GAMES_CHESS_ERROR_INVALID_COLOR
if (!in_array($color, array ('W', 'B'))) {
array ('color' => $color));
if ($this->inCheck ($color)) {
$moves = $this->_getPossibleChecks ($color);
foreach($moves as $name => $canmove) {
$a = $this->_getPiece ($name);
foreach($canmove as $move) {
PEAR ::pushErrorHandling (PEAR_ERROR_RETURN );
PEAR ::popErrorHandling ();
* Determines the presence of a forced draw
function inDraw($color = null )
* Determine whether draw by repetition has happened
* The game is drawn, upon a claim by the player having the move, when the
* same position, for the third time:
* (a) is about to appear, if he first writes the move on his
* scoresheet and declares to the arbiter his intention of making
* (b) has just appeared, the same player having the move each time.
* The position is considered the same if pieces of the same kind and
* colour occupy the same squares, and if all the possible moves of
* all the pieces are the same, including the rights to castle [at
* some future time] or to capture a pawn "en passant".
* This class determines draw by comparing FENs rendered after every move
if (isset ($this->_allFENs [$fen]) && $this->_allFENs [$fen] == 3 ) {
* Determine whether any pawn move or capture has occurred in the past 50 moves
return $this->_halfMoves >= 50;
* Determine the presence of a basic draw as defined by FIDE rules
* The game is drawn when one of the following endings arises:
* (b) king against king with only bishop or knight;
* (c) king and bishop against king and bishop, with both bishops
* on diagonals of the same colour.
$pieces = $this->_getPieceTypes ();
if (count($blackpieces) > 2 || count($whitepieces) > 2 ) {
if (count($blackpieces) == 1 ) {
if (count($whitepieces) == 1 ) {
if ($whitepieces[0 ] == 'K') {
if (in_array($whitepieces[1 ], array ('N', 'B'))) {
if (in_array($whitepieces[0 ], array ('N', 'B'))) {
if (count($whitepieces) == 1 ) {
if (count($blackpieces) == 1 ) {
if ($blackpieces[0 ] == 'K') {
if (in_array($blackpieces[1 ], array ('N', 'B'))) {
if (in_array($blackpieces[0 ], array ('N', 'B'))) {
$wpindex = ($whitepieces[0 ] == 'K') ? 1 : 0;
$bpindex = ($blackpieces[0 ] == 'K') ? 1 : 0;
if ($whitepieces[$wpindex] == 'B' && $blackpieces[$bpindex] == 'B') {
// bishops of same color?
if ($pieces['B']['B'][0 ] == $pieces['W']['B'][0 ]) {
* render the FEN notation for the current board
* @param boolean private parameter, used to determine whether to include
* move number/ply count - this is used to keep track of
* positions for draw detection
$fen = $this->_renderFen () . ' ';
// render castling rights
if (!$this->_WCastleQ && !$this->_WCastleK && !$this->_BCastleQ
// render en passant square
$fen .= $this->_enPassantSquare;
// render half moves since last pawn move or capture
$fen .= ' ' . $this->_halfMoves . ' ';
$fen .= $this->_moveNumber;
* Add a piece to the chessboard
* Must be overridden in child classes
* @param W|BColor of piece
* @param P|N|K|Q|R|BPiece type
* @param string algebraic location of piece
function addPiece($color, $type, $square)
trigger_error("Error: do not use abstract Games_Chess class", E_USER_ERROR );
* Generate a representation of the chess board and pieces for use as a
* direct translation to a visual chess board
* Must be overridden in child classes
trigger_error("Error: do not use abstract Games_Chess class", E_USER_ERROR );
* Determine whether moving a piece from one square to another requires
* @param string [a-h][1-8] location of the piece to move
* @param string [a-h][1-8] place to move the piece to
* @return boolean true if the move represented by moving from $from to $to
* is a pawn promotion move
if (strpos($test, '=Q') !== false ) {
* @return W|Breturn the color of the side to move (white or black)
* Determine legality of kingside castling
return $this->{'_' . $this->_move . 'CastleK'};
* Determine legality of queenside castling
return $this->{'_' . $this->_move . 'CastleQ'};
* Move a piece from one square to another, and mark the old square as empty
* NO validation is performed, use {@link moveSquare()} for validation.
* @param string [a-h][1-8] square to move from
* @param string [a-h][1-8] square to move to
* @param string piece to promote to, if this is a promotion move
if ($to == $this->_enPassantSquare && $this->isPawn ($this->_board [$from])) {
$rank = ($to{1 } == '3') ? '4' : '5';
// this piece was just taken
$this->_takePiece ($to{0 } . $rank);
$this->_board [$to{0 } . $rank] = $to{0 } . $rank;
if ($this->_board [$to] != $to) {
// this piece was just taken
// mark the piece as moved
$this->_movePiece ($from, $to, $promote);
$this->_board [$to] = $this->_board [$from];
$this->_board [$from] = $from;
* Parse out the segments of a move (minus any annotations)
if (preg_match('/^P?(([a-h])([1-8])?(x))?([a-h][1-8])(=?([QRNB]))?$/', $move, $match)) {
$takesfrom = $match[2 ]{0 };
'takesfrom' => $takesfrom,
$res['promote'] = $match[7 ];
} elseif (preg_match('/^(K)(x)?([a-h][1-8])$/', $move, $match)) {
} elseif (preg_match('/^([QRBN])([a-h]|[1-8]|[a-h][1-8])?(x)?([a-h][1-8])$/', $move, $match)) {
'disambiguate' => $match[2 ],
} elseif (preg_match('/^([QRBN])@([a-h][1-8])$/', $move, $match)) {
} elseif (preg_match('/^([P])@([a-h][2-7])$/', $move, $match)) {
} elseif (preg_match('/^([P])@([a-h][18])$/', $move, $match)) {
* Set up the board with the starting position
* Must be overridden in child classes
trigger_error("Error: do not use abstract Games_Chess class", E_USER_ERROR );
* Parse a Forsyth-Edwards Notation (FEN) chessboard position string, and
* set up the chessboard with this position
if (count($splitfen) != 6 ) {
array ('fen' => $fen, 'sections' => count($splitfen)));
foreach($splitfen as $index => $test) {
array ('fen' => $fen, 'section' => $index));
// parse position section
PEAR ::pushErrorHandling (PEAR_ERROR_RETURN );
PEAR ::popErrorHandling ();
array ('fen' => $fen, 'color' => 'W', 'piece' => $c));
PEAR ::pushErrorHandling (PEAR_ERROR_RETURN );
PEAR ::popErrorHandling ();
array ('fen' => $fen, 'color' => 'B', 'piece' => $c));
$loc{0 } = chr(ord($loc{0 }) + ($c - 1 ));
array ('fen' => $fen, 'fenchar' => $c));
$loc{0 } = chr(ord($loc{0 }) + 1 );
if (ord($loc{0 }) > ord('h')) {
if (strlen($FEN) > $idx && $FEN{$idx} != '/') {
if (!in_array($splitfen[1 ], array ('w', 'b', 'W', 'B'))) {
array ('fen' => $fen, 'tomove' => $splitfen[1 ]));
if (strlen($splitfen[2 ]) > 4 ) {
array ('fen' => $fen, 'castle' => $splitfen[2 ]));
$this->_WCastleQ = false;
$this->_WCastleK = false;
$this->_BCastleQ = false;
$this->_BCastleK = false;
if ($splitfen[2 ] != '-') {
for ($i = 0; $i < 4; $i++ ) {
if ($i >= strlen($splitfen[2 ])) {
switch ($splitfen[2 ]{$i}) {
array ('fen' => $fen, 'castle' => $splitfen[2 ]{$i}));
// parse en passant square
$this->_enPassantSquare = '-';
if ($splitfen[3 ] != '-') {
array ('fen' => $fen, 'enpassant' => $splitfen[3 ]));
$this->_enPassantSquare = $splitfen[3 ];
// parse half moves since last pawn move or capture
array ('fen' => $fen, 'ply' => $splitfen[4 ]));
$this->_halfMoves = $splitfen[4 ];
array ('fen' => $fen, 'movenumber' => $splitfen[5 ]));
$this->_moveNumber = $splitfen[5 ];
* @param array parsed move array from {@link _parsedMove()}
* @return true|PEAR_Error
* @throws GAMES_CHESS_ERROR_IN_CHECK
* @throws GAMES_CHESS_ERROR_CANT_CK
* @throws GAMES_CHESS_ERROR_CK_PIECES_IN_WAY
* @throws GAMES_CHESS_ERROR_CANT_CQ
* @throws GAMES_CHESS_ERROR_CQ_PIECES_IN_WAY
* @throws GAMES_CHESS_ERROR_CASTLE_WOULD_CHECK
* @throws GAMES_CHESS_ERROR_CANT_CAPTURE_OWN
* @throws GAMES_CHESS_ERROR_STILL_IN_CHECK
* @throws GAMES_CHESS_ERROR_MOVE_WOULD_CHECK
list ($type, $info) = each($move);
if ($this->inCheck ($this->_move )) {
if ($this->_move == 'W') {
if ($this->_board ['f1'] != 'f1' || $this->_board ['g1'] != 'g1') {
$kingsquares = array ('f1', 'g1');
if ($this->_board ['f8'] != 'f8' || $this->_board ['g8'] != 'g8') {
$kingsquares = array ('f8', 'g8');
if ($this->_move == 'W') {
if ($this->_board ['d1'] != 'd1' ||
$this->_board ['c1'] != 'c1' ||
$this->_board ['b1'] != 'b1') {
$kingsquares = array ('d1', 'c1');
if ($this->_board ['d8'] != 'd8' ||
$this->_board ['c8'] != 'c8' ||
$this->_board ['b8'] != 'b8') {
$kingsquares = array ('d8', 'c8');
// check every square the king could move to and make sure
// we wouldn't be in check
foreach ($kingsquares as $square) {
if ($this->inCheck ($this->_move )) {
if (!$this->isError($piecesq = $this->_getSquareFromParsedMove ($info))) {
$wasinCheck = $this->inCheck ($this->_move );
$piece = $this->_board [$info['square']];
if ($info['takes'] && $this->_board [$info['square']] ==
if (!($info['square'] == $this->_enPassantSquare &&
$info['piece'] == 'P')) {
array ('square' => $info['square']));
$valid = !$this->inCheck ($this->_move );
if ($wasinCheck && !$valid) {
* Convert a starting and ending algebraic square into SAN
* @param string [a-h][1-8] square piece is on
* @param string [a-h][1-8] square piece moves to
* @return string|PEAR_Error
* @throws GAMES_CHESS_ERROR_INVALID_PROMOTE
* @throws GAMES_CHESS_ERROR_INVALID_SQUARE
* @throws GAMES_CHESS_ERROR_NO_PIECE
* @throws GAMES_CHESS_ERROR_WRONG_COLOR
* @throws GAMES_CHESS_ERROR_CANT_MOVE_THAT_WAY
if (!in_array($promote, array ('Q', 'B', 'N', 'R'))) {
array ('piece' => $promote));
array ('square' => $from));
$piece = $this->_squareToPiece ($from);
array ('square' => $from));
if ($piece['color'] != $this->_move ) {
array ('square' => $from));
array ('from' => $from, 'to' => $to));
// this is a castling attempt
if ($piece['piece'] != 'K' && $piece['piece'] != 'P') {
$others = $this->_getAllPieceSquares ($piece['piece'],
foreach ($others as $square) {
// other pieces can move to this square - need to disambiguate
if (count($ambiguous) == 1 ) {
if ($ambiguous[0 ]{0 } != $from{0 }) {
$disambiguate = $from{0 };
} elseif ($ambiguous[0 ]{1 } != $from{1 }) {
$disambiguate = $from{1 };
} elseif (count($ambiguous)) {
if ($piece['piece'] == 'P') {
if ($from{0 } != $to{0 }) {
if ($this->_board [$to] != $to) {
if ($piece['piece'] == 'P' && $to == $this->_enPassantSquare ) {
if ($piece['piece'] == 'P' && ($to{1 } == '1' || $to{1 } == '8')) {
* Get a list of all possible theoretical squares a piece of this nature
* and color could move to with the current board and game setup.
* This method will return all valid moves without determining the presence
* @param K|P|Q|R|B|NPiece name
* @param string [a-h][1-8] algebraic location of the piece
* @param B|Wcolor of the piece
* @param boolean Whether to return shortcut king moves for castling
* @return array|PEAR_Error
* @throws GAMES_CHESS_ERROR_INVALID_COLOR
* @throws GAMES_CHESS_ERROR_INVALID_SQUARE
* @throws GAMES_CHESS_ERROR_INVALID_PIECE
function getPossibleMoves($piece, $square, $color = null , $returnCastleMoves = true )
if (!in_array($color, array ('W', 'B'))) {
array ('color' => $color));
array ('square' => $square));
if (!in_array($piece, array ('K', 'Q', 'B', 'N', 'R', 'P'))) {
array ('piece' => $piece));
* Get the set of squares that are diagonals from this square on an empty board.
* WARNING: assumes valid input
* @param string [a-h][1-8]
* @param boolean if true, simply returns an array of all squares
* 'NE' => array(square, square),
* 'NW' => array(square, square),
* 'SE' => array(square, square),
* 'SW' => array(square, square)
* Think of the diagonal directions as on a map. squares are listed with
function _getDiagonals ($square, $returnFlatArray = false )
$nw = ($square{0 } != 'a') && ($square{1 } != '8');
$ne = ($square{0 } != 'h') && ($square{1 } != '8');
$sw = ($square{0 } != 'a') && ($square{1 } != '1');
$se = ($square{0 } != 'h') && ($square{1 } != '1');
return array ('NE' => $ne, 'NW' => $nw, 'SE' => $se, 'SW' => $sw);
* Get the set of squares that are diagonals from this square on an empty board.
* WARNING: assumes valid input
* @param string [a-h][1-8]
* @param boolean if true, simply returns an array of all squares
* 'N' => array(square, square),
* 'E' => array(square, square),
* 'S' => array(square, square),
* 'W' => array(square, square)
* Think of the horizontal directions as on a map. squares are listed with
$n = ($square{1 } != '8');
$e = ($square{0 } != 'h');
$s = ($square{1 } != '1');
$w = ($square{0 } != 'a');
while (ord($i{1 }) < ord('8')) {
while (ord($i{0 }) < ord('h')) {
while (ord($i{1 }) > ord('1')) {
while (ord($i{0 }) > ord('a')) {
return array ('N' => $n, 'E' => $e, 'S' => $s, 'W' => $w);
* Get all the squares a queen could go to on a blank board
* WARNING: assumes valid input
* @return array combines contents of {@link _getRookSquares()} and
* {@link _getDiagonals()}
* @param string [a-h][1-8]
* @param boolean if true, simply returns an array of all squares
$this->_getDiagonals ($square, $returnFlatArray));
* Get all the squares a knight could move to on an empty board
* WARNING: assumes valid input
* @param string [a-h][1-8]
* @param boolean if true, simply returns an array of all squares
* @return array Returns an array of all the squares organized by compass
* point, that a knight can go to. These squares may be indexed
* by any of WNW, NNW, NNE, ENE, ESE, SSE, SSW or WSW, unless
* $returnFlatArray is true, in which case an array of squares
if (ord($square{0 }) > ord('b') && $square{1 } < 8 ) {
$squares['WNW'] = chr(ord($square{0 }) - 2 ) . ($square{1 } + 1 );
// north-northwest square
if (ord($square{0 }) > ord('a') && $square{1 } < 7 ) {
$squares['NNW'] = chr(ord($square{0 }) - 1 ) . ($square{1 } + 2 );
// north-northeast square
if (ord($square{0 }) < ord('h') && $square{1 } < 7 ) {
$squares['NNE'] = chr(ord($square{0 }) + 1 ) . ($square{1 } + 2 );
if (ord($square{0 }) < ord('g') && $square{1 } < 8 ) {
$squares['ENE'] = chr(ord($square{0 }) + 2 ) . ($square{1 } + 1 );
if (ord($square{0 }) < ord('g') && $square{1 } > 1 ) {
$squares['ESE'] = chr(ord($square{0 }) + 2 ) . ($square{1 } - 1 );
// south-southeast square
if (ord($square{0 }) < ord('h') && $square{1 } > 2 ) {
$squares['SSE'] = chr(ord($square{0 }) + 1 ) . ($square{1 } - 2 );
// south-southwest square
if (ord($square{0 }) > ord('a') && $square{1 } > 2 ) {
$squares['SSW'] = chr(ord($square{0 }) - 1 ) . ($square{1 } - 2 );
if (ord($square{0 }) > ord('b') && $square{1 } > 1 ) {
$squares['WSW'] = chr(ord($square{0 }) - 2 ) . ($square{1 } - 1 );
* Get a list of all the squares a king could castle to on an empty board
* WARNING: assumes valid input
* @param string [a-h][1-8]
if ($this->_move == 'W') {
if ($square == 'e1' && $this->_WCastleK ) {
if ($square == 'e1' && $this->_WCastleQ ) {
if ($square == 'e8' && $this->_BCastleK ) {
if ($square == 'e8' && $this->_BCastleQ ) {
* Get a list of all the squares a king could move to on an empty board
* WARNING: assumes valid input
* @param string [a-h][1-8]
if (ord($square{0 }) - ord('a')) {
$squares[] = chr(ord($square{0 }) - 1 ) . $square{1 };
$squares[] = chr(ord($square{0 }) - 1 ) . ($square{1 } + 1 );
$squares[] = chr(ord($square{0 }) - 1 ) . ($square{1 } - 1 );
if (ord($square{0 }) - ord('h')) {
$squares[] = chr(ord($square{0 }) + 1 ) . $square{1 };
$squares[] = chr(ord($square{0 }) + 1 ) . ($square{1 } + 1 );
$squares[] = chr(ord($square{0 }) + 1 ) . ($square{1 } - 1 );
$squares[] = $square{0 } . ($square{1 } - 1 );
$squares[] = $square{0 } . ($square{1 } + 1 );
* Get the location of all pieces on the board of a certain color
* Default is the color that is about to move
* @return array|PEAR_Error
* @throws GAMES_CHESS_ERROR_INVALID_COLOR
if (!in_array($color, array ('W', 'B'))) {
array ('color' => $color));
* Get the location of every piece on the board of color $color
* @param W|Bcolor of pieces to check
trigger_error('Error: do not use abstract Games_Chess class', E_USER_ERROR );
* Get all legal Knight moves (checking of the king is not taken into account)
* @param string [a-h][1-8] Location of piece
* @param W|Bcolor of piece, or null to use current piece to move
if (!in_array($color, array ('W', 'B'))) {
array ('color' => $color));
array ('square' => $square));
* Get all legal Bishop moves (checking of the king is not taken into account)
* @param string [a-h][1-8] Location of piece
* @param W|Bcolor of piece, or null to use current piece to move
if (!in_array($color, array ('W', 'B'))) {
array ('color' => $color));
array ('square' => $square));
$allmoves = $this->_getDiagonals ($square);
foreach($mypieces as $loc) {
// go through the diagonals, and remove squares behind our own pieces
// and also remove the piece's square
// as bishops cannot pass through any pieces.
$allmoves['NW'] = array_slice($allmoves['NW'], 0 , $pos);
$allmoves['NE'] = array_slice($allmoves['NE'], 0 , $pos);
$allmoves['SE'] = array_slice($allmoves['SE'], 0 , $pos);
$allmoves['SW'] = array_slice($allmoves['SW'], 0 , $pos);
foreach($enemypieces as $loc) {
// go through the diagonals, and remove squares behind enemy pieces
// and include the piece's square, since we can capture it
// but bishops cannot pass through any pieces.
$allmoves['NW'] = array_slice($allmoves['NW'], 0 , $pos + 1 );
$allmoves['NE'] = array_slice($allmoves['NE'], 0 , $pos + 1 );
$allmoves['SE'] = array_slice($allmoves['SE'], 0 , $pos + 1 );
$allmoves['SW'] = array_slice($allmoves['SW'], 0 , $pos + 1 );
foreach($allmoves as $key => $value) {
* Get all legal Rook moves (checking of the king is not taken into account)
* @param string [a-h][1-8] Location of piece
* @param W|Bcolor of piece, or null to use current piece to move
if (!in_array($color, array ('W', 'B'))) {
array ('color' => $color));
array ('square' => $square));
foreach($mypieces as $loc) {
// go through the rook squares, and remove squares behind our own pieces
// and also remove the piece's square
// as rooks cannot pass through any pieces.
foreach($enemypieces as $loc) {
// go through the rook squares, and remove squares behind enemy pieces
// and include the piece's square, since we can capture it
// but rooks cannot pass through any pieces.
$allmoves['N'] = array_slice($allmoves['N'], 0 , $pos + 1 );
$allmoves['E'] = array_slice($allmoves['E'], 0 , $pos + 1 );
$allmoves['S'] = array_slice($allmoves['S'], 0 , $pos + 1 );
$allmoves['W'] = array_slice($allmoves['W'], 0 , $pos + 1 );
foreach($allmoves as $key => $value) {
* Get all legal Queen moves (checking of the king is not taken into account)
* @param string [a-h][1-8] Location of piece
* @param W|Bcolor of piece, or null to use current piece to move
* Get all legal Pawn moves (checking of the king is not taken into account)
* @param string [a-h][1-8] Location of piece
* @param W|Bcolor of piece, or null to use current piece to move
if (!in_array($color, array ('W', 'B'))) {
array ('color' => $color));
array ('square' => $square));
$enpassant = $this->_enPassantSquare;
// en passant calculation
if ($square{1 } == '5' && in_array(ord($enpassant{0 }) - ord($square{0 }),
$allmoves[] = chr(ord($square{0 }) - 1 ) . 6;
$allmoves[] = chr(ord($square{0 }) + 1 ) . 6;
// en passant calculation
if ($square{1 } == '4' && in_array(ord($enpassant{0 }) - ord($square{0 }),
$allmoves[] = chr(ord($square{0 }) - 1 ) . 3;
$allmoves[] = chr(ord($square{0 }) + 1 ) . 3;
if (!in_array($square{0 } . ($square{1 } + $direction), $mypieces) &&
!in_array($square{0 } . ($square{1 } + $direction), $enemypieces))
$allmoves[] = $square{0 } . ($square{1 } + $direction);
if (count($allmoves) && $square{1 } == $dbl) {
if (!in_array($square{0 } . ($square{1 } + 2 * $direction), $mypieces) &&
!in_array($square{0 } . ($square{1 } + 2 * $direction), $enemypieces))
$allmoves[] = $square{0 } . ($square{1 } + 2 * $direction);
$allmoves[] = chr(ord($square{0 }) - 1 ) . ($square{1 } + $direction);
$allmoves[] = chr(ord($square{0 }) + 1 ) . ($square{1 } + $direction);
* Get all legal King moves (checking of the king is not taken into account)
* @param string [a-h][1-8] Location of piece
* @param W|Bcolor of piece, or null to use current piece to move
* @since 0.7alpha castling is possible by moving the king to the destination square
if (!in_array($color, array ('W', 'B'))) {
array ('color' => $color));
array ('square' => $square));
$newret = $castleret = array ();
if ($returnCastleMoves) {
foreach ($ret as $square) {
* Return the color of a square (black or white)
* @param string [a-h][1-8]
$map = array ('a' => 1 , 'b' => 2 , 'c' => 3 , 'd' => 4 , 'e' => 5 , 'f' => 6 ,
$rank = $map[$square{0 }];
$color = ($rank + $file) % 2;
return $color ? 'W' : 'B';
array ('square' => $square));
* Get all the squares between an attacker and the king where another
|