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?>