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
* piece can interpose, or capture the checking piece
* @param string algebraic square of the checking piece
* @param string algebraic square of the king
function _getPathToKing ($checkee, $king)
if ($this->_isKnight ($this->_board [$checkee])) {
foreach ($kingpaths as $subpath) {
foreach ($subpath as $square) {
if ($square == $checkee) {
* @param integer error code from {@link Chess.php}
* @param array associative array of additional error message data
* @uses PEAR::raiseError()
return PEAR ::raiseError ($this->getMessage($code, $extra), $code,
* Get an error message from the code
* Future versions of this method will be multi-language
* @param integer Error code
* @param array extra information to pass for error message creation
'"%pgn%" is not a valid algebraic move',
'Invalid FEN - "%fen%" has %sections% fields, 6 is required',
'Invalid FEN - "%fen%" has an empty field at index %section%',
'Invalid FEN - "%fen%" has too many pieces for a chessboard',
'Invalid FEN - "%fen%" has invalid to-move indicator, must be "w" or "b"',
'Invalid FEN - "%fen%" the castling indicator (KQkq) is too long',
'Invalid FEN - "%fen%" the castling indicator "%castle%" is invalid',
'Invalid FEN - "%fen%" the en passant square indicator "%enpassant%" is invalid',
'Invalid FEN - "%fen%" the half-move ply count "%ply%" is not a number',
'Invalid FEN - "%fen%" the move number "%movenumber%" is not a number',
'The king is in check and that move does not prevent check',
'Can\'t castle kingside, either the king or rook has moved',
'Can\'t castle kingside, pieces are in the way',
'Can\'t castle queenside, either the king or rook has moved',
'Can\'t castle queenside, pieces are in the way',
'Can\'t castle, it would put the king in check',
'That move would put the king in check',
'The move does not remove the check on the king',
'Cannot capture your own pieces',
'There is no piece on square %square%',
'The piece on %square% is not your piece',
'The piece on %from% cannot move to %to%',
'Too many %color% %piece%s',
'Invalid FEN - "%fen%" Too many %color% %piece%s',
'%dpiece% already occupies square %square%, cannot be replaced by %piece%',
'Invalid FEN - "%fen%" the character "%fenchar%" is not a valid piece, separator or number',
'Invalid FEN - "%fen%" has too few pieces for a chessboard',
'"%color%" is not a valid piece color, try W or B',
'"%square%" is not a valid square, must be between a1 and h8',
'"%piece%" is not a valid piece, must be P, Q, R, N, K or B',
'"%piece%" is not a valid promotion piece, must be Q, R, N or B',
'"%san%" does not resolve ambiguity between %piece%s on %squares%',
'There are no %color% pieces on the board that can do "%san%"',
'Capture is possible, "%san%" does not capture',
'There are no captured %color% %piece%s available to place',
'There is already a piece on %square%, cannot place another there',
'Placing a piece on the first or back rank is illegal (%san%)',
$message = $messages[$code];
foreach ($extra as $key => $value) {
if (strpos($key, 'piece') !== false ) {
$message = str_replace('%'. $key. '%', $value, $message);
* Determines whether the data returned from a method is a PEAR-related
return is_a($err, 'PEAR_Error');
* Begin a chess piece transaction
* Transactions are used to attempt moves that may be revoked later, especially
* in methods like {@link inCheckMate()}
unset ($state['_saveState']);
$this->_saveState = array ();
* Set the state of the chess game
* WARNING: this resets the state without any validation.
foreach($state as $name => $value) {
* Get the current state of the chess game
* Use this in conjunction with setState
* Remove any possibility of undo.
* Undo any changes to state since {@link startTransaction()} was first used
foreach($vars as $name => $value) {
Documentation generated on Mon, 11 Mar 2019 15:05:01 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.
|