Browse Source

Add initial implementation

master
Kaj Björklund 11 years ago
parent
commit
abbf478c84
  1. 66
      README.md
  2. 20
      build/compile.sh
  3. 3
      build/export.js
  4. 2252
      build/extern-jquery-1.8.js
  5. 17
      build/extern-jquery-ui.js
  6. 52
      build/jsl.conf
  7. 11
      build/lint.sh
  8. 237
      chess.css
  9. 36
      chess.html
  10. BIN
      chess.ico
  11. 28
      chess.min.js
  12. 400
      src/ai.js
  13. 504
      src/bitboard.js
  14. 237
      src/chess.css
  15. 36
      src/chess.html
  16. BIN
      src/chess.ico
  17. 15
      src/chess.include.js
  18. 173
      src/chess.js
  19. 117
      src/move.js
  20. 125
      src/parser.js
  21. 940
      src/position.js
  22. 15
      src/test.html
  23. 190
      src/test.js
  24. 321
      src/ui.js
  25. 144
      src/zobrist.js

66
README.md

@ -1,2 +1,64 @@
chess
=====
# Javascript Chess
This program is a Javascript implementation of the board game [Chess](http://en.wikipedia.org/wiki/Chess), with a computer player opponent. All move types are supported, including en passant, castling and promotion.
Try it out [here](http://www.iki.fi/kbjorklu/chess/). Usage is shown below the chessboard. At least Internet Explorer (8 or later), Chrome and Firefox should work.
# Code Structure
Source files are placed in the `src` directory. Minification and linting files are placed in the `build` directory. Source file contents:
* `chess.js`: Constants, utilities and the Chess namespace.
* `bitboard.js`: 64-bit bit twiddling tools.
* `zobrist.js`: Game state hash calculator. Currently unused, but will be used in the transposition table implementation.
* `move.js`: Piece movement representation.
* `position.js`: Chess game state and mutation.
* `parser.js`: Parser for various Chess notations.
* `ai.js`: Artificial intelligence, i.e. computer opponent. Basic alpha-beta pruned minimax with a simple evaluation function.
* `ui.js`: User interface code.
* `chess.include.js`: Includes all of the above files.
* `chess.css`: User interface style.
* `chess.ico`: Icon.
* `chess.html`: Main game file.
* `test.js`: Automated tests.
* `test.html`: Automated test runner.
# Building
To compile the minified version using `compile.sh` in the `build` directory, you need [bash](http://git-scm.com/download/win) and the [Closure compiler](https://developers.google.com/closure/compiler/). You may need to adjust the `.jar` location in `compile.sh`. Compiled files are placed in the top-level directory.
To lint using `lint.sh` in the build directory, you need bash and [JavaScript Lint](http://www.javascriptlint.com/). You may need to adjust lint's path in `lint.sh`.
To run the tests, open `test.html` in the `src` directory.
# TODO
* Static exchange evaluation
* Transposition table
* Iterative deepening
* Negamax formulation
* AI randomness
* Take game phase into account in evaluation
* Take mobility into account in evaluation
* Killer heuristic
* Late-check castling legality
* Tie-detection
* Move pieces without drag and drop
* Underpromotion
* Show captured pieces in the UI
* Don't hardcode board target div to UI
* UI for loading game state from parsable Chess notation(s)
* More tests
# License
The Chess implementation is distributed under the MIT license. See accompanying file LICENSE for details.
Third-party components are distributed/used under their respective license:
* jQuery: [MIT](https://github.com/jquery/jquery/blob/master/MIT-LICENSE.txt) (used via jQuery CDN)
* jQuery UI: [MIT](https://github.com/jquery/jquery-ui/blob/master/MIT-LICENSE.txt) (used via jQuery CDN)
* jQuery UI Touch Punch: [MIT](https://github.com/furf/jquery-ui-touch-punch/blob/master/jquery.ui.touch-punch.min.js) (used via CloudFlare CDN)
* Augment.js: [MIT](https://github.com/olivernn/augment.js/blob/master/LICENSE) (used via CloudFlare CDN)
* QUnit: [MIT](https://github.com/jquery/qunit/blob/master/MIT-LICENSE.txt) (used via jQuery CDN)
* The jQuery extern file in the build directory: [Apache 2](http://www.apache.org/licenses/LICENSE-2.0)

20
build/compile.sh

@ -0,0 +1,20 @@
#!/bin/bash
COMPILER_JAR="/c/Program Files/closure-compiler/compiler.jar"
src=`sed -E -n "s/^.*\"(\w+\.js)\".*$/--js ..\/src\/\1/p" ../src/chess.include.js`
java -jar "$COMPILER_JAR" \
--language_in ECMASCRIPT5_STRICT \
--compilation_level ADVANCED_OPTIMIZATIONS \
--output_wrapper "(function(){%output%})();" \
--use_types_for_optimization \
--summary_detail_level 3 \
--warning_level VERBOSE \
--js_output_file ../chess.min.js \
--process_jquery_primitives \
--externs extern-jquery-1.8.js \
--externs extern-jquery-ui.js \
$src \
--js export.js
cp ../src/chess.css ..
cp ../src/chess.ico ..
sed "s/chess\.include\.js/chess.min.js/" ../src/chess.html > ../chess.html

3
build/export.js

@ -0,0 +1,3 @@
"use strict";
window["makeChessGame"] = makeChessGame;

2252
build/extern-jquery-1.8.js

File diff suppressed because it is too large

17
build/extern-jquery-ui.js

@ -0,0 +1,17 @@
/** @externs */
/**
* @param {(string|Object.<string, *>)} x
* @param {*=} y
* @param {*=} z
* @return {(!jQuery|*)}
*/
jQuery.prototype.draggable = function(x, y, z) {};
/**
* @param {(string|Object.<string, *>)} x
* @param {*=} y
* @param {*=} z
* @return {(!jQuery|*)}
*/
jQuery.prototype.droppable = function(x, y, z) {};

52
build/jsl.conf

@ -0,0 +1,52 @@
+no_return_value # function {0} does not always return a value
+duplicate_formal # duplicate formal argument {0}
+equal_as_assign # test for equality (==) mistyped as assignment (=)?{0}
+var_hides_arg # variable {0} hides argument
+redeclared_var # redeclaration of {0} {1}
+anon_no_return_value # anonymous function does not always return a value
+missing_semicolon # missing semicolon
+meaningless_block # meaningless block; curly braces have no impact
+comma_separated_stmts # multiple statements separated by commas (use semicolons?)
+unreachable_code # unreachable code
+missing_break # missing break statement
+missing_break_for_last_case # missing break statement for last case in switch
+comparison_type_conv # comparisons against null, 0, true, false, or an empty string allowing implicit type conversion (use === or !==)
+inc_dec_within_stmt # increment (++) and decrement (--) operators used as part of greater statement
+useless_void # use of the void type may be unnecessary (void is always undefined)
+multiple_plus_minus # unknown order of operations for successive plus (e.g. x+++y) or minus (e.g. x---y) signs
+use_of_label # use of label
+block_without_braces # block statement without curly braces
+leading_decimal_point # leading decimal point may indicate a number or an object member
+trailing_decimal_point # trailing decimal point may indicate a number or an object member
+octal_number # leading zeros make an octal number
+nested_comment # nested comment
+misplaced_regex # regular expressions should be preceded by a left parenthesis, assignment, colon, or comma
+ambiguous_newline # unexpected end of line; it is ambiguous whether these lines are part of the same statement
+empty_statement # empty statement or extra semicolon
+missing_option_explicit # the "option explicit" control comment is missing
+partial_option_explicit # the "option explicit" control comment, if used, must be in the first script tag
+dup_option_explicit # duplicate "option explicit" control comment
+useless_assign # useless assignment
+ambiguous_nested_stmt # block statements containing block statements should use curly braces to resolve ambiguity
+ambiguous_else_stmt # the else statement could be matched with one of multiple if statements (use curly braces to indicate intent)
+missing_default_case # missing default case in switch statement
+duplicate_case_in_switch # duplicate case in switch statements
+default_not_at_end # the default case is not at the end of the switch statement
+legacy_cc_not_understood # couldn't understand control comment using /*@keyword@*/ syntax
+jsl_cc_not_understood # couldn't understand control comment using /*jsl:keyword*/ syntax
+useless_comparison # useless comparison; comparing identical expressions
+with_statement # with statement hides undeclared variables; use temporary variable instead
+trailing_comma_in_array # extra comma is not recommended in array initializers
+assign_to_function_call # assignment to a function call
+parseint_missing_radix # parseInt missing radix parameter
+context
+lambda_assign_requires_semicolon
-legacy_control_comments
-jscript_function_extensions
+always_use_option_explicit
+define window
+define document
+define $
+define alert

11
build/lint.sh

@ -0,0 +1,11 @@
#!/bin/bash
LINT="/c/Program Files (x86)/jsl-0.3.0/jsl.exe"
src=`sed -E -n "s/^.*\"(\w+\.js)\".*$/..\/src\/\1/p" ../src/chess.include.js`
echo "\"use strict\";" > chess.max.js
sed "s/^.use strict..$//g" $src >> chess.max.js
sed -i "/^$/N;/^\n$/D" chess.max.js
sed -i "s/\t/ /g" chess.max.js
"$LINT" -nologo -nofilelisting -conf jsl.conf -process chess.max.js
rm chess.max.js

237
chess.css

@ -0,0 +1,237 @@
body {
width: 960px;
margin-left: auto;
margin-right: auto;
text-align: left;
font-size: 12pt;
font-family: Arial, sans-serif;
color: black;
background: #303030 repeat fixed url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAAFUlEQVQImWNgYGD4D8UMDEgMFIH/AGu7Bfvz8g82AAAAAElFTkSuQmCC");
}
h1 {
text-transform: lowercase;
text-shadow: 0 1px 0 white;
letter-spacing: 0.5ex;
font-weight: bold;
font-size: 100%;
background: white;
text-shadow: 0 0 5px black;
margin: 0;
padding: 14px;
border-top-left-radius: 14px;
border-top-right-radius: 14px;
}
#content {
background: white;
padding: 14px 40px;
}
#help {
cursor: help;
}
#dim {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.6);
z-index: 10;
display: none;
cursor: wait;
}
#chessboard {
width: 700px;
height: 700px;
float: left;
padding: 0;
}
#moves {
border: 1px solid silver;
border-radius: 14px;
width: 138px;
/* max-width: 168px; */
padding: 5px;
padding-left: 15px;
float: right;
overflow: auto;
height: 688px;
}
#moves button {
width: 100%;
}
#clear {
clear: both;
width: 0;
height: 0;
}
#footer {
background: white;
margin: 0;
padding: 14px;
border-bottom-left-radius: 14px;
border-bottom-right-radius: 14px;
}
#chessboard table {
border-spacing: 0;
border-collapse: collapse;
border: none;
cursor: default;
/* see http://goo.gl/1dTy7 (css rule to disable text selection highlighting) */
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
#chessboard table tr th, #chessboard table tr td {
padding: 0;
margin: 0;
text-align: center;
vertical-align: middle;
}
#chessboard table tr th {
background: silver;
font-size: small;
font-weight: normal;
}
#chessboard table tr th.file {
width: 80px;
height: 30px;
}
#chessboard table tr th.rank {
width: 30px;
height: 80px;
}
#chessboard table tr:first-child th:first-child {
border-top-left-radius: 14px;
}
#chessboard table tr:first-child th:last-child {
border-top-right-radius: 14px;
}
#chessboard table tr:last-child th:first-child {
border-bottom-left-radius: 14px;
}
#chessboard table tr:last-child th:last-child {
border-bottom-right-radius: 14px;
}
#chessboard table tr td {
width: 80px;
height: 80px;
}
#chessboard table tr td.light {
text-shadow: 0 0 10px black;
background: #E0E0E0;
background: -moz-linear-gradient(-45deg, #ffffff 0%, #c0c0c0 100%);
background: -webkit-gradient(linear, left top, right bottom, color-stop(0%, #ffffff), color-stop(100%, #c0c0c0));
background: -webkit-linear-gradient(-45deg, #ffffff 0%, #c0c0c0 100%);
background: -o-linear-gradient(-45deg, #ffffff 0%, #c0c0c0 100%);
background: -ms-linear-gradient(-45deg, #ffffff 0%, #c0c0c0 100%);
background: linear-gradient(135deg, white, silver);
}
#chessboard table tr td.dark {
text-shadow: 0 0 10px white;
background: #404040;
background: -moz-linear-gradient(-45deg, #808080 0%, #000000 100%);
background: -webkit-gradient(linear, left top, right bottom, color-stop(0%, #808080), color-stop(100%, #000000));
background: -webkit-linear-gradient(-45deg, #808080 0%, #000000 100%);
background: -o-linear-gradient(-45deg, #808080 0%, #000000 100%);
background: -ms-linear-gradient(-45deg, #808080 0%, #000000 100%);
background: linear-gradient(135deg, gray, black);
}
#chessboard table tr td div {
font-size: 50px;
}
#chessboard table tr td.white {
color: white;
}
#chessboard table tr td.black {
color: black;
}
#chessboard table tr td.from {
font-weight: bold;
}
#chessboard table tr td.to {
box-shadow: inset 0 0 10px 1px green;
}
#chessboard table tr td.to.capture {
box-shadow: inset 0 0 10px 1px red;
}
#chessboard table tr td.to.en-passant:after {
color: red;
content: "e.p.";
}
#chessboard table tr td.to.king-castle:after {
color: magenta;
content: "0-0";
}
#chessboard table tr td.to.queen-castle:after {
color: magenta;
content: "0-0-0";
}
#chessboard table tr td.to.positional:after, #chessboard table tr td.to.double-push:after {
color: gray;
content: "\2022";
}
#chessboard table tr td.turn {
cursor: move;
}
#chessboard table tr td div.turn:not(.can-move) {
cursor: not-allowed;
}
#chessboard table tr td.last-move {
box-shadow: inset 0 0 10px 1px yellow;
}
#moves a {
color: gray;
font-size: 8pt;
text-decoration: none;
}
#moves a.cannot {
color: silver;
pointer-events: none;
cursor: default;
}

36
chess.html

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta charset="utf-8">
<title>Chess</title>
<link rel="stylesheet" href="http://code.jquery.com/ui/1.10.0/themes/base/jquery-ui.css">
<link rel="stylesheet" href="chess.css">
<link rel="shortcut icon" href="chess.ico">
</head>
<body>
<h1>Chess</h1>
<div id="content">
<div id="chessboard" role="main"></div>
<div id="moves"></div>
<div id="clear"></div>
<div id="help">
<ul>
<li>Moves can be made by dragging the pieces on the board, or by clicking the move links in the right panel.</li>
<li>Pawns dragged to the last rank are promoted to queens. Use the move links in the right panel to underpromote.</li>
<li>Castle by moving the king two squares towards a rook. The rook moves automatically.</li>
</ul>
</div>
</div>
<div id="footer"></div>
<div id="dim"></div>
<script src="http://code.jquery.com/jquery-1.9.0.min.js"></script>
<script src="http://code.jquery.com/ui/1.10.0/jquery-ui.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jqueryui-touch-punch/0.2.2/jquery.ui.touch-punch.min.js"></script>
<!--[if lt IE 9]><script src="http://cdnjs.cloudflare.com/ajax/libs/augment.js/1.0.0/augment.min.js"></script><![endif]-->
<script src="chess.min.js"></script>
<script>
$(makeChessGame);
</script>
</body>
</html>

BIN
chess.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

28
chess.min.js

@ -0,0 +1,28 @@
(function(){'use strict';var f=!0,j=null,m=!1,aa="pawn knight bishop rook queen king".split(" ");function ba(a,b){return"abcdefgh"[b]+"12345678"[a]}function ca(a){return"abcdefgh".indexOf(a[0])+8*"12345678".indexOf(a[1])}function q(a){return ba(a>>>3,a&7)};function da(a,b){this.a=a>>>0;this.b=b>>>0}function ea(a){a>>>=0;a-=a>>>1&1431655765;a=(a&858993459)+(a>>>2&858993459);return 16843009*(a+(a>>>4)&252645135)>>>24}function fa(a){a>>>=0;return(a&a-1)>>>0}function ga(a){a>>>=0;return ea((a&-a)-1)}function r(a){return ea(a.a)+ea(a.b)}function ha(a){return a.a?ga(a.a):32+ga(a.b)}function s(a){var b=ha(a);a.a?a.a=fa(a.a):a.b=fa(a.b);return b}function t(a){return!a.a&&!a.b}function u(a,b){b>>>=0;return 32>b?!(a.a&1<<b):!(a.b&1<<b-32)}
function ia(a,b){return!u(a,b)}function y(a,b){b>>>=0;32>b?a.a=(a.a|1<<b)>>>0:a.b=(a.b|1<<b-32)>>>0;return a}function ja(a,b){b>>>=0;32>b?a.a=(a.a&~(1<<b))>>>0:a.b=(a.b&~(1<<b-32))>>>0}function A(a,b){a.a=(a.a&b.a)>>>0;a.b=(a.b&b.b)>>>0;return a}function B(a,b){a.a=(a.a&~b.a)>>>0;a.b=(a.b&~b.b)>>>0;return a}function C(a,b){a.a=(a.a|b.a)>>>0;a.b=(a.b|b.b)>>>0;return a}function ka(a,b){a.a=(a.a^b.a)>>>0;a.b=(a.b^b.b)>>>0}function la(a){a.a=~a.a>>>0;a.b=~a.b>>>0;return a}
function D(a,b){b>>>=0;31<b?(a.b=a.a<<b-32>>>0,a.a=0):0<b&&(a.b=(a.b<<b|a.a>>>32-b)>>>0,a.a=a.a<<b>>>0);return a}function E(a,b){b>>>=0;31<b?(a.a=a.b>>>b-32,a.b=0):0<b&&(a.a=(a.a>>>b|a.b<<32-b)>>>0,a.b>>>=b);return a}function F(a,b){63<b||-63>b?a.a=a.b=0:0<b?D(a,b):0>b&&E(a,-b);return a}function ma(a,b){return a.a===b.a&&a.b===b.b}function G(a){return I(a.a,a.b)}function I(a,b){return new da(a,b)}function J(a){return y(I(0,0),a)}
function na(){var a=oa;return F(A(I(270549120,16909320),F(I(4294967295,4294967295),8*a)),a)}function pa(){var a=qa;return F(A(I(134480385,2151686160),F(I(4294967295,4294967295),8*-a)),a)}function ra(){var a=y(I(0,0),sa),b=B(E(G(a),1),K[7]),c=B(B(E(G(a),2),K[7]),K[6]),d=B(D(G(a),1),K[0]),a=B(B(D(G(a),2),K[0]),K[1]),c=C(c,a),b=C(b,d);return C(C(C(D(G(c),8),E(c,8)),D(G(b),16)),E(b,16))}
function ta(){var a=y(I(0,0),ua),b=C(B(E(G(a),1),K[7]),B(D(G(a),1),K[0])),c=E(C(G(a),b),8),a=D(C(G(a),b),8);return C(C(b,c),a)}for(var va=I(4294967295,4294967295),wa=I(1437226410,1437226410),xa=I(2857740885,2857740885),ya=[],za=0;8>za;++za)ya.push(D(I(16843009,16843009),za));for(var K=ya,L=[],Aa=0;8>Aa;++Aa)L.push(D(I(255,0),8*Aa));for(var Ba=[],oa=-7;8>oa;++oa)Ba.push(na());for(var Ca=[],qa=-7;8>qa;++qa)Ca.push(pa());for(var Da=[],sa=0;64>sa;++sa)Da.push(ra());for(var Ea=[],ua=0;64>ua;++ua)Ea.push(ta());function Fa(){this.a=this.b=0}for(var Ga=[],Ha=0;1586>Ha;++Ha)Ga.push(1+4294967295*Math.random()>>>0);function M(a,b){a.b=(a.b^Ga[b])>>>0;a.a=(a.a^Ga[b+1])>>>0}function N(a,b){0<=b&&M(a,1570+(b&7))};function Ia(a,b,c,d,g){this.a=b&63|(a&63)<<6|(c&15)<<12|(d&7)<<16|((g|0)&7)<<19}function O(a){return a.a>>>6&63}function P(a){return a.a>>>12&15}function Q(a){return a.a>>>16&7}function Ja(a){return 2===P(a)||3===P(a)}function Ka(a){return 5!==P(a)?a.a&63:(a.a&63)+(O(a)<(a.a&63)?-8:8)}function La(a){return!Ja(a)?" NBRQK".charAt(Q(a))+q(O(a))+(P(a)&4?"x":"-")+q(a.a&63)+(5===P(a)?"e.p.":"")+(P(a)&8?" NBRQK".charAt(P(a)&8?1+(P(a)&3):0):""):"0-0"+(3===P(a)?"-0":"")};function Ma(){this.c=new Fa;this.a=[C(G(L[1]),L[6]),C(C(C(J(1),J(6)),J(57)),J(62)),C(C(C(J(2),J(5)),J(58)),J(61)),C(C(C(J(0),J(7)),J(56)),J(63)),C(J(3),J(59)),C(J(4),J(60)),C(G(L[0]),L[1]),C(G(L[6]),L[7])];this.f=[];this.b=0;this.e=15;this.d=-1;this.h=0;this.g=[];this.i=[];for(var a=this.f.length=0;64>a;++a){var b;a:{for(b=0;5>=b;++b)if(!u(this.a[b],a))break a;b=j}this.f.push(b)}this.c=new Fa;this.b&&M(this.c,0);for(a=0;1>=a;++a)for(b=0;5>=b;++b)for(var c=this.c,d=b,g=a,e=R(this,b,a),e=G(e);!t(e);){var h=
s(e);M(c,2+d+6*g+12*h)}M(this.c,1538+this.e);N(this.c,this.d)}var S=[7,63,0,56],Na=[la(D(I(16843009,16843009),7)),va,la(D(I(16843009,16843009),0))];
function T(a,b,c){function d(a,b,c){for(;!t(b);){var d=s(b);u(ib,d)&&h.push(new Ia(a,d,!u(v,d)?4:0,c,H.f[d]))}}function g(a,b,c){e(G(a),b,c?15:11);e(G(a),b,c?14:10);e(G(a),b,c?13:9);e(G(a),b,c?12:8)}function e(a,b,c){for(;!t(a);){var d=s(a);h.push(new Ia(d-b,d,c,0,H.f[d]))}}c=!!c;var h=[],k=a.b,v=a.a[6+(k^1)],l=U(a),H=a,n=1-2*k,x=8*n,w=R(a,0,k),p=L[k?0:7];if(!c){var z=B(B(F(A(G(w),L[k?6:1]),2*x),l),F(G(l),x));e(z,2*x,1);z=B(F(G(w),x),l);e(B(G(z),p),x,0);g(A(G(z),p),x,m)}var Ra=K[k?7:0],z=x-n,Y=A(F(B(G(w),
Ra),z),v);e(B(G(Y),p),z,4);g(A(G(Y),p),z,f);var Y=K[k?0:7],x=x+n,Sa=A(F(B(G(w),Y),x),v);e(B(G(Sa),p),x,4);g(A(G(Sa),p),x,f);0<=a.d&&(p=F(B(A(J(a.d+n),w),Ra),z),e(p,z,5),n=F(B(A(J(a.d-n),w),Y),x),e(n,x,5));for(var ib=a.a[6+k],n=c?v:va,w=G(R(a,1,k));!t(w);)p=s(w),d(p,A(G(Da[p]),n),1);for(w=G(R(a,4,k));!t(w);)p=s(w),d(p,A(C(Oa(J(p),l),Pa(J(p),l)),n),4);for(w=G(R(a,2,k));!t(w);)p=s(w),d(p,A(Oa(J(p),l),n),2);for(w=G(R(a,3,k));!t(w);)p=s(w),d(p,A(Pa(J(p),l),n),3);l=ha(R(a,5,k));d(l,A(G(Ea[l]),n),5);c||
(Qa(a,k,f)&&h.push(new Ia(l,l+2,2,5,j)),Qa(a,k,m)&&h.push(new Ia(l,l-2,3,5,j)));c=h;return b?c:c.filter(Ma.prototype.j,a)}function R(a,b,c){return A(G(a.a[b]),a.a[6+c])}function U(a){return C(G(a.a[6]),a.a[7])}function V(a){return Ta(a,a.b^1,ha(R(a,5,a.b)))}function Ua(a,b){var c=0===a,d=F(B(G(b),K[0]),c?7:-9),c=F(B(G(b),K[7]),c?9:-7);return C(d,c)}function W(a,b,c,d){var g=I(0,0);c=8*c+d;d=Na[1+d];for(F(a,c);!t(A(a,d));F(B(a,b),c))C(g,a);return g}
function Oa(a,b){return C(C(C(W(G(a),b,1,1),W(G(a),b,1,-1)),W(G(a),b,-1,1)),W(G(a),b,-1,-1))}function Pa(a,b){return C(C(C(W(G(a),b,0,1),W(G(a),b,0,-1)),W(G(a),b,1,0)),W(G(a),b,-1,0))}function Ta(a,b,c){var d=R(a,0,b);if(ia(Ua(b,d),c))return f;d=R(a,1,b);if(!t(A(G(Da[c]),d)))return f;d=R(a,5,b);if(!t(A(G(Ea[c]),d)))return f;var d=U(a),g=R(a,4,b),e=C(G(R(a,2,b)),g);if(ia(Oa(e,d),c))return f;a=C(G(R(a,3,b)),g);return ia(Pa(a,d),c)?f:m}
function X(a,b,c){M(a.c,1538+a.e);a.e&=~(1<<b+(c?0:2));M(a.c,1538+a.e)}function Qa(a,b,c){if(0===(a.e&1<<b+(c?0:2)))return m;var d=c?1:-1,g=0===b?4:60,e=U(a);if(!(d=!u(e,g+d)||!u(e,g+2*d)||!c&&!u(e,g+3*d)))d=b^1,c=c?1:-1,b=0===b?4:60,d=!(!Ta(a,d,b)&&!Ta(a,d,b+c)&&!Ta(a,d,b+2*c));return d?m:f}function Va(a){if(!t(a.a[0])||!t(a.a[3])||!t(a.a[4]))return m;if(4>r(a.a[6])+r(a.a[7]))return f;if(!t(a.a[1]))return m;a=a.a[2];return ma(A(G(a),wa),a)||ma(A(G(a),xa),a)?f:m}
function Wa(a,b,c,d,g){var e=C(J(d),J(g));ka(a.a[b],e);ka(a.a[6+c],e);a.f[d]=j;a.f[g]=b;M(a.c,2+b+6*c+12*d);M(a.c,2+b+6*c+12*g)}function Xa(a,b,c,d,g){ja(a.a[b],g);y(a.a[c],g);a.f[g]=c;M(a.c,2+b+6*d+12*g);M(a.c,2+c+6*d+12*g)}function Ya(a,b){if(P(b)&4){var c=b.a>>>19&7,d=a.b^1,g=Ka(b);ja(a.a[c],g);ja(a.a[6+d],g);a.f[g]=j;M(a.c,2+c+6*d+12*g)}Ja(b)&&(c=a.b,d=2===P(b),g=S[c+(d?0:2)],Wa(a,3,c,g,g+(d?-2:3)));Wa(a,Q(b),a.b,O(b),b.a&63);P(b)&8&&Xa(a,0,P(b)&8?1+(P(b)&3):0,a.b,b.a&63)}
function Za(a,b){P(b)&8&&Xa(a,P(b)&8?1+(P(b)&3):0,0,a.b,b.a&63);Wa(a,Q(b),a.b,b.a&63,O(b));if(Ja(b)){var c=a.b,d=2===P(b),g=S[c+(d?0:2)];Wa(a,3,c,g+(d?-2:3),g)}P(b)&4&&(c=b.a>>>19&7,d=a.b^1,g=Ka(b),y(a.a[c],g),y(a.a[6+d],g),a.f[g]=c,M(a.c,2+c+6*d+12*g))}Ma.prototype.j=function(a){Ya(this,a);var b=V(this);Za(this,a);return!b};
function Z(a,b){Ya(a,b);if(V(a))return Za(a,b),m;a.g.push(b);a.i.push(a.d);a.i.push(a.e);a.i.push(a.h);N(a.c,a.d);a.d=1===P(b)?b.a&63:-1;N(a.c,a.d);var c=a.b;5===Q(b)?(X(a,c,f),X(a,c,m)):3===Q(b)&&(O(b)===S[c+0]?X(a,c,f):O(b)===S[c+2]&&X(a,c,m));c^=1;3===(b.a>>>19&7)&&(Ka(b)===S[c+0]?X(a,c,f):Ka(b)===S[c+2]&&X(a,c,m));P(b)&4||0===Q(b)?a.h=0:++a.h;a.b=c;M(a.c,0);return f}
function $a(a){if(a.g.length){var b=a.g.pop();a.b^=1;M(a.c,0);Za(a,b);a.i.pop();M(a.c,1538+a.e);a.e=a.i.pop();M(a.c,1538+a.e);N(a.c,a.d);a.d=a.i.pop();N(a.c,a.d)}};var ab=[100,300,300,500,900,2E4],bb=[[0,0,0,0,0,0,0,0,50,50,50,50,50,50,50,50,10,10,20,30,30,20,10,10,5,5,10,25,25,10,5,5,0,0,0,20,20,0,0,0,5,-5,-10,0,0,-10,-5,5,5,10,10,-20,-20,10,10,5,0,0,0,0,0,0,0,0],[-50,-40,-30,-30,-30,-30,-40,-50,-40,-20,0,0,0,0,-20,-40,-30,0,10,15,15,10,0,-30,-30,5,15,20,20,15,5,-30,-30,0,15,20,20,15,0,-30,-30,5,10,15,15,10,5,-30,-40,-20,0,5,5,0,-20,-40,-50,-40,-30,-30,-30,-30,-40,-50],[-20,-10,-10,-10,-10,-10,-10,-20,-10,0,0,0,0,0,0,-10,-10,0,5,10,10,5,0,-10,-10,5,5,10,10,
5,5,-10,-10,0,10,10,10,10,0,-10,-10,10,10,10,10,10,10,-10,-10,5,0,0,0,0,5,-10,-20,-10,-10,-10,-10,-10,-10,-20],[0,0,0,0,0,0,0,0,5,10,10,10,10,10,10,5,-5,0,0,0,0,0,0,-5,-5,0,0,0,0,0,0,-5,-5,0,0,0,0,0,0,-5,-5,0,0,0,0,0,0,-5,-5,0,0,0,0,0,0,-5,0,0,0,5,5,0,0,0],[-20,-10,-10,-5,-5,-10,-10,-20,-10,0,0,0,0,0,0,-10,-10,0,5,5,5,5,0,-10,-5,0,5,5,5,5,0,-5,0,0,5,5,5,5,0,-5,-10,5,5,5,5,5,0,-10,-10,0,5,0,0,0,0,-10,-20,-10,-10,-5,-5,-10,-10,-20],[-30,-40,-40,-50,-50,-40,-40,-30,-30,-40,-40,-50,-50,-40,-40,-30,-30,
-40,-40,-50,-50,-40,-40,-30,-30,-40,-40,-50,-50,-40,-40,-30,-20,-30,-30,-40,-40,-30,-30,-20,-10,-20,-20,-20,-20,-20,-20,-10,20,20,0,0,0,0,20,20,20,30,10,0,0,10,30,20]],cb=ab[0]/2;function db(a,b){for(var c=0,d=0;5>d;++d)c+=r(R(a,d,b))*ab[d];1<r(R(a,2,b))&&(c+=cb);return c}function eb(a,b){for(var c=0,d=0;5>=d;++d)for(var g=G(R(a,d,b));!t(g);)var e=s(g),c=c+bb[d][b?e:56^e];return c}
function fb(a,b){var c=a.a[6+b],d=a.a[6+(b^1)],g=U(a),e=la(U(a)),h;h=0;var k,v=R(a,0,b),l=0===b;k=A(F(G(v),l?8:-8),e);e=A(A(F(A(G(v),L[l?1:6]),l?16:-16),e),F(G(e),l?8:-8));k=C(k,e);h=h+r(k);h+=r(A(Ua(b,R(a,0,b)),d));for(d=G(R(a,1,b));!t(d);)h+=r(B(G(Da[s(d)]),c));h+=r(B(G(Ea[ha(R(a,5,b))]),c));d=R(a,4,b);k=C(G(R(a,2,b)),d);h+=r(B(Oa(k,g),c));d=C(G(R(a,3,b)),d);h+=r(B(Pa(d,g),c));return h*ab[0]/100};function gb(){this.a=new Ma}function hb(){$("#chessboard table tr td, #chessboard table tr td div").removeClass("from to positional capture double-push en-passant promotion castle king-castle queen-castle")}function jb(){$("#chessboard table tr td div.ui-draggable").draggable("destroy");$("#chessboard table tr td.ui-droppable").droppable("destroy")}
function kb(a){$("#moves").html("");var b=$("#dim");b.fadeIn(function(){function c(a,b,e,h){if(1>b)return d(a,e,h);for(var p=g(T(a,f,m)),k=0===a.b,l=m,n=0;n<p.length;++n)if(Z(a,p[n])){var l=f,z=c(a,b-1,e,h);$a(a);k?e=z>e?z:e:h=z<h?z:h;if(h<=e)break}if(!l){if(!V(a))return 0;a=ab[5];return k?-a:a}return 100<=a.h||Va(a)?0:k?e:h}function d(a,b,c){if(100<=a.h||Va(a))return 0;var e=db(a,0)-db(a,1)+(eb(a,0)-eb(a,1));++k;var h=0===a.b;if(h){if(e>=c)return c;b=e>b?e:b}else{if(e<=b)return b;c=e<c?e:c}for(var e=
g(T(a,f,!V(a))),p=0;p<e.length;++p)if(Z(a,e[p])){var n=d(a,b,c);$a(a);if(h){if(n>=c)return c;b=n>b?n:b}else{if(n<=b)return b;c=n<c?n:c}}return h?b:c}function g(a){function b(a){var c=P(a)&4?(1+(a.a>>>19&7))/(1+Q(a)):0,c=6*c+Q(a),c=16*c+P(a),c=64*c+(a.a&63);return c=64*c+O(a)}a.sort(function(a,c){return b(c)-b(a)});return a}var e,h=a.a,k=0;e=j;for(var v=-Infinity,l=Infinity,H=g(T(h,f)),n=0;n<H.length;++n)if(Z(h,H[n])){var x=c(h,3,v,l);$a(h);0===h.b?x>v&&(v=x,e=H[n]):x<l&&(l=x,e=H[n])}window.console.log("Evaluations: "+
k+", result move: "+La(e)+", alpha: "+v+", beta: "+l);if(!e)throw Error("Move not found");Z(a.a,e);h=$("#"+q(O(e)));e=$("#"+q(e.a&63));var w=e.offset().left-h.offset().left,p=e.offset().top-h.offset().top,z=h.children("div");z.css({position:"relative",top:"0px",left:"0px"});b.fadeOut(function(){z.animate({top:p+"px",left:w+"px"},function(){lb(a)})})})}
function lb(a){window.console.log("Moves: "+a.a.g.length+", white material: "+db(a.a,0)+", black material: "+db(a.a,1)+", white mobility: "+fb(a.a,0)+", black mobility: "+fb(a.a,1)+", white location: "+eb(a.a,0)+", black location: "+eb(a.a,1));hb();jb();$("#chessboard table tr td div").remove();$("#chessboard table tr td").removeClass("white black turn last-move "+aa.join(" "));for(var b=a.a.a[6],c=a.a.a[7],d=0;64>d;++d)for(var g=$("#"+q(d)),e=0;5>=e;++e)if(!u(a.a.a[e],d)){var h=0===a.a.b?!u(b,d):
!u(c,d),k=$("<div>");k.attr("title",g.attr("title")+"\nPiece: "+aa[e]+"\nColor: "+(!u(b,d)?"white":"black"));k.text("\u2659\u265f\u2658\u265e\u2657\u265d\u2656\u265c\u2655\u265b\u2654\u265a".charAt(2*e+(!u(b,d)?0:1)));var v=k.add(g);v.addClass(aa[e]);v.toggleClass("white",!u(b,d));v.toggleClass("black",!u(c,d));v.toggleClass("turn",h);g.append(k);break}b=!a.a.g.length?j:a.a.g[a.a.g.length-1];b!==j&&($("#"+q(O(b))).addClass("last-move"),$("#"+q(b.a&63)).addClass("last-move"));b=!T(a.a).length?V(a.a)?
1:2:100<=a.a.h?3:Va(a.a)?5:0;if(0===b&&1===a.a.b)kb(a);else{var l=T(a.a);$("#moves").html('<a href="#" id="undo" class="'+(a.a.g.length?"can":"cannot")+'">undo</a><br/><a href="#" id="auto" class="'+(0<l.length?"can":"cannot")+'">auto</a><br/>'+l.map(function(a,b){return'<a href="#" id="'+b+'">'+La(a)+"</a><br/>"}).join(""));$("#chessboard table tr td, #chessboard table tr td div").removeClass("can-move");l.forEach(function(a){a=$("#"+q(O(a)));a.add(a.children()).addClass("can-move")});var H=m;$("#chessboard table tr td div.can-move").mouseenter(function(){if(!H){var b=
$(this),c=b.parent(),d=ca(""+c.attr("id")),c=c.add(b);c.toggleClass("from",l.some(function(a){return O(a)===d}));c.hasClass("from")&&(l.forEach(function(a){if(O(a)===d){var b=$("#"+q(a.a&63)),b=b.add(b.children());b.addClass("to");b.addClass(0===P(a)?"positional":"");b.addClass(P(a)&4?"capture":"");b.addClass(1===P(a)?"double-push":"");b.addClass(5===P(a)?"en-passant":"");b.addClass(P(a)&8?"promotion":"");b.addClass(Ja(a)?"castle":"");b.addClass(2===P(a)?"king-castle":"");b.addClass(3===P(a)?"queen-castle":
"")}}),jb(),$("#chessboard table tr td.to").droppable({drop:function(){var b=ca(""+$(this).attr("id")),c=l.filter(function(a){return O(a)===d&&(a.a&63)===b});0<c.length?(Z(a.a,c[0]),lb(a)):(hb(),jb())}}),b.draggable({start:function(){H=f},stop:function(){H=m},containment:"#chessboard table",zIndex:10,revert:"invalid"}))}}).mouseleave(function(){H||hb()});$("#moves a").click(function(){var b=$(this).attr("id");"undo"===b?($a(a.a),$a(a.a),lb(a)):"auto"===b?kb(a):(Z(a.a,l[parseInt(b,10)]),lb(a))});$("#dim").css({display:"none"});
1===b?$("#moves").append("&#35;<br/>"+(a.a.b?"1-0":"0-1")):0!==b&&$("#moves").append("&frac12;-&frac12;")}};window.makeChessGame=function(){var a=$("<table>"),b="<tr><th></th>"+"abcdefgh".split("").map(function(a){return'<th class="file">'+a+"</th>"}).join("")+"<th></th></tr>";a.append(b);for(var c=0;8>c;++c){var d=7-c,g=$("<tr>");a.append(g);var e='<th class="rank">'+(8-c)+"</th>";g.append(e);for(var h=0;8>h;++h){var k=$("<td>"),v=(d+h)%2?"light":"dark";k.attr("id",ba(d,h));k.attr("title","Algebraic: "+ba(d,h)+"\nRank: "+d+"\nFile: "+h+"\nIndex: "+(h+8*d)+"\nColor: "+v);k.addClass(v);g.append(k)}g.append(e)}a.append(b);
$("#chessboard").append(a);lb(new gb)};})();

400
src/ai.js

@ -0,0 +1,400 @@
"use strict";
/**
* AI (artificial intelligence) is a computer player for chess.
* The implementation is an alpha-beta pruned minimax with a simple evaluation function.
* AI takes a chess position, evaluates possible moves up to a certain depth, and returns the move it considers best (or null if the game is lost).
* @constructor
* TODO: add some sort of randomness; per side so that two computers playing against each other act differently (and don't know how the other is acting).
* TODO: static exchange evaluation (see)
* TODO: transposition table
* TODO: iterative deepening
* TODO: negamax formulation
*/
Chess.AI = function() {
};
/**
* @const
* @type {!Array.<number>}
* @see http://goo.gl/zxAE9 (Chess piece relative value)
*/
Chess.AI.PIECE_VALUES = [100, 300, 300, 500, 900, 20000];
/**
* @const
* @type {!Array.<!Array.<number>>}
* @see http://goo.gl/X326e (Simplified evaluation function)
*/
Chess.AI.PIECE_SQUARE_TABLES = [
// pawn
[
0, 0, 0, 0, 0, 0, 0, 0,
50, 50, 50, 50, 50, 50, 50, 50,
10, 10, 20, 30, 30, 20, 10, 10,
5, 5, 10, 25, 25, 10, 5, 5,
0, 0, 0, 20, 20, 0, 0, 0,
5, -5, -10, 0, 0, -10, -5, 5,
5, 10, 10, -20, -20, 10, 10, 5,
0, 0, 0, 0, 0, 0, 0, 0
],
// knight
[
-50, -40, -30, -30, -30, -30, -40, -50,
-40, -20, 0, 0, 0, 0, -20, -40,
-30, 0, 10, 15, 15, 10, 0, -30,
-30, 5, 15, 20, 20, 15, 5, -30,
-30, 0, 15, 20, 20, 15, 0, -30,
-30, 5, 10, 15, 15, 10, 5, -30,
-40, -20, 0, 5, 5, 0, -20, -40,
-50, -40, -30, -30, -30, -30, -40, -50
],
// bishop
[
-20, -10, -10, -10, -10, -10, -10, -20,
-10, 0, 0, 0, 0, 0, 0, -10,
-10, 0, 5, 10, 10, 5, 0, -10,
-10, 5, 5, 10, 10, 5, 5, -10,
-10, 0, 10, 10, 10, 10, 0, -10,
-10, 10, 10, 10, 10, 10, 10, -10,
-10, 5, 0, 0, 0, 0, 5, -10,
-20, -10, -10, -10, -10, -10, -10, -20
],
// rook
[
0, 0, 0, 0, 0, 0, 0, 0,
5, 10, 10, 10, 10, 10, 10, 5,
-5, 0, 0, 0, 0, 0, 0, -5,
-5, 0, 0, 0, 0, 0, 0, -5,
-5, 0, 0, 0, 0, 0, 0, -5,
-5, 0, 0, 0, 0, 0, 0, -5,
-5, 0, 0, 0, 0, 0, 0, -5,
0, 0, 0, 5, 5, 0, 0, 0
],
// queen
[
-20, -10, -10, -5, -5, -10, -10, -20,
-10, 0, 0, 0, 0, 0, 0, -10,
-10, 0, 5, 5, 5, 5, 0, -10,
-5, 0, 5, 5, 5, 5, 0, -5,
0, 0, 5, 5, 5, 5, 0, -5,
-10, 5, 5, 5, 5, 5, 0, -10,
-10, 0, 5, 0, 0, 0, 0, -10,
-20, -10, -10, -5, -5, -10, -10, -20
],
// king middle game
[
-30, -40, -40, -50, -50, -40, -40, -30,
-30, -40, -40, -50, -50, -40, -40, -30,
-30, -40, -40, -50, -50, -40, -40, -30,
-30, -40, -40, -50, -50, -40, -40, -30,
-20, -30, -30, -40, -40, -30, -30, -20,
-10, -20, -20, -20, -20, -20, -20, -10,
20, 20, 0, 0, 0, 0, 20, 20,
20, 30, 10, 0, 0, 10, 30, 20
]/*,
// king end game
[
-50, -40, -30, -20, -20, -30, -40, -50,
-30, -20, -10, 0, 0, -10, -20, -30,
-30, -10, 20, 30, 30, 20, -10, -30,
-30, -10, 30, 40, 40, 30, -10, -30,
-30, -10, 30, 40, 40, 30, -10, -30,
-30, -10, 20, 30, 30, 20, -10, -30,
-30, -30, 0, 0, 0, 0, -30, -30,
-50, -30, -30, -30, -30, -30, -30, -50
]*/
];
/**
* @const
* @type {number}
* @see http://goo.gl/adkwe (Bishop pair)
*/
Chess.AI.BISHOP_PAIR_VALUE = Chess.AI.PIECE_VALUES[Chess.Piece.PAWN] / 2;
/**
* @param {!Chess.Position} chessPosition
* @param {!Chess.PieceColor} color
* @return {number}
*/
Chess.AI.getMaterialValue = function(chessPosition, color) {
var value = 0;
for (var piece = Chess.Piece.PAWN; piece < Chess.Piece.KING; ++piece) {
value += chessPosition.getPieceColorBitboard(piece, color).popcnt() * Chess.AI.PIECE_VALUES[piece];
}
if (chessPosition.getPieceColorBitboard(Chess.Piece.BISHOP, color).popcnt() > 1) {
value += Chess.AI.BISHOP_PAIR_VALUE;
}
return value;
};
/**
* @param {!Chess.Position} chessPosition
* @return {number}
*/
Chess.AI.evaluateMaterial = function(chessPosition) {
return Chess.AI.getMaterialValue(chessPosition, Chess.PieceColor.WHITE) - Chess.AI.getMaterialValue(chessPosition, Chess.PieceColor.BLACK);
};
/**
* @param {!Chess.Position} chessPosition
* @param {!Chess.PieceColor} color
* @return {number}
* TODO: game phase
*/
Chess.AI.getPieceSquareValue = function(chessPosition, color) {
var value = 0;
for (var piece = Chess.Piece.PAWN; piece <= Chess.Piece.KING; ++piece) {
var pieces = chessPosition.getPieceColorBitboard(piece, color).dup();
while (!pieces.isEmpty()) {
var index = pieces.extractLowestBitPosition();
value += Chess.AI.PIECE_SQUARE_TABLES[piece][color ? index : (56 ^ index)];
}
}
return value;
};
/**
* @param {!Chess.Position} chessPosition
* @return {number}
*/
Chess.AI.evaluateLocations = function(chessPosition) {
return Chess.AI.getPieceSquareValue(chessPosition, Chess.PieceColor.WHITE) - Chess.AI.getPieceSquareValue(chessPosition, Chess.PieceColor.BLACK);
};
/**
* @param {!Chess.PieceColor} color white = attacks by white pieces
* @param {!Chess.Bitboard} pawns
* @param {!Chess.Bitboard} empty
* @return {!Chess.Bitboard}
*/
Chess.AI.makePawnPositionalMask = function(color, pawns, empty) {
var white = (color === Chess.PieceColor.WHITE);
var positional = pawns.dup().shiftLeft(white ? 8 : -8).and(empty);
var doublePush = pawns.dup().and(Chess.Bitboard.RANKS[white ? 1 : 6]).shiftLeft(white ? 16 : -16).and(empty).and(empty.dup().shiftLeft(white ? 8 : -8));
return positional.or(doublePush);
};
/**
* @param {!Chess.Position} chessPosition
* @param {!Chess.PieceColor} color
* @return {number}
* TODO: it's easy to give bonuses for attack and defend here by and(us) or and(opponent)
* TODO: legality
* TODO: does not count all moves; e.g. two pawns can capture the same square, ditto two rooks, two queens
*/
Chess.AI.getMobilityValue = function(chessPosition, color) {
var us = chessPosition.getColorBitboard(color);
var opponent = chessPosition.getColorBitboard(Chess.getOtherPieceColor(color));
var occupied = chessPosition.getOccupiedBitboard();
var empty = chessPosition.getEmptyBitboard();
var mobility = 0;
mobility += Chess.AI.makePawnPositionalMask(color, chessPosition.getPieceColorBitboard(Chess.Piece.PAWN, color), empty).popcnt();
mobility += Chess.Position.makePawnAttackMask(color, chessPosition.getPieceColorBitboard(Chess.Piece.PAWN, color)).and(opponent).popcnt();
var knights = chessPosition.getPieceColorBitboard(Chess.Piece.KNIGHT, color).dup();
while (!knights.isEmpty()) {
mobility += Chess.Bitboard.KNIGHT_MOVEMENTS[knights.extractLowestBitPosition()].dup().and_not(us).popcnt();
}
mobility += Chess.Bitboard.KING_MOVEMENTS[chessPosition.getKingPosition(color)].dup().and_not(us).popcnt();
var queens = chessPosition.getPieceColorBitboard(Chess.Piece.QUEEN, color);
var bq = chessPosition.getPieceColorBitboard(Chess.Piece.BISHOP, color).dup().or(queens);
mobility += Chess.Position.makeBishopAttackMask(bq, occupied).and_not(us).popcnt();
var rq = chessPosition.getPieceColorBitboard(Chess.Piece.ROOK, color).dup().or(queens);
mobility += Chess.Position.makeRookAttackMask(rq, occupied).and_not(us).popcnt();
return mobility * Chess.AI.PIECE_VALUES[Chess.Piece.PAWN] / 100;
};
/**
* @param {!Chess.Position} chessPosition
* @return {number}
*/
Chess.AI.evaluate = function(chessPosition) {
return Chess.AI.evaluateMaterial(chessPosition) + Chess.AI.evaluateLocations(chessPosition);
};
/**
* @param {!Chess.Position} chessPosition
* @return {?Chess.Move}
*/
Chess.AI.prototype.search = function(chessPosition) {
/**
* @param {!Array.<!Chess.Move>} moves
* @return {!Array.<!Chess.Move>}
*/
function sortMoves(moves) {
/**
* @param {!Chess.Move} move
* @return {number}
* TODO: killer heuristic, history, etc
*/
function scoreMove(move) {
var score = move.isCapture() ? ((1 + move.getCapturedPiece()) / (1 + move.getPiece())) : 0;
score = 6 * score + move.getPiece();
score = 16 * score + move.getKind();
score = 64 * score + move.getTo();
score = 64 * score + move.getFrom();
return score;
}
/**
* @param {!Chess.Move} a
* @param {!Chess.Move} b
* @return {number}
*/
function compareMoves(a, b) {
return scoreMove(b) - scoreMove(a);
}
moves.sort(compareMoves);
return moves;
}
var evaluations = 0;
/**
* @param {!Chess.Position} chessPosition
* @param {number} alpha
* @param {number} beta
* @return {number}
*/
function quiescenceSearch(chessPosition, alpha, beta) {
if (chessPosition.isDraw()) {
// always assume the draw will be claimed
return 0;
}
var standPatValue = Chess.AI.evaluate(chessPosition);
++evaluations;
var white = (chessPosition.getTurnColor() === Chess.PieceColor.WHITE);
if (white) {
if (standPatValue >= beta) {
return beta;
}
alpha = (standPatValue > alpha) ? standPatValue : alpha;
} else {
if (standPatValue <= alpha) {
return alpha;
}
beta = (standPatValue < beta) ? standPatValue : beta;
}
var moves = sortMoves(chessPosition.getMoves(true, !chessPosition.isKingInCheck()));
for (var i = 0; i < moves.length; ++i) {
if (chessPosition.makeMove(moves[i])) {
var value = quiescenceSearch(chessPosition, alpha, beta);
chessPosition.unmakeMove();
if (white) {
if (value >= beta) {
return beta;
}
alpha = (value > alpha) ? value : alpha; // max player (white)
} else {
if (value <= alpha) {
return alpha;
}
beta = (value < beta) ? value : beta; // min player (black)
}
}
}
return /** @type {number} */(white ? alpha : beta);
}
/**
* @param {!Chess.Position} chessPosition
* @param {number} depth
* @param {number} alpha
* @param {number} beta
* @return {number}
*/
function alphaBeta(chessPosition, depth, alpha, beta) {
if (depth < 1) {
return quiescenceSearch(chessPosition, alpha, beta);
}
var moves = sortMoves(chessPosition.getMoves(true, false));
var white = (chessPosition.getTurnColor() === Chess.PieceColor.WHITE);
var legal = false;
for (var i = 0; i < moves.length; ++i) {
if (chessPosition.makeMove(moves[i])) {
legal = true;
var value = alphaBeta(chessPosition, depth - 1, alpha, beta);
chessPosition.unmakeMove();
if (white) {
alpha = (value > alpha) ? value : alpha; // max player (white)
} else {
beta = (value < beta) ? value : beta; // min player (black)
}
if (beta <= alpha) {
break;
}
}
}
if (!legal) {
// no legal moves
if (!chessPosition.isKingInCheck()) {
// stalemate, draw
return 0;
}
// checkmate, the player in turn loses
var mate = Chess.AI.PIECE_VALUES[Chess.Piece.KING];// - chessPosition.getMadeMoveCount(); TODO: punish longer games
return white ? -mate : mate;
}
// TODO: avoid the search above before checking this, just check for checkmate
if (chessPosition.isDraw()) {
// always assume the draw will be claimed
return 0;
}
return /** @type {number} */(white ? alpha : beta);
}
var bestMove = null;
var alpha = -Infinity;
var beta = Infinity;
var moves = sortMoves(chessPosition.getMoves(true));
for (var i = 0; i < moves.length; ++i) {
if (chessPosition.makeMove(moves[i])) {
var value = alphaBeta(chessPosition, 3, alpha, beta);
chessPosition.unmakeMove();
if (chessPosition.getTurnColor() === Chess.PieceColor.WHITE) {
// max player (white)
if (value > alpha) {
alpha = value;
bestMove = moves[i];
}
} else {
// min player (black)
if (value < beta) {
beta = value;
bestMove = moves[i];
}
}
// Notice that alpha is always smaller than beta here, because we only update one one them
// at the main level, the other stays infinite (+ or -)
}
}
window.console.log("Evaluations: " + evaluations + ", result move: " + bestMove.getString() + ", alpha: " + alpha + ", beta: " + beta);
return bestMove;
};

504
src/bitboard.js

@ -0,0 +1,504 @@
"use strict";
/**
* Chess.Bitboard is an unsigned 64 bit integer, each bit representing a boolean value on the corresponding chessboard square.
* The boolean values represent existence of a piece on the square.
* The 64 bit unsigned integer is implemented as combination of two 32 bit unsigned integers.
* @constructor
* @param {number} low Lower 32 bits of the 64 bit value
* @param {number} high Upper 32 bits of the 64 bit value
* TODO: test using three numbers here instead of two: 31 bit integers are faster than 32 bit ones in chrome (https://v8-io12.appspot.com/#35)
*/
Chess.Bitboard = function(low, high) {
/**
* Lower 32 bits of the 64 bit value
* @type {number}
*/
this.low = low >>> 0;
/**
* Upper 32 bits of the 64 bit value
* @type {number}
*/
this.high = high >>> 0;
};
/**
* @see http://goo.gl/pyzBq (Bit Twiddling Hacks)
* @see http://goo.gl/dnqDn (Bit-peeking bits of Javascript)
* @param {number} v 32 bit integer
* @return {number} 0-32 number of bits set in v
*/
Chess.Bitboard.popcnt32 = function(v) {
v >>>= 0;
v -= (v >>> 1) & 0x55555555;
v = (v & 0x33333333) + ((v >>> 2) & 0x33333333);
return ((v + (v >>> 4) & 0xF0F0F0F) * 0x1010101) >>> 24;
};
/**
* @param {number} v 32 bit integer
* @return {number} v with its lowest bit cleared
*/
Chess.Bitboard.popLowestBit32 = function (v) {
v >>>= 0;
return (v & (v - 1)) >>> 0;
};
/**
* @param {number} v 32 bit integer, non-zero. Undefined behavior if v is zero.
* @return {number} 0-31 Position of first set bit
*/
Chess.Bitboard.getLowestBitPosition32 = function(v) {
v >>>= 0;
return Chess.Bitboard.popcnt32((v & -v) - 1);
};
/** @return {number} 0-64 number of bits set in this Chess.Bitboard */
Chess.Bitboard.prototype.popcnt = function() {
return Chess.Bitboard.popcnt32(this.low) + Chess.Bitboard.popcnt32(this.high);
};
/**
* Clears the lowest set bit.
* @return {!Chess.Bitboard} this with the lowest bit cleared
*/
Chess.Bitboard.prototype.popLowestBit = function() {
if (this.low) {
this.low = Chess.Bitboard.popLowestBit32(this.low);
} else {
this.high = Chess.Bitboard.popLowestBit32(this.high);
}
return this;
};
/** @return {number} 0-63 position of the first set bit. Undefined behavior if this Chess.Bitboard is empty. */
Chess.Bitboard.prototype.getLowestBitPosition = function() {
if (this.low) {
return Chess.Bitboard.getLowestBitPosition32(this.low);
}
return 32 + Chess.Bitboard.getLowestBitPosition32(this.high);
};
/**
* Clears the lowest set bit and returns its position.
* @return {number} 0-63 position of the first set bit. Undefined behavior if this Chess.Bitboard is empty.
*/
Chess.Bitboard.prototype.extractLowestBitPosition = function() {
var index = this.getLowestBitPosition();
this.popLowestBit();
return index;
};
/** @return {boolean} true if all the bits in this Chess.Bitboard are zero */
Chess.Bitboard.prototype.isEmpty = function() {
return !this.low && !this.high;
};
/**
* @param {number} index 0-63
* @return {boolean} true if the bit at index is 0
*/
Chess.Bitboard.prototype.isClear = function(index) {
index >>>= 0;
if (index < 32) {
return !(this.low & (1 << index));
}
return !(this.high & (1 << (index - 32)));
};
/**
* @param {number} index 0-63
* @return {boolean} true if the bit at index is 1
*/
Chess.Bitboard.prototype.isSet = function(index) {
return !this.isClear(index);
};
/**
* @param {number} index 0-63
* @return {!Chess.Bitboard} this or 1 << index
*/
Chess.Bitboard.prototype.setBit = function(index) {
index >>>= 0;
if (index < 32) {
this.low = (this.low | (1 << index)) >>> 0;
} else {
this.high = (this.high | (1 << (index - 32))) >>> 0;
}
return this;
};
/**
* @param {number} index 0-63
* @return {!Chess.Bitboard} this and not 1 << index
*/
Chess.Bitboard.prototype.clearBit = function(index) {
index >>>= 0;
if (index < 32) {
this.low = (this.low & ~(1 << index)) >>> 0;
} else {
this.high = (this.high & ~(1 << (index - 32))) >>> 0;
}
return this;
};
/**
* @param {!Chess.Bitboard} other
* @return {!Chess.Bitboard} this and other
*/
Chess.Bitboard.prototype.and = function(other) {
this.low = (this.low & other.low) >>> 0;
this.high = (this.high & other.high) >>> 0;
return this;
};
/**
* @param {!Chess.Bitboard} other
* @return {!Chess.Bitboard} this and not other
*/
Chess.Bitboard.prototype.and_not = function(other) {
this.low = (this.low & ~other.low) >>> 0;
this.high = (this.high & ~other.high) >>> 0;
return this;
};
/**
* @param {!Chess.Bitboard} other
* @return {!Chess.Bitboard} this or other
*/
Chess.Bitboard.prototype.or = function(other) {
this.low = (this.low | other.low) >>> 0;
this.high = (this.high | other.high) >>> 0;
return this;
};
/**
* @param {!Chess.Bitboard} other
* @return {!Chess.Bitboard} this xor other
*/
Chess.Bitboard.prototype.xor = function(other) {
this.low = (this.low ^ other.low) >>> 0;
this.high = (this.high ^ other.high) >>> 0;
return this;
};
/** @return {!Chess.Bitboard} not this */
Chess.Bitboard.prototype.not = function() {
this.low = (~this.low) >>> 0;
this.high = (~this.high) >>> 0;
return this;
};
/**
* Shifts this Chess.Bitboard left v bits. Undefined behavior if v is not in 0-63.
* @param {number} v 0-63 number of bits to shift
* @return {!Chess.Bitboard} this << v
*/
Chess.Bitboard.prototype.shl = function(v) {
v >>>= 0;
if (v > 31) {
this.high = (this.low << (v - 32)) >>> 0;
this.low = 0 >>> 0;
} else if (v > 0) {
this.high = ((this.high << v) | (this.low >>> (32 - v))) >>> 0;
this.low = (this.low << v) >>> 0;
}
return this;
};
/**
* Shifts this Chess.Bitboard right v bits. Undefined behavior if v is not in 0-63.
* @param {number} v 0-63 number of bits to shift
* @return {!Chess.Bitboard} this >>> v
*/
Chess.Bitboard.prototype.shr = function(v) {
v >>>= 0;
if (v > 31) {
this.low = this.high >>> (v - 32);
this.high = 0 >>> 0;
} else if (v > 0) {
this.low = ((this.low >>> v) | (this.high << (32 - v))) >>> 0;
this.high >>>= v;
}
return this;
};
/**
* Shifts this Chess.Bitboard left v bits, where v can be negative for right shift.
* @param {number} v number of bits to shift
* @return {!Chess.Bitboard} this << v
*/
Chess.Bitboard.prototype.shiftLeft = function(v) {
if (v > 63 || v < -63) {
this.low = this.high = 0 >>> 0;
} else if (v > 0) {
this.shl(v);
} else if (v < 0) {
this.shr(-v);
}
return this;
};
/**
* @param {!Chess.Bitboard} other
* @return {boolean} 'this' equals 'other'
*/
Chess.Bitboard.prototype.isEqual = function(other) {
return this.low === other.low && this.high === other.high;
};
/** @return {!Chess.Bitboard} copy of this */
Chess.Bitboard.prototype.dup = function() {
return Chess.Bitboard.make(this.low, this.high);
};
/**
* @param {number} low Lower 32 bits of the 64 bit value
* @param {number} high Upper 32 bits of the 64 bit value
* @return {!Chess.Bitboard}
*/
Chess.Bitboard.make = function(low, high) {
return new Chess.Bitboard(low, high);
};
/** @return {!Chess.Bitboard} bitboard of all zeros */
Chess.Bitboard.makeZero = function() {
return Chess.Bitboard.make(0, 0);
};
/** @return {!Chess.Bitboard} bitboard of all ones */
Chess.Bitboard.makeOne = function() {
return Chess.Bitboard.make(0xFFFFFFFF, 0xFFFFFFFF);
};
/** @return {!Chess.Bitboard} bitboard of ones in light (white) squares, zeros in dark (black) squares */
Chess.Bitboard.makeLightSquares = function() {
return Chess.Bitboard.make(0x55AA55AA, 0x55AA55AA);
};
/** @return {!Chess.Bitboard} bitboard of ones in dark squares, zeros in light squares */
Chess.Bitboard.makeDarkSquares = function() {
return Chess.Bitboard.make(0xAA55AA55, 0xAA55AA55);
};
/**
* @param {number} file
* @return {!Chess.Bitboard} bitboard of ones in file, zeros elsewhere
*/
Chess.Bitboard.makeFile = function(file) {
return Chess.Bitboard.make(0x01010101, 0x01010101).shl(file);
};
/** @return {!Array.<!Chess.Bitboard>} bitboard for each file */
Chess.Bitboard.makeFiles = function() {
var b = [];
for (var i = 0; i < 8; ++i) {
b.push(Chess.Bitboard.makeFile(i));
}
return b;
};
/**
* @param {number} rank
* @return {!Chess.Bitboard} bitboard of ones in rank, zeros elsewhere
*/
Chess.Bitboard.makeRank = function(rank) {
return Chess.Bitboard.make(0xFF, 0).shl(rank * 8);
};
/** @return {!Array.<!Chess.Bitboard>} bitboard for each rank */
Chess.Bitboard.makeRanks = function() {
var b = [];
for (var i = 0; i < 8; ++i) {
b.push(Chess.Bitboard.makeRank(i));
}
return b;
};
/**
* @param {number} index 0-63
* @return {!Chess.Bitboard} bitboard of 1 at index, zero elsewhere
*/
Chess.Bitboard.makeIndex = function(index) {
return Chess.Bitboard.makeZero().setBit(index);
};
/** @return {!Array.<!Chess.Bitboard>} bitboard for each index */
Chess.Bitboard.makeIndices = function() {
var b = [];
for (var i = 0; i < 64; ++i) {
b.push(Chess.Bitboard.makeIndex(i));
}
return b;
};
/**
* 0 diagonal is the main diagonal, positive numbers are superdiagonals, negative numbers subdiagonals.
* @param {number} diagonal (-7)-7
* @return {!Chess.Bitboard} bitboard with ones on diagonal, zeros elsewhere
*/
Chess.Bitboard.makeDiagonal = function(diagonal) {
return Chess.Bitboard.make(0x10204080, 0x01020408).and(Chess.Bitboard.makeOne().shiftLeft(diagonal * 8)).shiftLeft(diagonal);
};
/** @return {!Array.<!Chess.Bitboard>} bitboard for each diagonal */
Chess.Bitboard.makeDiagonals = function() {
var b = [];
for (var i = -7; i < 8; ++i) {
b.push(Chess.Bitboard.makeDiagonal(i));
}
return b;
};
/**
* 0 diagonal is the main antidiagonal, positive numbers are subantidiagonals (below the main antidiagonal on the chessboard), negative numbers superantidiagonals.
* @param {number} antidiagonal (-7)-7
* @return {!Chess.Bitboard} bitboard with ones on antidiagonal, zeros elsewhere
*/
Chess.Bitboard.makeAntidiagonal = function(antidiagonal) {
return Chess.Bitboard.make(0x08040201, 0x80402010).and(Chess.Bitboard.makeOne().shiftLeft(-antidiagonal * 8)).shiftLeft(antidiagonal);
};
/** @return {!Array.<!Chess.Bitboard>} bitboard for each antidiagonal */
Chess.Bitboard.makeAntidiagonals = function() {
var b = [];
for (var i = -7; i < 8; ++i) {
b.push(Chess.Bitboard.makeAntidiagonal(i));
}
return b;
};
/**
* @see http://goo.gl/MRA5s (Knight Pattern)
* @param {number} index 0-63 chessboard square of the knight
* @return {!Chess.Bitboard} knight target squares
*/
Chess.Bitboard.makeKnightMovement = function(index) {
var b = Chess.Bitboard.makeZero().setBit(index);
var l1 = b.dup().shr(1).and_not(Chess.Bitboard.FILES[7]);
var l2 = b.dup().shr(2).and_not(Chess.Bitboard.FILES[7]).and_not(Chess.Bitboard.FILES[6]);
var r1 = b.dup().shl(1).and_not(Chess.Bitboard.FILES[0]);
var r2 = b.dup().shl(2).and_not(Chess.Bitboard.FILES[0]).and_not(Chess.Bitboard.FILES[1]);
var v1 = l2.or(r2);
var v2 = l1.or(r1);
return v1.dup().shl(8).or(v1.shr(8)).or(v2.dup().shl(16)).or(v2.shr(16));
};
/** @return {!Array.<!Chess.Bitboard>} bitboard for knight movement from each square */
Chess.Bitboard.makeKnightMovements = function() {
var b = [];
for (var i = 0; i < 64; ++i) {
b.push(Chess.Bitboard.makeKnightMovement(i));
}
return b;
};
/**
* @param {number} index 0-63 chessboard square of the king
* @return {!Chess.Bitboard} king target squares
*/
Chess.Bitboard.makeKingMovement = function(index) {
var b = Chess.Bitboard.makeZero().setBit(index);
var c = b.dup().shr(1).and_not(Chess.Bitboard.FILES[7]).or(b.dup().shl(1).and_not(Chess.Bitboard.FILES[0]));
var u = b.dup().or(c).shr(8);
var d = b.dup().or(c).shl(8);
return c.or(u).or(d);
};
/** @return {!Array.<!Chess.Bitboard>} bitboard for king movement from each square */
Chess.Bitboard.makeKingMovements = function() {
var b = [];
for (var i = 0; i < 64; ++i) {
b.push(Chess.Bitboard.makeKingMovement(i));
}
return b;
};
/**
* Chess.Bitboard of all zeros
* @const
* @type {!Chess.Bitboard}
*/
Chess.Bitboard.ZERO = Chess.Bitboard.makeZero();
/**
* Chess.Bitboard of all ones
* @const
* @type {!Chess.Bitboard}
*/
Chess.Bitboard.ONE = Chess.Bitboard.makeOne();
/**
* Chess.Bitboard of ones in light squares, zeros in dark squares
* @const
* @type {!Chess.Bitboard}
*/
Chess.Bitboard.LIGHT_SQUARES = Chess.Bitboard.makeLightSquares();
/**
* Chess.Bitboard of ones in dark squares, zeros in light squares
* @const
* @type {!Chess.Bitboard}
*/
Chess.Bitboard.DARK_SQUARES = Chess.Bitboard.makeDarkSquares();
/**
* Chess.Bitboards ones in corresponding file, zeros elsewhere
* @const
* @type {!Array.<!Chess.Bitboard>}
*/
Chess.Bitboard.FILES = Chess.Bitboard.makeFiles();
/**
* Chess.Bitboards ones in corresponding rank, zeros elsewhere
* @const
* @type {!Array.<!Chess.Bitboard>}
*/
Chess.Bitboard.RANKS = Chess.Bitboard.makeRanks();
/**
* Chess.Bitboards ones in corresponding diagonal, zeros elsewhere. Chess.Bitboard.DIAGONALS[7] = main diagonal, 0-6 = subdiagonals, 8-15 = superdiagonals
* @const
* @type {!Array.<!Chess.Bitboard>}
*/
Chess.Bitboard.DIAGONALS = Chess.Bitboard.makeDiagonals();
/**
* Chess.Bitboards ones in corresponding antidiagonal, zeros elsewhere. Chess.Bitboard.ANTIDIAGONALS[7] = main antidiagonal, 0-6 = superantidiagonals, 8-15 = subantidiagonals
* @const
* @type {!Array.<!Chess.Bitboard>}
*/
Chess.Bitboard.ANTIDIAGONALS = Chess.Bitboard.makeAntidiagonals();
/**
* 64 bitboards, one per chessboard square, for positions where knights can move from the corresponding square.
* @const
* @type {!Array.<!Chess.Bitboard>}
*/
Chess.Bitboard.KNIGHT_MOVEMENTS = Chess.Bitboard.makeKnightMovements();
/**
* 64 bitboards, one per chessboard square, for positions where kings can move from the corresponding square.
* @const
* @type {!Array.<!Chess.Bitboard>}
*/
Chess.Bitboard.KING_MOVEMENTS = Chess.Bitboard.makeKingMovements();

237
src/chess.css

@ -0,0 +1,237 @@
body {
width: 960px;
margin-left: auto;
margin-right: auto;
text-align: left;
font-size: 12pt;
font-family: Arial, sans-serif;
color: black;
background: #303030 repeat fixed url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAAFUlEQVQImWNgYGD4D8UMDEgMFIH/AGu7Bfvz8g82AAAAAElFTkSuQmCC");
}
h1 {
text-transform: lowercase;
text-shadow: 0 1px 0 white;
letter-spacing: 0.5ex;
font-weight: bold;
font-size: 100%;
background: white;
text-shadow: 0 0 5px black;
margin: 0;
padding: 14px;
border-top-left-radius: 14px;
border-top-right-radius: 14px;
}
#content {
background: white;
padding: 14px 40px;
}
#help {
cursor: help;
}
#dim {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.6);
z-index: 10;
display: none;
cursor: wait;
}
#chessboard {
width: 700px;
height: 700px;
float: left;
padding: 0;
}
#moves {
border: 1px solid silver;
border-radius: 14px;
width: 138px;
/* max-width: 168px; */
padding: 5px;
padding-left: 15px;
float: right;
overflow: auto;
height: 688px;
}
#moves button {
width: 100%;
}
#clear {
clear: both;
width: 0;
height: 0;
}
#footer {
background: white;
margin: 0;
padding: 14px;
border-bottom-left-radius: 14px;
border-bottom-right-radius: 14px;
}
#chessboard table {
border-spacing: 0;
border-collapse: collapse;
border: none;
cursor: default;
/* see http://goo.gl/1dTy7 (css rule to disable text selection highlighting) */
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
#chessboard table tr th, #chessboard table tr td {
padding: 0;
margin: 0;
text-align: center;
vertical-align: middle;
}
#chessboard table tr th {
background: silver;
font-size: small;
font-weight: normal;
}
#chessboard table tr th.file {
width: 80px;
height: 30px;
}
#chessboard table tr th.rank {
width: 30px;
height: 80px;
}
#chessboard table tr:first-child th:first-child {
border-top-left-radius: 14px;
}
#chessboard table tr:first-child th:last-child {
border-top-right-radius: 14px;
}
#chessboard table tr:last-child th:first-child {
border-bottom-left-radius: 14px;
}
#chessboard table tr:last-child th:last-child {
border-bottom-right-radius: 14px;
}
#chessboard table tr td {
width: 80px;
height: 80px;
}
#chessboard table tr td.light {
text-shadow: 0 0 10px black;
background: #E0E0E0;
background: -moz-linear-gradient(-45deg, #ffffff 0%, #c0c0c0 100%);
background: -webkit-gradient(linear, left top, right bottom, color-stop(0%, #ffffff), color-stop(100%, #c0c0c0));
background: -webkit-linear-gradient(-45deg, #ffffff 0%, #c0c0c0 100%);
background: -o-linear-gradient(-45deg, #ffffff 0%, #c0c0c0 100%);
background: -ms-linear-gradient(-45deg, #ffffff 0%, #c0c0c0 100%);
background: linear-gradient(135deg, white, silver);
}
#chessboard table tr td.dark {
text-shadow: 0 0 10px white;
background: #404040;
background: -moz-linear-gradient(-45deg, #808080 0%, #000000 100%);
background: -webkit-gradient(linear, left top, right bottom, color-stop(0%, #808080), color-stop(100%, #000000));
background: -webkit-linear-gradient(-45deg, #808080 0%, #000000 100%);
background: -o-linear-gradient(-45deg, #808080 0%, #000000 100%);
background: -ms-linear-gradient(-45deg, #808080 0%, #000000 100%);
background: linear-gradient(135deg, gray, black);
}
#chessboard table tr td div {
font-size: 50px;
}
#chessboard table tr td.white {
color: white;
}
#chessboard table tr td.black {
color: black;
}
#chessboard table tr td.from {
font-weight: bold;
}
#chessboard table tr td.to {
box-shadow: inset 0 0 10px 1px green;
}
#chessboard table tr td.to.capture {
box-shadow: inset 0 0 10px 1px red;
}
#chessboard table tr td.to.en-passant:after {
color: red;
content: "e.p.";
}
#chessboard table tr td.to.king-castle:after {
color: magenta;
content: "0-0";
}
#chessboard table tr td.to.queen-castle:after {
color: magenta;
content: "0-0-0";
}
#chessboard table tr td.to.positional:after, #chessboard table tr td.to.double-push:after {
color: gray;
content: "\2022";
}
#chessboard table tr td.turn {
cursor: move;
}
#chessboard table tr td div.turn:not(.can-move) {
cursor: not-allowed;
}
#chessboard table tr td.last-move {
box-shadow: inset 0 0 10px 1px yellow;
}
#moves a {
color: gray;
font-size: 8pt;
text-decoration: none;
}
#moves a.cannot {
color: silver;
pointer-events: none;
cursor: default;
}

36
src/chess.html

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta charset="utf-8">
<title>Chess</title>
<link rel="stylesheet" href="http://code.jquery.com/ui/1.10.0/themes/base/jquery-ui.css">
<link rel="stylesheet" href="chess.css">
<link rel="shortcut icon" href="chess.ico">
</head>
<body>
<h1>Chess</h1>
<div id="content">
<div id="chessboard" role="main"></div>
<div id="moves"></div>
<div id="clear"></div>
<div id="help">
<ul>
<li>Moves can be made by dragging the pieces on the board, or by clicking the move links in the right panel.</li>
<li>Pawns dragged to the last rank are promoted to queens. Use the move links in the right panel to underpromote.</li>
<li>Castle by moving the king two squares towards a rook. The rook moves automatically.</li>
</ul>
</div>
</div>
<div id="footer"></div>
<div id="dim"></div>
<script src="http://code.jquery.com/jquery-1.9.0.min.js"></script>
<script src="http://code.jquery.com/ui/1.10.0/jquery-ui.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jqueryui-touch-punch/0.2.2/jquery.ui.touch-punch.min.js"></script>
<!--[if lt IE 9]><script src="http://cdnjs.cloudflare.com/ajax/libs/augment.js/1.0.0/augment.min.js"></script><![endif]-->
<script src="chess.include.js"></script>
<script>
$(makeChessGame);
</script>
</body>
</html>

BIN
src/chess.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

15
src/chess.include.js

@ -0,0 +1,15 @@
"use strict";
/** @param {string} file */
function include(file) {
document.write('<script type="text/javascript" src="' + file + '"></script>');
}
include("chess.js");
include("bitboard.js");
include("zobrist.js");
include("move.js");
include("position.js");
include("parser.js");
include("ai.js");
include("ui.js");

173
src/chess.js

@ -0,0 +1,173 @@
"use strict";
/**
* Chess namespace, constants and utility functions.
* @constructor
*/
function Chess() {
}
/**
* Number of ranks (rows) in a chess board.
* Notice that we store a rank as 0-7 and display it as 1-8.
* @const
* @type {number}
*/
Chess.RANKS = 8;
/**
* @const
* @type {number}
*/
Chess.LAST_RANK = Chess.RANKS - 1;
/**
* Number of files (columns) in a chess board
* Notice that we store a file as 0-7 and display it as a-h.
* @const
* @type {number}
*
*/
Chess.FILES = 8;
/**
* @const
* @type {number}
*/
Chess.LAST_FILE = Chess.FILES - 1;
/**
* @const
* @type {string}
*/
Chess.FILE_CHARACTERS = "abcdefgh";
/**
* @const
* @type {string}
*/
Chess.RANK_CHARACTERS = "12345678";
/** @enum {number} */
Chess.Piece = {
PAWN: 0,
KNIGHT: 1,
BISHOP: 2,
ROOK: 3,
QUEEN: 4,
KING: 5
};
/** @enum {number} */
Chess.PieceColor = {
WHITE: 0,
BLACK: 1
};
/**
* @const
* @type {!Array.<string>}
*/
Chess.PIECE_NAMES = [ "pawn", "knight", "bishop", "rook", "queen", "king" ];
/**
* @const
* @type {string}
*/
Chess.PIECE_ALGEBRAIC_NAMES = " NBRQK";
/**
* @const
* @type {string}
* @see http://goo.gl/OHlAI
*/
Chess.PIECE_CHARACTERS = "\u2659\u265F\u2658\u265E\u2657\u265D\u2656\u265C\u2655\u265B\u2654\u265A";
/**
* @param {number} index 0-63
* @return {number} rank 0-7
*/
Chess.getRank = function(index) {
return index >>> 3;
};
/**
* @param {number} index 0-63
* @return {number} file 0-7
*/
Chess.getFile = function(index) {
return index & 7;
};
/**
* @param {number} rank
* @param {number} file
* @return {boolean} true if rank >= 0 && rank < 8 && file >= 0 && file < 8
*/
Chess.isInsideBoard = function(rank, file) {
return !((rank | file) & ~7);
};
/**
* Least significant file index
* @see http://goo.gl/9frpl
* @param {number} rank 0-7
* @param {number} file 0-7
* @return {number} 0-63
*/
Chess.getIndex = function(rank, file) {
return file + rank * Chess.FILES;
};
/**
* @param {number} rank 0-7
* @param {number} file 0-7
* @return {boolean} true if the Chess square at rank, file is light
*/
Chess.isLight = function(rank, file) {
return !!((rank + file) % 2);
};
/**
* @param {number} rank 0-7
* @param {number} file 0-7
* @return {string} a1-h8
*/
Chess.getAlgebraic = function(rank, file) {
return Chess.FILE_CHARACTERS[file] + Chess.RANK_CHARACTERS[rank];
};
/**
* @param {string} algebraic a1-h8
* @return {number} index 0-63
*/
Chess.getIndexFromAlgebraic = function(algebraic) {
var file = Chess.FILE_CHARACTERS.indexOf(algebraic[0]);
var rank = Chess.RANK_CHARACTERS.indexOf(algebraic[1]);
return Chess.getIndex(rank, file);
};
/**
* @param {number} index 0-63
* @return {string} a1-h8
*/
Chess.getAlgebraicFromIndex = function(index) {
return Chess.getAlgebraic(Chess.getRank(index), Chess.getFile(index));
};
/**
* @param {!Chess.Piece} piece
* @param {!Chess.PieceColor} color
* @return {string} A Unicode character corresponding to the piece and color
*/
Chess.getPieceCharacter = function(piece, color) {
return Chess.PIECE_CHARACTERS.charAt(piece * 2 + color);
};
/**
* @param {!Chess.PieceColor} color
* @return {!Chess.PieceColor}
*/
Chess.getOtherPieceColor = function(color) {
return /** @type {!Chess.PieceColor} */(color ^ 1);
};

117
src/move.js

@ -0,0 +1,117 @@
"use strict";
/**
* Representation of a chess move; a piece moves from square to another, possibly capturing another piece in the process.
* @constructor
* @param {number} from 0-63
* @param {number} to 0-63
* @param {!Chess.Move.Kind} kind
* @param {!Chess.Piece} piece moving piece
* @param {?Chess.Piece} capturedPiece N.B. null is stored as pawn
*/
Chess.Move = function(from, to, kind, piece, capturedPiece) {
/**
* An integer value containing the source and destination square indices, the move kind, the moving piece, etc.
* @type {number}
*/
this.value = (to & 0x3F) | ((from & 0x3F) << 6) | ((kind & 0xF) << 12) | ((piece & 0x7) << 16) | (((capturedPiece | 0) & 0x7) << 19);
};
/**
* @enum {number}
* @see http://goo.gl/z5Rpl (Encoding Moves)
*/
Chess.Move.Kind = {
POSITIONAL: 0,
DOUBLE_PAWN_PUSH: 1,
KING_CASTLE: 2, // kingside castle
QUEEN_CASTLE: 3, // queenside castle
CAPTURE: 4,
EN_PASSANT_CAPTURE: 5,
KNIGHT_PROMOTION: 8,
BISHOP_PROMOTION: 9,
ROOK_PROMOTION: 10,
QUEEN_PROMOTION: 11,
KNIGHT_PROMOTION_CAPTURE: 12,
BISHOP_PROMOTION_CAPTURE: 13,
ROOK_PROMOTION_CAPTURE: 14,
QUEEN_PROMOTION_CAPTURE: 15
};
/** @return {number} 0-63 */
Chess.Move.prototype.getTo = function() {
return this.value & 0x3F;
};
/** @return {number} 0-63 */
Chess.Move.prototype.getFrom = function() {
return (this.value >>> 6) & 0x3F;
};
/** @return {!Chess.Move.Kind} */
Chess.Move.prototype.getKind = function() {
return /** @type {!Chess.Move.Kind} */ ((this.value >>> 12) & 0xF);
};
/** @return {!Chess.Piece} */
Chess.Move.prototype.getPiece = function() {
return /** @type {!Chess.Piece} */ ((this.value >>> 16) & 0x7);
};
/** @return {boolean} */
Chess.Move.prototype.isCapture = function() {
return !!(this.getKind() & 4);
};
/**
* @return {!Chess.Piece}
*/
Chess.Move.prototype.getCapturedPiece = function(piece) {
return /** @type {!Chess.Piece} */ ((this.value >>> 19) & 0x7);
};
/** @return {boolean} */
Chess.Move.prototype.isPromotion = function() {
return !!(this.getKind() & 8);
};
/** @return {boolean} */
Chess.Move.prototype.isCastle = function() {
return this.getKind() === Chess.Move.Kind.KING_CASTLE || this.getKind() === Chess.Move.Kind.QUEEN_CASTLE;
};
/** @return {!Chess.Piece} */
Chess.Move.prototype.getPromotedPiece = function() {
if (this.isPromotion()) {
return /** @type {!Chess.Piece} */ (Chess.Piece.KNIGHT + (this.getKind() & 3));
}
return Chess.Piece.PAWN;
};
/** @return {number} 0-63 */
Chess.Move.prototype.getCaptureSquare = function() {
if (this.getKind() !== Chess.Move.Kind.EN_PASSANT_CAPTURE) {
return this.getTo();
}
return this.getTo() + ((this.getFrom() < this.getTo()) ? -Chess.FILES : Chess.FILES);
};
/**
* @return {string} long algebraic notation
* @see http://goo.gl/h8hhf (Long algebraic notation)
* We don't require the chess position here, so shorter notation is not used, and check, checkmate and game end are not reported.
*/
Chess.Move.prototype.getString = function() {
if (!this.isCastle()) {
return Chess.PIECE_ALGEBRAIC_NAMES.charAt(this.getPiece()) +
Chess.getAlgebraicFromIndex(this.getFrom()) +
(this.isCapture() ? "x" : "-") +
Chess.getAlgebraicFromIndex(this.getTo()) +
((this.getKind() === Chess.Move.Kind.EN_PASSANT_CAPTURE) ? "e.p." : "") +
(this.isPromotion() ? Chess.PIECE_ALGEBRAIC_NAMES.charAt(this.getPromotedPiece()) : "");
}
return "0-0" + ((this.getKind() === Chess.Move.Kind.QUEEN_CASTLE) ? "-0" : "");
};

125
src/parser.js

@ -0,0 +1,125 @@
"use strict";
/**
* Parser for chess moves in algebraic notation, e.g. "1. e4 c5 (comment here) 2. Nf3 d6".
* The idea is to parse a list of moves starting from the initial state, yielding a valid chess position.
* @constructor
* @see http://goo.gl/B39TC (Algebraic notation)
*/
Chess.Parser = function() {
};
/**
* Cleans uninteresting parts of a move string
* @param {string} text
* @return {string}
* @see http://goo.gl/uAijB (Dashes and hyphens)
*/
Chess.Parser.clean = function(text) {
text = text.replace(/[\r\n\t]/gm, " "); // normalize whitespace to spaces
text = text.replace(/[\u002D\u05BE\u1806\u2010\u2011\u2012\u2013\u2014\u2015\u207B\u208B\u2212\u2E3A\u2E3B\uFE58\uFE63\uFF0D]/g, "-"); // normalize dashes
while (true) { // remove comments, i.e. (nested) parentheses and characters between them
var replaced = text.replace(/\([^()]*\)/g, "");
if (replaced === text) {
break;
}
text = replaced;
}
text = text.replace(/[^ a-z0-9.=:\u00BD-]/gi, " "); // only keep interesting characters
text = text.replace(/ +/g, " "); // normalize whitespace to one space
return text;
};
/**
* @param {!Chess.Position} chessPosition
* @param {string} text
* @return {?Array.<!Chess.Move>}
*/
Chess.Parser.parseOneMove = function(chessPosition, text) {
var legalMoves = chessPosition.getMoves();
var castling = text.match(/0-0(?:-0)?|O-O(?:-O)?/i);
if (castling) {
var kind = (castling[0].length === 3) ? Chess.Move.Kind.KING_CASTLE : Chess.Move.Kind.QUEEN_CASTLE;
return legalMoves.filter(/** @param {!Chess.Move} move */function(move) { return move.getKind() === kind; });
}
var move = text.match(/([NBRQK])?([a-h])?([1-8])?([x:])?([a-h])([1-8])?(?:[=(]([NBRQ]))?/i);
if (move) {
var piece = move[1];
var fromFile = move[2];
var fromRank = move[3];
var capture = move[4];
var toFile = move[5];
var toRank = move[6];
var promotedPiece = move[7];
return legalMoves.filter(/** @param {!Chess.Move} move */function(move) {
if (piece !== undefined && Chess.PIECE_ALGEBRAIC_NAMES[move.getPiece()] !== piece) {
return false;
}
if (piece === undefined && move.getPiece() !== Chess.Piece.PAWN) {
return false;
}
if (fromFile !== undefined && Chess.FILE_CHARACTERS[Chess.getFile(move.getFrom())] !== fromFile) {
return false;
}
if (fromRank !== undefined && Chess.RANK_CHARACTERS[Chess.getRank(move.getFrom())] !== fromRank) {
return false;
}
if (capture !== undefined && !move.isCapture()) {
return false;
}
if (toFile !== undefined && Chess.FILE_CHARACTERS[Chess.getFile(move.getTo())] !== toFile) {
return false;
}
if (toRank !== undefined && Chess.RANK_CHARACTERS[Chess.getRank(move.getTo())] !== toRank) {
return false;
}
if (promotedPiece !== undefined && Chess.PIECE_ALGEBRAIC_NAMES[move.getPromotedPiece()] !== promotedPiece) {
return false;
}
return true;
});
}
return null;
};
/**
* @param {string} text
* @return {!Chess.Position}
* @throws {Error}
*/
Chess.Parser.parseMoves = function(text) {
var chessPosition = new Chess.Position;
Chess.Parser.clean(text).split(" ").every(/** @param {string} moveText */function(moveText) {
var moveNumber = moveText.match(/\d+\./);
if (moveNumber) {
return true;
}
var gameOver = moveText.match(/1-0|0-1|\u00BD-\u00BD/);
if (gameOver) {
return false;
}
var moves = Chess.Parser.parseOneMove(chessPosition, moveText);
if (!moves || moves.length !== 1) {
throw new Error("Parse error in '" + moveText + "'");
}
chessPosition.makeMove(moves[0]);
return true;
});
return chessPosition;
};

940
src/position.js

@ -0,0 +1,940 @@
"use strict";
/**
* Chess.Position contains current piece positions, player turn, etc; the game state.
* It can generate a list of possible moves in the current game state, and apply moves to change state.
* @constructor
*/
Chess.Position = function() {
/**
* @type {!Chess.Zobrist}
*/
this.hashKey = new Chess.Zobrist(0, 0);
/**
* Bitboards for each piece type, and for any white and black pieces.
* TODO: replace with a Uin32Array or a straight array of numbers? (then implement 64 bit bitboard operations on top of that)
* TODO: store kings as two indices? (there can only be one king per color)
* @type {!Array.<!Chess.Bitboard>}
*/
this.bitboards = [
Chess.Bitboard.RANKS[1].dup().or(Chess.Bitboard.RANKS[6]), // pawns
Chess.Bitboard.makeIndex(1).or(Chess.Bitboard.makeIndex(6)).or(Chess.Bitboard.makeIndex(57)).or(Chess.Bitboard.makeIndex(62)), // knights
Chess.Bitboard.makeIndex(2).or(Chess.Bitboard.makeIndex(5)).or(Chess.Bitboard.makeIndex(58)).or(Chess.Bitboard.makeIndex(61)), // bishops
Chess.Bitboard.makeIndex(0).or(Chess.Bitboard.makeIndex(7)).or(Chess.Bitboard.makeIndex(56)).or(Chess.Bitboard.makeIndex(63)), // rooks
Chess.Bitboard.makeIndex(3).or(Chess.Bitboard.makeIndex(59)), // queens
Chess.Bitboard.makeIndex(4).or(Chess.Bitboard.makeIndex(60)), // kings
Chess.Bitboard.RANKS[0].dup().or(Chess.Bitboard.RANKS[1]), // white pieces
Chess.Bitboard.RANKS[6].dup().or(Chess.Bitboard.RANKS[7]) // black pieces
];
/**
* 64 entry lookup table, holds a piece or null for each board square
* @type {!Array.<?Chess.Piece>}
*/
this.pieces = [];
/**
* @type {!Chess.PieceColor}
*/
this.turn = Chess.PieceColor.WHITE;
/**
* 1st bit: white can castle kingside
* 2nd bit: black can castle kingside
* 3rd bit: white can castle queenside
* 4th bit: black can castle queenside
* @type {number} 0-15
*/
this.castlingRights = 15;
/**
* @type {number} 0-63
*/
this.enPassantSquare = -1;
/**
* @type {number}
* @see http://goo.gl/xGY6o (Fifty-move rule)
*/
this.halfmoveClock = 0;
/**
* @type {!Array.<!Chess.Move>}
*/
this.madeMoves = [];
/**
* @type {!Array.<number>}
*/
this.irreversibleHistory = [];
this.fillPiecesFromBitboards();
this.updateHashKey();
// TODO: repetition rule data: array or hash of Zobrist keys
// TODO: checking pieces?
// TODO: separate occupied squares bitboard?
// TODO: store kings as indices instead of bitboards?
};
/**
* Chess.Bitboard for squares with white pieces in them
* @const
* @type {number}
*/
Chess.Position.ANY_WHITE = Chess.Piece.KING + 1;
/**
* Chess.Bitboard for squares with black pieces in them
* @const
* @type {number}
*/
Chess.Position.ANY_BLACK = Chess.Position.ANY_WHITE + 1;
/**
* Initial rook indices
* @const
* @type {!Array.<number>}
*/
Chess.Position.ROOK_INDICES = [7, 63, 0, 56];
/**
* Bitmasks for avoiding sliding piece wrapping.
* @const
* @type {!Array.<!Chess.Bitboard>}
*/
Chess.Position.SLIDING_MASKS = [Chess.Bitboard.makeFile(Chess.LAST_FILE).not(), Chess.Bitboard.ONE, Chess.Bitboard.makeFile(0).not()];
/** @enum {number} */
Chess.Position.Status = {
NORMAL: 0,
CHECKMATE: 1,
STALEMATE_DRAW: 2,
FIFTY_MOVE_RULE_DRAW: 3,
THREEFOLD_REPETITION_RULE_DRAW: 4,
INSUFFICIENT_MATERIAL_DRAW: 5
};
/**
* Perft: performance test
* @param {number} depth how many half-moves to make
* @param {Chess.Position=} chessPosition start position
* @return {number} how many leaf nodes does the game tree have at the depth
*/
Chess.Position.perft = function(depth, chessPosition) {
if (!depth) {
return 1;
}
if (!chessPosition) {
chessPosition = new Chess.Position;
}
/** @type {number} */
var nodes = 0;
chessPosition.getMoves(true).forEach(/** @param {!Chess.Move} move */ function(move) {
if (chessPosition.makeMove(move)) {
nodes += Chess.Position.perft(depth - 1, chessPosition);
chessPosition.unmakeMove();
}
});
return nodes;
};
/**
* @param {boolean=} pseudoLegal true: also return moves that may not be possible (leave king in check)
* @param {boolean=} onlyCaptures
* @return {!Array.<!Chess.Move>} Possible moves in this chess position
*/
Chess.Position.prototype.getMoves = function(pseudoLegal, onlyCaptures) {
var moves = this.generateMoves(!!onlyCaptures);
return pseudoLegal ? moves : moves.filter(Chess.Position.prototype.isMoveLegal, this);
};
/**
* @param {!Chess.PieceColor} color Chess.PieceColor.WHITE or Chess.PieceColor.BLACK
* @return {!Chess.Bitboard} Squares occupied by any piece of the specified color
*/
Chess.Position.prototype.getColorBitboard = function(color) {
return this.bitboards[Chess.Position.ANY_WHITE + color];
};
/**
* @param {!Chess.Piece} piece
* @return {!Chess.Bitboard} Bits set where specified piece exists
*/
Chess.Position.prototype.getPieceBitboard = function(piece) {
return this.bitboards[piece];
};
/**
* @param {!Chess.Piece} piece
* @param {!Chess.PieceColor} color
* @return {!Chess.Bitboard} Bits set where specified piece with the specified color exists
*/
Chess.Position.prototype.getPieceColorBitboard = function(piece, color) {
return this.bitboards[piece].dup().and(this.getColorBitboard(color));
};
/**
* @param {!Chess.PieceColor} color
* @return {number} 0-63
*/
Chess.Position.prototype.getKingPosition = function(color) {
return this.getPieceColorBitboard(Chess.Piece.KING, color).getLowestBitPosition();
};
/** @return {!Chess.Bitboard} Squares occupied by any piece */
Chess.Position.prototype.getOccupiedBitboard = function() {
return this.bitboards[Chess.Position.ANY_WHITE].dup().or(this.bitboards[Chess.Position.ANY_BLACK]);
};
/** @return {!Chess.Bitboard} Empty squares */
Chess.Position.prototype.getEmptyBitboard = function() {
return this.getOccupiedBitboard().not();
};
/** @return {!Chess.PieceColor} */
Chess.Position.prototype.getTurnColor = function() {
return this.turn;
};
/**
* @param {number} index 0-63
* @return {?Chess.Piece}
*/
Chess.Position.prototype.findPieceAtOrNull = function(index) {
for (var piece = Chess.Piece.PAWN; piece <= Chess.Piece.KING; ++piece) {
if (this.getPieceBitboard(piece).isSet(index)) {
return piece;
}
}
return null;
};
/**
* @param {number} index 0-63
* @return {?Chess.Piece}
*/
Chess.Position.prototype.getPieceAtOrNull = function(index) {
return this.pieces[index];
};
/** Fills the piece lookup table from bitboards */
Chess.Position.prototype.fillPiecesFromBitboards = function() {
this.pieces.length = 0;
for (var index = 0; index < 64; ++index) {
this.pieces.push(this.findPieceAtOrNull(index));
}
};
/** Updates the hash key from turn, bitboards, castling rights and en passant square. Halfmove clock is not part of the hash */
Chess.Position.prototype.updateHashKey = function() {
this.hashKey = new Chess.Zobrist(0, 0);
if (this.getTurnColor()) {
this.hashKey.updateTurn();
}
for (var color = Chess.PieceColor.WHITE; color <= Chess.PieceColor.BLACK; ++color) {
for (var piece = Chess.Piece.PAWN; piece <= Chess.Piece.KING; ++piece) {
this.hashKey.updatePieceColorBitboard(piece, color, this.getPieceColorBitboard(piece, color));
}
}
this.hashKey.updateCastlingRights(this.castlingRights);
this.hashKey.updateEnPassantSquare(this.enPassantSquare);
};
/**
* @return {boolean}
*/
Chess.Position.prototype.isKingInCheck = function() {
return this.isAttacked(Chess.getOtherPieceColor(this.getTurnColor()), this.getKingPosition(this.getTurnColor()));
};
/**
* @param {!Chess.PieceColor} color white = attacks by white pieces
* @param {!Chess.Bitboard} pawns
* @return {!Chess.Bitboard}
* N.B. no en passant attacks
*/
Chess.Position.makePawnAttackMask = function(color, pawns) {
var white = (color === Chess.PieceColor.WHITE);
var attacks1 = pawns.dup().and_not(Chess.Bitboard.FILES[0]).shiftLeft(white ? 7 : -9);
var attacks2 = pawns.dup().and_not(Chess.Bitboard.FILES[Chess.LAST_FILE]).shiftLeft(white ? 9 : -7);
return attacks1.or(attacks2);
};
/**
* @param {!Chess.Bitboard} fromBB
* @param {!Chess.Bitboard} occupied
* @param {number} rankDirection
* @param {number} fileDirection
* @return {!Chess.Bitboard}
* TODO: Kogge-Stone: http://chessprogramming.wikispaces.com/Kogge-Stone+Algorithm
*/
Chess.Position.makeSlidingAttackMask = function(fromBB, occupied, rankDirection, fileDirection) {
var bb = Chess.Bitboard.makeZero();
var direction = rankDirection * Chess.FILES + fileDirection;
var mask = Chess.Position.SLIDING_MASKS[1 + fileDirection];
for (fromBB.shiftLeft(direction); !fromBB.and(mask).isEmpty(); fromBB.and_not(occupied).shiftLeft(direction)) {
bb.or(fromBB);
}
return bb;
};
/**
* @param {!Chess.Bitboard} fromBB
* @param {!Chess.Bitboard} occupied
* @return {!Chess.Bitboard}
*/
Chess.Position.makeBishopAttackMask = function(fromBB, occupied) {
return Chess.Position.makeSlidingAttackMask(fromBB.dup(), occupied, 1, 1).or(
Chess.Position.makeSlidingAttackMask(fromBB.dup(), occupied, 1, -1)).or(
Chess.Position.makeSlidingAttackMask(fromBB.dup(), occupied, -1, 1)).or(
Chess.Position.makeSlidingAttackMask(fromBB.dup(), occupied, -1, -1));
};
/**
* @param {!Chess.Bitboard} fromBB
* @param {!Chess.Bitboard} occupied
* @return {!Chess.Bitboard}
*/
Chess.Position.makeRookAttackMask = function(fromBB, occupied) {
return Chess.Position.makeSlidingAttackMask(fromBB.dup(), occupied, 0, 1).or(
Chess.Position.makeSlidingAttackMask(fromBB.dup(), occupied, 0, -1)).or(
Chess.Position.makeSlidingAttackMask(fromBB.dup(), occupied, 1, 0)).or(
Chess.Position.makeSlidingAttackMask(fromBB.dup(), occupied, -1, 0));
};
/**
* @param {!Chess.PieceColor} color attacked by color
* @param {number} index
* @return {boolean}
* @see http://goo.gl/UYzOw (Square Attacked By)
*/
Chess.Position.prototype.isAttacked = function(color, index) {
var pawns = this.getPieceColorBitboard(Chess.Piece.PAWN, color);
if (Chess.Position.makePawnAttackMask(color, pawns).isSet(index)) {
return true;
}
var knights = this.getPieceColorBitboard(Chess.Piece.KNIGHT, color);
if (!Chess.Bitboard.KNIGHT_MOVEMENTS[index].dup().and(knights).isEmpty()) {
return true;
}
var king = this.getPieceColorBitboard(Chess.Piece.KING, color);
if (!Chess.Bitboard.KING_MOVEMENTS[index].dup().and(king).isEmpty()) {
return true;
}
var occupied = this.getOccupiedBitboard();
var queens = this.getPieceColorBitboard(Chess.Piece.QUEEN, color);
var bq = this.getPieceColorBitboard(Chess.Piece.BISHOP, color).dup().or(queens);
if (Chess.Position.makeBishopAttackMask(bq, occupied).isSet(index)) {
return true;
}
var rq = this.getPieceColorBitboard(Chess.Piece.ROOK, color).dup().or(queens);
if (Chess.Position.makeRookAttackMask(rq, occupied).isSet(index)) {
return true;
}
return false;
};
/**
* @param {!Chess.PieceColor} color
* @param {boolean} kingSide true = castle kingside, false = castle queenside
* @return {number} 0-3 index to castling rights
*/
Chess.Position.getCastlingIndex = function(color, kingSide) {
return color + (kingSide ? 0 : 2);
};
/**
* @param {!Chess.PieceColor} color
* @param {boolean} kingSide true = castle kingside, false = castle queenside
* @return {number} home square of the castling rook
*/
Chess.Position.getCastlingRookSquare = function(color, kingSide) {
return Chess.Position.ROOK_INDICES[Chess.Position.getCastlingIndex(color, kingSide)];
};
/**
* @param {!Chess.PieceColor} color
* @param {boolean} kingSide true = castle kingside, false = castle queenside
* @return {boolean}
*/
Chess.Position.prototype.hasCastlingRight = function(color, kingSide) {
return 0 !== (this.castlingRights & (1 << Chess.Position.getCastlingIndex(color, kingSide)));
};
/**
* @param {!Chess.PieceColor} color
* @param {boolean} kingSide true = castle kingside, false = castle queenside
*/
Chess.Position.prototype.clearCastlingRight = function(color, kingSide) {
this.hashKey.updateCastlingRights(this.castlingRights);
this.castlingRights &= ~(1 << Chess.Position.getCastlingIndex(color, kingSide));
this.hashKey.updateCastlingRights(this.castlingRights);
};
/**
* @param {!Chess.PieceColor} color
* @param {boolean} kingSide true = castle kingside, false = castle queenside
* @param {boolean} onlyLegal true = check that king's route is not attacked
* @return {boolean}
* TODO: allow pseudo-legal castle moves, i.e. don't check attacked until makeMove
*/
Chess.Position.prototype.canCastle = function(color, kingSide, onlyLegal) {
if (!this.hasCastlingRight(color, kingSide)) {
return false;
}
var direction = kingSide ? 1 : -1;
var kingPosition = (color === Chess.PieceColor.WHITE) ? 4 : 60;
var occupied = this.getOccupiedBitboard();
if (occupied.isSet(kingPosition + direction) || occupied.isSet(kingPosition + 2 * direction)) {
return false;
}
if (!kingSide && occupied.isSet(kingPosition + 3 * direction)) {
return false;
}
if (onlyLegal && !this.isCastlingLegal(color, kingSide)) {
return false;
}
return true;
};
/**
* @param {!Chess.PieceColor} color
* @param {boolean} kingSide true = castle kingside, false = castle queenside
* @return {boolean}
*/
Chess.Position.prototype.isCastlingLegal = function(color, kingSide) {
var otherColor = Chess.getOtherPieceColor(color);
var direction = kingSide ? 1 : -1;
var kingPosition = (color === Chess.PieceColor.WHITE) ? 4 : 60;
return !this.isAttacked(otherColor, kingPosition) && !this.isAttacked(otherColor, kingPosition + direction) && !this.isAttacked(otherColor, kingPosition + 2 * direction);
};
/** @return {boolean} */
Chess.Position.prototype.canEnPassant = function() {
return this.getEnPassantSquare() >= 0;
};
/** @return {number} */
Chess.Position.prototype.getEnPassantSquare = function() {
return this.enPassantSquare;
};
/** @return {boolean} */
Chess.Position.prototype.isFiftyMoveRuleDraw = function() {
return this.halfmoveClock >= 100;
};
/** @return {boolean} */
Chess.Position.prototype.isThreefoldRepetitionRuleDraw = function() {
// TODO: implement e.g. by using hash history
return false;
};
/**
* @return {boolean}
* TODO: find a good source for how this is supposed to work
*/
Chess.Position.prototype.isInsufficientMaterialDraw = function() {
if (!this.getPieceBitboard(Chess.Piece.PAWN).isEmpty()) {
return false;
}
if (!this.getPieceBitboard(Chess.Piece.ROOK).isEmpty()) {
return false;
}
if (!this.getPieceBitboard(Chess.Piece.QUEEN).isEmpty()) {
return false;
}
// only kings, knights and bishops on the board
var whiteCount = this.getColorBitboard(Chess.PieceColor.WHITE).popcnt();
var blackCount = this.getColorBitboard(Chess.PieceColor.BLACK).popcnt();
if (whiteCount + blackCount < 4) {
// king vs king, king&bishop vs king, king&knight vs king
return true;
}
if (!this.getPieceBitboard(Chess.Piece.KNIGHT).isEmpty()) {
return false;
}
// only kings and bishops on the board
var bishops = this.getPieceBitboard(Chess.Piece.BISHOP);
if (bishops.dup().and(Chess.Bitboard.LIGHT_SQUARES).isEqual(bishops) || bishops.dup().and(Chess.Bitboard.DARK_SQUARES).isEqual(bishops)) {
return true;
}
return false;
};
/** @return {boolean} */
Chess.Position.prototype.isDraw = function() {
return this.isFiftyMoveRuleDraw() || this.isThreefoldRepetitionRuleDraw() || this.isInsufficientMaterialDraw();
};
/** @return {!Chess.Position.Status} */
Chess.Position.prototype.getStatus = function() {
if (!this.getMoves().length) {
return this.isKingInCheck() ? Chess.Position.Status.CHECKMATE : Chess.Position.Status.STALEMATE_DRAW;
}
if (this.isFiftyMoveRuleDraw()) {
return Chess.Position.Status.FIFTY_MOVE_RULE_DRAW;
}
if (this.isThreefoldRepetitionRuleDraw()) {
return Chess.Position.Status.THREEFOLD_REPETITION_RULE_DRAW;
}
if (this.isInsufficientMaterialDraw()) {
return Chess.Position.Status.INSUFFICIENT_MATERIAL_DRAW;
}
return Chess.Position.Status.NORMAL;
};
/**
* @param {boolean} onlyCaptures
* @return {!Array.<!Chess.Move>} pseudo-legal moves in this chess position
* TODO: special-case move generation when king is check
*/
Chess.Position.prototype.generateMoves = function(onlyCaptures) {
var moves = [];
var turnColor = this.getTurnColor();
var opponentBB = this.getColorBitboard(Chess.getOtherPieceColor(turnColor));
var occupied = this.getOccupiedBitboard();
var chessPosition = this;
// Pawn moves: double pushes, positional moves, captures, promotions, en passant
/**
* @param {!Chess.Bitboard} toMask
* @param {number} movement
* @param {!Chess.Move.Kind} kind
*/
function addPawnMoves(toMask, movement, kind) {
while (!toMask.isEmpty()) {
var index = toMask.extractLowestBitPosition();
moves.push(new Chess.Move(index - movement, index, kind, Chess.Piece.PAWN, chessPosition.getPieceAtOrNull(index)));
}
}
/**
* @param {!Chess.Bitboard} toMask
* @param {number} movement
* @param {boolean} capture
*/
function addPawnPromotions(toMask, movement, capture) {
addPawnMoves(toMask.dup(), movement, capture ? Chess.Move.Kind.QUEEN_PROMOTION_CAPTURE : Chess.Move.Kind.QUEEN_PROMOTION);
addPawnMoves(toMask.dup(), movement, capture ? Chess.Move.Kind.ROOK_PROMOTION_CAPTURE : Chess.Move.Kind.ROOK_PROMOTION);
addPawnMoves(toMask.dup(), movement, capture ? Chess.Move.Kind.BISHOP_PROMOTION_CAPTURE : Chess.Move.Kind.BISHOP_PROMOTION);
addPawnMoves(toMask.dup(), movement, capture ? Chess.Move.Kind.KNIGHT_PROMOTION_CAPTURE : Chess.Move.Kind.KNIGHT_PROMOTION);
}
var fileDirection = 1 - 2 * turnColor;
var rankDirection = Chess.FILES * fileDirection;
var turnPawns = this.getPieceColorBitboard(Chess.Piece.PAWN, turnColor);
var lastRow = Chess.Bitboard.RANKS[turnColor ? 0 : Chess.LAST_RANK];
if (!onlyCaptures) {
// Double pawn pushes: pawns that are at their initial position, with nothing in the next two rows
var doublePawnPushed = turnPawns.dup().and(Chess.Bitboard.RANKS[turnColor ? 6 : 1]).shiftLeft(2 * rankDirection).and_not(occupied).and_not(occupied.dup().shiftLeft(rankDirection));
addPawnMoves(doublePawnPushed, 2 * rankDirection, Chess.Move.Kind.DOUBLE_PAWN_PUSH);
// Positional pawn moves: advance one square to an empty square; not to the last row
// Pawn promotion: to the last row
var positionalPawnMoved = turnPawns.dup().shiftLeft(rankDirection).and_not(occupied);
addPawnMoves(positionalPawnMoved.dup().and_not(lastRow), rankDirection, Chess.Move.Kind.POSITIONAL);
addPawnPromotions(positionalPawnMoved.dup().and(lastRow), rankDirection, false);
}
// Pawn captures: advance diagonally to the next row to a square occupied by an opponent piece; not to the last row. Also, don't wrap the board from left/right.
// Pawn promotion w/ capture: to the last row
var leftFile = Chess.Bitboard.FILES[turnColor ? Chess.LAST_FILE : 0];
var leftCaptureMovement = rankDirection - fileDirection;
var pawnLeftCaptured = turnPawns.dup().and_not(leftFile).shiftLeft(leftCaptureMovement).and(opponentBB);
addPawnMoves(pawnLeftCaptured.dup().and_not(lastRow), leftCaptureMovement, Chess.Move.Kind.CAPTURE);
addPawnPromotions(pawnLeftCaptured.dup().and(lastRow), leftCaptureMovement, true);
var rightFile = Chess.Bitboard.FILES[turnColor ? 0 : Chess.LAST_FILE];
var rightCaptureMovement = rankDirection + fileDirection;
var pawnRightCaptured = turnPawns.dup().and_not(rightFile).shiftLeft(rightCaptureMovement).and(opponentBB);
addPawnMoves(pawnRightCaptured.dup().and_not(lastRow), rightCaptureMovement, Chess.Move.Kind.CAPTURE);
addPawnPromotions(pawnRightCaptured.dup().and(lastRow), rightCaptureMovement, true);
// Pawn en passant captures: opponent has just double pawn pushed in the last move next to our pawn, we move diagonally behind the opponent pawn, capturing it
if (this.canEnPassant()) {
var pawnLeftEnPassant = Chess.Bitboard.makeIndex(this.getEnPassantSquare() + fileDirection).and(turnPawns).and_not(leftFile).shiftLeft(leftCaptureMovement);
addPawnMoves(pawnLeftEnPassant, leftCaptureMovement, Chess.Move.Kind.EN_PASSANT_CAPTURE);
var pawnRightEnPassant = Chess.Bitboard.makeIndex(this.getEnPassantSquare() - fileDirection).and(turnPawns).and_not(rightFile).shiftLeft(rightCaptureMovement);
addPawnMoves(pawnRightEnPassant, rightCaptureMovement, Chess.Move.Kind.EN_PASSANT_CAPTURE);
}
// Positional and capture moves for knight, bishop, rook, queen, king
var turnBB = this.getColorBitboard(turnColor);
/**
* @param {number} from 0-63
* @param {!Chess.Bitboard} toMask
* @param {!Chess.Piece} piece
*/
function addNormalMoves(from, toMask, piece) {
while (!toMask.isEmpty()) {
var to = toMask.extractLowestBitPosition();
if (turnBB.isClear(to)) {
moves.push(new Chess.Move(from, to, opponentBB.isSet(to) ? Chess.Move.Kind.CAPTURE : Chess.Move.Kind.POSITIONAL, piece, chessPosition.getPieceAtOrNull(to)));
}
}
}
var mask = onlyCaptures ? opponentBB : Chess.Bitboard.ONE;
var turnKnights = this.getPieceColorBitboard(Chess.Piece.KNIGHT, turnColor).dup();
while (!turnKnights.isEmpty()) {
var knightPosition = turnKnights.extractLowestBitPosition();
addNormalMoves(knightPosition, Chess.Bitboard.KNIGHT_MOVEMENTS[knightPosition].dup().and(mask), Chess.Piece.KNIGHT);
}
var turnQueens = this.getPieceColorBitboard(Chess.Piece.QUEEN, turnColor).dup();
while (!turnQueens.isEmpty()) {
var queenPosition = turnQueens.extractLowestBitPosition();
addNormalMoves(queenPosition, Chess.Position.makeBishopAttackMask(Chess.Bitboard.makeIndex(queenPosition), occupied).or(
Chess.Position.makeRookAttackMask(Chess.Bitboard.makeIndex(queenPosition), occupied)).and(mask), Chess.Piece.QUEEN);
}
var turnBishops = this.getPieceColorBitboard(Chess.Piece.BISHOP, turnColor).dup();
while (!turnBishops.isEmpty()) {
var bishopPosition = turnBishops.extractLowestBitPosition();
addNormalMoves(bishopPosition, Chess.Position.makeBishopAttackMask(Chess.Bitboard.makeIndex(bishopPosition), occupied).and(mask), Chess.Piece.BISHOP);
}
var turnRooks = this.getPieceColorBitboard(Chess.Piece.ROOK, turnColor).dup();
while (!turnRooks.isEmpty()) {
var rookPosition = turnRooks.extractLowestBitPosition();
addNormalMoves(rookPosition, Chess.Position.makeRookAttackMask(Chess.Bitboard.makeIndex(rookPosition), occupied).and(mask), Chess.Piece.ROOK);
}
var kingPosition = this.getKingPosition(turnColor);
addNormalMoves(kingPosition, Chess.Bitboard.KING_MOVEMENTS[kingPosition].dup().and(mask), Chess.Piece.KING);
if (!onlyCaptures) {
// King & queen side castle
if (this.canCastle(turnColor, true, true)) {
moves.push(new Chess.Move(kingPosition, kingPosition + 2, Chess.Move.Kind.KING_CASTLE, Chess.Piece.KING, null));
}
if (this.canCastle(turnColor, false, true)) {
moves.push(new Chess.Move(kingPosition, kingPosition - 2, Chess.Move.Kind.QUEEN_CASTLE, Chess.Piece.KING, null));
}
}
return moves;
};
/**
* @param {!Chess.Piece} piece
* @param {!Chess.PieceColor} color of the captured piece
* @param {number} index
*/
Chess.Position.prototype.capturePiece = function(piece, color, index) {
this.getPieceBitboard(piece).clearBit(index);
this.getColorBitboard(color).clearBit(index);
this.pieces[index] = null;
this.hashKey.updatePieceColorSquare(piece, color, index);
};
/**
* @param {!Chess.Piece} piece
* @param {!Chess.PieceColor} color of the captured piece
* @param {number} index
*/
Chess.Position.prototype.unCapturePiece = function(piece, color, index) {
this.getPieceBitboard(piece).setBit(index);
this.getColorBitboard(color).setBit(index);
this.pieces[index] = /** @type {!Chess.Piece} */(piece);
this.hashKey.updatePieceColorSquare(piece, color, index);
};
/**
* @param {!Chess.Piece} piece
* @param {!Chess.PieceColor} color
* @param {number} from 0-63
* @param {number} to 0-63
*/
Chess.Position.prototype.movePiece = function(piece, color, from, to) {
var fromToBB = Chess.Bitboard.makeIndex(from).or(Chess.Bitboard.makeIndex(to));
this.getPieceBitboard(piece).xor(fromToBB);
this.getColorBitboard(color).xor(fromToBB);
this.pieces[from] = null;
this.pieces[to] = /** @type {!Chess.Piece} */(piece);
this.hashKey.updatePieceColorSquare(piece, color, from);
this.hashKey.updatePieceColorSquare(piece, color, to);
};
/**
* @param {!Chess.PieceColor} color
* @param {boolean} kingSide true = castle kingside, false = castle queenside
* N.B. only moves the rook, not the king
*/
Chess.Position.prototype.castleRook = function(color, kingSide) {
var from = Chess.Position.getCastlingRookSquare(color, kingSide);
var to = from + (kingSide ? -2 : 3);
this.movePiece(Chess.Piece.ROOK, color, from, to);
};
/**
* @param {!Chess.PieceColor} color
* @param {boolean} kingSide true = castle kingside, false = castle queenside
* N.B. only moves the rook, not the king
*/
Chess.Position.prototype.unCastleRook = function(color, kingSide) {
var to = Chess.Position.getCastlingRookSquare(color, kingSide);
var from = to + (kingSide ? -2 : 3);
this.movePiece(Chess.Piece.ROOK, color, from, to);
};
/**
* @param {!Chess.Piece} pieceOld
* @param {!Chess.Piece} pieceNew
* @param {!Chess.PieceColor} color
* @param {number} index 0-63
* @see http://goo.gl/jkRj9 (Update by Move)
*/
Chess.Position.prototype.promotePiece = function(pieceOld, pieceNew, color, index) {
this.getPieceBitboard(pieceOld).clearBit(index);
this.getPieceBitboard(pieceNew).setBit(index);
this.pieces[index] = /** @type {!Chess.Piece} */(pieceNew);
this.hashKey.updatePieceColorSquare(pieceOld, color, index);
this.hashKey.updatePieceColorSquare(pieceNew, color, index);
};
/**
* Changes the chess pieces according to move
* @param {!Chess.Move} move to make
*/
Chess.Position.prototype.updatePieces = function(move) {
if (move.isCapture()) {
this.capturePiece(move.getCapturedPiece(), Chess.getOtherPieceColor(this.getTurnColor()), move.getCaptureSquare());
}
if (move.isCastle()) {
this.castleRook(this.getTurnColor(), move.getKind() === Chess.Move.Kind.KING_CASTLE);
}
this.movePiece(move.getPiece(), this.getTurnColor(), move.getFrom(), move.getTo());
if (move.isPromotion()) {
this.promotePiece(Chess.Piece.PAWN, move.getPromotedPiece(), this.getTurnColor(), move.getTo());
}
};
/**
* Reverts the chess pieces to the positions before making move
* @param {!Chess.Move} move to unmake
*/
Chess.Position.prototype.revertPieces = function(move) {
if (move.isPromotion()) {
this.promotePiece(move.getPromotedPiece(), Chess.Piece.PAWN, this.getTurnColor(), move.getTo());
}
this.movePiece(move.getPiece(), this.getTurnColor(), move.getTo(), move.getFrom());
if (move.isCastle()) {
this.unCastleRook(this.getTurnColor(), move.getKind() === Chess.Move.Kind.KING_CASTLE);
}
if (move.isCapture()) {
this.unCapturePiece(move.getCapturedPiece(), Chess.getOtherPieceColor(this.getTurnColor()), move.getCaptureSquare());
}
};
/**
* Checks a pseudo-legal move's legality
* @param {!Chess.Move} move to test
* @return {boolean}
*/
Chess.Position.prototype.isMoveLegal = function(move) {
this.updatePieces(move);
var kingInCheck = this.isKingInCheck();
this.revertPieces(move);
return !kingInCheck;
};
/**
* Changes the pieces according to the move, and adds the move to the move history list
* @param {!Chess.Move} move to make
* @return {boolean} true if the move was made
*/
Chess.Position.prototype.makeMove = function(move) {
this.updatePieces(move);
if (this.isKingInCheck()) {
this.revertPieces(move);
return false;
}
this.madeMoves.push(move);
this.irreversibleHistory.push(this.enPassantSquare);
this.irreversibleHistory.push(this.castlingRights);
this.irreversibleHistory.push(this.halfmoveClock);
this.hashKey.updateEnPassantSquare(this.enPassantSquare);
if (move.getKind() === Chess.Move.Kind.DOUBLE_PAWN_PUSH) {
this.enPassantSquare = move.getTo();
} else {
this.enPassantSquare = -1;
}
this.hashKey.updateEnPassantSquare(this.enPassantSquare);
var turnColor = this.getTurnColor();
if (move.getPiece() === Chess.Piece.KING) {
this.clearCastlingRight(turnColor, true);
this.clearCastlingRight(turnColor, false);
} else if (move.getPiece() === Chess.Piece.ROOK) {
if (move.getFrom() === Chess.Position.getCastlingRookSquare(turnColor, true)) {
this.clearCastlingRight(turnColor, true);
} else if (move.getFrom() === Chess.Position.getCastlingRookSquare(turnColor, false)) {
this.clearCastlingRight(turnColor, false);
}
}
var otherColor = Chess.getOtherPieceColor(turnColor);
if (move.getCapturedPiece() === Chess.Piece.ROOK) {
if (move.getCaptureSquare() === Chess.Position.getCastlingRookSquare(otherColor, true)) {
this.clearCastlingRight(otherColor, true);
} else if (move.getCaptureSquare() === Chess.Position.getCastlingRookSquare(otherColor, false)) {
this.clearCastlingRight(otherColor, false);
}
}
if (move.isCapture() || move.getPiece() === Chess.Piece.PAWN) {
this.halfmoveClock = 0;
} else {
++this.halfmoveClock;
}
this.turn = otherColor;
this.hashKey.updateTurn();
return true;
};
/**
* @return {number} number of moves made
*/
Chess.Position.prototype.getMadeMoveCount = function() {
return this.madeMoves.length;
};
/**
* @return {boolean} if a move has been made
*/
Chess.Position.prototype.canUndo = function() {
return !!this.getMadeMoveCount();
};
/**
* @return {?Chess.Move} the latest move or null if the board was at the initial state
*/
Chess.Position.prototype.getLastMove = function() {
if (!this.canUndo()) {
return null;
}
return this.madeMoves[this.madeMoves.length - 1];
};
/**
* Unmakes the latest move made
* @return {?Chess.Move} the unmade move or null if the board was at the initial state
*/
Chess.Position.prototype.unmakeMove = function() {
if (!this.canUndo()) {
return null;
}
var move = /** @type {!Chess.Move} */(this.madeMoves.pop());
this.turn = Chess.getOtherPieceColor(this.getTurnColor());
this.hashKey.updateTurn();
this.revertPieces(move);
this.halfMoveClock = /** @type {number} */(this.irreversibleHistory.pop());
this.hashKey.updateCastlingRights(this.castlingRights);
this.castlingRights = /** @type {number} */(this.irreversibleHistory.pop());
this.hashKey.updateCastlingRights(this.castlingRights);
this.hashKey.updateEnPassantSquare(this.enPassantSquare);
this.enPassantSquare = /** @type {number} */(this.irreversibleHistory.pop());
this.hashKey.updateEnPassantSquare(this.enPassantSquare);
return move;
};
/*
function test() {
var cache = new Array(1000000);
var collisions = 0;
var hits = 0;
function perft(depth, chessPosition) {
if (!depth) {
return 1;
}
var zobrist = chessPosition.hashKey;
var hashKey = zobrist.getHashKey();
if (cache[hashKey % cache.length]) {
if (cache[hashKey % cache.length].zobrist.isEqual(zobrist)) {
++hits;
return cache[hashKey % cache.length].nodes;
}
++collisions;
}
var nodes = 0;
chessPosition.getMoves(true).forEach(function(move) {
if (chessPosition.makeMove(move)) {
nodes += perft(depth - 1, chessPosition);
chessPosition.unmakeMove();
}
});
cache[hashKey % cache.length] = { zobrist: zobrist.dup(), nodes: nodes };
return nodes;
}
window.console.log("Perft: " + perft(4, new Chess.Position));
window.console.log("Hits: " + hits + ", collisions: " + collisions);
};
test();
*/

15
src/test.html

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta charset="utf-8">
<title>Chess QUnit</title>
<link rel="stylesheet" href="http://code.jquery.com/qunit/qunit-1.10.0.css">
</head>
<body>
<div id="qunit"></div>
<script src="http://code.jquery.com/qunit/qunit-1.10.0.js"></script>
<script src="chess.include.js"></script>
<script src="test.js"></script>
</body>
</html>

190
src/test.js

@ -0,0 +1,190 @@
/*
* Automated tests for the chess logic. UI is not tested.
*/
"use strict";
module("chess.js");
test("utilities", function() {
strictEqual(Chess.getRank(0), 0);
strictEqual(Chess.getRank(7), 0);
strictEqual(Chess.getRank(56), 7);
strictEqual(Chess.getRank(63), 7);
strictEqual(Chess.getFile(0), 0);
strictEqual(Chess.getFile(7), 7);
strictEqual(Chess.getFile(56), 0);
strictEqual(Chess.getFile(63), 7);
strictEqual(Chess.isInsideBoard(0, 0), true);
strictEqual(Chess.isInsideBoard(7, 7), true);
strictEqual(Chess.isInsideBoard(-1, -1), false);
strictEqual(Chess.isInsideBoard(8, 8), false);
strictEqual(Chess.isInsideBoard(-1, 0), false);
strictEqual(Chess.isInsideBoard(8, 0), false);
strictEqual(Chess.isInsideBoard(0, -1), false);
strictEqual(Chess.isInsideBoard(0, 8), false);
strictEqual(Chess.getIndex(0, 0), 0);
strictEqual(Chess.getIndex(0, 7), 7);
strictEqual(Chess.getIndex(7, 0), 56);
strictEqual(Chess.getIndex(7, 7), 63);
strictEqual(Chess.isLight(0, 0), false);
strictEqual(Chess.isLight(0, 7), true);
strictEqual(Chess.getAlgebraic(0, 0), "a1");
strictEqual(Chess.getAlgebraic(7, 7), "h8");
strictEqual(Chess.getIndexFromAlgebraic("a1"), 0);
strictEqual(Chess.getIndexFromAlgebraic("h8"), 63);
strictEqual(Chess.getAlgebraicFromIndex(0), "a1");
strictEqual(Chess.getAlgebraicFromIndex(63), "h8");
strictEqual(Chess.getOtherPieceColor(Chess.PieceColor.WHITE), Chess.PieceColor.BLACK);
strictEqual(Chess.getOtherPieceColor(Chess.PieceColor.BLACK), Chess.PieceColor.WHITE);
});
module("bitboard.js");
test("Bitboard", function() {
strictEqual(Chess.Bitboard.makeLightSquares().popcnt(), 32);
strictEqual(Chess.Bitboard.makeDarkSquares().popcnt(), 32);
strictEqual(Chess.Bitboard.makeLightSquares().xor(Chess.Bitboard.makeDarkSquares()).popcnt(), 64);
for (var k = 0; k < 8; ++k) {
strictEqual(Chess.Bitboard.makeFile(k).popcnt(), 8);
strictEqual(Chess.Bitboard.makeRank(k).popcnt(), 8);
}
for (var l = -7; l < 8; ++l) {
strictEqual(Chess.Bitboard.makeDiagonal(l).popcnt(), 8 - Math.abs(l));
strictEqual(Chess.Bitboard.makeAntidiagonal(l).popcnt(), 8 - Math.abs(l));
}
var is = [0, 1, 7, 8, 31, 32, 55, 56, 62, 63];
var js = [0, 15, 31, 32, 40, 63];
for (var ii = 0; ii < is.length; ++ii) {
var i = is[ii];
var bbi = Chess.Bitboard.makeIndex(i);
strictEqual(bbi.popcnt(), 1);
strictEqual(bbi.isEmpty(), false);
for (var ji = 0; ji < js.length; ++ji) {
var j = js[ji];
var or = Chess.Bitboard.makeIndex(i).or(Chess.Bitboard.makeIndex(j));
var hi = Chess.Bitboard.makeIndex((i > j) ? i : j);
deepEqual(or.popLowestBit(), (i === j) ? Chess.Bitboard.ZERO : hi);
or = Chess.Bitboard.makeIndex(i).or(Chess.Bitboard.makeIndex(j));
strictEqual(or.isEmpty(), false);
strictEqual(or.popcnt(), (i === j) ? 1 : 2);
strictEqual(or.getLowestBitPosition(), (i < j) ? i : j);
strictEqual(or.isSet(i), true);
strictEqual(or.isSet(j), true);
strictEqual(or.isClear(i), false);
strictEqual(or.isClear(j), false);
strictEqual(bbi.isSet(j), j === i);
strictEqual(bbi.isClear(j), j !== i);
var xor = Chess.Bitboard.makeIndex(i).xor(Chess.Bitboard.makeIndex(j));
strictEqual(xor.popcnt(), (i === j) ? 0 : 2);
strictEqual(xor.isEmpty(), i === j);
var bbj = bbi.dup();
bbj.setBit(j);
deepEqual(bbj, or);
strictEqual(bbj.isEqual(or), true);
bbj.clearBit(j);
deepEqual(bbj, (i === j) ? Chess.Bitboard.ZERO : bbi);
}
}
// TODO: test knight and king movement
// TODO: test shifts
});
module("zobrist.js");
test("Zobrist", function() {
var zobrist = new Chess.Zobrist(0, 0);
var dup = zobrist.dup();
deepEqual(zobrist, dup);
zobrist.updateTurn();
notDeepEqual(zobrist, dup);
zobrist.updateTurn();
deepEqual(zobrist, dup);
var lightSquares = Chess.Bitboard.LIGHT_SQUARES;
strictEqual(lightSquares.popcnt(), 32);
zobrist.updatePieceColorBitboard(Chess.Piece.QUEEN, Chess.PieceColor.BLACK, lightSquares);
strictEqual(lightSquares.popcnt(), 32);
notDeepEqual(zobrist, dup);
});
module("move.js");
test("Move", function() {
var move = new Chess.Move(32, 33, Chess.Move.Kind.CAPTURE, Chess.Piece.ROOK, Chess.Piece.QUEEN);
strictEqual(move.getFrom(), 32);
strictEqual(move.getTo(), 33);
strictEqual(move.getKind(), Chess.Move.Kind.CAPTURE);
strictEqual(move.getPiece(), Chess.Piece.ROOK);
strictEqual(move.isCapture(), true);
strictEqual(move.getCapturedPiece(), Chess.Piece.QUEEN);
strictEqual(move.isPromotion(), false);
strictEqual(move.isCastle(), false);
strictEqual(move.getCaptureSquare(), 33);
});
module("position.js");
// TODO: getPieceAtOrNull vs findPieceAtOrNull
// TODO: castling
// TODO: capture
// TODO: piece consistency, move consistency
// TODO: promotion
test("Position", function() {
var position = new Chess.Position;
strictEqual(position.getPieceBitboard(Chess.Piece.PAWN).popcnt(), 16);
});
test("Perft", function() {
strictEqual(Chess.Position.perft(3), 8902, "Perft(3)");
strictEqual(Chess.Position.perft(4), 197281, "Perft(4)");
});
function checkPositionHash(chessPosition) {
var old = chessPosition.hashKey.dup();
chessPosition.updateHashKey();
return chessPosition.hashKey.isEqual(old);
}
test("Hashing", function() {
var position = new Chess.Position;
ok(checkPositionHash(position));
var dup = position.hashKey.dup();
position.makeMove(position.getMoves(true)[0]);
ok(checkPositionHash(position));
notDeepEqual(position.hashKey, dup);
ok(position.canUndo());
position.unmakeMove();
ok(checkPositionHash(position));
deepEqual(position.hashKey, dup);
});
module("parser.js");
/**
* @see http://goo.gl/B39TC (Algebraic notation)
* @see http://goo.gl/ZgMC1 (Peruvian Immortal)
*/
test("Parser", function() {
strictEqual(Chess.Parser.clean("A4 )( (B- (C! ( ( D? ) ) E F)) G5"), "A4 G5");
var chessPosition = Chess.Parser.parseMoves("1. e4 c5 2. Nf3 d6 3. Bb5+ Bd7 4. Bxd7+ Qxd7 5. c4 Nc6 6. Nc3 Nf6 7. 0-0 g6 8. d4 cxd4 9. Nxd4 Bg7 10. Nde2 Qe6!? (a novelty suggested by Irina Krush and considered a turning point for the World Team) 11. Nd5 Qxe4 12. Nc7+ Kd7 13. Nxa8 Qxc4 14. Nb6+ axb6 15. Nc3 Ra8 16. a4 Ne4 17. Nxe4 Qxe4 18. Qb3 f5 19. Bg5 Qb4 20. Qf7 Be5 21. h3 Rxa4 22. Rxa4 Qxa4 23. Qxh7 Bxb2 24. Qxg6 Qe4 25. Qf7 Bd4 26. Qb3 f4 27. Qf7 Be5 28. h4 b5 29. h5 Qc4 30. Qf5+ Qe6 31. Qxe6+ Kxe6 (see diagram) 32. g3 fxg3 33. fxg3 b4 (the World Team did not trust 33...Bxg3 34.h6 Be5 35.h7 Bg7 36.Rf8 b4 37.h8=Q Bxh8 38.Rxh8) 34. Bf4 Bd4+ 35. Kh1! b3 36. g4 Kd5 37. g5 e6 38. h6 Ne7 39. Rd1 e5 40. Be3 Kc4 41. Bxd4 exd4 42. Kg2 b2 43. Kf3 Kc3 44. h7 Ng6 45. Ke4 Kc2 46. Rh1 d3 (46...b1=Q? 47.Rxb1 Kxb1 48.Kxd4 and White will win) 47. Kf5 b1=Q 48. Rxb1 Kxb1 49. Kxg6 d2 50. h8=Q d1=Q 51. Qh7 b5?! 52. Kf6+ Kb2 53. Qh2+ Ka1 54. Qf4 b4? 55. Qxb4 Qf3+ 56. Kg7 d5 57. Qd4+ Kb1 58. g6 Qe4 59. Qg1+ Kb2 60. Qf2+ Kc1 61. Kf6 d4 62. g7 1–0");
strictEqual(chessPosition.getColorBitboard(Chess.PieceColor.BLACK).popcnt(), 3);
ok(chessPosition.getColorBitboard(Chess.PieceColor.BLACK).isSet(2));
ok(chessPosition.getColorBitboard(Chess.PieceColor.BLACK).isSet(Chess.getIndex(3, 3)));
ok(chessPosition.getColorBitboard(Chess.PieceColor.BLACK).isSet(Chess.getIndex(3, 4)));
strictEqual(chessPosition.getColorBitboard(Chess.PieceColor.WHITE).popcnt(), 3);
ok(chessPosition.getColorBitboard(Chess.PieceColor.WHITE).isSet(Chess.getIndex(1, 5)));
ok(chessPosition.getColorBitboard(Chess.PieceColor.WHITE).isSet(Chess.getIndex(5, 5)));
ok(chessPosition.getColorBitboard(Chess.PieceColor.WHITE).isSet(Chess.getIndex(6, 6)));
var chessPosition2 = Chess.Parser.parseMoves("1. e4 d5 2. exd5 Qxd5 3. Nc3 Qa5 4. d4 c6 5. Nf3 Bg4 6. Bf4 e6 7. h3 Bxf3 8. Qxf3 Bb4 9. Be2 Nd7 10. a3 0-0-0");
strictEqual(chessPosition2.getColorBitboard(Chess.PieceColor.BLACK).popcnt(), 14);
strictEqual(chessPosition2.getColorBitboard(Chess.PieceColor.WHITE).popcnt(), 14);
strictEqual(chessPosition2.getPieceBitboard(Chess.Piece.PAWN).popcnt(), 14);
});
module("ai.js");
test("AI", function() {
var ai = new Chess.AI;
notStrictEqual(ai.search(new Chess.Position), null);
});

321
src/ui.js

@ -0,0 +1,321 @@
"use strict";
// TODO: extract anonymous functions to Chess.UI member functions where it makes sense (e.g. drag&drop handlers)
// TODO: receive the div id as an argument
// TODO: implement getters for the most common selectors (which are now constants)
// TODO: show captured pieces next to the board
// TODO: tablet drag&drop
// TODO: click-click moving (=no drag&drop)
/**
* Chess user interface implementation. A chessboard is created as a html table.
* Chess pieces are created as html divs, and placed as children of the chessboard tds.
* Dragging and dropping is implemented with jQuery UI's draggable.
* Chess game state (position.js) and computer player (ai.js) are wired to the pieces. Computer is the black player.
* @constructor
*/
Chess.UI = function() {
/** @type {!Chess.Position} */
this.chessPosition = new Chess.Position;
/** @type {!Chess.AI} */
this.ai = new Chess.AI;
};
/**
* @const
* @type {string}
*/
Chess.UI.CHESSBOARD_ID = "#chessboard";
/**
* @const
* @type {string}
*/
Chess.UI.CHESSBOARD_TABLE = Chess.UI.CHESSBOARD_ID + " table";
/**
* @const
* @type {string}
*/
Chess.UI.CHESSBOARD_SQUARE = Chess.UI.CHESSBOARD_ID + " table tr td";
/**
* @const
* @type {string}
*/
Chess.UI.CHESSBOARD_PIECE = Chess.UI.CHESSBOARD_SQUARE + " div";
/**
* @const
* @type {string}
*/
Chess.UI.CHESSBOARD_PIECES_AND_SQUARES = Chess.UI.CHESSBOARD_SQUARE + ", " + Chess.UI.CHESSBOARD_PIECE;
/**
* Creates a new chessboard table under an element with id="chessboard"
*/
Chess.UI.makeBoard = function() {
var table = $("<table>");
var filesRow = '<tr><th></th>' + "abcdefgh".split("").map(/** @param {string} x @return {string} */ function(x) { return '<th class="file">' + x + "</th>"; }).join("") + "<th></th></tr>";
table.append(filesRow);
for (var row = 0; row < Chess.RANKS; ++row) {
var rank = Chess.LAST_RANK - row;
var tr = $("<tr>");
table.append(tr);
var rankCell = '<th class="rank">' + (Chess.RANKS - row) + "</th>";
tr.append(rankCell);
for (var file = 0; file < Chess.FILES; ++file) {
var td = $("<td>");
var color = Chess.isLight(rank, file) ? "light" : "dark";
td.attr("id", Chess.getAlgebraic(rank, file));
td.attr("title",
"Algebraic: " + Chess.getAlgebraic(rank, file) +
"\nRank: " + rank +
"\nFile: " + file +
"\nIndex: " + Chess.getIndex(rank, file) +
"\nColor: " + color);
td.addClass(color);
tr.append(td);
}
tr.append(rankCell);
}
table.append(filesRow);
$(Chess.UI.CHESSBOARD_ID).append(table);
};
/**
* Clears move related classes from chessboard table cells
*/
Chess.UI.clearMoving = function() {
$(Chess.UI.CHESSBOARD_PIECES_AND_SQUARES).removeClass("from to positional capture double-push en-passant promotion castle king-castle queen-castle");
};
/**
* Removes dragging and dropping capabilities from chessboard table cells
*/
Chess.UI.clearDragging = function() {
$(Chess.UI.CHESSBOARD_PIECE + ".ui-draggable").draggable("destroy");
$(Chess.UI.CHESSBOARD_SQUARE + ".ui-droppable").droppable("destroy");
};
/** Adds chess pieces to the chessboard
*/
Chess.UI.prototype.updatePieces = function() {
$(Chess.UI.CHESSBOARD_PIECE).remove();
$(Chess.UI.CHESSBOARD_SQUARE).removeClass("white black turn last-move " + Chess.PIECE_NAMES.join(" "));
var whites = this.chessPosition.getColorBitboard(Chess.PieceColor.WHITE);
var blacks = this.chessPosition.getColorBitboard(Chess.PieceColor.BLACK);
for (var index = 0; index < Chess.RANKS * Chess.FILES; ++index) {
var td = $("#" + Chess.getAlgebraicFromIndex(index));
for (var piece = Chess.Piece.PAWN; piece <= Chess.Piece.KING; ++piece) {
if (this.chessPosition.getPieceBitboard(piece).isSet(index)) {
var isTurn = (this.chessPosition.getTurnColor() === Chess.PieceColor.WHITE) ? whites.isSet(index) : blacks.isSet(index);
var div = $("<div>");
div.attr("title", td.attr("title") + "\nPiece: " + Chess.PIECE_NAMES[piece] + "\nColor: " + (whites.isSet(index) ? "white" : "black"));
div.text(Chess.getPieceCharacter(piece, whites.isSet(index) ? Chess.PieceColor.WHITE : Chess.PieceColor.BLACK));
var elements = div.add(td);
elements.addClass(Chess.PIECE_NAMES[piece]);
elements.toggleClass("white", whites.isSet(index));
elements.toggleClass("black", blacks.isSet(index));
elements.toggleClass("turn", isTurn);
td.append(div);
break;
}
}
}
var lastMove = this.chessPosition.getLastMove();
if (lastMove !== null) {
$("#" + Chess.getAlgebraicFromIndex(lastMove.getFrom())).addClass("last-move");
$("#" + Chess.getAlgebraicFromIndex(lastMove.getTo())).addClass("last-move");
// TODO: en passant, castling
}
};
/**
* Adds chessboard cell hover, and chess piece dragging and dropping capabilities to the chessboard
*/
Chess.UI.prototype.updateMoves = function() {
var moves = this.chessPosition.getMoves();
$("#moves").html(
'<a href="#" id="undo" class="' + (this.chessPosition.canUndo() ? "can" : "cannot") + '">undo</a><br/>' +
'<a href="#" id="auto" class="' + ((moves.length > 0) ? "can" : "cannot") + '">auto</a><br/>' +
moves.map(
/**
* @param {!Chess.Move} move
* @param {number} index
* @return {string}
*/
function(move, index) {
return '<a href="#" id="' + index + '">' + move.getString() + "</a><br/>";
}).join(""));
$(Chess.UI.CHESSBOARD_PIECES_AND_SQUARES).removeClass("can-move");
moves.forEach(/** @param {!Chess.Move} move */ function(move) {
var td = $("#" + Chess.getAlgebraicFromIndex(move.getFrom()));
var elements = td.add(td.children());
elements.addClass("can-move");
});
/** @type {boolean} */
var dragging = false;
var ui = this;
$(Chess.UI.CHESSBOARD_PIECE + ".can-move").mouseenter(/** @this {!Element} */ function(event) {
if (dragging) {
return;
}
var div = $(this);
var td = div.parent();
var from = Chess.getIndexFromAlgebraic("" + td.attr("id"));
var fromElements = td.add(div);
fromElements.toggleClass("from", moves.some(/** @param {!Chess.Move} move @return {boolean} */ function(move) { return move.getFrom() === from; }));
if (fromElements.hasClass("from")) {
moves.forEach(/** @param {!Chess.Move} move */ function(move) {
if (move.getFrom() === from) {
var toElements = $("#" + Chess.getAlgebraicFromIndex(move.getTo()));
toElements = toElements.add(toElements.children());
toElements.addClass("to");
toElements.addClass(move.getKind() === Chess.Move.Kind.POSITIONAL ? "positional" : "");
toElements.addClass(move.isCapture() ? "capture" : "");
toElements.addClass(move.getKind() === Chess.Move.Kind.DOUBLE_PAWN_PUSH ? "double-push" : "");
toElements.addClass(move.getKind() === Chess.Move.Kind.EN_PASSANT_CAPTURE ? "en-passant" : "");
toElements.addClass(move.isPromotion() ? "promotion" : "");
toElements.addClass(move.isCastle() ? "castle" : "");
toElements.addClass(move.getKind() === Chess.Move.Kind.KING_CASTLE ? "king-castle" : "");
toElements.addClass(move.getKind() === Chess.Move.Kind.QUEEN_CASTLE ? "queen-castle" : "");
}
});
Chess.UI.clearDragging();
// Quote "drop", "start", "stop", etc to prevent the closure compiler from removing them
$(Chess.UI.CHESSBOARD_SQUARE + ".to").droppable({
"drop": /** @this {!Element} */ function() {
var to = Chess.getIndexFromAlgebraic("" + $(this).attr("id"));
var makeMoves = moves.filter(/** @param {!Chess.Move} move */ function(move) { return move.getFrom() === from && move.getTo() === to; });
if (makeMoves.length > 0) {
// TODO: it's possible that there is more than one move (promotions). Either ask the user here or have a droplist somewhere ("promote to")
ui.chessPosition.makeMove(makeMoves[0]);
ui.updateChessPosition();
} else {
// Dropped to an invalid square
Chess.UI.clearMoving();
Chess.UI.clearDragging();
}
}
});
div.draggable({
"start": function() { dragging = true; },
"stop": function() { dragging = false; },
"containment": Chess.UI.CHESSBOARD_TABLE,
"zIndex": 10,
"revert": "invalid"
});
}
}).mouseleave(function() {
if (!dragging) {
Chess.UI.clearMoving();
}
});
$("#moves a").click(function() {
var id = $(this).attr("id");
if (id === "undo") {
ui.chessPosition.unmakeMove(); // computer (black) move
ui.chessPosition.unmakeMove(); // user (white) move
ui.updateChessPosition();
} else if (id === "auto") {
ui.doComputerMove();
} else {
ui.chessPosition.makeMove(moves[parseInt(id, 10)]);
ui.updateChessPosition();
}
});
};
/**
* @throws {Error}
*/
Chess.UI.prototype.doComputerMove = function() {
$("#moves").html("");
var ui = this;
var dim = $("#dim");
dim.fadeIn(function() {
var move = ui.ai.search(ui.chessPosition);
if (!move) {
// Mates should have been checked in updateChessPosition
throw new Error("Move not found");
}
ui.chessPosition.makeMove(move);
var from = $("#" + Chess.getAlgebraicFromIndex(move.getFrom()));
var to = $("#" + Chess.getAlgebraicFromIndex(move.getTo()));
var dx = (to.offset().left - from.offset().left);
var dy = (to.offset().top - from.offset().top);
var piece = from.children("div");
piece.css({"position": "relative", "top": "0px", "left": "0px" });
dim.fadeOut(function() {
piece.animate({"top": dy + "px", "left": dx + "px"}, function() { ui.updateChessPosition(); });
});
});
};
/**
* Updates the chessboard according to the current chess position
*/
Chess.UI.prototype.updateChessPosition = function() {
window.console.log("Moves: " + this.chessPosition.getMadeMoveCount() +
", white material: " + Chess.AI.getMaterialValue(this.chessPosition, Chess.PieceColor.WHITE) +
", black material: " + Chess.AI.getMaterialValue(this.chessPosition, Chess.PieceColor.BLACK) +
", white mobility: " + Chess.AI.getMobilityValue(this.chessPosition, Chess.PieceColor.WHITE) +
", black mobility: " + Chess.AI.getMobilityValue(this.chessPosition, Chess.PieceColor.BLACK) +
", white location: " + Chess.AI.getPieceSquareValue(this.chessPosition, Chess.PieceColor.WHITE) +
", black location: " + Chess.AI.getPieceSquareValue(this.chessPosition, Chess.PieceColor.BLACK));
Chess.UI.clearMoving();
Chess.UI.clearDragging();
this.updatePieces();
var status = this.chessPosition.getStatus();
if (status === Chess.Position.Status.NORMAL && this.chessPosition.getTurnColor() === Chess.PieceColor.BLACK) {
this.doComputerMove();
} else {
this.updateMoves();
$("#dim").css({"display": "none"});
if (status === Chess.Position.Status.CHECKMATE) {
$("#moves").append("&#35;<br/>" + (this.chessPosition.getTurnColor() ? "1-0" : "0-1"));
} else if (status !== Chess.Position.Status.NORMAL) {
$("#moves").append("&frac12;-&frac12;");
}
}
};
/**
* Creates a new chessboard and sets up the game at the standard chess initial position.
*/
function makeChessGame() {
Chess.UI.makeBoard();
var ui = new Chess.UI;
ui.updateChessPosition();
}

144
src/zobrist.js

@ -0,0 +1,144 @@
"use strict";
/**
* Chess.Zobrist is a 64 bit Zobrist hash value.
* Updates to the value can be easily reverted by making the same update again.
* The idea is to maintain a hash of the chess position, so that detecting repeating states and caching information about seen states is faster.
* The 64 bit value is implemented as two 32 bit integers, similarly to Bitboard.js.
* @constructor
* @param {number} low lower 32 bits of the 64 bit value
* @param {number} high upper 32 bits of the 64 bit value
* @see http://goo.gl/WNBQp (Zobrist hashing)
*/
Chess.Zobrist = function(low, high) {
/** @type {number} */
this.low = low >>> 0;
/** @type {number} */
this.high = high >>> 0;
};
/** @enum {number} */
Chess.Zobrist.Count = {
TURN: 1 * 2,
PIECE_COLOR_SQUARE: 6 * 2 * 64 * 2,
CASTLING_RIGHTS: 16 * 2,
EN_PASSANT_FILE: 8 * 2
};
/** @enum {number} */
Chess.Zobrist.Position = {
TURN: 0,
PIECE_COLOR_SQUARE: Chess.Zobrist.Count.TURN,
CASTLING_RIGHTS: Chess.Zobrist.Count.TURN + Chess.Zobrist.Count.PIECE_COLOR_SQUARE,
EN_PASSANT_FILE: Chess.Zobrist.Count.TURN + Chess.Zobrist.Count.PIECE_COLOR_SQUARE + Chess.Zobrist.Count.CASTLING_RIGHTS
};
/**
* @param {number} count
* @return {!Array.<number>}
*/
Chess.Zobrist.createRandomValues = function(count) {
var a = [];
for (var i = 0; i < count; ++i) {
a.push((1 + Math.random() * 0xFFFFFFFF) >>> 0);
}
return a;
};
/**
* @const
* @type {!Array.<number>}
*/
Chess.Zobrist.RANDOM_VALUES = Chess.Zobrist.createRandomValues(Chess.Zobrist.Position.EN_PASSANT_FILE + Chess.Zobrist.Count.EN_PASSANT_FILE);
/**
* @return {!Chess.Zobrist}
*/
Chess.Zobrist.prototype.dup = function() {
return new Chess.Zobrist(this.low, this.high);
};
/**
* @return {number} 32 bit key
*/
Chess.Zobrist.prototype.getHashKey = function() {
return (this.low ^ this.high) >>> 0;
};
/**
* @param {!Chess.Zobrist} zobrist
* @return {boolean}
*/
Chess.Zobrist.prototype.isEqual = function(zobrist) {
return (this.low === zobrist.low && this.high === zobrist.high);
};
/**
* Xors Chess.Zobrist.RANDOM_VALUES[position .. position + RANDOM_VALUES_PER_ITEM] into this Zobrist hash key.
* @param {number} position
* @return {!Chess.Zobrist} this
*/
Chess.Zobrist.prototype.update = function(position) {
this.low = (this.low ^ Chess.Zobrist.RANDOM_VALUES[position]) >>> 0;
this.high = (this.high ^ Chess.Zobrist.RANDOM_VALUES[position + 1]) >>> 0;
return this;
};
/**
* @return {!Chess.Zobrist} this
*/
Chess.Zobrist.prototype.updateTurn = function() {
return this.update(Chess.Zobrist.Position.TURN);
};
/**
* @param {!Chess.Piece} piece
* @param {!Chess.PieceColor} color
* @param {number} index 0-63
* @return {!Chess.Zobrist} this
*/
Chess.Zobrist.prototype.updatePieceColorSquare = function(piece, color, index) {
return this.update(Chess.Zobrist.Position.PIECE_COLOR_SQUARE + piece + color * 6 + index * 6 * 2);
};
/**
* @param {!Chess.Piece} piece
* @param {!Chess.PieceColor} color
* @param {!Chess.Bitboard} bitboard
* @return {!Chess.Zobrist} this
*/
Chess.Zobrist.prototype.updatePieceColorBitboard = function(piece, color, bitboard) {
var bb = bitboard.dup();
while (!bb.isEmpty()) {
this.updatePieceColorSquare(piece, color, bb.extractLowestBitPosition());
}
return this;
};
/**
* @param {number} castlingRights 0-15
* @return {!Chess.Zobrist} this
*/
Chess.Zobrist.prototype.updateCastlingRights = function(castlingRights) {
return this.update(Chess.Zobrist.Position.CASTLING_RIGHTS + castlingRights);
};
/**
* @param {number} enPassantFile 0-7
* @return {!Chess.Zobrist} this
*/
Chess.Zobrist.prototype.updateEnPassantFile = function(enPassantFile) {
return this.update(Chess.Zobrist.Position.EN_PASSANT_FILE + enPassantFile);
};
/**
* @param {number} enPassantSquare 0-63
* @return {!Chess.Zobrist} this
*/
Chess.Zobrist.prototype.updateEnPassantSquare = function(enPassantSquare) {
if (enPassantSquare >= 0) {
return this.updateEnPassantFile(Chess.getFile(enPassantSquare));
}
return this;
};
Loading…
Cancel
Save