├── .gitignore ├── .scrutinizer.yml ├── .travis.yml ├── LICENSE ├── PEAR.php ├── README.md ├── Tests ├── ChessGameTest.php └── bootstrap.php ├── composer.json ├── phpunit.xml.dist └── src └── Chess └── Game └── ChessGame.php /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | vendor 3 | composer.phar 4 | phpunit.xml 5 | bin 6 | phpunit-report 7 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | tools: 2 | php_cs_fixer: true 3 | php_code_sniffer: 4 | config: 5 | standard: PSR1 6 | php_mess_detector: true 7 | php_cpd: true 8 | php_analyzer: 9 | filter: 10 | excluded_paths: ["*/Tests/*"] 11 | sensiolabs_security_checker: true -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - "7.1" 5 | - "7.2" 6 | - "7.3" 7 | - "7.4" 8 | - "8.0" 9 | - "8.1" 10 | - "8.2" 11 | 12 | matrix: 13 | fast_finish: true 14 | 15 | before_script: 16 | - curl -s http://getcomposer.org/installer | php -- --quiet 17 | - php composer.phar install --dev --prefer-source 18 | 19 | script: bin/phpunit 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------- 2 | The PHP License, version 3.01 3 | Copyright (c) 1999 - 2012 The PHP Group. All rights reserved. 4 | -------------------------------------------------------------------- 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, is permitted provided that the following conditions 8 | are met: 9 | 10 | 1. Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | 13 | 2. Redistributions in binary form must reproduce the above copyright 14 | notice, this list of conditions and the following disclaimer in 15 | the documentation and/or other materials provided with the 16 | distribution. 17 | 18 | 3. The name "PHP" must not be used to endorse or promote products 19 | derived from this software without prior written permission. For 20 | written permission, please contact group@php.net. 21 | 22 | 4. Products derived from this software may not be called "PHP", nor 23 | may "PHP" appear in their name, without prior written permission 24 | from group@php.net. You may indicate that your software works in 25 | conjunction with PHP by saying "Foo for PHP" instead of calling 26 | it "PHP Foo" or "phpfoo" 27 | 28 | 5. The PHP Group may publish revised and/or new versions of the 29 | license from time to time. Each version will be given a 30 | distinguishing version number. 31 | Once covered code has been published under a particular version 32 | of the license, you may always continue to use it under the terms 33 | of that version. You may also choose to use such covered code 34 | under the terms of any subsequent version of the license 35 | published by the PHP Group. No one other than the PHP Group has 36 | the right to modify the terms applicable to covered code created 37 | under this License. 38 | 39 | 6. Redistributions of any form whatsoever must retain the following 40 | acknowledgment: 41 | "This product includes PHP software, freely available from 42 | ". 43 | 44 | THIS SOFTWARE IS PROVIDED BY THE PHP DEVELOPMENT TEAM ``AS IS'' AND 45 | ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 46 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 47 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PHP 48 | DEVELOPMENT TEAM OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 49 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 50 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 51 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 52 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 53 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 54 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 55 | OF THE POSSIBILITY OF SUCH DAMAGE. 56 | 57 | -------------------------------------------------------------------- 58 | 59 | This software consists of voluntary contributions made by many 60 | individuals on behalf of the PHP Group. 61 | 62 | The PHP Group can be contacted via Email at group@php.net. 63 | 64 | For more information on the PHP Group and the PHP project, 65 | please see . 66 | 67 | PHP includes the Zend Engine, freely available at 68 | . 69 | -------------------------------------------------------------------------------- /PEAR.php: -------------------------------------------------------------------------------- 1 | 18 | * @author Stig Bakken 19 | * @author Tomas V.V.Cox 20 | * @author Greg Beaver 21 | * @copyright 1997-2006 The PHP Group 22 | * @license http://www.php.net/license/3_0.txt PHP License 3.0 23 | * @version CVS: $Id: PEAR.php,v 1.4 2011/01/07 00:35:39 chris Exp $ 24 | * @link http://pear.php.net/package/PEAR 25 | * @since File available since Release 0.1 26 | */ 27 | 28 | /**#@+ 29 | * ERROR constants 30 | */ 31 | define('PEAR_ERROR_RETURN', 1); 32 | define('PEAR_ERROR_PRINT', 2); 33 | define('PEAR_ERROR_TRIGGER', 4); 34 | define('PEAR_ERROR_DIE', 8); 35 | define('PEAR_ERROR_CALLBACK', 16); 36 | /** 37 | * WARNING: obsolete 38 | * @deprecated 39 | */ 40 | define('PEAR_ERROR_EXCEPTION', 32); 41 | /**#@-*/ 42 | define('PEAR_ZE2', (function_exists('version_compare') && 43 | version_compare(zend_version(), "2-dev", "ge"))); 44 | 45 | if (substr(PHP_OS, 0, 3) == 'WIN') { 46 | define('OS_WINDOWS', true); 47 | define('OS_UNIX', false); 48 | define('PEAR_OS', 'Windows'); 49 | } else { 50 | define('OS_WINDOWS', false); 51 | define('OS_UNIX', true); 52 | define('PEAR_OS', 'Unix'); // blatant assumption 53 | } 54 | 55 | // instant backwards compatibility 56 | if (!defined('PATH_SEPARATOR')) { 57 | if (OS_WINDOWS) { 58 | define('PATH_SEPARATOR', ';'); 59 | } else { 60 | define('PATH_SEPARATOR', ':'); 61 | } 62 | } 63 | 64 | $GLOBALS['_PEAR_default_error_mode'] = PEAR_ERROR_RETURN; 65 | $GLOBALS['_PEAR_default_error_options'] = E_USER_NOTICE; 66 | $GLOBALS['_PEAR_destructor_object_list'] = array(); 67 | $GLOBALS['_PEAR_shutdown_funcs'] = array(); 68 | $GLOBALS['_PEAR_error_handler_stack'] = array(); 69 | 70 | @ini_set('track_errors', true); 71 | 72 | /** 73 | * Base class for other PEAR classes. Provides rudimentary 74 | * emulation of destructors. 75 | * 76 | * If you want a destructor in your class, inherit PEAR and make a 77 | * destructor method called _yourclassname (same name as the 78 | * constructor, but with a "_" prefix). Also, in your constructor you 79 | * have to call the PEAR constructor: $this->PEAR();. 80 | * The destructor method will be called without parameters. Note that 81 | * at in some SAPI implementations (such as Apache), any output during 82 | * the request shutdown (in which destructors are called) seems to be 83 | * discarded. If you need to get any debug information from your 84 | * destructor, use error_log(), syslog() or something similar. 85 | * 86 | * IMPORTANT! To use the emulated destructors you need to create the 87 | * objects by reference: $obj = new PEAR_child; 88 | * 89 | * @category pear 90 | * @package PEAR 91 | * @author Stig Bakken 92 | * @author Tomas V.V. Cox 93 | * @author Greg Beaver 94 | * @copyright 1997-2006 The PHP Group 95 | * @license http://www.php.net/license/3_0.txt PHP License 3.0 96 | * @version Release: 1.5.2 97 | * @link http://pear.php.net/package/PEAR 98 | * @see PEAR_Error 99 | * @since Class available since PHP 4.0.2 100 | * @link http://pear.php.net/manual/en/core.pear.php#core.pear.pear 101 | */ 102 | class PEAR 103 | { 104 | // {{{ properties 105 | 106 | /** 107 | * Whether to enable internal debug messages. 108 | * 109 | * @var bool 110 | * @access private 111 | */ 112 | var $_debug = false; 113 | 114 | /** 115 | * Default error mode for this object. 116 | * 117 | * @var int 118 | * @access private 119 | */ 120 | var $_default_error_mode = null; 121 | 122 | /** 123 | * Default error options used for this object when error mode 124 | * is PEAR_ERROR_TRIGGER. 125 | * 126 | * @var int 127 | * @access private 128 | */ 129 | var $_default_error_options = null; 130 | 131 | /** 132 | * Default error handler (callback) for this object, if error mode is 133 | * PEAR_ERROR_CALLBACK. 134 | * 135 | * @var string 136 | * @access private 137 | */ 138 | var $_default_error_handler = ''; 139 | 140 | /** 141 | * Which class to use for error objects. 142 | * 143 | * @var string 144 | * @access private 145 | */ 146 | var $_error_class = 'PEAR_Error'; 147 | 148 | /** 149 | * An array of expected errors. 150 | * 151 | * @var array 152 | * @access private 153 | */ 154 | var $_expected_errors = array(); 155 | 156 | // }}} 157 | 158 | // {{{ constructor 159 | 160 | /** 161 | * Constructor. Registers this object in 162 | * $_PEAR_destructor_object_list for destructor emulation if a 163 | * destructor object exists. 164 | * 165 | * @param string $error_class (optional) which class to use for 166 | * error objects, defaults to PEAR_Error. 167 | * @access public 168 | * @return void 169 | */ 170 | function __construct($error_class = null) 171 | { 172 | $classname = strtolower(get_class($this)); 173 | if ($this->_debug) { 174 | print "PEAR constructor called, class=$classname\n"; 175 | } 176 | if ($error_class !== null) { 177 | $this->_error_class = $error_class; 178 | } 179 | while ($classname && strcasecmp($classname, "pear")) { 180 | $destructor = "_$classname"; 181 | if (method_exists($this, $destructor)) { 182 | global $_PEAR_destructor_object_list; 183 | $_PEAR_destructor_object_list[] = &$this; 184 | if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) { 185 | register_shutdown_function("_PEAR_call_destructors"); 186 | $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true; 187 | } 188 | break; 189 | } else { 190 | $classname = get_parent_class($classname); 191 | } 192 | } 193 | } 194 | 195 | // }}} 196 | // {{{ destructor 197 | 198 | /** 199 | * Destructor (the emulated type of...). Does nothing right now, 200 | * but is included for forward compatibility, so subclass 201 | * destructors should always call it. 202 | * 203 | * See the note in the class desciption about output from 204 | * destructors. 205 | * 206 | * @access public 207 | * @return void 208 | */ 209 | function _PEAR() { 210 | if ($this->_debug) { 211 | printf("PEAR destructor called, class=%s\n", strtolower(get_class($this))); 212 | } 213 | } 214 | 215 | // }}} 216 | // {{{ getStaticProperty() 217 | 218 | /** 219 | * If you have a class that's mostly/entirely static, and you need static 220 | * properties, you can use this method to simulate them. Eg. in your method(s) 221 | * do this: $myVar = &PEAR::getStaticProperty('myclass', 'myVar'); 222 | * You MUST use a reference, or they will not persist! 223 | * 224 | * @access public 225 | * @param string $class The calling classname, to prevent clashes 226 | * @param string $var The variable to retrieve. 227 | * @return mixed A reference to the variable. If not set it will be 228 | * auto initialised to NULL. 229 | */ 230 | static function &getStaticProperty($class, $var) 231 | { 232 | static $properties; 233 | if (!isset($properties[$class])) { 234 | $properties[$class] = array(); 235 | } 236 | if (!array_key_exists($var, $properties[$class])) { 237 | $properties[$class][$var] = null; 238 | } 239 | return $properties[$class][$var]; 240 | } 241 | 242 | // }}} 243 | // {{{ registerShutdownFunc() 244 | 245 | /** 246 | * Use this function to register a shutdown method for static 247 | * classes. 248 | * 249 | * @access public 250 | * @param mixed $func The function name (or array of class/method) to call 251 | * @param mixed $args The arguments to pass to the function 252 | * @return void 253 | */ 254 | function registerShutdownFunc($func, $args = array()) 255 | { 256 | // if we are called statically, there is a potential 257 | // that no shutdown func is registered. Bug #6445 258 | if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) { 259 | register_shutdown_function("_PEAR_call_destructors"); 260 | $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true; 261 | } 262 | $GLOBALS['_PEAR_shutdown_funcs'][] = array($func, $args); 263 | } 264 | 265 | // }}} 266 | // {{{ isError() 267 | 268 | /** 269 | * Tell whether a value is a PEAR error. 270 | * 271 | * @param mixed $data the value to test 272 | * @param int $code if $data is an error object, return true 273 | * only if $code is a string and 274 | * $obj->getMessage() == $code or 275 | * $code is an integer and $obj->getCode() == $code 276 | * @access public 277 | * @return bool true if parameter is an error 278 | */ 279 | function isError($data, $code = null) 280 | { 281 | if (is_a($data, 'PEAR_Error')) { 282 | if (is_null($code)) { 283 | return true; 284 | } elseif (is_string($code)) { 285 | return $data->getMessage() == $code; 286 | } else { 287 | return $data->getCode() == $code; 288 | } 289 | } 290 | return false; 291 | } 292 | 293 | // }}} 294 | // {{{ setErrorHandling() 295 | 296 | /** 297 | * Sets how errors generated by this object should be handled. 298 | * Can be invoked both in objects and statically. If called 299 | * statically, setErrorHandling sets the default behaviour for all 300 | * PEAR objects. If called in an object, setErrorHandling sets 301 | * the default behaviour for that object. 302 | * 303 | * @param int $mode 304 | * One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT, 305 | * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE, 306 | * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION. 307 | * 308 | * @param mixed $options 309 | * When $mode is PEAR_ERROR_TRIGGER, this is the error level (one 310 | * of E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR). 311 | * 312 | * When $mode is PEAR_ERROR_CALLBACK, this parameter is expected 313 | * to be the callback function or method. A callback 314 | * function is a string with the name of the function, a 315 | * callback method is an array of two elements: the element 316 | * at index 0 is the object, and the element at index 1 is 317 | * the name of the method to call in the object. 318 | * 319 | * When $mode is PEAR_ERROR_PRINT or PEAR_ERROR_DIE, this is 320 | * a printf format string used when printing the error 321 | * message. 322 | * 323 | * @access public 324 | * @return void 325 | * @see PEAR_ERROR_RETURN 326 | * @see PEAR_ERROR_PRINT 327 | * @see PEAR_ERROR_TRIGGER 328 | * @see PEAR_ERROR_DIE 329 | * @see PEAR_ERROR_CALLBACK 330 | * @see PEAR_ERROR_EXCEPTION 331 | * 332 | * @since PHP 4.0.5 333 | */ 334 | 335 | function setErrorHandling($mode = null, $options = null) 336 | { 337 | if (isset($this) && is_a($this, 'PEAR')) { 338 | $setmode = &$this->_default_error_mode; 339 | $setoptions = &$this->_default_error_options; 340 | } else { 341 | $setmode = &$GLOBALS['_PEAR_default_error_mode']; 342 | $setoptions = &$GLOBALS['_PEAR_default_error_options']; 343 | } 344 | 345 | switch ($mode) { 346 | case PEAR_ERROR_EXCEPTION: 347 | case PEAR_ERROR_RETURN: 348 | case PEAR_ERROR_PRINT: 349 | case PEAR_ERROR_TRIGGER: 350 | case PEAR_ERROR_DIE: 351 | case null: 352 | $setmode = $mode; 353 | $setoptions = $options; 354 | break; 355 | 356 | case PEAR_ERROR_CALLBACK: 357 | $setmode = $mode; 358 | // class/object method callback 359 | if (is_callable($options)) { 360 | $setoptions = $options; 361 | } else { 362 | trigger_error("invalid error callback", E_USER_WARNING); 363 | } 364 | break; 365 | 366 | default: 367 | trigger_error("invalid error mode", E_USER_WARNING); 368 | break; 369 | } 370 | } 371 | 372 | // }}} 373 | // {{{ expectError() 374 | 375 | /** 376 | * This method is used to tell which errors you expect to get. 377 | * Expected errors are always returned with error mode 378 | * PEAR_ERROR_RETURN. Expected error codes are stored in a stack, 379 | * and this method pushes a new element onto it. The list of 380 | * expected errors are in effect until they are popped off the 381 | * stack with the popExpect() method. 382 | * 383 | * Note that this method can not be called statically 384 | * 385 | * @param mixed $code a single error code or an array of error codes to expect 386 | * 387 | * @return int the new depth of the "expected errors" stack 388 | * @access public 389 | */ 390 | function expectError($code = '*') 391 | { 392 | if (is_array($code)) { 393 | array_push($this->_expected_errors, $code); 394 | } else { 395 | array_push($this->_expected_errors, array($code)); 396 | } 397 | return sizeof($this->_expected_errors); 398 | } 399 | 400 | // }}} 401 | // {{{ popExpect() 402 | 403 | /** 404 | * This method pops one element off the expected error codes 405 | * stack. 406 | * 407 | * @return array the list of error codes that were popped 408 | */ 409 | function popExpect() 410 | { 411 | return array_pop($this->_expected_errors); 412 | } 413 | 414 | // }}} 415 | // {{{ _checkDelExpect() 416 | 417 | /** 418 | * This method checks unsets an error code if available 419 | * 420 | * @param mixed error code 421 | * @return bool true if the error code was unset, false otherwise 422 | * @access private 423 | * @since PHP 4.3.0 424 | */ 425 | function _checkDelExpect($error_code) 426 | { 427 | $deleted = false; 428 | 429 | foreach ($this->_expected_errors AS $key => $error_array) { 430 | if (in_array($error_code, $error_array)) { 431 | unset($this->_expected_errors[$key][array_search($error_code, $error_array)]); 432 | $deleted = true; 433 | } 434 | 435 | // clean up empty arrays 436 | if (0 == count($this->_expected_errors[$key])) { 437 | unset($this->_expected_errors[$key]); 438 | } 439 | } 440 | return $deleted; 441 | } 442 | 443 | // }}} 444 | // {{{ delExpect() 445 | 446 | /** 447 | * This method deletes all occurences of the specified element from 448 | * the expected error codes stack. 449 | * 450 | * @param mixed $error_code error code that should be deleted 451 | * @return mixed list of error codes that were deleted or error 452 | * @access public 453 | * @since PHP 4.3.0 454 | */ 455 | function delExpect($error_code) 456 | { 457 | $deleted = false; 458 | 459 | if ((is_array($error_code) && (0 != count($error_code)))) { 460 | // $error_code is a non-empty array here; 461 | // we walk through it trying to unset all 462 | // values 463 | foreach($error_code as $key => $error) { 464 | if ($this->_checkDelExpect($error)) { 465 | $deleted = true; 466 | } else { 467 | $deleted = false; 468 | } 469 | } 470 | return $deleted ? true : PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME 471 | } elseif (!empty($error_code)) { 472 | // $error_code comes alone, trying to unset it 473 | if ($this->_checkDelExpect($error_code)) { 474 | return true; 475 | } else { 476 | return PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME 477 | } 478 | } else { 479 | // $error_code is empty 480 | return PEAR::raiseError("The expected error you submitted is empty"); // IMPROVE ME 481 | } 482 | } 483 | 484 | // }}} 485 | // {{{ raiseError() 486 | 487 | /** 488 | * This method is a wrapper that returns an instance of the 489 | * configured error class with this object's default error 490 | * handling applied. If the $mode and $options parameters are not 491 | * specified, the object's defaults are used. 492 | * 493 | * @param mixed $message a text error message or a PEAR error object 494 | * 495 | * @param int $code a numeric error code (it is up to your class 496 | * to define these if you want to use codes) 497 | * 498 | * @param int $mode One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT, 499 | * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE, 500 | * PEAR_ERROR_CALLBACK, PEAR_ERROR_EXCEPTION. 501 | * 502 | * @param mixed $options If $mode is PEAR_ERROR_TRIGGER, this parameter 503 | * specifies the PHP-internal error level (one of 504 | * E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR). 505 | * If $mode is PEAR_ERROR_CALLBACK, this 506 | * parameter specifies the callback function or 507 | * method. In other error modes this parameter 508 | * is ignored. 509 | * 510 | * @param string $userinfo If you need to pass along for example debug 511 | * information, this parameter is meant for that. 512 | * 513 | * @param string $error_class The returned error object will be 514 | * instantiated from this class, if specified. 515 | * 516 | * @param bool $skipmsg If true, raiseError will only pass error codes, 517 | * the error message parameter will be dropped. 518 | * 519 | * @access public 520 | * @return object a PEAR error object 521 | * @see PEAR::setErrorHandling 522 | * @since PHP 4.0.5 523 | */ 524 | function &raiseError($message = null, 525 | $code = null, 526 | $mode = null, 527 | $options = null, 528 | $userinfo = null, 529 | $error_class = null, 530 | $skipmsg = false) 531 | { 532 | // The error is yet a PEAR error object 533 | if (is_object($message)) { 534 | $code = $message->getCode(); 535 | $userinfo = $message->getUserInfo(); 536 | $error_class = $message->getType(); 537 | $message->error_message_prefix = ''; 538 | $message = $message->getMessage(); 539 | } 540 | 541 | if (isset($this) && isset($this->_expected_errors) && sizeof($this->_expected_errors) > 0 && sizeof($exp = end($this->_expected_errors))) { 542 | if ($exp[0] == "*" || 543 | (is_int(reset($exp)) && in_array($code, $exp)) || 544 | (is_string(reset($exp)) && in_array($message, $exp))) { 545 | $mode = PEAR_ERROR_RETURN; 546 | } 547 | } 548 | // No mode given, try global ones 549 | if ($mode === null) { 550 | // Class error handler 551 | if (isset($this) && isset($this->_default_error_mode)) { 552 | $mode = $this->_default_error_mode; 553 | $options = $this->_default_error_options; 554 | // Global error handler 555 | } elseif (isset($GLOBALS['_PEAR_default_error_mode'])) { 556 | $mode = $GLOBALS['_PEAR_default_error_mode']; 557 | $options = $GLOBALS['_PEAR_default_error_options']; 558 | } 559 | } 560 | 561 | if ($error_class !== null) { 562 | $ec = $error_class; 563 | } elseif (isset($this) && isset($this->_error_class)) { 564 | $ec = $this->_error_class; 565 | } else { 566 | $ec = 'PEAR_Error'; 567 | } 568 | if ($skipmsg) { 569 | $a = new $ec($code, $mode, $options, $userinfo); 570 | return $a; 571 | } else { 572 | $a = new $ec($message, $code, $mode, $options, $userinfo); 573 | return $a; 574 | } 575 | } 576 | 577 | // }}} 578 | // {{{ throwError() 579 | 580 | /** 581 | * Simpler form of raiseError with fewer options. In most cases 582 | * message, code and userinfo are enough. 583 | * 584 | * @param string $message 585 | * 586 | */ 587 | function &throwError($message = null, 588 | $code = null, 589 | $userinfo = null) 590 | { 591 | if (isset($this) && is_a($this, 'PEAR')) { 592 | $a = &$this->raiseError($message, $code, null, null, $userinfo); 593 | return $a; 594 | } else { 595 | $a = &PEAR::raiseError($message, $code, null, null, $userinfo); 596 | return $a; 597 | } 598 | } 599 | 600 | // }}} 601 | function staticPushErrorHandling($mode, $options = null) 602 | { 603 | $stack = &$GLOBALS['_PEAR_error_handler_stack']; 604 | $def_mode = &$GLOBALS['_PEAR_default_error_mode']; 605 | $def_options = &$GLOBALS['_PEAR_default_error_options']; 606 | $stack[] = array($def_mode, $def_options); 607 | switch ($mode) { 608 | case PEAR_ERROR_EXCEPTION: 609 | case PEAR_ERROR_RETURN: 610 | case PEAR_ERROR_PRINT: 611 | case PEAR_ERROR_TRIGGER: 612 | case PEAR_ERROR_DIE: 613 | case null: 614 | $def_mode = $mode; 615 | $def_options = $options; 616 | break; 617 | 618 | case PEAR_ERROR_CALLBACK: 619 | $def_mode = $mode; 620 | // class/object method callback 621 | if (is_callable($options)) { 622 | $def_options = $options; 623 | } else { 624 | trigger_error("invalid error callback", E_USER_WARNING); 625 | } 626 | break; 627 | 628 | default: 629 | trigger_error("invalid error mode", E_USER_WARNING); 630 | break; 631 | } 632 | $stack[] = array($mode, $options); 633 | return true; 634 | } 635 | 636 | function staticPopErrorHandling() 637 | { 638 | $stack = &$GLOBALS['_PEAR_error_handler_stack']; 639 | $setmode = &$GLOBALS['_PEAR_default_error_mode']; 640 | $setoptions = &$GLOBALS['_PEAR_default_error_options']; 641 | array_pop($stack); 642 | list($mode, $options) = $stack[sizeof($stack) - 1]; 643 | array_pop($stack); 644 | switch ($mode) { 645 | case PEAR_ERROR_EXCEPTION: 646 | case PEAR_ERROR_RETURN: 647 | case PEAR_ERROR_PRINT: 648 | case PEAR_ERROR_TRIGGER: 649 | case PEAR_ERROR_DIE: 650 | case null: 651 | $setmode = $mode; 652 | $setoptions = $options; 653 | break; 654 | 655 | case PEAR_ERROR_CALLBACK: 656 | $setmode = $mode; 657 | // class/object method callback 658 | if (is_callable($options)) { 659 | $setoptions = $options; 660 | } else { 661 | trigger_error("invalid error callback", E_USER_WARNING); 662 | } 663 | break; 664 | 665 | default: 666 | trigger_error("invalid error mode", E_USER_WARNING); 667 | break; 668 | } 669 | return true; 670 | } 671 | 672 | // {{{ pushErrorHandling() 673 | 674 | /** 675 | * Push a new error handler on top of the error handler options stack. With this 676 | * you can easily override the actual error handler for some code and restore 677 | * it later with popErrorHandling. 678 | * 679 | * @param mixed $mode (same as setErrorHandling) 680 | * @param mixed $options (same as setErrorHandling) 681 | * 682 | * @return bool Always true 683 | * 684 | * @see PEAR::setErrorHandling 685 | */ 686 | function pushErrorHandling($mode, $options = null) 687 | { 688 | $stack = &$GLOBALS['_PEAR_error_handler_stack']; 689 | if (isset($this) && is_a($this, 'PEAR')) { 690 | $def_mode = &$this->_default_error_mode; 691 | $def_options = &$this->_default_error_options; 692 | } else { 693 | $def_mode = &$GLOBALS['_PEAR_default_error_mode']; 694 | $def_options = &$GLOBALS['_PEAR_default_error_options']; 695 | } 696 | $stack[] = array($def_mode, $def_options); 697 | 698 | if (isset($this) && is_a($this, 'PEAR')) { 699 | $this->setErrorHandling($mode, $options); 700 | } else { 701 | PEAR::setErrorHandling($mode, $options); 702 | } 703 | $stack[] = array($mode, $options); 704 | return true; 705 | } 706 | 707 | // }}} 708 | // {{{ popErrorHandling() 709 | 710 | /** 711 | * Pop the last error handler used 712 | * 713 | * @return bool Always true 714 | * 715 | * @see PEAR::pushErrorHandling 716 | */ 717 | function popErrorHandling() 718 | { 719 | $stack = &$GLOBALS['_PEAR_error_handler_stack']; 720 | array_pop($stack); 721 | list($mode, $options) = $stack[sizeof($stack) - 1]; 722 | array_pop($stack); 723 | if (isset($this) && is_a($this, 'PEAR')) { 724 | $this->setErrorHandling($mode, $options); 725 | } else { 726 | PEAR::setErrorHandling($mode, $options); 727 | } 728 | return true; 729 | } 730 | 731 | // }}} 732 | // {{{ loadExtension() 733 | 734 | /** 735 | * OS independant PHP extension load. Remember to take care 736 | * on the correct extension name for case sensitive OSes. 737 | * 738 | * @param string $ext The extension name 739 | * @return bool Success or not on the dl() call 740 | */ 741 | function loadExtension($ext) 742 | { 743 | if (!extension_loaded($ext)) { 744 | // if either returns true dl() will produce a FATAL error, stop that 745 | if ((ini_get('enable_dl') != 1) || (ini_get('safe_mode') == 1)) { 746 | return false; 747 | } 748 | if (OS_WINDOWS) { 749 | $suffix = '.dll'; 750 | } elseif (PHP_OS == 'HP-UX') { 751 | $suffix = '.sl'; 752 | } elseif (PHP_OS == 'AIX') { 753 | $suffix = '.a'; 754 | } elseif (PHP_OS == 'OSX') { 755 | $suffix = '.bundle'; 756 | } else { 757 | $suffix = '.so'; 758 | } 759 | return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix); 760 | } 761 | return true; 762 | } 763 | 764 | // }}} 765 | } 766 | 767 | // {{{ _PEAR_call_destructors() 768 | 769 | function _PEAR_call_destructors() 770 | { 771 | global $_PEAR_destructor_object_list; 772 | if (is_array($_PEAR_destructor_object_list) && 773 | sizeof($_PEAR_destructor_object_list)) 774 | { 775 | reset($_PEAR_destructor_object_list); 776 | if (PEAR::getStaticProperty('PEAR', 'destructlifo')) { 777 | $_PEAR_destructor_object_list = array_reverse($_PEAR_destructor_object_list); 778 | } 779 | while (list($k, $objref) = each($_PEAR_destructor_object_list)) { 780 | $classname = get_class($objref); 781 | while ($classname) { 782 | $destructor = "_$classname"; 783 | if (method_exists($objref, $destructor)) { 784 | $objref->$destructor(); 785 | break; 786 | } else { 787 | $classname = get_parent_class($classname); 788 | } 789 | } 790 | } 791 | // Empty the object list to ensure that destructors are 792 | // not called more than once. 793 | $_PEAR_destructor_object_list = array(); 794 | } 795 | 796 | // Now call the shutdown functions 797 | if (is_array($GLOBALS['_PEAR_shutdown_funcs']) AND !empty($GLOBALS['_PEAR_shutdown_funcs'])) { 798 | foreach ($GLOBALS['_PEAR_shutdown_funcs'] as $value) { 799 | call_user_func_array($value[0], $value[1]); 800 | } 801 | } 802 | } 803 | 804 | // }}} 805 | /** 806 | * Standard PEAR error class for PHP 4 807 | * 808 | * This class is supserseded by {@link PEAR_Exception} in PHP 5 809 | * 810 | * @category pear 811 | * @package PEAR 812 | * @author Stig Bakken 813 | * @author Tomas V.V. Cox 814 | * @author Gregory Beaver 815 | * @copyright 1997-2006 The PHP Group 816 | * @license http://www.php.net/license/3_0.txt PHP License 3.0 817 | * @version Release: 1.5.2 818 | * @link http://pear.php.net/manual/en/core.pear.pear-error.php 819 | * @see PEAR::raiseError(), PEAR::throwError() 820 | * @since Class available since PHP 4.0.2 821 | */ 822 | class PEAR_Error 823 | { 824 | // {{{ properties 825 | 826 | var $error_message_prefix = ''; 827 | var $mode = PEAR_ERROR_RETURN; 828 | var $level = E_USER_NOTICE; 829 | var $code = -1; 830 | var $message = ''; 831 | var $userinfo = ''; 832 | var $backtrace = null; 833 | var $callback = null; 834 | 835 | // }}} 836 | // {{{ constructor 837 | 838 | /** 839 | * PEAR_Error constructor 840 | * 841 | * @param string $message message 842 | * 843 | * @param int $code (optional) error code 844 | * 845 | * @param int $mode (optional) error mode, one of: PEAR_ERROR_RETURN, 846 | * PEAR_ERROR_PRINT, PEAR_ERROR_DIE, PEAR_ERROR_TRIGGER, 847 | * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION 848 | * 849 | * @param mixed $options (optional) error level, _OR_ in the case of 850 | * PEAR_ERROR_CALLBACK, the callback function or object/method 851 | * tuple. 852 | * 853 | * @param string $userinfo (optional) additional user/debug info 854 | * 855 | * @access public 856 | * 857 | */ 858 | function __construct($message = 'unknown error', $code = null, 859 | $mode = null, $options = null, $userinfo = null) 860 | { 861 | if ($mode === null) { 862 | $mode = PEAR_ERROR_RETURN; 863 | } 864 | $this->message = $message; 865 | $this->code = $code; 866 | $this->mode = $mode; 867 | $this->userinfo = $userinfo; 868 | if (!PEAR::getStaticProperty('PEAR_Error', 'skiptrace')) { 869 | $this->backtrace = debug_backtrace(); 870 | if (isset($this->backtrace[0]) && isset($this->backtrace[0]['object'])) { 871 | unset($this->backtrace[0]['object']); 872 | } 873 | } 874 | if ($mode & PEAR_ERROR_CALLBACK) { 875 | $this->level = E_USER_NOTICE; 876 | $this->callback = $options; 877 | } else { 878 | if ($options === null) { 879 | $options = E_USER_NOTICE; 880 | } 881 | $this->level = $options; 882 | $this->callback = null; 883 | } 884 | if ($this->mode & PEAR_ERROR_PRINT) { 885 | if (is_null($options) || is_int($options)) { 886 | $format = "%s"; 887 | } else { 888 | $format = $options; 889 | } 890 | printf($format, $this->getMessage()); 891 | } 892 | if ($this->mode & PEAR_ERROR_TRIGGER) { 893 | trigger_error($this->getMessage(), $this->level); 894 | } 895 | if ($this->mode & PEAR_ERROR_DIE) { 896 | $msg = $this->getMessage(); 897 | if (is_null($options) || is_int($options)) { 898 | $format = "%s"; 899 | if (substr($msg, -1) != "\n") { 900 | $msg .= "\n"; 901 | } 902 | } else { 903 | $format = $options; 904 | } 905 | die(sprintf($format, $msg)); 906 | } 907 | if ($this->mode & PEAR_ERROR_CALLBACK) { 908 | if (is_callable($this->callback)) { 909 | call_user_func($this->callback, $this); 910 | } 911 | } 912 | if ($this->mode & PEAR_ERROR_EXCEPTION) { 913 | trigger_error("PEAR_ERROR_EXCEPTION is obsolete, use class PEAR_Exception for exceptions", E_USER_WARNING); 914 | eval('$e = new Exception($this->message, $this->code);throw($e);'); 915 | } 916 | } 917 | 918 | // }}} 919 | // {{{ getMode() 920 | 921 | /** 922 | * Get the error mode from an error object. 923 | * 924 | * @return int error mode 925 | * @access public 926 | */ 927 | function getMode() { 928 | return $this->mode; 929 | } 930 | 931 | // }}} 932 | // {{{ getCallback() 933 | 934 | /** 935 | * Get the callback function/method from an error object. 936 | * 937 | * @return mixed callback function or object/method array 938 | * @access public 939 | */ 940 | function getCallback() { 941 | return $this->callback; 942 | } 943 | 944 | // }}} 945 | // {{{ getMessage() 946 | 947 | 948 | /** 949 | * Get the error message from an error object. 950 | * 951 | * @return string full error message 952 | * @access public 953 | */ 954 | function getMessage() 955 | { 956 | return ($this->error_message_prefix . $this->message); 957 | } 958 | 959 | 960 | // }}} 961 | // {{{ getCode() 962 | 963 | /** 964 | * Get error code from an error object 965 | * 966 | * @return int error code 967 | * @access public 968 | */ 969 | function getCode() 970 | { 971 | return $this->code; 972 | } 973 | 974 | // }}} 975 | // {{{ getType() 976 | 977 | /** 978 | * Get the name of this error/exception. 979 | * 980 | * @return string error/exception name (type) 981 | * @access public 982 | */ 983 | function getType() 984 | { 985 | return get_class($this); 986 | } 987 | 988 | // }}} 989 | // {{{ getUserInfo() 990 | 991 | /** 992 | * Get additional user-supplied information. 993 | * 994 | * @return string user-supplied information 995 | * @access public 996 | */ 997 | function getUserInfo() 998 | { 999 | return $this->userinfo; 1000 | } 1001 | 1002 | // }}} 1003 | // {{{ getDebugInfo() 1004 | 1005 | /** 1006 | * Get additional debug information supplied by the application. 1007 | * 1008 | * @return string debug information 1009 | * @access public 1010 | */ 1011 | function getDebugInfo() 1012 | { 1013 | return $this->getUserInfo(); 1014 | } 1015 | 1016 | // }}} 1017 | // {{{ getBacktrace() 1018 | 1019 | /** 1020 | * Get the call backtrace from where the error was generated. 1021 | * Supported with PHP 4.3.0 or newer. 1022 | * 1023 | * @param int $frame (optional) what frame to fetch 1024 | * @return array Backtrace, or NULL if not available. 1025 | * @access public 1026 | */ 1027 | function getBacktrace($frame = null) 1028 | { 1029 | if (defined('PEAR_IGNORE_BACKTRACE')) { 1030 | return null; 1031 | } 1032 | if ($frame === null) { 1033 | return $this->backtrace; 1034 | } 1035 | return $this->backtrace[$frame]; 1036 | } 1037 | 1038 | // }}} 1039 | // {{{ addUserInfo() 1040 | 1041 | function addUserInfo($info) 1042 | { 1043 | if (empty($this->userinfo)) { 1044 | $this->userinfo = $info; 1045 | } else { 1046 | $this->userinfo .= " ** $info"; 1047 | } 1048 | } 1049 | 1050 | // }}} 1051 | // {{{ toString() 1052 | 1053 | /** 1054 | * Make a string representation of this object. 1055 | * 1056 | * @return string a string with an object summary 1057 | * @access public 1058 | */ 1059 | function toString() { 1060 | $modes = array(); 1061 | $levels = array(E_USER_NOTICE => 'notice', 1062 | E_USER_WARNING => 'warning', 1063 | E_USER_ERROR => 'error'); 1064 | if ($this->mode & PEAR_ERROR_CALLBACK) { 1065 | if (is_array($this->callback)) { 1066 | $callback = (is_object($this->callback[0]) ? 1067 | strtolower(get_class($this->callback[0])) : 1068 | $this->callback[0]) . '::' . 1069 | $this->callback[1]; 1070 | } else { 1071 | $callback = $this->callback; 1072 | } 1073 | return sprintf('[%s: message="%s" code=%d mode=callback '. 1074 | 'callback=%s prefix="%s" info="%s"]', 1075 | strtolower(get_class($this)), $this->message, $this->code, 1076 | $callback, $this->error_message_prefix, 1077 | $this->userinfo); 1078 | } 1079 | if ($this->mode & PEAR_ERROR_PRINT) { 1080 | $modes[] = 'print'; 1081 | } 1082 | if ($this->mode & PEAR_ERROR_TRIGGER) { 1083 | $modes[] = 'trigger'; 1084 | } 1085 | if ($this->mode & PEAR_ERROR_DIE) { 1086 | $modes[] = 'die'; 1087 | } 1088 | if ($this->mode & PEAR_ERROR_RETURN) { 1089 | $modes[] = 'return'; 1090 | } 1091 | return sprintf('[%s: message="%s" code=%d mode=%s level=%s '. 1092 | 'prefix="%s" info="%s"]', 1093 | strtolower(get_class($this)), $this->message, $this->code, 1094 | implode("|", $modes), $levels[$this->level], 1095 | $this->error_message_prefix, 1096 | $this->userinfo); 1097 | } 1098 | 1099 | // }}} 1100 | } 1101 | 1102 | /* 1103 | * Local Variables: 1104 | * mode: php 1105 | * tab-width: 4 1106 | * c-basic-offset: 4 1107 | * End: 1108 | */ 1109 | ?> 1110 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ##Chess-Game [![Build Status](https://travis-ci.org/ChessCom/Chess-Game.png?branch=develop)](https://travis-ci.org/ChessCom/Chess-Game) 2 | 3 | Based on Pear Games_Chess - represents a chess game as a php object 4 | 5 | ####Install with composer: 6 | 7 | ```sh 8 | ~ composer require chesscom/chess-game 9 | ~ composer update chesscom/chess-game 10 | ``` 11 | 12 | ####Running the test suite: 13 | 14 | ``` 15 | composer install 16 | phpunit 17 | ``` 18 | 19 | -------------------------------------------------------------------------------- /Tests/ChessGameTest.php: -------------------------------------------------------------------------------- 1 | game = new ChessGame(); 15 | } 16 | 17 | public function testAddPiece() 18 | { 19 | $this->game->blankBoard(); 20 | $this->game->addPiece('B', 'P', 'a7'); 21 | $this->assertEquals('8/p7/8/8/8/8/8/8 w KQkq - 1 1', $this->game->renderFen()); 22 | } 23 | 24 | public function testAddPiecePromotedPawnForBishopKnightRook() 25 | { 26 | foreach (array('B', 'N', 'R') as $piece) { 27 | $this->game->blankBoard(); 28 | $this->game->addPiece('B', $piece, 'a8'); 29 | $this->game->addPiece('B', $piece, 'h8'); 30 | $this->assertTrue($this->game->addPiece('B', $piece, 'a1')); 31 | } 32 | } 33 | 34 | public function testAddPiecePromotedPawnForQueen() 35 | { 36 | $this->game->blankBoard(); 37 | $this->game->addPiece('B', 'Q', 'd8'); 38 | $this->assertTrue($this->game->addPiece('B', 'Q', 'a1')); 39 | } 40 | 41 | public function testAddPieceInvalidAddingKingWhenAlreadyExists() 42 | { 43 | $this->game->blankBoard(); 44 | $this->game->addPiece('B', 'K', 'e8'); 45 | $this->assertInstanceOf('PEAR_Error', $this->game->addPiece('B', 'K', 'a6')); 46 | } 47 | 48 | public function testAddPieceInvalidSquareParameterError() 49 | { 50 | $this->game->resetGame(); 51 | $this->assertInstanceOf('PEAR_Error', $this->game->addPiece('W', 'P', 'g9')); 52 | } 53 | 54 | public function testAddPieceSquareInvalidBecauseSquareIsAlreadyOccupiedNonPawnStartingRank() 55 | { 56 | $this->game->blankBoard(); 57 | $this->game->addPiece('B', 'Q', 'a8'); 58 | $this->assertInstanceOf('PEAR_Error', $this->game->addPiece('B', 'K', 'a8')); 59 | } 60 | 61 | public function testAddPieceSquareInvalidBecauseSquareIsAlreadyOccupiedPawnStartingRank() 62 | { 63 | $this->game->blankBoard(); 64 | $this->game->addPiece('B', 'P', 'a7'); 65 | $this->assertInstanceOf('PEAR_Error', $this->game->addPiece('B', 'Q', 'a7')); 66 | } 67 | 68 | public function testBlankBoardFen() 69 | { 70 | $this->game->blankBoard(); 71 | $this->assertEquals('8/8/8/8/8/8/8/8 w KQkq - 1 1', $this->game->renderFen()); 72 | } 73 | 74 | public function testCastlingBlackFromTheKingSide() 75 | { 76 | $startFen = 'rnbqk2r/pppp1ppp/5n2/2b1p3/P1P1P1P1/8/1P1P1P1P/RNBQKBNR b KQkq a3 0 4'; 77 | $endFen = 'rnbq1rk1/pppp1ppp/5n2/2b1p3/P1P1P1P1/8/1P1P1P1P/RNBQKBNR w KQ - 1 5'; 78 | $moves = array('O-O'); 79 | 80 | $this->game->resetGame($startFen); 81 | 82 | foreach ($moves as $move) { 83 | $this->game->moveSAN($move); 84 | } 85 | 86 | $this->assertEquals($endFen, $this->game->renderFen()); 87 | $this->assertTrue($this->game->canCastleKingside()); 88 | } 89 | 90 | public function testCastlingBlackFromTheQueenSide() 91 | { 92 | $startFen = 'r3kbnr/pp3ppp/n2pb3/q1p1p3/P3P1PP/1PPP4/5P2/RNBQKBNR b KQkq - 0 7'; 93 | $endFen = '2kr1bnr/pp3ppp/n2pb3/q1p1p3/P3P1PP/1PPP4/5P2/RNBQKBNR w KQ - 1 8'; 94 | $moves = array('O-O-O'); 95 | 96 | $this->game->resetGame($startFen); 97 | 98 | foreach ($moves as $move) { 99 | $this->game->moveSAN($move); 100 | } 101 | 102 | $this->assertEquals($endFen, $this->game->renderFen()); 103 | $this->assertTrue($this->game->canCastleQueenside()); 104 | } 105 | 106 | public function testCastlingWhiteFromTheKingSide() 107 | { 108 | $startFen = 'rnbqkbnr/4pppp/8/pppp4/5PP1/5N1B/PPPPP2P/RNBQK2R w KQkq a6 0 5'; 109 | $endFen = 'rnbqkbnr/4pppp/8/pppp4/5PP1/5N1B/PPPPP2P/RNBQ1RK1 b kq - 1 5'; 110 | $moves = array('O-O'); 111 | 112 | $this->game->resetGame($startFen); 113 | 114 | foreach ($moves as $move) { 115 | $this->game->moveSAN($move); 116 | } 117 | 118 | $this->assertEquals($endFen, $this->game->renderFen()); 119 | $this->assertTrue($this->game->canCastleKingside()); 120 | } 121 | 122 | public function testCastlingWhiteFromTheQueenSide() 123 | { 124 | $startFen = 'rnbqkbnr/3pppp1/p1p5/1p5p/3P1B2/2NQ4/PPP1PPPP/R3KBNR w KQkq - 0 5'; 125 | $endFen = 'rnbqkbnr/3pppp1/p1p5/1p5p/3P1B2/2NQ4/PPP1PPPP/2KR1BNR b kq - 1 5'; 126 | $moves = array('O-O-O'); 127 | 128 | $this->game->resetGame($startFen); 129 | 130 | foreach ($moves as $move) { 131 | $this->game->moveSAN($move); 132 | } 133 | 134 | $this->assertEquals($endFen, $this->game->renderFen()); 135 | $this->assertTrue($this->game->canCastleQueenside()); 136 | } 137 | 138 | /** 139 | * Test a complete game 140 | * 141 | * This game was taken from ChessTempo.com 142 | * @link http://chesstempo.com/gamedb/game/184599 143 | */ 144 | public function testCompleteGame() 145 | { 146 | $endFen = '1r2r1k1/5pp1/3q4/2n1p3/R1P1P1Pp/4BP2/6BP/R3Q1K1 b - - 0 36'; 147 | 148 | $moves = array( 149 | 1 => array('white' => 'Nf3', 'black' => 'Nf6'), 150 | 2 => array('white' => 'c4', 'black' => 'e6'), 151 | 3 => array('white' => 'Nc3', 'black' => 'Bb4'), 152 | 4 => array('white' => 'Qc2', 'black' => 'O-O'), 153 | 5 => array('white' => 'a3', 'black' => 'Bxc3'), 154 | 6 => array('white' => 'Qxc3', 'black' => 'b6'), 155 | 7 => array('white' => 'b4', 'black' => 'd6'), 156 | 8 => array('white' => 'Bb2', 'black' => 'Bb7'), 157 | 9 => array('white' => 'g3', 'black' => 'c5'), 158 | 10 => array('white' => 'Bg2', 'black' => 'Nbd7'), 159 | 11 => array('white' => 'O-O', 'black' => 'Rc8'), 160 | 12 => array('white' => 'd3', 'black' => 'Re8'), 161 | 13 => array('white' => 'e4', 'black' => 'a6'), 162 | 14 => array('white' => 'Qb3', 'black' => 'b5'), 163 | 15 => array('white' => 'Nd2', 'black' => 'Rb8'), 164 | 16 => array('white' => 'Rfc1', 'black' => 'Ba8'), 165 | 17 => array('white' => 'Qd1', 'black' => 'Qe7'), 166 | 18 => array('white' => 'cxb5', 'black' => 'axb5'), 167 | 19 => array('white' => 'Nb3', 'black' => 'e5'), 168 | 20 => array('white' => 'f3', 'black' => 'h5'), 169 | 21 => array('white' => 'bxc5', 'black' => 'dxc5'), 170 | 22 => array('white' => 'a4', 'black' => 'h4'), 171 | 23 => array('white' => 'g4', 'black' => 'c4'), 172 | 24 => array('white' => 'dxc4', 'black' => 'bxa4'), 173 | 25 => array('white' => 'Ba3', 'black' => 'Qd8'), 174 | 26 => array('white' => 'Nc5', 'black' => 'Bc6'), 175 | 27 => array('white' => 'Nxa4', 'black' => 'Nh7'), 176 | 28 => array('white' => 'Nc5', 'black' => 'Ng5'), 177 | 29 => array('white' => 'Nxd7', 'black' => 'Bxd7'), 178 | 30 => array('white' => 'Rc3', 'black' => 'Qa5'), 179 | 31 => array('white' => 'Rd3', 'black' => 'Ba4'), 180 | 32 => array('white' => 'Qe1', 'black' => 'Qa6'), 181 | 33 => array('white' => 'Bc1', 'black' => 'Ne6'), 182 | 34 => array('white' => 'Rda3', 'black' => 'Nc5'), 183 | 35 => array('white' => 'Be3', 'black' => 'Qd6'), 184 | 36 => array('white' => 'Rxa4'), 185 | ); 186 | 187 | $this->game->resetGame(); 188 | 189 | foreach ($moves as $playerMoves) { 190 | foreach ($playerMoves as $move) { 191 | $this->game->moveSAN($move); 192 | } 193 | } 194 | 195 | $this->assertEquals($endFen, $this->game->renderFen()); 196 | } 197 | 198 | /** 199 | * Test two knights that can both move to the same square, but one is pinned to the king. 200 | * 201 | * 1.e4 e5 2.Bc4 Nf6 3.d3 Nc6 4.Nc3 Bb4 5.Ne2 202 | */ 203 | public function testAmbiguousKnightMoves() 204 | { 205 | $endFen = 'r1bqk2r/pppp1ppp/2n2n2/4p3/1b2P3/2NP4/PPP1NPPP/R1BQKB1R w KQkq - 3 5'; 206 | 207 | $moves = array( 208 | 1 => array('white' => 'e4', 'black' => 'e5'), 209 | 3 => array('white' => 'Nc3', 'black' => 'Nf6'), 210 | 4 => array('white' => 'd3', 'black' => 'Bb4'), 211 | 5 => array('white' => 'Ne2', 'black' => 'Nc6'), 212 | 6 => array('white' => 'Ng2'), 213 | ); 214 | 215 | $this->game->resetGame(); 216 | 217 | foreach ($moves as $playerMoves) { 218 | foreach ($playerMoves as $move) { 219 | $this->game->moveSAN($move); 220 | } 221 | } 222 | 223 | $this->assertEquals($endFen, $this->game->renderFen()); 224 | } 225 | 226 | public function testGameOverDueToCheckmate() 227 | { 228 | $startFen = '3k2R1/8/3K4/8/8/8/8/8 b - -'; 229 | 230 | $this->game->resetGame($startFen); 231 | $this->assertEquals('W', $this->game->gameOver()); 232 | } 233 | 234 | public function testGameOverDueTo50MoveDraw() 235 | { 236 | $startFen = '7k/4N3/4NK2/5B2/8/8/8/r7 w - - 100 112'; 237 | 238 | $this->game->resetGame($startFen); 239 | $this->assertFalse($this->game->gameOver()); 240 | $this->assert50MoveDraw(); 241 | } 242 | 243 | public function testGetDiagonalColor() 244 | { 245 | $this->game->resetGame(); 246 | $this->assertEquals('B', $this->game->getDiagonalColor('d4')); 247 | $this->assertEquals('W', $this->game->getDiagonalColor('a8')); 248 | } 249 | 250 | public function testGetDiagonalColorInvalidSquareParameterError() 251 | { 252 | $this->game->resetGame(); 253 | $this->assertInstanceOf('PEAR_Error', $this->game->getDiagonalColor('SQUARE_X')); 254 | } 255 | 256 | public function testGetPieceLocationsColorBlack() 257 | { 258 | $this->game->blankBoard(); 259 | $this->game->addPiece('W', 'K', 'a1'); 260 | $this->game->addPiece('W', 'Q', 'h1'); 261 | $this->game->addPiece('B', 'P', 'c7'); 262 | 263 | $locations = $this->invokeMethod($this->game, 'getPieceLocations', array('B')); 264 | $this->assertEquals(1, count($locations)); 265 | $this->assertTrue(in_array('c7', $locations)); 266 | } 267 | 268 | public function testGetPieceLocationsColorWhite() 269 | { 270 | $this->game->blankBoard(); 271 | $this->game->addPiece('W', 'K', 'a1'); 272 | $this->game->addPiece('W', 'Q', 'h1'); 273 | $this->game->addPiece('B', 'P', 'c7'); 274 | 275 | $locations = $this->invokeMethod($this->game, 'getPieceLocations', array('W')); 276 | $this->assertEquals(2, count($locations)); 277 | $this->assertTrue(in_array('a1', $locations)); 278 | $this->assertTrue(in_array('h1', $locations)); 279 | } 280 | 281 | public function testGetPieceLocationsInvalidColorParameterError() 282 | { 283 | $this->game->blankBoard(); 284 | $this->game->addPiece('W', 'K', 'a1'); 285 | $this->game->addPiece('W', 'Q', 'h1'); 286 | $this->game->addPiece('B', 'P', 'c7'); 287 | 288 | $error = $this->invokeMethod($this->game, 'getPieceLocations', array('COLOR_X')); 289 | $this->assertInstanceOf('PEAR_Error', $error); 290 | } 291 | 292 | public function testGetPieceLocationsNoColorSpecified() 293 | { 294 | $this->game->blankBoard(); 295 | $this->game->addPiece('W', 'K', 'a1'); 296 | $this->game->addPiece('W', 'Q', 'h1'); 297 | $this->game->addPiece('B', 'P', 'c7'); 298 | 299 | $locations = $this->invokeMethod($this->game, 'getPieceLocations'); 300 | $this->assertEquals(2, count($locations)); 301 | $this->assertTrue(in_array('a1', $locations)); 302 | $this->assertTrue(in_array('h1', $locations)); 303 | } 304 | 305 | public function testGetPossibleBishopMovesNoColorSpecified() 306 | { 307 | $this->game->resetGame(); 308 | $this->game->moveSAN('e4'); 309 | $this->game->moveSAN('c5'); 310 | 311 | $locations = $this->game->getPossibleBishopMoves('f1'); 312 | $this->assertEquals(5, count($locations)); 313 | $this->assertTrue(in_array('e2', $locations)); 314 | $this->assertTrue(in_array('d3', $locations)); 315 | $this->assertTrue(in_array('c4', $locations)); 316 | $this->assertTrue(in_array('b5', $locations)); 317 | $this->assertTrue(in_array('a6', $locations)); 318 | } 319 | 320 | public function testGetPossibleBishopMovesInvalidColorParameterError() 321 | { 322 | $this->game->resetGame(); 323 | $this->assertInstanceOf('PEAR_Error', $this->game->getPossibleBishopMoves('f1', 'COLOR_X')); 324 | } 325 | 326 | public function testGetPossibleBishopMovesInvalidSquareParameterError() 327 | { 328 | $this->game->resetGame(); 329 | $this->assertInstanceOf('PEAR_Error', $this->game->getPossibleBishopMoves('SQUARE_X')); 330 | } 331 | 332 | public function testGetPossibleKingMovesNoColorSpecified() 333 | { 334 | $this->game->resetGame(); 335 | $this->game->moveSAN('e4'); 336 | $this->game->moveSAN('c5'); 337 | $locations = $this->game->getPossibleKingMoves('e1'); 338 | 339 | $this->assertEquals(3, count($locations)); 340 | $this->assertTrue(in_array('c1', $locations)); 341 | $this->assertTrue(in_array('g1', $locations)); 342 | $this->assertTrue(in_array('e2', $locations)); 343 | } 344 | 345 | public function testGetPossibleKingMovesInvalidColorParameterError() 346 | { 347 | $this->game->resetGame(); 348 | $this->assertInstanceOf('PEAR_Error', $this->game->getPossibleKingMoves('e1', 'COLOR_X')); 349 | } 350 | 351 | public function testGetPossibleKingMovesInvalidSquareParameterError() 352 | { 353 | $this->game->resetGame(); 354 | $this->assertInstanceOf('PEAR_Error', $this->game->getPossibleKingMoves('SQUARE_X')); 355 | } 356 | 357 | public function testGetPossibleKnightMovesNoColorSpecified() 358 | { 359 | $this->game->resetGame(); 360 | 361 | $locations = $this->game->getPossibleKnightMoves('g1'); 362 | $this->assertEquals(2, count($locations)); 363 | $this->assertTrue(in_array('f3', $locations)); 364 | $this->assertTrue(in_array('h3', $locations)); 365 | } 366 | 367 | public function testGetPossibleKnightMovesInvalidColorParameterError() 368 | { 369 | $this->game->resetGame(); 370 | $this->assertInstanceOf('PEAR_Error', $this->game->getPossibleKnightMoves('g1', 'COLOR_X')); 371 | } 372 | 373 | public function testGetPossibleKnightMovesInvalidSquareParameterError() 374 | { 375 | $this->game->resetGame(); 376 | $this->assertInstanceOf('PEAR_Error', $this->game->getPossibleKnightMoves('SQUARE_X')); 377 | } 378 | 379 | public function testGetPossibleMovesInvalidColorParameterError() 380 | { 381 | $this->game->resetGame(); 382 | $this->assertInstanceOf('PEAR_Error', $this->game->getPossibleMoves('P', 'a2', 'COLOR_X')); 383 | } 384 | 385 | public function testGetPossibleMovesInvalidPieceParameterError() 386 | { 387 | $this->game->resetGame(); 388 | $this->assertInstanceOf('PEAR_Error', $this->game->getPossibleMoves('PIECE_X', 'a2')); 389 | } 390 | 391 | public function testGetPossibleMovesInvalidSquareParameterError() 392 | { 393 | $this->game->resetGame(); 394 | $this->assertInstanceOf('PEAR_Error', $this->game->getPossibleMoves('P', 'SQUARE_X')); 395 | } 396 | 397 | public function testGetPossibleMovesNoColorSpecified() 398 | { 399 | $this->game->resetGame(); 400 | $moves = $this->game->getPossibleMoves('P', 'a2'); 401 | $this->assertEquals(2, count($moves)); 402 | $this->assertTrue(in_array('a3', $moves)); 403 | $this->assertTrue(in_array('a4', $moves)); 404 | } 405 | 406 | public function testGetPossiblePawnMovesNoColorSpecified() 407 | { 408 | $this->game->resetGame(); 409 | 410 | $locations = $this->game->getPossiblePawnMoves('h2'); 411 | $this->assertEquals(2, count($locations)); 412 | $this->assertTrue(in_array('h3', $locations)); 413 | $this->assertTrue(in_array('h3', $locations)); 414 | } 415 | 416 | public function testGetPossiblePawnMovesInvalidColorParameterError() 417 | { 418 | $this->game->resetGame(); 419 | $this->assertInstanceOf('PEAR_Error', $this->game->getPossiblePawnMoves('h2', 'COLOR_X')); 420 | } 421 | 422 | public function testGetPossiblePawnMovesInvalidSquareParameterError() 423 | { 424 | $this->game->resetGame(); 425 | $this->assertInstanceOf('PEAR_Error', $this->game->getPossiblePawnMoves('SQUARE_X')); 426 | } 427 | 428 | public function testGetPossiblePawnMovesEnPassantBlackSideOne() 429 | { 430 | $this->game->blankBoard(); 431 | $this->game->addPiece('W', 'P', 'c2'); 432 | $this->game->addPiece('B', 'P', 'd4'); 433 | 434 | $this->game->moveSAN('c4'); 435 | 436 | $locations = $this->game->getPossiblePawnMoves('d4'); 437 | $this->assertEquals(2, count($locations)); 438 | $this->assertTrue(in_array('c3', $locations)); 439 | $this->assertTrue(in_array('d3', $locations)); 440 | } 441 | 442 | public function testGetPossiblePawnMovesEnPassantBlackSideTwo() 443 | { 444 | $this->game->blankBoard(); 445 | $this->game->addPiece('W', 'P', 'e2'); 446 | $this->game->addPiece('B', 'P', 'd4'); 447 | 448 | $this->game->moveSAN('e4'); 449 | 450 | $locations = $this->game->getPossiblePawnMoves('d4'); 451 | $this->assertEquals(2, count($locations)); 452 | $this->assertTrue(in_array('d3', $locations)); 453 | $this->assertTrue(in_array('e3', $locations)); 454 | } 455 | 456 | public function testGetPossiblePawnMovesEnPassantWhiteSideOne() 457 | { 458 | $this->game->blankBoard(); 459 | $this->game->addPiece('W', 'P', 'e4'); 460 | $this->game->addPiece('B', 'P', 'd7'); 461 | 462 | $this->game->moveSAN('e5'); 463 | $this->game->moveSAN('d5'); 464 | 465 | $locations = $this->game->getPossiblePawnMoves('e5'); 466 | $this->assertEquals(2, count($locations)); 467 | $this->assertTrue(in_array('d6', $locations)); 468 | $this->assertTrue(in_array('e6', $locations)); 469 | } 470 | 471 | public function testGetPossiblePawnMovesEnPassantWhiteSideTwo() 472 | { 473 | $this->game->blankBoard(); 474 | $this->game->addPiece('W', 'P', 'e4'); 475 | $this->game->addPiece('B', 'P', 'f7'); 476 | 477 | $this->game->moveSAN('e5'); 478 | $this->game->moveSAN('f5'); 479 | 480 | $locations = $this->game->getPossiblePawnMoves('e5'); 481 | $this->assertEquals(2, count($locations)); 482 | $this->assertTrue(in_array('e6', $locations)); 483 | $this->assertTrue(in_array('f6', $locations)); 484 | } 485 | 486 | public function testGetPossibleRookMovesInvalidColorParameterError() 487 | { 488 | $this->game->resetGame(); 489 | $this->assertInstanceOf('PEAR_Error', $this->game->getPossibleRookMoves('h1', 'COLOR_X')); 490 | } 491 | 492 | public function testGetPossibleRookMovesInvalidSquareParameterError() 493 | { 494 | $this->game->resetGame(); 495 | $this->assertInstanceOf('PEAR_Error', $this->game->getPossibleRookMoves('SQUARE_X')); 496 | } 497 | 498 | public function testGetPossibleRookMovesNoColorSpecified() 499 | { 500 | $this->game->resetGame(); 501 | $this->game->moveSAN('h4'); 502 | $this->game->moveSAN('c5'); 503 | 504 | $locations = $this->game->getPossibleRookMoves('h1'); 505 | $this->assertEquals(2, count($locations)); 506 | $this->assertTrue(in_array('h2', $locations)); 507 | $this->assertTrue(in_array('h3', $locations)); 508 | } 509 | 510 | /* 511 | * According to the Wikipedia page describing FEN notation, segment 5 (Half-Move clock) 512 | * is the number of half moves since the last pawn advance or capture. This means that 513 | * after either of those events, the clock should be reset to 0. The game library 514 | * presently resets to one, thus the counter is off by one. 515 | * 516 | * The following sequence of moves and the resulting FEN is provided on the linked 517 | * page and was used to verify the bug's existence and subsequent fix. 518 | * 519 | * @link http://en.wikipedia.org/wiki/Forsyth-Edwards_Notation 520 | */ 521 | public function testHalfMoveBugFix() 522 | { 523 | $this->game->resetGame(); 524 | $this->assertEquals('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1', $this->game->renderFen()); 525 | 526 | $this->game->moveSAN('e4'); 527 | $this->assertEquals('rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1', $this->game->renderFen()); 528 | 529 | $this->game->moveSAN('c5'); 530 | $this->assertEquals('rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR w KQkq c6 0 2', $this->game->renderFen()); 531 | 532 | $this->game->moveSAN('Nf3'); 533 | $this->assertEquals('rnbqkbnr/pp1ppppp/8/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2', $this->game->renderFen()); 534 | } 535 | 536 | public function testIsInBasicDrawKingBishopVersusKingBishopWithBishopsOnSameColor() 537 | { 538 | $startFen = '7B/8/8/8/8/6k1/1b6/5K2 w - -'; 539 | 540 | $this->game->resetGame($startFen); 541 | $this->assertBasicDraw(); 542 | } 543 | 544 | public function testIsInBasicDrawKingBishopVersusKingBishopWithBishopsOnDifferentColor() 545 | { 546 | $startFen = '8/8/8/6k1/1K6/8/2B4b/8 w - -'; 547 | 548 | $this->game->resetGame($startFen); 549 | $this->assertBasicDraw(); 550 | } 551 | 552 | public function testIsInBasicDrawKingVersusKing() 553 | { 554 | $startFen = '8/5k2/8/8/6K1/8/8/8 w - -'; 555 | 556 | $this->game->resetGame($startFen); 557 | $this->assertBasicDraw(); 558 | } 559 | 560 | public function testIsInBasicDrawKingVersusKingWithBishopOrKnight() 561 | { 562 | foreach (array('b', 'B', 'N', 'n') as $piece) { 563 | $startFen = sprintf('8/2%s2k2/8/8/6K1/8/8/8 w - -', $piece); 564 | 565 | $this->game->resetGame($startFen); 566 | $this->assertBasicDraw(); 567 | } 568 | } 569 | 570 | public function testIsInBasicDrawKingAndTwoKnightsVsKing() 571 | { 572 | $startFen = '8/8/4K3/8/4k3/3nn3/8/8 w - -'; 573 | 574 | $this->game->resetGame($startFen); 575 | $this->assertTrue($this->game->inBasicDraw()); 576 | } 577 | 578 | public function testIsNotInBasicDrawWith4pieces() 579 | { 580 | $startFen = '8/8/6B1/3k4/8/4K3/5Q2/8 w - -'; 581 | 582 | $this->game->resetGame($startFen); 583 | $this->assertFalse($this->game->inBasicDraw()); 584 | } 585 | 586 | public function testIsInCheckmate() 587 | { 588 | $startFen = '3k2R1/8/3K4/8/8/8/8/8 b - -'; 589 | 590 | $this->game->resetGame($startFen); 591 | $this->assertTrue($this->game->inCheckMate()); 592 | } 593 | 594 | public function testIsInCheckmateInvalidColorParameterError() 595 | { 596 | $this->game->resetGame(); 597 | $this->assertInstanceOf('PEAR_Error', $this->game->inCheckMate('COLOR_X')); 598 | } 599 | 600 | public function testIsInFiftyRuleDraw() 601 | { 602 | $startFen = '7k/4N3/4NK2/5B2/8/8/8/r7 w - - 100 112'; 603 | 604 | $this->game->resetGame($startFen); 605 | $this->assert50MoveDraw(); 606 | } 607 | 608 | /** 609 | * FEN for board setup from Karpov vs. Kasparov, Tilburg, 1991 610 | * 611 | * @link http://en.wikipedia.org/wiki/Fifty-move_rule#Karpov_vs._Kasparov 612 | */ 613 | public function testIsInStalemate() 614 | { 615 | $startFen = '7k/5Q2/6K1/8/8/8/8/8 b - -'; 616 | 617 | $this->game->resetGame($startFen); 618 | $this->assertStalemate(); 619 | } 620 | 621 | /** 622 | * FEN for board setup and subsequent moves from Fischer versus Petrosian, 1971 623 | * 624 | * @link http://en.wikipedia.org/wiki/Threefold_repetition#Fischer_versus_Petrosian.2C_1971 625 | * @link http://chesstempo.com/gamedb/game/2695095 626 | */ 627 | public function testIsInThreefoldRepetitionDraw() 628 | { 629 | $this->game->resetGame('8/pp3p1k/2p2q1p/3r1P1Q/5R2/7P/P1P2P2/7K w - - 1 30'); 630 | 631 | $moves = array( 632 | 30 => array('white' => 'Qe2', 'black' => 'Qe5'), 633 | 31 => array('white' => 'Qh5', 'black' => 'Qf6'), 634 | 32 => array('white' => 'Qe2', 'black' => 'Re5'), 635 | 33 => array('white' => 'Qd3', 'black' => 'Rd5'), 636 | 34 => array('white' => 'Qe2',), 637 | ); 638 | 639 | foreach ($moves as $playerMoves) { 640 | foreach ($playerMoves as $move) { 641 | $this->game->moveSAN($move); 642 | } 643 | } 644 | 645 | $this->assertRepetitionDraw(); 646 | } 647 | 648 | public function testIsNotInBasicDrawTooManyBlackBishops() 649 | { 650 | $startFen = '6B1/1b6/8/8/8/6k1/1b6/5K2 w - -'; 651 | 652 | $this->game->resetGame($startFen); 653 | $this->assertFalse($this->game->inBasicDraw()); 654 | } 655 | 656 | public function testIsNotInBasicDrawTooManyBlackPieces() 657 | { 658 | $startFen = '6B1/1p6/8/8/8/6k1/1b6/5K2 w - -'; 659 | 660 | $this->game->resetGame($startFen); 661 | $this->assertFalse($this->game->inBasicDraw()); 662 | } 663 | 664 | public function testIsNotInBasicDrawTooManyWhiteBishops() 665 | { 666 | $startFen = '6B1/1B6/8/8/8/6k1/1b6/5K2 w - -'; 667 | 668 | $this->game->resetGame($startFen); 669 | $this->assertFalse($this->game->inBasicDraw()); 670 | } 671 | 672 | public function testIsNotInBasicDrawTooManyWhitePieces() 673 | { 674 | $startFen = '6B1/1P6/8/8/8/6k1/1b6/5K2 w - -'; 675 | 676 | $this->game->resetGame($startFen); 677 | $this->assertFalse($this->game->inBasicDraw()); 678 | } 679 | 680 | public function testIsNotInFiftyRuleDraw() 681 | { 682 | $this->game->resetGame(); 683 | $this->assertFalse($this->game->in50MoveDraw()); 684 | } 685 | 686 | public function testIsNotGameOver() 687 | { 688 | $this->game->resetGame(); 689 | $this->assertFalse($this->game->gameOver()); 690 | } 691 | 692 | public function testIsNotInStalemateBecauseHasLegalMoves() 693 | { 694 | $startFen = 'rnbqkbnr/pp1ppppp/8/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2'; 695 | 696 | $this->game->resetGame($startFen); 697 | $this->assertFalse($this->game->inStaleMate()); 698 | } 699 | 700 | public function testIsNotInStalemateBecauseIsInCheck() 701 | { 702 | $startFen = '8/8/2k4/8/8/8/2R4/4K3 b KQkq - 1 2'; 703 | 704 | $this->game->resetGame($startFen); 705 | $this->assertFalse($this->game->inStaleMate()); 706 | } 707 | 708 | public function testIsInNotThreefoldRepetitionDraw() 709 | { 710 | $this->game->resetGame(); 711 | $this->assertFalse($this->game->inRepetitionDraw()); 712 | } 713 | 714 | public function testIsNotPromoteMove() 715 | { 716 | $this->game->resetGame(); 717 | $this->game->moveSAN('e4'); 718 | $this->game->moveSAN('c5'); 719 | $this->assertFalse($this->game->isPromoteMove('e4', 'e5')); 720 | } 721 | 722 | public function testIsNotPromoteMoveIllegalMove() 723 | { 724 | $this->game->blankBoard(); 725 | $this->game->addPiece('W', 'B', 'a1'); 726 | $this->game->addPiece('W', 'B', 'h1'); 727 | $this->game->addPiece('W', 'P', 'b7'); 728 | $this->assertFalse($this->game->isPromoteMove('b7', 'a7')); 729 | } 730 | 731 | public function testIsPromoteMove() 732 | { 733 | $this->game->blankBoard(); 734 | $this->game->addPiece('W', 'B', 'a1'); 735 | $this->game->addPiece('W', 'B', 'h1'); 736 | $this->game->addPiece('W', 'P', 'b7'); 737 | $this->assertTrue($this->game->isPromoteMove('b7', 'b8')); 738 | } 739 | 740 | public function testMoveList() 741 | { 742 | $moves = array('Nf3', 'Nf6', 'c4', 'e6', 'Nc3', 'Bb4'); 743 | 744 | $this->game->resetGame(); 745 | 746 | foreach ($moves as $move) { 747 | $this->game->moveSAN($move); 748 | } 749 | 750 | $moveList = $this->game->getMoveList(); 751 | $this->assertTrue(is_array($moveList)); 752 | $this->assertEquals(3, count($moveList)); 753 | 754 | foreach ($moveList as $madeMoves) { 755 | $this->assertTrue(is_array($madeMoves)); 756 | $this->assertEquals(2, count($madeMoves)); 757 | } 758 | 759 | $this->assertEquals('Nf3', $moveList[1][0]); 760 | $this->assertEquals('Nf6', $moveList[1][1]); 761 | 762 | $this->assertEquals('c4', $moveList[2][0]); 763 | $this->assertEquals('e6', $moveList[2][1]); 764 | 765 | $this->assertEquals('Nc3', $moveList[3][0]); 766 | $this->assertEquals('Bb4', $moveList[3][1]); 767 | } 768 | 769 | public function testMoveListString() 770 | { 771 | $moves = array('Nf3', 'Nf6', 'c4', 'e6', 'Nc3'); 772 | 773 | $this->game->resetGame(); 774 | 775 | foreach ($moves as $move) { 776 | $this->game->moveSAN($move); 777 | } 778 | 779 | $moveList = $this->game->getMoveListString(); 780 | $this->assertTrue(is_string($moveList)); 781 | $this->assertEquals('1.Nf3 Nf6 2.c4 e6 3.Nc3', $moveList); 782 | } 783 | 784 | public function testMoveListWithChecks() 785 | { 786 | $moves = array('Rf1', 'Kb6', 'Rf6', 'Kb5', 'Rf5', 'Kb6'); 787 | $startFen = '8/8/k7/8/8/8/4K3/4R3 w - -'; 788 | 789 | $this->game->resetGame($startFen); 790 | 791 | foreach ($moves as $move) { 792 | $this->game->moveSAN($move); 793 | } 794 | 795 | $moveList = $this->game->getMoveList(true); 796 | $this->assertTrue(is_array($moveList)); 797 | $this->assertEquals(3, count($moveList)); 798 | 799 | foreach ($moveList as $madeMoves) { 800 | $this->assertTrue(is_array($madeMoves)); 801 | $this->assertEquals(2, count($madeMoves)); 802 | } 803 | 804 | $this->assertEquals('Rf1', $moveList[1][0]); 805 | $this->assertEquals('Kb6', $moveList[1][1]); 806 | 807 | $this->assertEquals('Rf6+', $moveList[2][0]); 808 | $this->assertEquals('Kb5', $moveList[2][1]); 809 | 810 | $this->assertEquals('Rf5+', $moveList[3][0]); 811 | $this->assertEquals('Kb6', $moveList[3][1]); 812 | } 813 | 814 | public function testMoveListStringWithChecks() 815 | { 816 | $moves = array('Rf1', 'Kb6', 'Rf6', 'Kb5', 'Rf5', 'Kb6'); 817 | $startFen = '8/8/k7/8/8/8/4K3/4R3 w - -'; 818 | 819 | $this->game->resetGame($startFen); 820 | 821 | foreach ($moves as $move) { 822 | $this->game->moveSAN($move); 823 | } 824 | 825 | $moveList = $this->game->getMoveListString(true); 826 | $this->assertTrue(is_string($moveList)); 827 | $this->assertEquals('1.Rf1 Kb6 2.Rf6+ Kb5 3.Rf5+ Kb6', $moveList); 828 | } 829 | 830 | public function testNewChess960GameRenderedFen() 831 | { 832 | $this->game->resetGame(false, true); 833 | $this->assertEquals('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1', $this->game->renderFen()); 834 | } 835 | 836 | // XXX: Parsing a legitimate Chess960 FEN appears to be horked 837 | /** 838 | * Set up a new Chess960 board. FEN generated at lichness.org 839 | * 840 | * @link http://en.lichess.org 841 | */ 842 | // public function testNewChess960GameRenderedFen() 843 | // { 844 | // $fen = 'nrnbbkqr/pppppppp/8/8/8/8/PPPPPPPP/NRNBBKQR w KQkq - 0 1 '; 845 | // $this->game->resetGame($fen, true); 846 | // $this->assertEquals($fen, $this->game->renderFen()); 847 | // } 848 | 849 | public function testNewStandardGameFen() 850 | { 851 | $this->game->resetGame(); 852 | $this->assertEquals('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1', $this->game->renderFen()); 853 | } 854 | 855 | public function testNewStandardGameWithProvidedFenRenderedFen() 856 | { 857 | $providedFen = 'rnbqkbnr/pp1ppppp/8/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2'; 858 | 859 | $this->game->resetGame($providedFen); 860 | $this->assertEquals($providedFen, $this->game->renderFen()); 861 | } 862 | 863 | public function testNewStandardGameRenderedFenWithoutEnPassant() 864 | { 865 | $this->game->resetGame(); 866 | $this->assertEquals('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq', $this->game->renderFen(true, false)); 867 | } 868 | 869 | public function testNewStandardGameRenderedFenWithoutMoves() 870 | { 871 | $this->game->resetGame(); 872 | $this->assertEquals('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -', $this->game->renderFen(false)); 873 | } 874 | 875 | public function testState() 876 | { 877 | $this->game->resetGame(); 878 | 879 | $originalState = $this->game->getState(); 880 | $this->game->moveSAN('e4'); 881 | 882 | $newState = $this->game->getState(); 883 | $this->assertTrue($newState === $this->game->getState()); 884 | $this->assertFalse($newState === $originalState); 885 | 886 | $this->game->setState($originalState); 887 | $this->assertFalse($newState === $this->game->getState()); 888 | $this->assertTrue($originalState === $this->game->getState()); 889 | 890 | $this->game->commitTransaction(); 891 | $this->assertTrue($originalState === $this->game->getState()); 892 | } 893 | 894 | public function testToArray() 895 | { 896 | $piecesToSet = array( 897 | 'a2' => array('color' => 'B', 'piece' => 'P'), 898 | 'd1' => array('color' => 'B', 'piece' => 'Q'), 899 | 'd8' => array('color' => 'W', 'piece' => 'Q'), 900 | ); 901 | 902 | $this->game->blankBoard(); 903 | 904 | foreach ($piecesToSet as $square => $data) { 905 | $this->game->addPiece($data['color'], $data['piece'], $square); 906 | } 907 | 908 | $gameBoardArray = $this->game->toArray(); 909 | $this->assertTrue(is_array($gameBoardArray)); 910 | $this->assertEquals(64, count($gameBoardArray)); 911 | 912 | $referenceArray = array(); 913 | 914 | foreach (array(1, 2, 3, 4, 5, 6, 7, 8) as $column) { 915 | foreach (array('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h') as $rank) { 916 | $index = $rank . $column; 917 | 918 | if (array_key_exists($index, $piecesToSet)) { 919 | $color = $piecesToSet[$index]['color']; 920 | $piece = $piecesToSet[$index]['piece']; 921 | $referenceArray[$index] = ('W' == $color) ? $piece : strtolower($piece); 922 | } else { 923 | $referenceArray[$index] = false; 924 | } 925 | } 926 | } 927 | 928 | $this->assertEquals($referenceArray, $gameBoardArray); 929 | } 930 | 931 | public function testToMoveBlack() 932 | { 933 | $this->game->resetGame(); 934 | $this->assertEquals('W', $this->game->toMove()); 935 | } 936 | 937 | public function testToMoveWhite() 938 | { 939 | $this->game->resetGame(); 940 | $this->game->moveSAN('e4'); 941 | $this->assertEquals('B', $this->game->toMove()); 942 | } 943 | 944 | public function testMovesShouldNotBeChangedAfterCheckingIfGameInStalemate() 945 | { 946 | $this->game->resetGame(); 947 | $this->game->moveSAN('h3'); 948 | $this->assertFalse($this->game->inStaleMate()); 949 | $this->assertEquals(array(1 => array('h3')), $this->game->getMoveList()); 950 | } 951 | 952 | public function testInitialSetupWithDuplicatedPiecesShouldBeValid() 953 | { 954 | $this->game->resetGame('nnnnknnn/pppppppp/8/8/8/8/PPPPPPPP/R1BQKB1R w KQ -'); 955 | $this->assertTrue($this->game->moveSAN('b3')); 956 | } 957 | 958 | public function testGameShouldNotBeOverInClaimableDraw() 959 | { 960 | $moves = array('e4', 'e5', 'Nf3', 'Nc6', 'Ng1', 'Nb8', 'Nf3', 'Nc6', 'Ng1', 'Nb8', 'Nf3'); 961 | $this->game->resetGame(); 962 | foreach ($moves as $move) { 963 | $this->game->moveSAN($move); 964 | } 965 | 966 | $this->assertRepetitionDraw(); 967 | } 968 | 969 | private function assertBasicDraw() 970 | { 971 | $this->assertTrue($this->game->inBasicDraw()); 972 | $this->assertTrue($this->game->inForcedDraw()); 973 | $this->assertTrue($this->game->inDraw()); 974 | $this->assertFalse($this->game->inClaimableDraw()); 975 | $this->assertNotFalse($this->game->gameOver()); 976 | } 977 | 978 | private function assertStalemate() 979 | { 980 | $this->assertTrue($this->game->inStaleMate()); 981 | $this->assertTrue($this->game->inForcedDraw()); 982 | $this->assertTrue($this->game->inDraw()); 983 | $this->assertFalse($this->game->inClaimableDraw()); 984 | $this->assertNotFalse($this->game->gameOver()); 985 | } 986 | 987 | private function assert50MoveDraw() 988 | { 989 | $this->assertTrue($this->game->in50MoveDraw()); 990 | $this->assertTrue($this->game->inDraw()); 991 | $this->assertTrue($this->game->inClaimableDraw()); 992 | $this->assertFalse($this->game->inForcedDraw()); 993 | $this->assertFalse($this->game->gameOver()); 994 | } 995 | 996 | private function assertRepetitionDraw() 997 | { 998 | $this->assertTrue($this->game->inRepetitionDraw()); 999 | $this->assertTrue($this->game->inDraw()); 1000 | $this->assertTrue($this->game->inClaimableDraw()); 1001 | $this->assertFalse($this->game->inForcedDraw()); 1002 | $this->assertFalse($this->game->gameOver()); 1003 | } 1004 | 1005 | public function testKingValidMovesWithMoveSAN() 1006 | { 1007 | $this->game->resetGame(); 1008 | $this->game->moveSAN('e4'); 1009 | $this->game->moveSAN('e5'); 1010 | $this->game->moveSAN('d4'); 1011 | $this->game->moveSAN('exd4'); 1012 | $this->game->moveSAN('Qxd4'); 1013 | $this->game->moveSAN('Nc6'); 1014 | $this->game->moveSAN('Qd1'); 1015 | $this->game->moveSAN('Bd6'); 1016 | $this->game->moveSAN('a3'); 1017 | $this->game->moveSAN('Nf6'); 1018 | $this->game->moveSAN('Nc3'); 1019 | $this->game->moveSAN('Nxe4'); 1020 | $this->game->moveSAN('Qe7'); 1021 | $this->game->moveSAN('Qe2'); 1022 | $this->assertTrue($this->game->isError($this->game->moveSAN('Kxe7'))); 1023 | } 1024 | 1025 | public function testKingValidMovesMakingMoves() 1026 | { 1027 | $this->game->resetGame(); 1028 | $this->game->moveSquare('e2', 'e4'); 1029 | $this->game->moveSquare('e7', 'e5'); 1030 | $this->game->moveSquare('d2', 'd4'); 1031 | $this->game->moveSquare('e5', 'd4'); 1032 | $this->game->moveSquare('d1', 'd4'); 1033 | $this->game->moveSquare('b8', 'c6'); 1034 | $this->game->moveSquare('d4', 'd1'); 1035 | $this->game->moveSquare('f8', 'd6'); 1036 | $this->game->moveSquare('a2', 'a3'); 1037 | $this->game->moveSquare('g8', 'f6'); 1038 | $this->game->moveSquare('b1', 'c3'); 1039 | $this->game->moveSquare('f6', 'e4'); 1040 | $this->game->moveSquare('c3', 'e4'); 1041 | $this->game->moveSquare('d8', 'e7'); 1042 | $this->game->moveSquare('d1', 'e2'); 1043 | $this->assertTrue($this->game->isError($this->game->moveSquare('e8', 'e7'))); 1044 | } 1045 | 1046 | public function testKingShouldNotTakeOwnPiece() 1047 | { 1048 | $this->game->resetGame('5b1r/1b2k1p1/2N1pn1p/BBP5/3P4/1PN1P3/1P3PPP/4K2R b K - 0 22'); 1049 | $this->assertTrue($this->game->isError($this->game->moveSquare('e7', 'e6'))); 1050 | } 1051 | 1052 | public function testHasMatingMaterialShouldReturnTrueOnStandardSetup() 1053 | { 1054 | $this->game->resetGame(); 1055 | $this->assertTrue($this->game->hasBlackMatingMaterial()); 1056 | $this->assertTrue($this->game->hasWhiteMatingMaterial()); 1057 | } 1058 | 1059 | public function testHasMatingMaterialShouldReturnTrueForBlackAndFalseForWhite() 1060 | { 1061 | $this->game->resetGame('8/8/8/8/5b2/8/4kpK1/8 b - - 1 86'); 1062 | $this->assertFalse($this->game->hasWhiteMatingMaterial()); 1063 | $this->assertTrue($this->game->hasBlackMatingMaterial()); 1064 | } 1065 | 1066 | public function testGameShouldEndInFiveFoldRepetition() 1067 | { 1068 | $moves = array( 1069 | 'Nc3', 1070 | 'Nc6', 1071 | 'Nb1', 1072 | 'Nb8', 1073 | 'Nc3', 1074 | 'Nc6', 1075 | 'Nb1', 1076 | 'Nb8', 1077 | 'Nc3', 1078 | 'Nc6', 1079 | 'Nb1', 1080 | 'Nb8', 1081 | 'Nc3', 1082 | 'Nc6', 1083 | 'Nb1', 1084 | 'Nb8', 1085 | 'Nc3', 1086 | 'Nc6', 1087 | 'Nb1', 1088 | 'Nb8', 1089 | 'Nc3', 1090 | 'Nc6' 1091 | ); 1092 | $this->game->resetGame(); 1093 | foreach ($moves as $move) { 1094 | $this->game->moveSAN($move); 1095 | } 1096 | $this->assertFiveFoldRepetitionDraw(); 1097 | } 1098 | 1099 | public function testNotInBasicDrawTwoWhiteVsTwoBlack() 1100 | { 1101 | $fen = '8/8/8/8/4k3/8/3Kn3/6Q1 w - -'; 1102 | $this->game->resetGame($fen); 1103 | $this->assertFalse($this->game->inBasicDraw()); 1104 | } 1105 | 1106 | public function testInBasicDrawThreePiecesRemained() 1107 | { 1108 | $fen = '8/8/8/8/4k3/8/3K4/6n1 w - -'; 1109 | $this->game->resetGame($fen); 1110 | $this->assertTrue($this->game->inBasicDraw()); 1111 | } 1112 | 1113 | private function assertFiveFoldRepetitionDraw() 1114 | { 1115 | $this->assertTrue($this->game->inRepetitionDraw()); 1116 | $this->assertTrue($this->game->inDraw()); 1117 | $this->assertFalse($this->game->inClaimableDraw()); 1118 | $this->assertTrue($this->game->inForcedDraw()); 1119 | $this->assertEquals('D', $this->game->gameOver()); 1120 | } 1121 | 1122 | /** 1123 | * Call protected/private method of a class. 1124 | * 1125 | * @param object &$object Instantiated object that we will run method on. 1126 | * @param string $methodName Method name to call 1127 | * @param array $parameters Array of parameters to pass into method. 1128 | * 1129 | * @return mixed Method return. 1130 | */ 1131 | private function invokeMethod(&$object, $methodName, array $parameters = array()) 1132 | { 1133 | $reflection = new \ReflectionClass(get_class($object)); 1134 | $method = $reflection->getMethod($methodName); 1135 | $method->setAccessible(true); 1136 | return $method->invokeArgs($object, $parameters); 1137 | } 1138 | } 1139 | -------------------------------------------------------------------------------- /Tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | add('Chess\Game\\', __DIR__); -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chesscom/chess-game", 3 | "description": "Represents a chess game as a php object", 4 | "keywords": ["chess", "php"], 5 | "homepage": "https://github.com/ChessCom/Chess-Game", 6 | "type": "library", 7 | "license": "PHP-3.01", 8 | "authors": [ 9 | { 10 | "name": "Chess team", 11 | "homepage": "http://www.chess.com" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=7.1" 16 | }, 17 | "require-dev": { 18 | "friendsofphp/php-cs-fixer": "~2.3", 19 | "phpunit/phpunit": "^7.3 || ^9.5" 20 | }, 21 | "config": { 22 | "bin-dir": "bin" 23 | }, 24 | "autoload": { 25 | "psr-0": { 26 | "Chess\\Game": "src/" 27 | }, 28 | "files": ["PEAR.php"] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 15 | ./Tests 16 | 17 | 18 | 19 | 20 | 21 | ./ 22 | 23 | ./bin 24 | ./Tests 25 | ./vendor 26 | 27 | 28 | 29 | 30 | --------------------------------------------------------------------------------