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: Standard.php,v 1.8 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.php';
29
30/**
31 * Standard chess game
32 * @package Games_Chess
33 * @author Gregory Beaver <cellog@php.net>
34 */
35class Games_Chess_Standard extends Games_Chess {
36    /**
37     * The chess pieces
38     * @access private
39     * @var array
40     */
41    var $_pieces;
42
43    /**
44     * Set up a blank chess board
45     */
46    function blankBoard()
47    {
48        parent::blankBoard();
49        $this->_pieces =
50        array(
51            'WR1' => false,
52            'WN1' => false,
53            'WB1' => false,
54            'WQ' => false,
55            'WK' => false,
56            'WB2' => false,
57            'WN2' => false,
58            'WR2' => false,
59
60            'WP1' => false,
61            'WP2' => false,
62            'WP3' => false,
63            'WP4' => false,
64            'WP5' => false,
65            'WP6' => false,
66            'WP7' => false,
67            'WP8' => false,
68
69            'BP1' => false,
70            'BP2' => false,
71            'BP3' => false,
72            'BP4' => false,
73            'BP5' => false,
74            'BP6' => false,
75            'BP7' => false,
76            'BP8' => false,
77
78            'BR1' => false,
79            'BN1' => false,
80            'BB1' => false,
81            'BQ' => false,
82            'BK' => false,
83            'BB2' => false,
84            'BN2' => false,
85            'BR2' => false,
86        );
87    }
88
89    /**
90     * Set up a starting position for a new chess game
91     * @access protected
92     */
93    function _setupStartingPosition()
94    {
95        $this->_board = array(
96'a8' => 'BR1', 'b8' => 'BN1', 'c8' => 'BB1', 'd8' => 'BQ', 'e8' => 'BK', 'f8' => 'BB2', 'g8' => 'BN2', 'h8' => 'BR2',
97'a7' => 'BP1', 'b7' => 'BP2', 'c7' => 'BP3', 'd7' => 'BP4', 'e7' => 'BP5', 'f7' => 'BP6', 'g7' => 'BP7', 'h7' => 'BP8',
98'a6' => 'a6', 'b6' => 'b6', 'c6' => 'c6', 'd6' => 'd6', 'e6' => 'e6', 'f6' => 'f6', 'g6' => 'g6', 'h6' => 'h6',
99'a5' => 'a5', 'b5' => 'b5', 'c5' => 'c5', 'd5' => 'd5', 'e5' => 'e5', 'f5' => 'f5', 'g5' => 'g5', 'h5' => 'h5',
100'a4' => 'a4', 'b4' => 'b4', 'c4' => 'c4', 'd4' => 'd4', 'e4' => 'e4', 'f4' => 'f4', 'g4' => 'g4', 'h4' => 'h4',
101'a3' => 'a3', 'b3' => 'b3', 'c3' => 'c3', 'd3' => 'd3', 'e3' => 'e3', 'f3' => 'f3', 'g3' => 'g3', 'h3' => 'h3',
102'a2' => 'WP1', 'b2' => 'WP2', 'c2' => 'WP3', 'd2' => 'WP4', 'e2' => 'WP5', 'f2' => 'WP6', 'g2' => 'WP7', 'h2' => 'WP8',
103'a1' => 'WR1', 'b1' => 'WN1', 'c1' => 'WB1', 'd1' => 'WQ', 'e1' => 'WK', 'f1' => 'WB2', 'g1' => 'WN2', 'h1' => 'WR2',
104        );
105        $this->_halfMoves = 0;
106        $this->_moveNumber = 1;
107        $this->_move = 'W';
108        $this->_WCastleQ = true;
109        $this->_WCastleK = true;
110        $this->_BCastleQ = true;
111        $this->_BCastleK = true;
112        $this->_enPassantSquare = '-';
113        $this->_pieces =
114        array(
115            'WR1' => 'a1',
116            'WN1' => 'b1',
117            'WB1' => 'c1',
118            'WQ' => 'd1',
119            'WK' => 'e1',
120            'WB2' => 'f1',
121            'WN2' => 'g1',
122            'WR2' => 'h1',
123
124            'WP1' => array('a2', 'P'),
125            'WP2' => array('b2', 'P'),
126            'WP3' => array('c2', 'P'),
127            'WP4' => array('d2', 'P'),
128            'WP5' => array('e2', 'P'),
129            'WP6' => array('f2', 'P'),
130            'WP7' => array('g2', 'P'),
131            'WP8' => array('h2', 'P'),
132
133            'BP1' => array('a7', 'P'),
134            'BP2' => array('b7', 'P'),
135            'BP3' => array('c7', 'P'),
136            'BP4' => array('d7', 'P'),
137            'BP5' => array('e7', 'P'),
138            'BP6' => array('f7', 'P'),
139            'BP7' => array('g7', 'P'),
140            'BP8' => array('h7', 'P'),
141
142            'BR1' => 'a8',
143            'BN1' => 'b8',
144            'BB1' => 'c8',
145            'BQ' => 'd8',
146            'BK' => 'e8',
147            'BB2' => 'f8',
148            'BN2' => 'g8',
149            'BR2' => 'h8',
150        );
151    }
152
153    /**
154     * Add a piece to the chessboard
155     * @param W|B piece color
156     * @param K|Q|R|N|P|B Piece type
157     * @param string [a-h][1-8] algebraic location of piece
158     * @return true|PEAR_Error
159     * @throws GAMES_CHESS_ERROR_INVALIDSQUARE
160     * @throws GAMES_CHESS_ERROR_DUPESQUARE
161     * @throws GAMES_CHESS_ERROR_MULTIPIECE
162     */
163    function addPiece($color, $type, $square)
164    {
165        if (!isset($this->_board[$square])) {
166            return $this->raiseError(GAMES_CHESS_ERROR_INVALIDSQUARE,
167                array('square' => $square));
168        }
169        if ($this->_board[$square] != $square) {
170            $dpiece = $this->_board[$square];
171            if ($dpiece{1} == 'P') {
172                $dpiece = $this->_pieces[$dpiece][1];
173            } else {
174                $dpiece = $dpiece{1};
175            }
176            return $this->raiseError(GAMES_CHESS_ERROR_DUPESQUARE,
177                array('piece' => $type, 'dpiece' => $dpiece, 'square' => $square));
178        }
179        switch ($type) {
180            case 'B' :
181            case 'N' :
182            case 'R' :
183                $piece_name = $color . $type;
184                if (!$this->_pieces[$piece_name . '1']) {
185                    $this->_board[$square] = $piece_name . '1';
186                    $this->_pieces[$piece_name . '1'] = $square;
187                } elseif (!$this->_pieces[$piece_name . '2']) {
188                    $this->_board[$square] = $piece_name . '2';
189                    $this->_pieces[$piece_name . '2'] = $square;
190                } else {
191                    // handle promoted pawns
192                    for ($col = 1; $col <= 8; $col++) {
193                        if (!$this->_pieces[$color . 'P' . $col]) {
194                            $this->_pieces[$color . 'P' . $col] =
195                                array($square, $type);
196                            $this->_board[$square] = $color . 'P' . $col;
197                            break 2;
198                        }
199                    }
200                    return $this->raiseError(GAMES_CHESS_ERROR_MULTIPIECE,
201                        array('color' => $color, 'piece' => $type));
202
203                }
204            break;
205            case 'Q' :
206                $piece_name = $color . 'Q';
207                if (!$this->_pieces[$piece_name]) {
208                    $this->_board[$square] = $piece_name;
209                    $this->_pieces[$piece_name] = $square;
210                } else {
211                    // handle promoted pawns
212                    for ($col = 1; $col <= 8; $col++) {
213                        if (!$this->_pieces[$color . 'P' . $col]) {
214                            $this->_pieces[$color . 'P' . $col] =
215                                array($square, 'Q');
216                            $this->_board[$square] = $color . 'P' . $col;
217                            break 2;
218                        }
219                    }
220                    return $this->raiseError(GAMES_CHESS_ERROR_MULTIPIECE,
221                        array('color' => $color, 'piece' => $type));
222                }
223            break;
224            case 'P' :
225                // handle regular pawns
226                for ($col = 1; $col <= 8; $col++) {
227                    if (!$this->_pieces[$color . 'P' . $col]) {
228                        $this->_pieces[$color . 'P' . $col] =
229                            array($square, 'P');
230                        $this->_board[$square] = $color . 'P' . $col;
231                        break 2;
232                    }
233                }
234                return $this->raiseError(GAMES_CHESS_ERROR_MULTIPIECE,
235                    array('color' => $color, 'piece' => $type));
236            break;
237            case 'K' :
238                if (!$this->_pieces[$color . 'K']) {
239                    $this->_pieces[$color . 'K'] = $square;
240                    $this->_board[$square] = $color . 'K';
241                } else {
242                    return $this->raiseError(GAMES_CHESS_ERROR_MULTIPIECE,
243                        array('color' => $color, 'piece' => $type));
244                }
245            break;
246        }
247        return true;
248    }
249
250    /**
251     * Generate a representation of the chess board and pieces for use as a
252     * direct translation to a visual chess board
253     * @return array
254     */
255    function toArray()
256    {
257        $ret = array();
258        foreach ($this->_board as $square => $piece) {
259            if ($piece == $square) {
260                $ret[$square] = false;
261                continue;
262            }
263            $lower = $piece{0};
264            if (is_array($this->_pieces[$piece])) {
265                $piece = $this->_pieces[$piece][1];
266            } else {
267                $piece = $piece{1};
268            }
269            if ($lower == 'B') {
270                $piece = strtolower($piece);
271            }
272            $ret[$square] = $piece;
273        }
274        uksort($ret, array($this, '_sortToArray'));
275        return $ret;
276    }
277
278    /**
279     * Sort two algebraic coordinates for easy display by foreach() iteration
280     * @param string
281     * @param string
282     * @access private
283     */
284    function _sortToArray($a, $b)
285    {
286        if ($a == $b) {
287            return 0;
288        }
289        if ($a{1} == $b{1}) {
290            return strnatcmp($a{0}, $b{0});
291        }
292        if ($a{0} == $b{0}) {
293            return strnatcmp($b{1}, $a{1});
294        }
295        if ($b{1} > $a{1}) {
296            return 1;
297        }
298        if ($a{1} > $b{1}) {
299            return -1;
300        }
301    }
302
303    /**
304     * Render the current board position into Forsyth-Edwards Notation
305     *
306     * This method only renders the board contents, not the castling and other
307     * information
308     * @return string
309     * @access protected
310     */
311    function _renderFen()
312    {
313        $fen = '';
314        $ws = 0;
315        $saverow = '8';
316        foreach ($this->_board as $square => $piece) {
317            if ($square{1} != $saverow) {
318                // if we have just moved to the next rank,
319                // output any whitespace, and a '/'
320                if ($ws) {
321                    $fen .= $ws;
322                }
323                $fen .= '/';
324                $ws = 0;
325                $saverow = $square{1};
326            }
327            if ($square == $piece) {
328                // increment whitespace - no piece on this square
329                $ws++;
330            } else {
331                // add any whitespace and reset
332                if ($ws) {
333                    $fen .= $ws;
334                }
335                $ws = 0;
336                if (is_array($this->_pieces[$piece])) {
337                    // add pawns/promoted pawns
338                    $p = ($piece{0} == 'W') ? $this->_pieces[$piece][1] :
339                        strtolower($this->_pieces[$piece][1]);
340                } else {
341                    // add pieces
342                    $p = ($piece{0} == 'W') ? $piece{1} : strtolower($piece{1});
343                }
344                $fen .= $p;
345            }
346        }
347        // add any trailing whitespace
348        if ($ws) {
349            $fen .= $ws;
350        }
351        return $fen;
352    }
353
354    /**
355     * Get the location of every piece on the board of color $color
356     * @access protected
357     * @param W|B color of pieces to check
358     */
359    function _getAllPieceLocations($color)
360    {
361        $ret = array();
362        foreach ($this->_pieces as $name => $loc) {
363            if ($name{0} == $color) {
364                $where =  (is_array($loc) ? $loc[0] : $loc);
365                if ($where) {
366                    $ret[] = $where;
367                }
368            }
369        }
370        return $ret;
371    }
372
373    /**
374     * Used to determine check
375     *
376     * Retrieve all of the moves of the pieces matching the color passed in.
377     * @param W|B
378     * @return array
379     * @access protected
380     */
381    function _getPossibleChecks($color)
382    {
383        $ret = array();
384        foreach ($this->_pieces as $name => $loc) {
385            if (!$loc) {
386                continue;
387            }
388            if ($name{0} == $color) {
389                if ($name{1} == 'P') {
390                    $ret[$name] = $this->getPossibleMoves($loc[1], $loc[0], $color, false);
391                } else {
392                    $ret[$name] = $this->getPossibleMoves($name{1}, $loc, $color, false);
393                }
394            }
395        }
396        return $ret;
397    }
398
399    /**
400     * Determine whether one side's king is in check by the other side's pieces
401     * @param W|B color of pieces to determine enemy check
402     * @return string|array|false square of checking piece(s) or false
403     */
404    function inCheck($color)
405    {
406        $ret = array();
407        $king = $this->_pieces[$color . 'K'];
408        $possible = $this->_getPossibleChecks($color == 'W' ? 'B' : 'W');
409        foreach ($possible as $piece => $squares) {
410            if (in_array($king, $squares)) {
411                $loc = $this->_pieces[$piece];
412                $ret[] = is_array($loc) ? $loc[0] : $loc;
413            }
414        }
415        if (!count($ret)) {
416            return false;
417        }
418        if (count($ret) == 1) {
419            return $ret[0];
420        }
421        return $ret;
422    }
423
424    /**
425     * Mark a piece as having been taken.  No validation is performed
426     * @param string [a-h][1-8]
427     * @access protected
428     */
429    function _takePiece($piece)
430    {
431        if (isset($this->_pieces[$this->_board[$piece]])) {
432            $this->_pieces[$this->_board[$piece]] = false;
433        }
434    }
435
436    /**
437     * Move a piece from one square to another, disregarding any existing pieces
438     *
439     * {@link _takePiece()} should always be used prior to this method.  No
440     * validation is performed
441     * @param string [a-h][1-8] square the piece resides on
442     * @param string [a-h][1-8] square the piece moves to
443     * @param string Piece to promote to if this is a promotion move
444     */
445    function _movePiece($from, $to, $promote = '')
446    {
447        if (isset($this->_pieces[$this->_board[$from]])) {
448            $newto = $this->_pieces[$this->_board[$from]];
449            if (is_array($newto)) {
450                $newto[0] = $to;
451                if ($promote && ($to{1} == '8' || $to{1} == '1')) {
452                    $newto[1] = $promote;
453                }
454            } else {
455                $newto = $to;
456            }
457            $this->_pieces[$this->_board[$from]] = $newto;
458        }
459    }
460
461    /**
462     * Translate an algebraic coordinate into the color and name of a piece,
463     * or false if no piece is on that square
464     * @return false|array Format array('color' => B|W, 'piece' => P|R|Q|N|K|B)
465     * @param string [a-h][1-8]
466     * @access protected
467     */
468    function _squareToPiece($square)
469    {
470        if ($this->_board[$square] != $square) {
471            $piece = $this->_board[$square];
472            if ($piece{1} == 'P') {
473                $color = $piece{0};
474                $piece = $this->_pieces[$piece][1];
475            } else {
476                $color = $piece{0};
477                $piece = $piece{1};
478            }
479            return array('color' => $color, 'piece' => $piece);
480        } else {
481            return false;
482        }
483    }
484
485    /**
486     * Retrieve the locations of all pieces of the same type as $piece
487     * @param K|B|N|R|W|P
488     * @param W|B
489     * @param string [a-h][1-8] optional square of piece to exclude from the listing
490     * @access protected
491     * @return array
492     */
493    function _getAllPieceSquares($piece, $color, $exclude = null)
494    {
495        $ret = array();
496        foreach ($this->_pieces as $name => $loc) {
497            if (!$loc) {
498                continue;
499            }
500            if ($name{0} != $color) {
501                continue;
502            }
503            if ($name{1} == 'P') {
504                if ($loc[1] != $piece || $loc[0] == $exclude) {
505                    continue;
506                } else {
507                    $ret[] = $loc[0];
508                    continue;
509                }
510            }
511            if ($loc == $exclude) {
512                continue;
513            }
514            if ($name{1} != $piece) {
515                continue;
516            }
517            $ret[] = $loc;
518        }
519        return $ret;
520    }
521
522    /**
523     * @return string|PEAR_Error
524     * @param array contents returned from {@link parent::_parseMove()}
525     *              in other words, not array(GAMES_CHESS_PIECEMOVE =>
526     *              array('piece' => 'K', ...)), but array('piece' => 'K', ...)
527     * @param W|B current side moving
528     */
529    function _getSquareFromParsedMove($parsedmove, $color = null)
530    {
531        if (is_null($color)) {
532            $color = $this->_move;
533        }
534        switch ($parsedmove['piece']) {
535            case 'K' :
536                if (in_array($parsedmove['square'],
537                    $this->getPossibleKingMoves($this->_pieces[$color . 'K'], $color))) {
538                    return $this->_pieces[$color . 'K'];
539                }
540            break;
541            case 'Q' :
542            case 'B' :
543            case 'R' :
544            case 'N' :
545                if ($parsedmove['disambiguate']) {
546                    if (strlen($parsedmove['disambiguate']) == 2) {
547                        $square = $parsedmove['disambiguate'];
548                    } elseif (is_numeric($parsedmove['disambiguate'])) {
549                        $row = $parsedmove['disambiguate'];
550                    } else {
551                        $col = $parsedmove['disambiguate'];
552                    }
553                } else {
554                    $others = array();
555                    $others = $this->_getAllPieceSquares($parsedmove['piece'],
556                                                         $color);
557                    $disambiguate = '';
558                    $ambiguous = array();
559                    if (count($others)) {
560                        foreach ($others as $square) {
561                            if (in_array($parsedmove['square'],
562                                    $this->getPossibleMoves($parsedmove['piece'],
563                                                            $square,
564                                                            $color))) {
565                                // other pieces can move to this square - need to disambiguate
566                                $ambiguous[] = $square;
567                            }
568                        }
569                    }
570                    if (count($ambiguous) > 1) {
571                        $pieces = implode($ambiguous, ' ');
572                        return $this->raiseError(
573                            GAMES_CHESS_ERROR_TOO_AMBIGUOUS,
574                            array('san' => $parsedmove['piece'] .
575                                $parsedmove['disambiguate'] . $parsedmove['takes']
576                                . $parsedmove['square'],
577                                  'squares' => $pieces,
578                                  'piece' => $parsedmove['piece']));
579                    }
580                    $square = $col = $row = null;
581                }
582                $potentials = array();
583                foreach ($this->_pieces as $name => $value) {
584                    if (!$value) {
585                        continue;
586                    }
587                    if ($name{0} != $color) {
588                        continue;
589                    }
590                    if (isset($square)) {
591                        if ($name{1} == $parsedmove['piece'] &&
592                              $value[0] == $square) {
593                            return $square;
594                        }
595                        if ($name{1} == 'P' && $value[0] == $square &&
596                              $value[1] == $parsedmove['piece']) {
597                            return $square;
598                        }
599                    } elseif (isset($col)) {
600                        if ($name{1} == $parsedmove['piece'] &&
601                              $value{0} == $col) {
602                            if (in_array($parsedmove['square'],
603                                  $this->getPossibleMoves($parsedmove['piece'],
604                                                            $value, $color))) {
605                                $potentials[] = $value;
606                            }
607                        }
608                        if ($name{1} == 'P' && $value[0]{0} == $col &&
609                              $value[1] == $parsedmove['piece']) {
610                            if (in_array($parsedmove['square'],
611                                  $this->getPossibleMoves($parsedmove['piece'],
612                                                            $value[0], $color))) {
613                                $potentials[] = $value[0];
614                            }
615                        }
616                    } elseif (isset($row)) {
617                        if ($name{1} == $parsedmove['piece'] &&
618                              $value{1} == $row) {
619                            if (in_array($parsedmove['square'],
620                                  $this->getPossibleMoves($parsedmove['piece'],
621                                                            $value, $color))) {
622                                $potentials[] = $value;
623                            }
624                        }
625                        if ($name{1} == 'P' && $value[0]{1} == $row &&
626                              $value[1] == $parsedmove['piece']) {
627                            if (in_array($parsedmove['square'],
628                                  $this->getPossibleMoves($parsedmove['piece'],
629                                                            $value[0], $color))) {
630                                $potentials[] = $value[0];
631                            }
632                        }
633                    } else {
634                        if ($name{1} == $parsedmove['piece']) {
635                            if (in_array($parsedmove['square'],
636                                  $this->getPossibleMoves($parsedmove['piece'],
637                                                            $value, $color))) {
638                                $potentials[] = $value;
639                            }
640                        } elseif ($name{1} == 'P' &&
641                              $value[1] == $parsedmove['piece']) {
642                            if (in_array($parsedmove['square'],
643                                  $this->getPossibleMoves($parsedmove['piece'],
644                                                            $value[0], $color))) {
645                                $potentials[] = $value[0];
646                            }
647                        }
648                    }
649                }
650                if (count($potentials) == 1) {
651                    return $potentials[0];
652                }
653            break;
654            case 'P' :
655                if ($parsedmove['disambiguate']) {
656                    $square = $parsedmove['disambiguate'] . $parsedmove['takesfrom'];
657                } else {
658                    $square = null;
659                }
660                if ($parsedmove['takesfrom']) {
661                    $col = $parsedmove['takesfrom'];
662                } else {
663                    $col = null;
664                }
665                $potentials = array();
666                foreach ($this->_pieces as $name => $value) {
667                    if ($name{0} != $color) {
668                        continue;
669                    }
670                    if (isset($square)) {
671                        if ($name{1} == 'P' && $value[0] == $square && $value[1] == 'P') {
672                            return $square;
673                        }
674                    } elseif (isset($col)) {
675                        if ($name{1} == 'P' && $value[0]{0} == $col && $value[1] == 'P') {
676                            if (in_array($parsedmove['square'],
677                                  $this->getPossiblePawnMoves($value[0], $color))) {
678                                $potentials[] = $value[0];
679                            }
680                        }
681                    } else {
682                        if ($name{1} == 'P' && $value[1] == 'P') {
683                            if (in_array($parsedmove['square'],
684                                  $this->getPossiblePawnMoves($value[0], $color))) {
685                                $potentials[] = $value[0];
686                            }
687                        }
688                    }
689                }
690                if (count($potentials) == 1) {
691                    return $potentials[0];
692                }
693            break;
694        }
695        if ($parsedmove['piece'] == 'P') {
696            $san = $parsedmove['takesfrom'] . $parsedmove['takes'] . $parsedmove['square'];
697        } else {
698            $san = $parsedmove['piece'] .
699                           $parsedmove['disambiguate'] . $parsedmove['takes'] .
700                           $parsedmove['square'];
701        }
702        return $this->raiseError(GAMES_CHESS_ERROR_NOPIECE_CANDOTHAT,
703            array('san' => $san,
704                  'color' => $color));
705    }
706
707    /**
708     * Get the location of the king
709     *
710     * assumes valid color input
711     * @return false|string
712     * @access protected
713     */
714    function _getKing($color = null)
715    {
716        if (!is_null($color)) {
717            return $this->_pieces[$color . 'K'];
718        } else {
719            return $this->_pieces[$this->_move . 'K'];
720        }
721    }
722
723    /**
724     * Get the location of a piece
725     *
726     * This does NOT take an algebraic square as the argument, but the contents
727     * of _board[algebraic square]
728     * @param string
729     * @return string|array
730     * @access protected
731     */
732    function _getPiece($piecename)
733    {
734        return is_array($this->_pieces[$piecename]) ?
735            $this->_pieces[$piecename][0] :
736            $this->_pieces[$piecename];
737    }
738
739    /**
740     * Determine whether a piece name is a knight
741     *
742     * This does NOT take an algebraic square as the argument, but the contents
743     * of _board[algebraic square]
744     * @param string
745     * @return boolean
746     * @access protected
747     */
748    function _isKnight($piecename)
749    {
750        return $piecename{1} == 'N' ||
751            ($piecename{1} == 'P' &&
752                $this->_pieces[$piecename][1] == 'N');
753    }
754
755    /**
756     * Determine whether a piece name is a queen
757     *
758     * This does NOT take an algebraic square as the argument, but the contents
759     * of _board[algebraic square]
760     * @param string
761     * @return boolean
762     * @access protected
763     */
764    function _isQueen($piecename)
765    {
766        return $piecename{1} == 'Q' ||
767            ($piecename{1} == 'P' &&
768                $this->_pieces[$piecename][1] == 'Q');
769    }
770
771    /**
772     * Determine whether a piece name is a bishop
773     *
774     * This does NOT take an algebraic square as the argument, but the contents
775     * of _board[algebraic square]
776     * @param string
777     * @return boolean
778     * @access protected
779     */
780    function isBishop($piecename)
781    {
782        return $piecename{1} == 'B' ||
783            ($piecename{1} == 'P' &&
784                $this->_pieces[$piecename][1] == 'B');
785    }
786
787    /**
788     * Determine whether a piece name is a rook
789     *
790     * This does NOT take an algebraic square as the argument, but the contents
791     * of _board[algebraic square]
792     * @param string
793     * @return boolean
794     * @access protected
795     */
796    function isRook($piecename)
797    {
798        return $piecename{1} == 'R' ||
799            ($piecename{1} == 'P' &&
800                $this->_pieces[$piecename][1] == 'R');
801    }
802
803    /**
804     * Determine whether a piece name is a pawn
805     *
806     * This does NOT take an algebraic square as the argument, but the contents
807     * of _board[algebraic square]
808     * @param string
809     * @return boolean
810     * @access protected
811     */
812    function isPawn($piecename)
813    {
814        return $piecename{1} == 'P' &&
815                $this->_pieces[$piecename][1] == 'P';
816    }
817
818    /**
819     * Determine whether a piece name is a king
820     *
821     * This does NOT take an algebraic square as the argument, but the contents
822     * of _board[algebraic square]
823     * @param string
824     * @return boolean
825     * @access protected
826     */
827    function isKing($piecename)
828    {
829        return $piecename{1} == 'K';
830    }
831
832    /**
833     * Determine whether it is possible to capture the piece delivering check,
834     * or to interpose a piece in between the checking piece and the king
835     * @param array squares that will block a checkmate
836     * @param W|B color of the side attempting to prevent checkmate
837     * @return boolean true if it is possible to remove check
838     */
839    function _interposeOrCapture($squares, $color)
840    {
841        foreach ($this->_pieces as $name => $value) {
842            if (!$value) {
843                continue;
844            }
845            if ($name{0} != $color) {
846                continue;
847            }
848            if ($name{1} == 'K') {
849                continue;
850            }
851            if (is_array($value)) {
852                $name = $value[1];
853                $value = $value[0];
854            } else {
855                $name = $name{1};
856            }
857            $allmoves = $this->getPossibleMoves($name, $value, $color);
858            foreach($squares as $square) {
859                if (in_array($square, $allmoves)) {
860                    // try the move, see if we're still in check
861                    // if so, then the piece is pinned and cannot move
862                    $this->startTransaction();
863                    $this->_move = $color;
864                    if (!class_exists('PEAR')) {
865                        require_once 'PEAR.php';
866                    }
867                    PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
868                    $ret = $this->moveSquare($value, $square);
869                    PEAR::popErrorHandling(PEAR_ERROR_RETURN);
870                    $this->_move = $color;
871                    $stillchecked = $this->inCheck($color);
872                    $this->rollbackTransaction();
873                    if (!$stillchecked) {
874                        return true;
875                    }
876                }
877            }
878        }
879        return false;
880    }
881
882    /**
883     * Retrieve the color of a piece from its name
884     *
885     * Game-specific method of retrieving the color of a piece
886     * @access protected
887     */
888    function _getColor($name)
889    {
890        return $name{0};
891    }
892
893    /**
894     * Get a list of all pieces on the board organized by the type of piece,
895     * and the color of the square the piece is on.
896     *
897     * Used to determine basic draw conditions
898     * @return array Format:
899     *
900     * <pre>
901     * array(
902     *   // white pieces
903     *   'W' => array('B' => array('W', 'B'), // all bishops
904     *                'K' => array('W'),...
905     *               ),
906     *   // black pieces
907     *   'B' => array('Q' => array('B'), // all queens
908     *                'K' => array('W'),... // king is on white square
909     * </pre>
910     * @access protected
911     */
912    function _getPieceTypes()
913    {
914        $ret = array('W' => array(), 'B' => array());
915        foreach($this->_pieces as $name => $loc) {
916            if (!$loc) {
917                continue;
918            }
919            $type = $name{1};
920            if (is_array($loc)) {
921                $type = $loc[1];
922                $loc = $loc[0];
923            }
924            $ret[$name{0}][$type][] = $this->_getDiagonalColor($loc);
925        }
926        return $ret;
927    }
928}
929?>