25 changed files with 5937 additions and 2 deletions
@ -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) |
|||
|
@ -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 |
|||
|
@ -0,0 +1,3 @@ |
|||
"use strict"; |
|||
|
|||
window["makeChessGame"] = makeChessGame; |
File diff suppressed because it is too large
@ -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) {}; |
@ -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 |
@ -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 |
@ -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; |
|||
} |
@ -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> |
After Width: | Height: | Size: 22 KiB |
@ -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("#<br/>"+(a.a.b?"1-0":"0-1")):0!==b&&$("#moves").append("½-½")}};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)};})(); |
@ -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; |
|||
}; |
@ -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); |
|||