├── .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 [](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 |
--------------------------------------------------------------------------------