Source for file Crazyhouse.php
Documentation is available at Crazyhouse.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: Crazyhouse.php,v 1.10 2006/11/18 00:09:20 cellog Exp $
* A standard chess game representation
* @author Gregory Beaver <cellog@php.net>
require_once 'Games/Chess/Standard.php';
* A captured piece may be placed on the board as your own piece!
* Note that FEN is incapable of setting up a game mid-swing - no
* record of captured pieces is possible. If requested, a future version
* may extend the FEN standard to allow this, particularly if ICC follows
* @author Gregory Beaver <cellog@php.net>
* Each sub-array consists of pieces owned by the color, so
* 'W' (white) has captured the pieces in the 'W' sub-array
* Set up a blank chess board
* Set up a starting position for a new chess game
'a8' => 'BR0', 'b8' => 'BN0', 'c8' => 'BB0', 'd8' => 'BQ0', 'e8' => 'BK0', 'f8' => 'BB1', 'g8' => 'BN1', 'h8' => 'BR1',
'a7' => 'BP0', 'b7' => 'BP1', 'c7' => 'BP2', 'd7' => 'BP3', 'e7' => 'BP4', 'f7' => 'BP5', 'g7' => 'BP6', 'h7' => 'BP7',
'a6' => 'a6', 'b6' => 'b6', 'c6' => 'c6', 'd6' => 'd6', 'e6' => 'e6', 'f6' => 'f6', 'g6' => 'g6', 'h6' => 'h6',
'a5' => 'a5', 'b5' => 'b5', 'c5' => 'c5', 'd5' => 'd5', 'e5' => 'e5', 'f5' => 'f5', 'g5' => 'g5', 'h5' => 'h5',
'a4' => 'a4', 'b4' => 'b4', 'c4' => 'c4', 'd4' => 'd4', 'e4' => 'e4', 'f4' => 'f4', 'g4' => 'g4', 'h4' => 'h4',
'a3' => 'a3', 'b3' => 'b3', 'c3' => 'c3', 'd3' => 'd3', 'e3' => 'e3', 'f3' => 'f3', 'g3' => 'g3', 'h3' => 'h3',
'a2' => 'WP0', 'b2' => 'WP1', 'c2' => 'WP2', 'd2' => 'WP3', 'e2' => 'WP4', 'f2' => 'WP5', 'g2' => 'WP6', 'h2' => 'WP7',
'a1' => 'WR0', 'b1' => 'WN0', 'c1' => 'WB0', 'd1' => 'WQ0', 'e1' => 'WK0', 'f1' => 'WB1', 'g1' => 'WN1', 'h1' => 'WR1',
* 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
return parent ::moveSAN ($move);
$this->_captured[$this->_move][$p]--;
$set = ($p == 'P') ? array ($sq, 'P') : $sq;
$this->_pieces[$this->_move][$p][] = $set;
$this->_board[$sq] = $this->_move . $p .
(count($this->_pieces[$this->_move][$p]) - 1 );
$this->_enPassantSquare = '-';
$this->_moves[$this->_moveNumber][($this->_move == 'W') ? 0 : 1 ] = $move;
$oldMoveNumber = $this->_moveNumber;
$this->_moveNumber += ($this->_move == 'W') ? 0 : 1;
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])) {
list ($type, $info) = each($move);
if (!$this->_captured[$this->_move][$info['piece']]) {
array ('color' => $this->_move == 'W' ? 'B' : 'W', 'piece' => $info['piece']));
if ($this->_board[$info['square']] != $info['square']) {
array ('square' => $info['square']));
return parent ::_validMove ($move);
$piece = $this->_board[$square];
unset ($this->_pieces[$piece{0 }][$piece{1 }][$piece{2 } + 0 ]);
// add a piece to the list of pieces captured by the enemy
$this->_captured[$piece{0 } == 'W' ? 'B' : 'W'][$piece{1 }]++;
// ensure integrity of the remaining pieces
for ($i = $piece{2 } + 1; $i <= count($this->_pieces[$piece{0 }][$piece{1 }]); $i++ ) {
$value = $this->_pieces[$piece{0 }][$piece{1 }][$i];
// get the square this piece is on
// adjust to the right value
$this->_board[$value]{2 } = ($this->_board[$value]{2 } - 1 ) . '';
$this->_pieces[$piece{0 }][$piece{1 }] = array_values($this->_pieces[$piece{0 }][$piece{1 }]);
* Move a piece from one square to another, disregarding any existing pieces
* {@link _takePiece()} should always be used prior to this method. No
* validation is performed
* @param string [a-h][1-8] square the piece resides on
* @param string [a-h][1-8] square the piece moves to
* @param string Piece to promote to if this is a promotion move
function _movePiece ($from, $to, $promote = '')
$piece = $this->_board[$from];
if (isset ($this->_pieces[$piece{0 }][$piece{1 }][$piece{2 }])) {
$newto = $this->_pieces[$piece{0 }][$piece{1 }][$piece{2 }];
if ($to{1 } == '8' || $to{1 } == '1') {
$this->_pieces[$piece{0 }][$piece{1 }][$piece{2 }] = $newto;
* Translate an algebraic coordinate into the color and name of a piece,
* or false if no piece is on that square
* @return false|arrayFormat array('color' => B|W, 'piece' => P|R|Q|N|K|B)
* @param string [a-h][1-8]
if ($this->_board[$square] != $square) {
$piece = $this->_board[$square];
$piece = $this->_pieces[$piece{0 }][$piece{1 }][$piece{2 }][1 ];
return array ('color' => $color, 'piece' => $piece);
* Retrieve the locations of all pieces of the same type as $piece
* @param string [a-h][1-8] optional square of piece to exclude from the listing
foreach ($this->_pieces[$color]['P'] as $loc) {
if ($loc[1 ] != $piece || $loc[0 ] == $exclude) {
foreach ($this->_pieces[$color][$piece] as $loc) {
* @return string|PEAR_Error
* @param array contents returned from {@link parent::_parseMove()}
* in other words, not array(GAMES_CHESS_PIECEMOVE =>
* array('piece' => 'K', ...)), but array('piece' => 'K', ...)
* @param W|Bcurrent side moving
function _getSquareFromParsedMove ($parsedmove, $color = null )
switch ($parsedmove['piece']) {
if ($parsedmove['disambiguate']) {
if (strlen($parsedmove['disambiguate']) == 2 ) {
$square = $parsedmove['disambiguate'];
} elseif (is_numeric($parsedmove['disambiguate'])) {
$row = $parsedmove['disambiguate'];
$col = $parsedmove['disambiguate'];
foreach ($others as $square) {
// other pieces can move to this square - need to disambiguate
if (count($ambiguous) > 1 ) {
$pieces = implode($ambiguous, ' ');
array ('san' => $parsedmove['piece'] .
$parsedmove['disambiguate'] . $parsedmove['takes']
'piece' => $parsedmove['piece']));
$square = $col = $row = null;
foreach ($this->_pieces[$color]['P'] as $name => $value) {
if ($value[0 ] == $square &&
$value[1 ] == $parsedmove['piece']) {
if ($value[0 ]{0 } == $col &&
$value[1 ] == $parsedmove['piece']) {
$potentials[] = $value[0 ];
if ($value[0 ]{1 } == $row &&
$value[1 ] == $parsedmove['piece']) {
$potentials[] = $value[0 ];
if ($value[1 ] == $parsedmove['piece']) {
$potentials[] = $value[0 ];
foreach ($this->_pieces[$color][$parsedmove['piece']] as $name => $value) {
if (count($potentials) == 1 ) {
if ($parsedmove['disambiguate']) {
$square = $parsedmove['disambiguate'] . $parsedmove['takesfrom'];
if ($parsedmove['takesfrom']) {
$col = $parsedmove['takesfrom'];
foreach ($this->_pieces[$color]['P'] as $name => $value) {
if ($value[0 ] == $square && $value[1 ] == 'P') {
if ($value[0 ]{0 } == $col && $value[1 ] == 'P') {
$potentials[] = $value[0 ];
$potentials[] = $value[0 ];
if (count($potentials) == 1 ) {
if ($parsedmove['piece'] == 'P') {
$san = $parsedmove['takesfrom'] . $parsedmove['takes'] . $parsedmove['square'];
$san = $parsedmove['piece'] .
$parsedmove['disambiguate'] . $parsedmove['takes'] .
* Get the location of the king
* assumes valid color input
if (!isset ($this->_pieces[$color]['K'][0 ])) {
return $this->_pieces[$color]['K'][0 ];
if (!isset ($this->_pieces[$this->_move]['K'][0 ])) {
return $this->_pieces[$this->_move]['K'][0 ];
* Get the location of a piece
* This does NOT take an algebraic square as the argument, but the contents
* of _board[algebraic square]
if (!isset ($this->_pieces[$piece{0 }][$piece{1 }][$piece{2 }])) {
return $piece{1 } == 'P' ?
$this->_pieces[$piece{0 }][$piece{1 }][$piece{2 }][0 ] :
$this->_pieces[$piece{0 }][$piece{1 }][$piece{2 }];
* Determine whether a piece name is a knight
* This does NOT take an algebraic square as the argument, but the contents
* of _board[algebraic square]
if (!isset ($this->_pieces[$piece{0 }][$piece{1 }][$piece{2 }])) {
return $piece{1 } == 'N' ||
$this->_pieces[$piece{0 }][$piece{1 }][$piece{2 }][1 ] == 'N');
* Determine whether a piece name is a king
* This does NOT take an algebraic square as the argument, but the contents
* of _board[algebraic square]
if ($piecename{2 } != '0') {
return $piecename{1 } == 'K';
* Determine whether a piece name is a queen
* This does NOT take an algebraic square as the argument, but the contents
* of _board[algebraic square]
if (!isset ($this->_pieces[$piece{0 }][$piece{1 }][$piece{2 }])) {
return $piece{1 } == 'Q' ||
$this->_pieces[$piece{0 }][$piece{1 }][$piece{2 }][1 ] == 'Q');
* Determine whether a piece name is a bishop
* This does NOT take an algebraic square as the argument, but the contents
* of _board[algebraic square]
if (!isset ($this->_pieces[$piece{0 }][$piece{1 }][$piece{2 }])) {
return $piece{1 } == 'B' ||
$this->_pieces[$piece{0 }][$piece{1 }][$piece{2 }][1 ] == 'B');
* Determine whether a piece name is a rook
* This does NOT take an algebraic square as the argument, but the contents
* of _board[algebraic square]
if (!isset ($this->_pieces[$piece{0 }][$piece{1 }][$piece{2 }])) {
return $piece{1 } == 'R' ||
$this->_pieces[$piece{0 }][$piece{1 }][$piece{2 }][1 ] == 'R');
* Determine whether a piece name is a pawn
* This does NOT take an algebraic square as the argument, but the contents
* of _board[algebraic square]
if (!isset ($this->_pieces[$piece{0 }][$piece{1 }][$piece{2 }])) {
return $piece{1 } == 'P' &&
$this->_pieces[$piece{0 }][$piece{1 }][$piece{2 }][1 ] == 'P';
* Determine whether it is possible to capture the piece delivering check,
* or to interpose a piece in between the checking piece and the king
* @param array squares that will block a checkmate
* @param W|Bcolor of the side attempting to prevent checkmate
* @return boolean true if it is possible to remove check
function _interposeOrCapture ($squares, $color)
foreach ($squares as $square) {
// if any squares are unoccupied, and we can place a piece,
// then it is possible to interpose through piece placement
foreach ($this->_captured[$color] as $name => $count) {
if ($square[1 ] == '1' || $square[1 ] == '8') {
// placement is not possible, try regular interpose/capture
foreach ($this->_pieces[$color] as $name => $pieces) {
foreach ($pieces as $value) {
foreach($squares as $square) {
// try the move, see if we're still in check
// if so, then the piece is pinned and cannot move
PEAR ::pushErrorHandling (PEAR_ERROR_RETURN );
PEAR ::popErrorHandling (PEAR_ERROR_RETURN );
$stillchecked = $this->inCheck($color);
* Get a list of all pieces on the board organized by the type of piece,
* and the color of the square the piece is on.
* Used to determine basic draw conditions
* 'W' => array('B' => array('W', 'B'), // all bishops
* 'B' => array('Q' => array('B'), // all queens
* 'K' => array('W'),... // king is on white square
$ret = array ('W' => array (), 'B' => array ());
foreach($this->_pieces as $color => $all) {
foreach ($all as $name => $pieces) {
foreach ($pieces as $loc) {
* Used to determine check
* Retrieve all of the moves of the pieces matching the color passed in.
foreach ($this->_pieces[$color] as $name => $pieces) {
foreach ($pieces as $i => $loc) {
$ret[$color . $name . $i] = $this->getPossibleMoves($loc[1 ], $loc[0 ], $color, false );
$ret[$color . $name . $i] = $this->getPossibleMoves($name, $loc, $color, false );
* Get the location of every piece on the board of color $color
* @param W|Bcolor of pieces to check
foreach ($this->_pieces[$color] as $name => $pieces) {
foreach ($pieces as $loc) {
$where = (is_array($loc) ? $loc[0 ] : $loc);
* Render the current board position into Forsyth-Edwards Notation
* This method only renders the board contents, not the castling and other
foreach ($this->_board as $square => $piece) {
if ($square{1 } != $saverow) {
// if we have just moved to the next rank,
// output any whitespace, and a '/'
// increment whitespace - no piece on this square
// add any whitespace and reset
if (is_array($this->_pieces[$piece{0 }][$piece{1 }][$piece{2 }])) {
// add pawns/promoted pawns
$p = ($piece{0 } == 'W') ? $this->_pieces[$piece{0 }][$piece{1 }][$piece{2 }][1 ] :
strtolower($this->_pieces[$piece{0 }][$piece{1 }][$piece{2 }][1 ]);
$p = ($piece{0 } == 'W') ? $piece{1 } : strtolower($piece{1 });
// add any trailing whitespace
* Determine whether one side's king is in check by the other side's pieces
* @param W|Bcolor of pieces to determine enemy check
* @return string|array|falsesquare of checking piece(s) or false
foreach ($possible as $piece => $squares) {
$loc = $this->_pieces[$piece{0 }][$piece{1 }][$piece{2 }];
$ret[] = is_array($loc) ? $loc[0 ] : $loc;
foreach ($this->_board as $square => $piece) {
if (is_array($this->_pieces[$piece{0 }][$piece{1 }][$piece{2 }])) {
$piece = $this->_pieces[$piece{0 }][$piece{1 }][$piece{2 }][1 ];
uksort($ret, array ($this, '_sortToArray'));
return array ('board' => $ret, 'captured' => $this->_captured);
* Add a piece to the chessboard
* @param K|Q|R|N|P|BPiece type
* @param string [a-h][1-8] algebraic location of piece
* @return true|PEAR_Error
* @throws GAMES_CHESS_ERROR_INVALIDSQUARE
* @throws GAMES_CHESS_ERROR_DUPESQUARE
* @throws GAMES_CHESS_ERROR_MULTIPIECE
function addPiece($color, $type, $square)
if (!isset ($this->_board[$square])) {
return $this->raiseError(GAMES_CHESS_ERROR_INVALIDSQUARE ,
array ('square' => $square));
if ($this->_board[$square] != $square) {
$dpiece = $this->_board[$square];
$dpiece = $this->_pieces[$dpiece{0 }][$dpiece{1 }][$dpiece{2 }][1 ];
array ('piece' => $type, 'dpiece' => $dpiece, 'square' => $square));
$addas = $this->_canAddPiece ($type, $color);
array ('color' => $color, 'piece' => $type));
$add = array ($square, $type);
// using a captured piece to place, so decrease captured count
$this->_captured[$color][$type]--;
$this->_pieces[$color][$addas[0 ]][] = $add;
$this->_board[$square] = $color . $addas[0 ] .
(count($this->_pieces[$color][$addas[0 ]]) - 1 );
$addas = $this->_canAddPiece ($type, $color);
array ('color' => $color, 'piece' => $type));
// using a captured pawn to place, so decrease captured count
$this->_captured[$color]['P']--;
$this->_pieces[$color]['P'][] =
$this->_board[$square] = $color . 'P' . (count($this->_pieces[$color]['P']) - 1 );
if (!isset ($this->_pieces[$color]['K'][0 ])) {
$this->_pieces[$color]['K'][0 ] = $square;
$this->_board[$square] = $color . 'K0';
array ('color' => $color, 'piece' => $type));
* Determine whether we can add a piece to the board legally
* A piece can be added if it meets any of these conditions in this order:
* 1. it is one of the existing pieces, and has not already been placed
* 2. it can be created from a promoted pawn
* 3. it can be placed from captured enemy pieces
* 4. the enemy piece can be "captured" (is not present on the board)
* @return false|stringeither the name of the piece to add this as, or false if no room
function _canAddPiece ($piece, $color)
$enemy = $color == 'W' ? 'B' : 'W';
// determine if the enemy has captured any of our pieces and placed them
$total = count($this->_pieces[$enemy][$piece]) - $possible[$piece];
// we only care about captured pieces that have been placed
// add the number of these pieces the enemy has captured
$total += $this->_captured[$enemy][$piece];
// add the number of this piece (not promoted pawns) we have on the board
$total += count($this->_pieces[$color][$piece]);
if ($total < $possible[$piece]) {
// can add it as a normal piece
// only non-pawns can be promoted to a pawn
// extract the number of placed captured pawns on the enemy's side
$ptotal = count($this->_pieces[$enemy]['P']) - 8;
$ptotal += count($this->_pieces[$color]['P']) +
$this->_captured[$enemy]['P'];
// no space available for promoted pawns
if ($this->_captured[$color][$piece]) {
// determine whether we have captured any enemy pieces of this type
$total += $this->_captured[$color][$piece];
if ($total < $possible[$piece] * 2 ) {
// allowed, through placement move
if (count($this->_pieces[$color][$piece]) + $this->_captured[$enemy][$piece] ==
// full - we've captured/placed all enemy pieces and promoted
if (count($this->_pieces[$enemy][$piece]) < $possible[$piece]) {
// simulate a capture followed by placement
* Basic draw is impossible in crazyhouse, because it is always possible
* Repetition draw is not allowed in crazyhouse
* 50 move draw is not allowed in crazyhouse
Documentation generated on Sun, 17 Jun 2007 02:00:49 -0400 by phpDocumentor 1.3.2. PEAR Logo Copyright © PHP Group 2004.
|