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: Crazyhouse.php,v 1.10 2006/11/18 00:09:20 cellog Exp $
20/**
21 * A standard chess game representation
22 * @package Games_Chess
23 * @author Gregory Beaver <cellog@php.net>
24 */
25/**
26 * The parent class
27 */
28require_once 'Games/Chess/Standard.php';
29
30/**
31 * Crazyhouse chess game
32 *
33 * A captured piece may be placed on the board as your own piece!
34 * Note that FEN is incapable of setting up a game mid-swing - no
35 * record of captured pieces is possible.  If requested, a future version
36 * may extend the FEN standard to allow this, particularly if ICC follows
37 * the same standard
38 * @package Games_Chess
39 * @author Gregory Beaver <cellog@php.net>
40 */
41class Games_Chess_Crazyhouse extends Games_Chess_Standard {
42    /**
43     * Captured piece count.
44     *
45     * Each sub-array consists of pieces owned by the color, so
46     * 'W' (white) has captured the pieces in the 'W' sub-array
47     * @var array
48     */
49    var $_captured =
50        array(
51            'W' =>
52                array(
53                    'P' => 0,
54                    'B' => 0,
55                    'N' => 0,
56                    'Q' => 0,
57                    'R' => 0,
58                ),
59            'B' =>
60                array(
61                    'P' => 0,
62                    'B' => 0,
63                    'N' => 0,
64                    'Q' => 0,
65                    'R' => 0,
66                )
67        );
68
69    /**
70     *
71     */
72    var $_pieces =
73        array(
74            'W' =>
75                array(
76                    'P' =>
77                        array(),
78                    'B' =>
79                        array(),
80                    'N' =>
81                        array(),
82                    'Q' =>
83                        array(),
84                    'R' =>
85                        array(),
86                    'K' =>
87                        array(),
88                ),
89            'B' =>
90                array(
91                    'P' =>
92                        array(),
93                    'B' =>
94                        array(),
95                    'N' =>
96                        array(),
97                    'Q' =>
98                        array(),
99                    'R' =>
100                        array(),
101                    'K' =>
102                        array(),
103                ),
104        );
105    /**
106     * Set up a blank chess board
107     */
108    function blankBoard()
109    {
110        Games_Chess::blankBoard();
111        $this->_captured =
112        array(
113            'W' =>
114                array(
115                    'P' => 0,
116                    'B' => 0,
117                    'N' => 0,
118                    'Q' => 0,
119                    'R' => 0,
120                ),
121            'B' =>
122                array(
123                    'P' => 0,
124                    'B' => 0,
125                    'N' => 0,
126                    'Q' => 0,
127                    'R' => 0,
128                )
129        );
130        $this->_pieces =
131        array(
132            'W' =>
133                array(
134                    'P' =>
135                        array(),
136                    'B' =>
137                        array(),
138                    'N' =>
139                        array(),
140                    'Q' =>
141                        array(),
142                    'R' =>
143                        array(),
144                    'K' =>
145                        array(),
146                ),
147            'B' =>
148                array(
149                    'P' =>
150                        array(),
151                    'B' =>
152                        array(),
153                    'N' =>
154                        array(),
155                    'Q' =>
156                        array(),
157                    'R' =>
158                        array(),
159                    'K' =>
160                        array(),
161                ),
162        );
163    }
164
165    /**
166     * Set up a starting position for a new chess game
167     * @access protected
168     */
169    function _setupStartingPosition()
170    {
171        parent::_setupStartingPosition();
172        $this->_board = array(
173'a8' => 'BR0', 'b8' => 'BN0', 'c8' => 'BB0', 'd8' => 'BQ0', 'e8' => 'BK0', 'f8' => 'BB1', 'g8' => 'BN1', 'h8' => 'BR1',
174'a7' => 'BP0', 'b7' => 'BP1', 'c7' => 'BP2', 'd7' => 'BP3', 'e7' => 'BP4', 'f7' => 'BP5', 'g7' => 'BP6', 'h7' => 'BP7',
175'a6' => 'a6', 'b6' => 'b6', 'c6' => 'c6', 'd6' => 'd6', 'e6' => 'e6', 'f6' => 'f6', 'g6' => 'g6', 'h6' => 'h6',
176'a5' => 'a5', 'b5' => 'b5', 'c5' => 'c5', 'd5' => 'd5', 'e5' => 'e5', 'f5' => 'f5', 'g5' => 'g5', 'h5' => 'h5',
177'a4' => 'a4', 'b4' => 'b4', 'c4' => 'c4', 'd4' => 'd4', 'e4' => 'e4', 'f4' => 'f4', 'g4' => 'g4', 'h4' => 'h4',
178'a3' => 'a3', 'b3' => 'b3', 'c3' => 'c3', 'd3' => 'd3', 'e3' => 'e3', 'f3' => 'f3', 'g3' => 'g3', 'h3' => 'h3',
179'a2' => 'WP0', 'b2' => 'WP1', 'c2' => 'WP2', 'd2' => 'WP3', 'e2' => 'WP4', 'f2' => 'WP5', 'g2' => 'WP6', 'h2' => 'WP7',
180'a1' => 'WR0', 'b1' => 'WN0', 'c1' => 'WB0', 'd1' => 'WQ0', 'e1' => 'WK0', 'f1' => 'WB1', 'g1' => 'WN1', 'h1' => 'WR1',
181        );
182        $this->_captured =
183        array(
184            'W' =>
185                array(
186                    'P' => 0,
187                    'B' => 0,
188                    'N' => 0,
189                    'Q' => 0,
190                    'R' => 0,
191                ),
192            'B' =>
193                array(
194                    'P' => 0,
195                    'B' => 0,
196                    'N' => 0,
197                    'Q' => 0,
198                    'R' => 0,
199                )
200        );
201        $this->_pieces =
202        array(
203            'W' =>
204                array(
205                    'P' =>
206                        array(
207                            array('a2', 'P'),
208                            array('b2', 'P'),
209                            array('c2', 'P'),
210                            array('d2', 'P'),
211                            array('e2', 'P'),
212                            array('f2', 'P'),
213                            array('g2', 'P'),
214                            array('h2', 'P'),
215                        ),
216                    'B' =>
217                        array(
218                            'c1',
219                            'f1',
220                        ),
221                    'N' =>
222                        array(
223                            'b1',
224                            'g1',
225                        ),
226                    'Q' =>
227                        array(
228                            'd1'
229                        ),
230                    'R' =>
231                        array(
232                            'a1',
233                            'h1',
234                        ),
235                    'K' =>
236                        array(
237                            'e1'
238                        ),
239                ),
240            'B' =>
241                array(
242                    'P' =>
243                        array(
244                            array('a7', 'P'),
245                            array('b7', 'P'),
246                            array('c7', 'P'),
247                            array('d7', 'P'),
248                            array('e7', 'P'),
249                            array('f7', 'P'),
250                            array('g7', 'P'),
251                            array('h7', 'P'),
252                        ),
253                    'B' =>
254                        array(
255                            'c8',
256                            'f8',
257                        ),
258                    'N' =>
259                        array(
260                            'b8',
261                            'g8',
262                        ),
263                    'Q' =>
264                        array(
265                            'd8',
266                        ),
267                    'R' =>
268                        array(
269                            'a8',
270                            'h8',
271                        ),
272                    'K' =>
273                        array(
274                            'e8'
275                        ),
276                ),
277        );
278    }
279
280    /**
281     * Make a move from a Standard Algebraic Notation (SAN) format
282     *
283     * SAN is just a normal chess move like Na4, instead of the English Notation,
284     * like NR4
285     * @param string
286     * @return true|PEAR_Error
287     */
288    function moveSAN($move)
289    {
290        if (!is_array($this->_board)) {
291            $this->resetGame();
292        }
293        if (!strpos($move, '@')) {
294            return parent::moveSAN($move);
295        }
296        if (!$this->isError($parsedMove = $this->_parseMove($move))) {
297            if (!$this->isError($err = $this->_validMove($parsedMove))) {
298                $p = $parsedMove[GAMES_CHESS_PIECEPLACEMENT]['piece'];
299                $sq = $parsedMove[GAMES_CHESS_PIECEPLACEMENT]['square'];
300                $this->_captured[$this->_move][$p]--;
301                $set = ($p == 'P') ? array($sq, 'P') : $sq;
302                $this->_pieces[$this->_move][$p][] = $set;
303                $this->_board[$sq] = $this->_move . $p .
304                    (count($this->_pieces[$this->_move][$p]) - 1);
305                $this->_enPassantSquare = '-';
306                $this->_moves[$this->_moveNumber][($this->_move == 'W') ? 0 : 1] = $move;
307                $oldMoveNumber = $this->_moveNumber;
308                $this->_moveNumber += ($this->_move == 'W') ? 0 : 1;
309                $this->_halfMoves++;
310                $moveWithCheck = $move;
311                if ($this->inCheckMate(($this->_move == 'W') ? 'B' : 'W')) {
312                    $moveWithCheck .= '#';
313                } elseif ($this->inCheck(($this->_move == 'W') ? 'B' : 'W')) {
314                    $moveWithCheck .= '+';
315                }
316                $this->_movesWithCheck[$oldMoveNumber][($this->_move == 'W') ? 0 : 1] = $moveWithCheck;
317                $this->_move = ($this->_move == 'W' ? 'B' : 'W');
318
319                // increment the position counter for this position
320                $x = $this->renderFen(false);
321                if (!isset($this->_allFENs[$x])) {
322                    $this->_allFENs[$x] = 0;
323                }
324                $this->_allFENs[$x]++;
325                return true;
326            } else {
327                return $err;
328            }
329        } else {
330            return $parsedMove;
331        }
332    }
333
334    function _validMove($move)
335    {
336        list($type, $info) = each($move);
337        reset($move);
338        if ($type == GAMES_CHESS_PIECEPLACEMENT) {
339            if (!$this->_captured[$this->_move][$info['piece']]) {
340                return $this->raiseError(GAMES_CHESS_ERROR_NOPIECES_TOPLACE,
341                    array('color' => $this->_move == 'W' ? 'B' : 'W', 'piece' => $info['piece']));
342            }
343            if ($this->_board[$info['square']] != $info['square']) {
344                return $this->raiseError(GAMES_CHESS_ERROR_PIECEINTHEWAY,
345                    array('square' => $info['square']));
346            }
347            return true;
348        } else {
349            return parent::_validMove($move);
350        }
351    }
352
353    function _takePiece($square)
354    {
355        $piece = $this->_board[$square];
356        unset($this->_pieces[$piece{0}][$piece{1}][$piece{2} + 0]);
357        // add a piece to the list of pieces captured by the enemy
358        $this->_captured[$piece{0} == 'W' ? 'B' : 'W'][$piece{1}]++;
359        // ensure integrity of the remaining pieces
360        for ($i = $piece{2} + 1; $i <= count($this->_pieces[$piece{0}][$piece{1}]); $i++) {
361            $value = $this->_pieces[$piece{0}][$piece{1}][$i];
362            // get the square this piece is on
363            if (is_array($value)) {
364                $value = $value[0];
365            }
366            // adjust to the right value
367            $this->_board[$value]{2} = ($this->_board[$value]{2} - 1) . '';
368        }
369        // fix the indices
370        $this->_pieces[$piece{0}][$piece{1}] = array_values($this->_pieces[$piece{0}][$piece{1}]);
371    }
372
373    /**
374     * Move a piece from one square to another, disregarding any existing pieces
375     *
376     * {@link _takePiece()} should always be used prior to this method.  No
377     * validation is performed
378     * @param string [a-h][1-8] square the piece resides on
379     * @param string [a-h][1-8] square the piece moves to
380     * @param string Piece to promote to if this is a promotion move
381     */
382    function _movePiece($from, $to, $promote = '')
383    {
384        $piece = $this->_board[$from];
385        if ($piece == $from) {
386            return;
387        }
388        if (isset($this->_pieces[$piece{0}][$piece{1}][$piece{2}])) {
389            $newto = $this->_pieces[$piece{0}][$piece{1}][$piece{2}];
390            if (is_array($newto)) {
391                $newto[0] = $to;
392                if ($to{1} == '8' || $to{1} == '1') {
393                    $newto[1] = $promote;
394                }
395            } else {
396                $newto = $to;
397            }
398            $this->_pieces[$piece{0}][$piece{1}][$piece{2}] = $newto;
399        }
400    }
401
402    /**
403     * Translate an algebraic coordinate into the color and name of a piece,
404     * or false if no piece is on that square
405     * @return false|array Format array('color' => B|W, 'piece' => P|R|Q|N|K|B)
406     * @param string [a-h][1-8]
407     * @access protected
408     */
409    function _squareToPiece($square)
410    {
411        if ($this->_board[$square] != $square) {
412            $piece = $this->_board[$square];
413            if ($piece{1} == 'P') {
414                $color = $piece{0};
415                $piece = $this->_pieces[$piece{0}][$piece{1}][$piece{2}][1];
416            } else {
417                $color = $piece{0};
418                $piece = $piece{1};
419            }
420            return array('color' => $color, 'piece' => $piece);
421        } else {
422            return false;
423        }
424    }
425
426    /**
427     * Retrieve the locations of all pieces of the same type as $piece
428     * @param K|B|N|R|W|P
429     * @param W|B
430     * @param string [a-h][1-8] optional square of piece to exclude from the listing
431     * @access protected
432     * @return array
433     */
434    function _getAllPieceSquares($piece, $color, $exclude = null)
435    {
436        $ret = array();
437        if ($piece != 'P') {
438            foreach ($this->_pieces[$color]['P'] as $loc) {
439                if ($loc[1] != $piece || $loc[0] == $exclude) {
440                    continue;
441                }
442                $ret[] = $loc[0];
443            }
444        }
445        foreach ($this->_pieces[$color][$piece] as $loc) {
446            if ($loc != $exclude) {
447                $ret[] = $loc;
448            }
449        }
450        return $ret;
451    }
452
453    /**
454     * @return string|PEAR_Error
455     * @param array contents returned from {@link parent::_parseMove()}
456     *              in other words, not array(GAMES_CHESS_PIECEMOVE =>
457     *              array('piece' => 'K', ...)), but array('piece' => 'K', ...)
458     * @param W|B current side moving
459     */
460    function _getSquareFromParsedMove($parsedmove, $color = null)
461    {
462        if (is_null($color)) {
463            $color = $this->_move;
464        }
465        switch ($parsedmove['piece']) {
466            case 'K' :
467                if (in_array($parsedmove['square'],
468                    $this->getPossibleKingMoves($king = $this->_getKing($color), $color))) {
469                    return $king;
470                }
471            break;
472            case 'Q' :
473            case 'B' :
474            case 'R' :
475            case 'N' :
476                if ($parsedmove['disambiguate']) {
477                    if (strlen($parsedmove['disambiguate']) == 2) {
478                        $square = $parsedmove['disambiguate'];
479                    } elseif (is_numeric($parsedmove['disambiguate'])) {
480                        $row = $parsedmove['disambiguate'];
481                    } else {
482                        $col = $parsedmove['disambiguate'];
483                    }
484                } else {
485                    $others = array();
486                    $others = $this->_getAllPieceSquares($parsedmove['piece'],
487                                                         $color);
488                    $disambiguate = '';
489                    $ambiguous = array();
490                    if (count($others)) {
491                        foreach ($others as $square) {
492                            if (in_array($parsedmove['square'],
493                                    $this->getPossibleMoves($parsedmove['piece'],
494                                                            $square,
495                                                            $color))) {
496                                // other pieces can move to this square - need to disambiguate
497                                $ambiguous[] = $square;
498                            }
499                        }
500                    }
501                    if (count($ambiguous) > 1) {
502                        $pieces = implode($ambiguous, ' ');
503                        return $this->raiseError(
504                            GAMES_CHESS_ERROR_TOO_AMBIGUOUS,
505                            array('san' => $parsedmove['piece'] .
506                                $parsedmove['disambiguate'] . $parsedmove['takes']
507                                . $parsedmove['square'],
508                                  'squares' => $pieces,
509                                  'piece' => $parsedmove['piece']));
510                    }
511                    $square = $col = $row = null;
512                }
513                $potentials = array();
514                foreach ($this->_pieces[$color]['P'] as $name => $value) {
515                    if (isset($square)) {
516                        if ($value[0] == $square &&
517                              $value[1] == $parsedmove['piece']) {
518                            return $square;
519                        }
520                    } elseif (isset($col)) {
521                        if ($value[0]{0} == $col &&
522                              $value[1] == $parsedmove['piece']) {
523                            if (in_array($parsedmove['square'],
524                                  $this->getPossibleMoves($parsedmove['piece'],
525                                                            $value[0], $color))) {
526                                $potentials[] = $value[0];
527                            }
528                        }
529                    } elseif (isset($row)) {
530                        if ($value[0]{1} == $row &&
531                              $value[1] == $parsedmove['piece']) {
532                            if (in_array($parsedmove['square'],
533                                  $this->getPossibleMoves($parsedmove['piece'],
534                                                            $value[0], $color))) {
535                                $potentials[] = $value[0];
536                            }
537                        }
538                    } else {
539                        if ($value[1] == $parsedmove['piece']) {
540                            if (in_array($parsedmove['square'],
541                                  $this->getPossibleMoves($parsedmove['piece'],
542                                                            $value[0], $color))) {
543                                $potentials[] = $value[0];
544                            }
545                        }
546                    }
547                }
548                foreach ($this->_pieces[$color][$parsedmove['piece']] as $name => $value) {
549                    if (isset($square)) {
550                        if ($value == $square) {
551                            return $square;
552                        }
553                    } elseif (isset($col)) {
554                        if ($value{0} == $col) {
555                            if (in_array($parsedmove['square'],
556                                  $this->getPossibleMoves($parsedmove['piece'],
557                                                            $value, $color))) {
558                                $potentials[] = $value;
559                            }
560                        }
561                    } elseif (isset($row)) {
562                        if ($value{1} == $row) {
563                            if (in_array($parsedmove['square'],
564                                  $this->getPossibleMoves($parsedmove['piece'],
565                                                            $value, $color))) {
566                                $potentials[] = $value;
567                            }
568                        }
569                    } else {
570                        if (in_array($parsedmove['square'],
571                              $this->getPossibleMoves($parsedmove['piece'],
572                                                        $value, $color))) {
573                            $potentials[] = $value;
574                        }
575                    }
576                }
577                if (count($potentials) == 1) {
578                    return $potentials[0];
579                }
580            break;
581            case 'P' :
582                if ($parsedmove['disambiguate']) {
583                    $square = $parsedmove['disambiguate'] . $parsedmove['takesfrom'];
584                } else {
585                    $square = null;
586                }
587                if ($parsedmove['takesfrom']) {
588                    $col = $parsedmove['takesfrom'];
589                } else {
590                    $col = null;
591                }
592                $potentials = array();
593                foreach ($this->_pieces[$color]['P'] as $name => $value) {
594                    if (isset($square)) {
595                        if ($value[0] == $square && $value[1] == 'P') {
596                            return $square;
597                        }
598                    } elseif (isset($col)) {
599                        if ($value[0]{0} == $col && $value[1] == 'P') {
600                            if (in_array($parsedmove['square'],
601                                  $this->getPossiblePawnMoves($value[0], $color))) {
602                                $potentials[] = $value[0];
603                            }
604                        }
605                    } else {
606                        if ($value[1] == 'P') {
607                            if (in_array($parsedmove['square'],
608                                  $this->getPossiblePawnMoves($value[0], $color))) {
609                                $potentials[] = $value[0];
610                            }
611                        }
612                    }
613                }
614                if (count($potentials) == 1) {
615                    return $potentials[0];
616                }
617            break;
618        }
619        if ($parsedmove['piece'] == 'P') {
620            $san = $parsedmove['takesfrom'] . $parsedmove['takes'] . $parsedmove['square'];
621        } else {
622            $san = $parsedmove['piece'] .
623                           $parsedmove['disambiguate'] . $parsedmove['takes'] .
624                           $parsedmove['square'];
625        }
626        return $this->raiseError(GAMES_CHESS_ERROR_NOPIECE_CANDOTHAT,
627            array('san' => $san,
628                  'color' => $color));
629    }
630
631    /**
632     * Get the location of the king
633     *
634     * assumes valid color input
635     * @return false|string
636     * @access protected
637     */
638    function _getKing($color = null)
639    {
640        if (!is_null($color)) {
641            if (!isset($this->_pieces[$color]['K'][0])) {
642                return false;
643            }
644            return $this->_pieces[$color]['K'][0];
645        } else {
646            if (!isset($this->_pieces[$this->_move]['K'][0])) {
647                return false;
648            }
649            return $this->_pieces[$this->_move]['K'][0];
650        }
651    }
652
653    /**
654     * Get the location of a piece
655     *
656     * This does NOT take an algebraic square as the argument, but the contents
657     * of _board[algebraic square]
658     * @param string
659     * @return string|array
660     * @access protected
661     */
662    function _getPiece($piece)
663    {
664        if (!isset($this->_pieces[$piece{0}][$piece{1}][$piece{2}])) {
665            return false;
666        }
667        return $piece{1} == 'P' ?
668            $this->_pieces[$piece{0}][$piece{1}][$piece{2}][0] :
669            $this->_pieces[$piece{0}][$piece{1}][$piece{2}];
670    }
671
672    /**
673     * Determine whether a piece name is a knight
674     *
675     * This does NOT take an algebraic square as the argument, but the contents
676     * of _board[algebraic square]
677     * @param string
678     * @return boolean
679     * @access protected
680     */
681    function _isKnight($piece)
682    {
683        if (!isset($this->_pieces[$piece{0}][$piece{1}][$piece{2}])) {
684            return false;
685        }
686        return $piece{1} == 'N' ||
687            ($piece{1} == 'P' &&
688                $this->_pieces[$piece{0}][$piece{1}][$piece{2}][1] == 'N');
689    }
690
691   /**
692     * Determine whether a piece name is a king
693     *
694     * This does NOT take an algebraic square as the argument, but the contents
695     * of _board[algebraic square]
696     * @param string
697     * @return boolean
698     * @access protected
699     */
700    function isKing($piecename)
701    {
702        if ($piecename{2} != '0') {
703            return false;
704        }
705        return $piecename{1} == 'K';
706    }
707
708    /**
709     * Determine whether a piece name is a queen
710     *
711     * This does NOT take an algebraic square as the argument, but the contents
712     * of _board[algebraic square]
713     * @param string
714     * @return boolean
715     * @access protected
716     */
717    function _isQueen($piece)
718    {
719        if (!isset($this->_pieces[$piece{0}][$piece{1}][$piece{2}])) {
720            return false;
721        }
722        return $piece{1} == 'Q' ||
723            ($piece{1} == 'P' &&
724                $this->_pieces[$piece{0}][$piece{1}][$piece{2}][1] == 'Q');
725    }
726
727    /**
728     * Determine whether a piece name is a bishop
729     *
730     * This does NOT take an algebraic square as the argument, but the contents
731     * of _board[algebraic square]
732     * @param string
733     * @return boolean
734     */
735    function isBishop($piece)
736    {
737        if (!isset($this->_pieces[$piece{0}][$piece{1}][$piece{2}])) {
738            return false;
739        }
740        return $piece{1} == 'B' ||
741            ($piece{1} == 'P' &&
742                $this->_pieces[$piece{0}][$piece{1}][$piece{2}][1] == 'B');
743    }
744
745    /**
746     * Determine whether a piece name is a rook
747     *
748     * This does NOT take an algebraic square as the argument, but the contents
749     * of _board[algebraic square]
750     * @param string
751     * @return boolean
752     */
753    function isRook($piece)
754    {
755        if (!isset($this->_pieces[$piece{0}][$piece{1}][$piece{2}])) {
756            return false;
757        }
758        return $piece{1} == 'R' ||
759            ($piece{1} == 'P' &&
760                $this->_pieces[$piece{0}][$piece{1}][$piece{2}][1] == 'R');
761    }
762
763    /**
764     * Determine whether a piece name is a pawn
765     *
766     * This does NOT take an algebraic square as the argument, but the contents
767     * of _board[algebraic square]
768     * @param string
769     * @return boolean
770     */
771    function isPawn($piece)
772    {
773        if (!isset($this->_pieces[$piece{0}][$piece{1}][$piece{2}])) {
774            return false;
775        }
776        return $piece{1} == 'P' &&
777                $this->_pieces[$piece{0}][$piece{1}][$piece{2}][1] == 'P';
778    }
779
780    /**
781     * Determine whether it is possible to capture the piece delivering check,
782     * or to interpose a piece in between the checking piece and the king
783     * @param array squares that will block a checkmate
784     * @param W|B color of the side attempting to prevent checkmate
785     * @return boolean true if it is possible to remove check
786     */
787    function _interposeOrCapture($squares, $color)
788    {
789        foreach ($squares as $square) {
790            // if any squares are unoccupied, and we can place a piece,
791            // then it is possible to interpose through piece placement
792            if (!$this->_squareToPiece($square)) {
793                foreach ($this->_captured[$color] as $name => $count) {
794                    if (!$count) {
795                        continue;
796                    }
797                    if ($name == 'P') {
798                        // can't place on 1 or 8
799                        if ($square[1] == '1' || $square[1] == '8') {
800                            continue;
801                        }
802                    }
803                    return true;
804                }
805            }
806        }
807        // placement is not possible, try regular interpose/capture
808        foreach ($this->_pieces[$color] as $name => $pieces) {
809            if ($name == 'K') {
810                continue;
811            }
812            foreach ($pieces as $value) {
813                if (is_array($value)) {
814                    $name = $value[1];
815                    $value = $value[0];
816                }
817                $allmoves = $this->getPossibleMoves($name, $value, $color);
818                foreach($squares as $square) {
819                    if (in_array($square, $allmoves)) {
820                        // try the move, see if we're still in check
821                        // if so, then the piece is pinned and cannot move
822                        $this->startTransaction();
823                        $this->_move = $color;
824                        if (!class_exists('PEAR')) {
825                            require_once 'PEAR.php';
826                        }
827                        PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
828                        $ret = $this->moveSquare($value, $square);
829                        PEAR::popErrorHandling(PEAR_ERROR_RETURN);
830                        $this->_move = $color;
831                        $stillchecked = $this->inCheck($color);
832                        $this->rollbackTransaction();
833                        if (!$stillchecked) {
834                            return true;
835                        }
836                    }
837                }
838            }
839        }
840        return false;
841    }
842
843    /**
844     * Get a list of all pieces on the board organized by the type of piece,
845     * and the color of the square the piece is on.
846     *
847     * Used to determine basic draw conditions
848     * @return array Format:
849     *
850     * <pre>
851     * array(
852     *   // white pieces
853     *   'W' => array('B' => array('W', 'B'), // all bishops
854     *                'K' => array('W'),...
855     *               ),
856     *   // black pieces
857     *   'B' => array('Q' => array('B'), // all queens
858     *                'K' => array('W'),... // king is on white square
859     * </pre>
860     * @access protected
861     */
862    function _getPieceTypes()
863    {
864        $ret = array('W' => array(), 'B' => array());
865        foreach($this->_pieces as $color => $all) {
866            foreach ($all as $name => $pieces) {
867                foreach ($pieces as $loc) {
868                    if (is_array($loc)) {
869                        $name = $loc[1];
870                        $loc = $loc[0];
871                    }
872                    $ret[$color][$name][] = $this->_getDiagonalColor($loc);
873                }
874            }
875        }
876        return $ret;
877    }
878
879    /**
880     * Used to determine check
881     *
882     * Retrieve all of the moves of the pieces matching the color passed in.
883     * @param W|B
884     * @return array
885     * @access protected
886     */
887    function _getPossibleChecks($color)
888    {
889        $ret = array();
890        foreach ($this->_pieces[$color] as $name => $pieces) {
891            foreach ($pieces as $i => $loc) {
892                if ($name == 'P') {
893                    $ret[$color . $name . $i] = $this->getPossibleMoves($loc[1], $loc[0], $color, false);
894                } else {
895                    $ret[$color . $name . $i] = $this->getPossibleMoves($name, $loc, $color, false);
896                }
897            }
898        }
899        return $ret;
900    }
901
902    /**
903     * Get the location of every piece on the board of color $color
904     * @access protected
905     * @param W|B color of pieces to check
906     */
907    function _getAllPieceLocations($color)
908    {
909        $ret = array();
910        foreach ($this->_pieces[$color] as $name => $pieces) {
911            foreach ($pieces as $loc) {
912                $where =  (is_array($loc) ? $loc[0] : $loc);
913                $ret[] = $where;
914            }
915        }
916        return $ret;
917    }
918
919    /**
920     * Render the current board position into Forsyth-Edwards Notation
921     *
922     * This method only renders the board contents, not the castling and other
923     * information
924     * @return string
925     * @access protected
926     */
927    function _renderFen()
928    {
929        $fen = '';
930        $ws = 0;
931        $saverow = '8';
932        foreach ($this->_board as $square => $piece) {
933            if ($square{1} != $saverow) {
934                // if we have just moved to the next rank,
935                // output any whitespace, and a '/'
936                if ($ws) {
937                    $fen .= $ws;
938                }
939                $fen .= '/';
940                $ws = 0;
941                $saverow = $square{1};
942            }
943            if ($square == $piece) {
944                // increment whitespace - no piece on this square
945                $ws++;
946            } else {
947                // add any whitespace and reset
948                if ($ws) {
949                    $fen .= $ws;
950                }
951                $ws = 0;
952                if (is_array($this->_pieces[$piece{0}][$piece{1}][$piece{2}])) {
953                    // add pawns/promoted pawns
954                    $p = ($piece{0} == 'W') ? $this->_pieces[$piece{0}][$piece{1}][$piece{2}][1] :
955                        strtolower($this->_pieces[$piece{0}][$piece{1}][$piece{2}][1]);
956                } else {
957                    // add pieces
958                    $p = ($piece{0} == 'W') ? $piece{1} : strtolower($piece{1});
959                }
960                $fen .= $p;
961            }
962        }
963        // add any trailing whitespace
964        if ($ws) {
965            $fen .= $ws;
966        }
967        return $fen;
968    }
969
970    /**
971     * Determine whether one side's king is in check by the other side's pieces
972     * @param W|B color of pieces to determine enemy check
973     * @return string|array|false square of checking piece(s) or false
974     */
975    function inCheck($color)
976    {
977        $ret = array();
978        $king = $this->_getKing($color);
979        $possible = $this->_getPossibleChecks($color == 'W' ? 'B' : 'W');
980        foreach ($possible as $piece => $squares) {
981            if (in_array($king, $squares)) {
982                $loc = $this->_pieces[$piece{0}][$piece{1}][$piece{2}];
983                $ret[] = is_array($loc) ? $loc[0] : $loc;
984            }
985        }
986        if (!count($ret)) {
987            return false;
988        }
989        if (count($ret) == 1) {
990            return $ret[0];
991        }
992        return $ret;
993    }
994
995    function toArray()
996    {
997        $ret = array();
998        foreach ($this->_board as $square => $piece) {
999            if ($piece == $square) {
1000                $ret[$square] = false;
1001                continue;
1002            }
1003            $lower = $piece{0};
1004            if (is_array($this->_pieces[$piece{0}][$piece{1}][$piece{2}])) {
1005                $piece = $this->_pieces[$piece{0}][$piece{1}][$piece{2}][1];
1006            } else {
1007                $piece = $piece{1};
1008            }
1009            if ($lower == 'B') {
1010                $piece = strtolower($piece);
1011            }
1012            $ret[$square] = $piece;
1013        }
1014        uksort($ret, array($this, '_sortToArray'));
1015        return array('board' => $ret, 'captured' => $this->_captured);
1016    }
1017
1018    /**
1019     * Add a piece to the chessboard
1020     * @param W|B piece color
1021     * @param K|Q|R|N|P|B Piece type
1022     * @param string [a-h][1-8] algebraic location of piece
1023     * @return true|PEAR_Error
1024     * @throws GAMES_CHESS_ERROR_INVALIDSQUARE
1025     * @throws GAMES_CHESS_ERROR_DUPESQUARE
1026     * @throws GAMES_CHESS_ERROR_MULTIPIECE
1027     */
1028    function addPiece($color, $type, $square)
1029    {
1030        if (!isset($this->_board[$square])) {
1031            return $this->raiseError(GAMES_CHESS_ERROR_INVALIDSQUARE,
1032                array('square' => $square));
1033        }
1034        if ($this->_board[$square] != $square) {
1035            $dpiece = $this->_board[$square];
1036            if ($dpiece{1} == 'P') {
1037                $dpiece = $this->_pieces[$dpiece{0}][$dpiece{1}][$dpiece{2}][1];
1038            } else {
1039                $dpiece = $dpiece{1};
1040            }
1041            return $this->raiseError(GAMES_CHESS_ERROR_DUPESQUARE,
1042                array('piece' => $type, 'dpiece' => $dpiece, 'square' => $square));
1043        }
1044        switch ($type) {
1045            case 'B' :
1046            case 'N' :
1047            case 'R' :
1048            case 'Q' :
1049                $addas = $this->_canAddPiece($type, $color);
1050                if (!$addas) {
1051                    return $this->raiseError(GAMES_CHESS_ERROR_MULTIPIECE,
1052                        array('color' => $color, 'piece' => $type));
1053                }
1054                if ($addas[0] == 'P') {
1055                    $add = array($square, $type);
1056                } else {
1057                    $add = $square;
1058                }
1059                if ($addas[1] == 2) {
1060                    // using a captured piece to place, so decrease captured count
1061                    $this->_captured[$color][$type]--;
1062                }
1063                $this->_pieces[$color][$addas[0]][] = $add;
1064                $this->_board[$square] = $color . $addas[0] .
1065                    (count($this->_pieces[$color][$addas[0]]) - 1);
1066            break;
1067            case 'P' :
1068                $addas = $this->_canAddPiece($type, $color);
1069                if (!$addas) {
1070                    return $this->raiseError(GAMES_CHESS_ERROR_MULTIPIECE,
1071                        array('color' => $color, 'piece' => $type));
1072                }
1073                if ($addas[1] == 2) {
1074                    // using a captured pawn to place, so decrease captured count
1075                    $this->_captured[$color]['P']--;
1076                }
1077                // handle regular pawns
1078                $this->_pieces[$color]['P'][] =
1079                    array($square, 'P');
1080                $this->_board[$square] = $color . 'P' . (count($this->_pieces[$color]['P']) - 1);
1081            break;
1082            case 'K' :
1083                if (!isset($this->_pieces[$color]['K'][0])) {
1084                    $this->_pieces[$color]['K'][0] = $square;
1085                    $this->_board[$square] = $color . 'K0';
1086                } else {
1087                    return $this->raiseError(GAMES_CHESS_ERROR_MULTIPIECE,
1088                        array('color' => $color, 'piece' => $type));
1089                }
1090            break;
1091        }
1092        return true;
1093    }
1094
1095    /**
1096     * Determine whether we can add a piece to the board legally
1097     *
1098     * A piece can be added if it meets any of these conditions in this order:
1099     *
1100     *  1. it is one of the existing pieces, and has not already been placed
1101     *     on the enemy side
1102     *  2. it can be created from a promoted pawn
1103     *  3. it can be placed from captured enemy pieces
1104     *  4. the enemy piece can be "captured" (is not present on the board)
1105     * @param P|Q|R|N $piece
1106     * @return false|string either the name of the piece to add this as, or false if no room
1107     */
1108    function _canAddPiece($piece, $color)
1109    {
1110        $enemy = $color == 'W' ? 'B' : 'W';
1111        $possible = array(
1112            'P' => 8,
1113            'Q' => 1,
1114            'R' => 2,
1115            'N' => 2,
1116            'B' => 2,
1117        );
1118        // determine if the enemy has captured any of our pieces and placed them
1119        $total = count($this->_pieces[$enemy][$piece]) - $possible[$piece];
1120        if ($total < 0) {
1121            // we only care about captured pieces that have been placed
1122            $total = 0;
1123        }
1124        // add the number of these pieces the enemy has captured
1125        $total += $this->_captured[$enemy][$piece];
1126        // add the number of this piece (not promoted pawns) we have on the board
1127        $total += count($this->_pieces[$color][$piece]);
1128        if ($total < $possible[$piece]) {
1129            // can add it as a normal piece
1130            return array($piece, 1);
1131        }
1132
1133        // try promotion next
1134        if ($piece != 'P') {
1135            do {
1136                // only non-pawns can be promoted to a pawn
1137                // extract the number of placed captured pawns on the enemy's side
1138                $ptotal = count($this->_pieces[$enemy]['P']) - 8;
1139                if ($ptotal < 0) {
1140                    $ptotal = 0;
1141                }
1142                $ptotal += count($this->_pieces[$color]['P']) +
1143                    $this->_captured[$enemy]['P'];
1144                if ($ptotal == 8) {
1145                    // no space available for promoted pawns
1146                    break;
1147                }
1148                // add it as a pawn
1149                return array('P', 1);
1150            } while (false);
1151        }
1152        if ($this->_captured[$color][$piece]) {
1153            // determine whether we have captured any enemy pieces of this type
1154            $total += $this->_captured[$color][$piece];
1155            if ($total < $possible[$piece] * 2) {
1156                // allowed, through placement move
1157                return array($piece, 2);
1158            }
1159        }
1160        if (count($this->_pieces[$color][$piece]) + $this->_captured[$enemy][$piece] ==
1161              2 * $possible[$piece]) {
1162            // full - we've captured/placed all enemy pieces and promoted
1163            // all pawns as well
1164            return false;
1165        }
1166        if (count($this->_pieces[$enemy][$piece]) < $possible[$piece]) {
1167            // simulate a capture followed by placement
1168            return array($piece, 3);
1169        }
1170        return false;
1171    }
1172
1173    /**
1174     * Basic draw is impossible in crazyhouse, because it is always possible
1175     * to place another piece
1176     * @return false
1177     */
1178    function inBasicDraw()
1179    {
1180        return false;
1181    }
1182
1183    /**
1184     * Repetition draw is not allowed in crazyhouse
1185     * @return false
1186     */
1187    function inRepetitionDraw()
1188    {
1189        return false;
1190    }
1191
1192    /**
1193     * 50 move draw is not allowed in crazyhouse
1194     * @return false
1195     */
1196    function in50MoveDraw()
1197    {
1198        return false;
1199    }
1200}
1201?>