├── img └── chesspieces │ └── wikipedia │ ├── bB.png │ ├── bK.png │ ├── bN.png │ ├── bP.png │ ├── bQ.png │ ├── bR.png │ ├── wB.png │ ├── wK.png │ ├── wN.png │ ├── wP.png │ ├── wQ.png │ └── wR.png ├── package.json ├── server.js ├── css ├── chessboard-0.3.0.min.css └── chessboard-0.3.0.css ├── test.html └── js ├── chessboard-0.3.0.min.js ├── chess.min.js ├── chessboard-0.3.0.js └── chess.js /img/chesspieces/wikipedia/bB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bs/jschesstest/master/img/chesspieces/wikipedia/bB.png -------------------------------------------------------------------------------- /img/chesspieces/wikipedia/bK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bs/jschesstest/master/img/chesspieces/wikipedia/bK.png -------------------------------------------------------------------------------- /img/chesspieces/wikipedia/bN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bs/jschesstest/master/img/chesspieces/wikipedia/bN.png -------------------------------------------------------------------------------- /img/chesspieces/wikipedia/bP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bs/jschesstest/master/img/chesspieces/wikipedia/bP.png -------------------------------------------------------------------------------- /img/chesspieces/wikipedia/bQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bs/jschesstest/master/img/chesspieces/wikipedia/bQ.png -------------------------------------------------------------------------------- /img/chesspieces/wikipedia/bR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bs/jschesstest/master/img/chesspieces/wikipedia/bR.png -------------------------------------------------------------------------------- /img/chesspieces/wikipedia/wB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bs/jschesstest/master/img/chesspieces/wikipedia/wB.png -------------------------------------------------------------------------------- /img/chesspieces/wikipedia/wK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bs/jschesstest/master/img/chesspieces/wikipedia/wK.png -------------------------------------------------------------------------------- /img/chesspieces/wikipedia/wN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bs/jschesstest/master/img/chesspieces/wikipedia/wN.png -------------------------------------------------------------------------------- /img/chesspieces/wikipedia/wP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bs/jschesstest/master/img/chesspieces/wikipedia/wP.png -------------------------------------------------------------------------------- /img/chesspieces/wikipedia/wQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bs/jschesstest/master/img/chesspieces/wikipedia/wQ.png -------------------------------------------------------------------------------- /img/chesspieces/wikipedia/wR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bs/jschesstest/master/img/chesspieces/wikipedia/wR.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jschesstest", 3 | "version": "0.0.1", 4 | "description": "testing chessboardjs", 5 | "dependencies": { 6 | "express": "3.x" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var app = express(); 3 | 4 | // Configure express server 5 | app.configure(function() { 6 | app.use(express.static(__dirname + '/')); 7 | }); 8 | 9 | // Routes 10 | app.get('/', function (req, res) { 11 | res.sendfile(__dirname + '/test.html'); 12 | }); 13 | 14 | app.listen(3000); 15 | -------------------------------------------------------------------------------- /css/chessboard-0.3.0.min.css: -------------------------------------------------------------------------------- 1 | /*! chessboard.js v0.3.0 | (c) 2013 Chris Oakman | MIT License chessboardjs.com/license */ 2 | .clearfix-7da63{clear:both}.board-b72b1{border:2px solid #404040;-moz-box-sizing:content-box;box-sizing:content-box}.square-55d63{float:left;position:relative;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.white-1e1d7{background-color:#f0d9b5;color:#b58863}.black-3c85d{background-color:#b58863;color:#f0d9b5}.highlight1-32417,.highlight2-9c5d2{-webkit-box-shadow:inset 0 0 3px 3px yellow;-moz-box-shadow:inset 0 0 3px 3px yellow;box-shadow:inset 0 0 3px 3px yellow}.notation-322f9{cursor:default;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;position:absolute}.alpha-d2270{bottom:1px;right:3px}.numeric-fc462{top:2px;left:2px} -------------------------------------------------------------------------------- /test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 36 | 37 | 38 |
39 | 40 | 41 | -------------------------------------------------------------------------------- /css/chessboard-0.3.0.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * chessboard.js v0.3.0 3 | * 4 | * Copyright 2013 Chris Oakman 5 | * Released under the MIT license 6 | * https://github.com/oakmac/chessboardjs/blob/master/LICENSE 7 | * 8 | * Date: 10 Aug 2013 9 | */ 10 | 11 | /* clearfix */ 12 | .clearfix-7da63 { 13 | clear: both; 14 | } 15 | 16 | /* board */ 17 | .board-b72b1 { 18 | border: 2px solid #404040; 19 | -moz-box-sizing: content-box; 20 | box-sizing: content-box; 21 | } 22 | 23 | /* square */ 24 | .square-55d63 { 25 | float: left; 26 | position: relative; 27 | 28 | /* disable any native browser highlighting */ 29 | -webkit-touch-callout: none; 30 | -webkit-user-select: none; 31 | -khtml-user-select: none; 32 | -moz-user-select: none; 33 | -ms-user-select: none; 34 | user-select: none; 35 | } 36 | 37 | /* white square */ 38 | .white-1e1d7 { 39 | background-color: #f0d9b5; 40 | color: #b58863; 41 | } 42 | 43 | /* black square */ 44 | .black-3c85d { 45 | background-color: #b58863; 46 | color: #f0d9b5; 47 | } 48 | 49 | /* highlighted square */ 50 | .highlight1-32417, .highlight2-9c5d2 { 51 | -webkit-box-shadow: inset 0 0 3px 3px yellow; 52 | -moz-box-shadow: inset 0 0 3px 3px yellow; 53 | box-shadow: inset 0 0 3px 3px yellow; 54 | } 55 | 56 | /* notation */ 57 | .notation-322f9 { 58 | cursor: default; 59 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 60 | font-size: 14px; 61 | position: absolute; 62 | } 63 | .alpha-d2270 { 64 | bottom: 1px; 65 | right: 3px; 66 | } 67 | .numeric-fc462 { 68 | top: 2px; 69 | left: 2px; 70 | } -------------------------------------------------------------------------------- /js/chessboard-0.3.0.min.js: -------------------------------------------------------------------------------- 1 | /*! chessboard.js v0.3.0 | (c) 2013 Chris Oakman | MIT License chessboardjs.com/license */ 2 | (function(){function l(f){return"string"!==typeof f?!1:-1!==f.search(/^[a-h][1-8]$/)}function Q(f){if("string"!==typeof f)return!1;f=f.replace(/ .+$/,"");f=f.split("/");if(8!==f.length)return!1;for(var b=0;8>b;b++)if(""===f[b]||8m;m++){for(var l=f[m].split(""),r=0,w=0;wm;m++){for(var l=0;8>l;l++){var r=B[l]+n;!0===f.hasOwnProperty(r)?(r=f[r].split(""),r="w"===r[0]?r[1].toUpperCase(): 4 | r[1].toLowerCase(),b+=r):b+="1"}7!==m&&(b+="/");n--}b=b.replace(/11111111/g,"8");b=b.replace(/1111111/g,"7");b=b.replace(/111111/g,"6");b=b.replace(/11111/g,"5");b=b.replace(/1111/g,"4");b=b.replace(/111/g,"3");return b=b.replace(/11/g,"2")}var B="abcdefgh".split("");window.ChessBoard=window.ChessBoard||function(f,b){function n(){return"xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx".replace(/x/g,function(a){return(16*Math.random()|0).toString(16)})}function m(a){return JSON.parse(JSON.stringify(a))}function X(a){a= 5 | a.split(".");return{major:parseInt(a[0],10),minor:parseInt(a[1],10),patch:parseInt(a[2],10)}}function r(a,e,c){if(!0===b.hasOwnProperty("showErrors")&&!1!==b.showErrors){var d="ChessBoard Error "+a+": "+e;"console"===b.showErrors&&"object"===typeof console&&"function"===typeof console.log?(console.log(d),2<=arguments.length&&console.log(c)):"alert"===b.showErrors?(c&&(d+="\n\n"+JSON.stringify(c)),window.alert(d)):"function"===typeof b.showErrors&&b.showErrors(a,e,c)}}function w(a){return"fast"=== 6 | a||"slow"===a?!0:parseInt(a,10)+""!==a+""?!1:0<=a}function I(){for(var a=0;a=b;b++){var c=B[a]+b;s[c]=c+"-"+n()}b="KQRBNP".split("");for(a=0;a';!0===b.sparePieces&&(a+='
');a+='
';!0===b.sparePieces&&(a+='
'); 7 | return a+""}function A(a){"black"!==a&&(a="white");var e="",c=m(B),d=8;"black"===a&&(c.reverse(),d=1);for(var C="white",f=0;8>f;f++){for(var e=e+('
'),k=0;8>k;k++){var g=c[k]+d,e=e+('
');if(!0===b.showNotation){if("white"===a&&1===d||"black"===a&&8===d)e+='
'+c[k]+"
";0===k&&(e+='
'+d+"
")}e+="
";C="white"===C?"black":"white"}e+='
';C="white"===C?"black":"white";"white"===a?d--:d++}return e}function Y(a){if("function"===typeof b.pieceTheme)return b.pieceTheme(a);if("string"===typeof b.pieceTheme)return b.pieceTheme.replace(/{piece}/g,a);r(8272,"Unable to build image source for cfg.pieceTheme.");return""}function D(a,b,c){var d=''}function N(a){var b="wK wQ wR wB wN wP".split(" ");"black"===a&&(b="bK bQ bR bB bN bP".split(" "));a="";for(var c=0;c=d?c:d}function la(a){for(var b=[],c=0;8>c;c++)for(var d=0;8>d;d++){var g=B[c]+(d+1);a!==g&&b.push({square:g,distance:ka(a,g)})}b.sort(function(a,b){return a.distance-b.distance});a=[];for(c=0;c=d.left&&a=d.top&&b=a)p=0;else{for(a-=1;0!==a%8&&0=1E8*b.major+1E4*b.minor+b.patch;return a?!0:(window.alert("ChessBoard Error 1005: Unable to find a valid version of jQuery. Please include jQuery 1.7.0 or higher on the page.\n\nExiting..."), 29 | !1)}()){if("string"===typeof b||!0===F(b))b={position:b};"black"!==b.orientation&&(b.orientation="white");u=b.orientation;!1!==b.showNotation&&(b.showNotation=!0);!0!==b.draggable&&(b.draggable=!1);"trash"!==b.dropOffBoard&&(b.dropOffBoard="snapback");!0!==b.sparePieces&&(b.sparePieces=!1);!0===b.sparePieces&&(b.draggable=!0);if(!0!==b.hasOwnProperty("pieceTheme")||"string"!==typeof b.pieceTheme&&"function"!==typeof b.pieceTheme)b.pieceTheme="img/chesspieces/wikipedia/{piece}.png";if(!0!==b.hasOwnProperty("appearSpeed")|| 30 | !0!==w(b.appearSpeed))b.appearSpeed=200;if(!0!==b.hasOwnProperty("moveSpeed")||!0!==w(b.moveSpeed))b.moveSpeed=200;if(!0!==b.hasOwnProperty("snapbackSpeed")||!0!==w(b.snapbackSpeed))b.snapbackSpeed=50;if(!0!==b.hasOwnProperty("snapSpeed")||!0!==w(b.snapSpeed))b.snapSpeed=25;if(!0!==b.hasOwnProperty("trashSpeed")||!0!==w(b.trashSpeed))b.trashSpeed=100;!0===b.hasOwnProperty("position")&&("start"===b.position?g=m(fa):!0===Q(b.position)?g=K(b.position):!0===F(b.position)?g=m(b.position):r(7263,"Invalid value passed to config.position.", 31 | b.position));W=!0}W&&(I(),ya(),xa());return q};window.ChessBoard.fenToObj=K;window.ChessBoard.objToFen=L})(); -------------------------------------------------------------------------------- /js/chess.min.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /*! Copyright (c) 2013, Jeff Hlywa (jhlywa@gmail.com) 3 | * Released under the BSD license 4 | * https://github.com/jhlywa/chess.js/blob/master/LICENSE 5 | */ 6 | ;var Chess=function(ao){var p="b";var k="w";var E=-1;var X="p";var S="n";var s="b";var H="r";var j="q";var ad="k";var f="pnbrqkPNBRQK";var an="rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";var B=["1-0","0-1","1/2-1/2","*"];var u={b:[16,32,17,15],w:[-16,-32,-17,-15]};var J={n:[-18,-33,-31,-14,18,33,31,14],b:[-17,-15,17,15],r:[-16,1,16,-1],q:[-17,-16,-15,1,17,16,15,-1],k:[-17,-16,-15,1,17,16,15,-1]};var ac=[20,0,0,0,0,0,0,24,0,0,0,0,0,0,20,0,0,20,0,0,0,0,0,24,0,0,0,0,0,20,0,0,0,0,20,0,0,0,0,24,0,0,0,0,20,0,0,0,0,0,0,20,0,0,0,24,0,0,0,20,0,0,0,0,0,0,0,0,20,0,0,24,0,0,20,0,0,0,0,0,0,0,0,0,0,20,2,24,2,20,0,0,0,0,0,0,0,0,0,0,0,2,53,56,53,2,0,0,0,0,0,0,24,24,24,24,24,24,56,0,56,24,24,24,24,24,24,0,0,0,0,0,0,2,53,56,53,2,0,0,0,0,0,0,0,0,0,0,0,20,2,24,2,20,0,0,0,0,0,0,0,0,0,0,20,0,0,24,0,0,20,0,0,0,0,0,0,0,0,20,0,0,0,24,0,0,0,20,0,0,0,0,0,0,20,0,0,0,0,24,0,0,0,0,20,0,0,0,0,20,0,0,0,0,0,24,0,0,0,0,0,20,0,0,20,0,0,0,0,0,0,24,0,0,0,0,0,0,20];var o=[17,0,0,0,0,0,0,16,0,0,0,0,0,0,15,0,0,17,0,0,0,0,0,16,0,0,0,0,0,15,0,0,0,0,17,0,0,0,0,16,0,0,0,0,15,0,0,0,0,0,0,17,0,0,0,16,0,0,0,15,0,0,0,0,0,0,0,0,17,0,0,16,0,0,15,0,0,0,0,0,0,0,0,0,0,17,0,16,0,15,0,0,0,0,0,0,0,0,0,0,0,0,17,16,15,0,0,0,0,0,0,0,1,1,1,1,1,1,1,0,-1,-1,-1,-1,-1,-1,-1,0,0,0,0,0,0,0,-15,-16,-17,0,0,0,0,0,0,0,0,0,0,0,0,-15,0,-16,0,-17,0,0,0,0,0,0,0,0,0,0,-15,0,0,-16,0,0,-17,0,0,0,0,0,0,0,0,-15,0,0,0,-16,0,0,0,-17,0,0,0,0,0,0,-15,0,0,0,0,-16,0,0,0,0,-17,0,0,0,0,-15,0,0,0,0,0,-16,0,0,0,0,0,-17,0,0,-15,0,0,0,0,0,0,-16,0,0,0,0,0,0,-17];var av={p:0,n:1,b:2,r:3,q:4,k:5};var aj={NORMAL:"n",CAPTURE:"c",BIG_PAWN:"b",EP_CAPTURE:"e",PROMOTION:"p",KSIDE_CASTLE:"k",QSIDE_CASTLE:"q"};var D={NORMAL:1,CAPTURE:2,BIG_PAWN:4,EP_CAPTURE:8,PROMOTION:16,KSIDE_CASTLE:32,QSIDE_CASTLE:64};var W=7;var T=6;var Q=5;var P=4;var O=3;var N=2;var M=1;var K=0;var v={a8:0,b8:1,c8:2,d8:3,e8:4,f8:5,g8:6,h8:7,a7:16,b7:17,c7:18,d7:19,e7:20,f7:21,g7:22,h7:23,a6:32,b6:33,c6:34,d6:35,e6:36,f6:37,g6:38,h6:39,a5:48,b5:49,c5:50,d5:51,e5:52,f5:53,g5:54,h5:55,a4:64,b4:65,c4:66,d4:67,e4:68,f4:69,g4:70,h4:71,a3:80,b3:81,c3:82,d3:83,e3:84,f3:85,g3:86,h3:87,a2:96,b2:97,c2:98,d2:99,e2:100,f2:101,g2:102,h2:103,a1:112,b1:113,c1:114,d1:115,e1:116,f1:117,g1:118,h1:119};var V={w:[{square:v.a1,flag:D.QSIDE_CASTLE},{square:v.h1,flag:D.KSIDE_CASTLE}],b:[{square:v.a8,flag:D.QSIDE_CASTLE},{square:v.h8,flag:D.KSIDE_CASTLE}]};var C=new Array(128);var ak={w:E,b:E};var t=k;var d={w:0,b:0};var x=E;var n=0;var g=1;var Z=[];var ab={};if(typeof ao==="undefined"){h(an)}else{h(ao)}function ae(){C=new Array(128);ak={w:E,b:E};t=k;d={w:0,b:0};x=E;n=0;g=1;Z=[];ab={};q(z())}function Y(){h(an)}function h(ay){var aD=ay.split(/\s+/);var aw=aD[0];var aC=0;var aB=f+"12345678/";if(!aa(ay).valid){return false}ae();for(var az=0;az-1){d.w|=D.KSIDE_CASTLE}if(aD[2].indexOf("Q")>-1){d.w|=D.QSIDE_CASTLE}if(aD[2].indexOf("k")>-1){d.b|=D.KSIDE_CASTLE}if(aD[2].indexOf("q")>-1){d.b|=D.QSIDE_CASTLE}x=(aD[3]==="-")?E:v[aD[3]];n=parseInt(aD[4],10);g=parseInt(aD[5],10);q(z());return true}function aa(ay){var aD={0:"No errors.",1:"FEN string must contain six space-delimited fields.",2:"6th field (move number) must be a positive integer.",3:"5th field (half move counter) must be a non-negative integer.",4:"4th field (en-passant square) is invalid.",5:"3rd field (castling availability) is invalid.",6:"2nd field (side to move) is invalid.",7:"1st field (piece positions) does not contain 8 '/'-delimited rows.",8:"1st field (piece positions) is invalid [consecutive numbers].",9:"1st field (piece positions) is invalid [invalid piece].",10:"1st field (piece positions) is invalid [row too large]."};var aC=ay.split(/\s+/);if(aC.length!==6){return{valid:false,error_number:1,error:aD[1]}}if(isNaN(aC[5])||(parseInt(aC[5],10)<=0)){return{valid:false,error_number:2,error:aD[2]}}if(isNaN(aC[4])||(parseInt(aC[4],10)<0)){return{valid:false,error_number:3,error:aD[3]}}if(!/^(-|[abcdefgh][36])$/.test(aC[3])){return{valid:false,error_number:4,error:aD[4]}}if(!/^(KQ?k?q?|Qk?q?|kq?|q|-)$/.test(aC[2])){return{valid:false,error_number:5,error:aD[5]}}if(!/^(w|b)$/.test(aC[1])){return{valid:false,error_number:6,error:aD[6]}}var aB=aC[0].split("/");if(aB.length!==8){return{valid:false,error_number:7,error:aD[7]}}for(var az=0;az0){ay+=aC;aC=0}var ax=C[aA].color;var aB=C[aA].type;ay+=(ax===k)?aB.toUpperCase():aB.toLowerCase()}if((aA+1)&136){if(aC>0){ay+=aC}if(aA!==v.h1){ay+="/"}aC=0;aA+=8}}var az="";if(d[k]&D.KSIDE_CASTLE){az+="K"}if(d[k]&D.QSIDE_CASTLE){az+="Q"}if(d[p]&D.KSIDE_CASTLE){az+="k"}if(d[p]&D.QSIDE_CASTLE){az+="q"}az=az||"-";var aw=(x===E)?"-":m(x);return[ay,t,az,aw,n,g].join(" ")}function at(aw){for(var ax=0;ax0){return}if(aw!==an){ab.SetUp=aw;ab.FEN="1"}else{delete ab.SetUp;delete ab.FEN}}function ag(ax){var aw=C[v[ax]];return(aw)?{type:aw.type,color:aw.color}:null}function aq(aw,ay){if(!("type" in aw&&"color" in aw)){return false}if(f.indexOf(aw.type.toLowerCase())===-1){return false}if(!(ay in v)){return false}var ax=v[ay];C[ax]={type:aw.type,color:aw.color};if(aw.type===ad){ak[aw.color]=ax}q(z());return true}function F(ax){var aw=ag(ax);C[v[ax]]=null;if(aw&&aw.type===ad){ak[aw.color]=E}q(z());return aw}function am(ay,aB,aA,ax,az){var aw={color:t,from:aB,to:aA,flags:ax,piece:ay[aB].type};if(az){aw.flags|=D.PROMOTION;aw.promotion=az}if(ay[aA]){aw.captured=ay[aA].type}else{if(ax&D.EP_CAPTURE){aw.captured=X}}return aw}function b(ay){function aN(aT,aR,aW,aV,aQ){if(aT[aW].type===X&&(U(aV)===K||U(aV)===W)){var aU=[j,H,s,S];for(var aS=0,aP=aU.length;aS0){if(aE.color===k){return true}}else{if(aE.color===p){return true}}continue}if(aE.type==="n"||aE.type==="k"){return true}var ay=o[aC];var az=aA+ay;var aB=false;while(az!==aD){if(C[az]!=null){aB=true;break}az+=ay}if(!aB){return true}}}return false}function L(aw){return af(au(aw),ak[aw])}function R(){return L(t)}function I(){return R()&&b().length===0}function ah(){return !R()&&b().length===0}function A(){var aC={};var aB=[];var ax=0;var aD=0;for(var ay=v.a8;ay<=v.h1;ay++){aD=(aD+1)%2;if(ay&136){ay+=7;continue}var aA=C[ay];if(aA){aC[aA.type]=(aA.type in aC)?aC[aA.type]+1:1;if(aA.type===s){aB.push(aD)}ax++}}if(ax===2){return true}else{if(ax===3&&(aC[s]===1||aC[S]===1)){return true}else{if(ax===aC[s]+2){var az=0;var aw=aB.length;for(var ay=0;ay=3){aA=true}if(!ay.length){break}al(ay.pop())}return aA}function e(aw){Z.push({move:aw,kings:{b:ak.b,w:ak.w},turn:t,castling:{b:d.b,w:d.w},ep_square:x,half_moves:n,move_number:g})}function al(ax){var aB=t;var aC=au(aB);e(ax);C[ax.to]=C[ax.from];C[ax.from]=null;if(ax.flags&D.EP_CAPTURE){if(t===p){C[ax.to-16]=null}else{C[ax.to+16]=null}}if(ax.flags&D.PROMOTION){C[ax.to]={type:ax.promotion,color:aB}}if(C[ax.to].type===ad){ak[C[ax.to].color]=ax.to;if(ax.flags&D.KSIDE_CASTLE){var az=ax.to-1;var aA=ax.to+1;C[az]=C[aA];C[aA]=null}else{if(ax.flags&D.QSIDE_CASTLE){var az=ax.to+1;var aA=ax.to-2;C[az]=C[aA];C[aA]=null}}d[aB]=""}if(d[aB]){for(var ay=0,aw=V[aB].length;ay0){if(aD>0&&aB>0){return m(aE)}else{if(aB>0){return m(aE).charAt(1)}else{return m(aE).charAt(0)}}}return""}function a(){var az=" +------------------------+\n";for(var ax=v.a8;ax<=v.h1;ax++){if(y(ax)===0){az+=" 87654321"[U(ax)]+" |"}if(C[ax]==null){az+=" . "}else{var ay=C[ax].type;var aw=C[ax].color;var aA=(aw===k)?ay.toUpperCase():ay.toLowerCase();az+=" "+aA+" "}if((ax+1)&136){az+="|\n";ax+=8}}az+=" +------------------------+\n";az+=" a b c d e f g h\n";return az}function U(aw){return aw>>4}function y(aw){return aw&15}function m(aw){var ay=y(aw),ax=U(aw);return"abcdefgh".substring(ay,ay+1)+"87654321".substring(ax,ax+1)}function au(aw){return aw===k?p:k}function r(aw){return"0123456789".indexOf(aw)!==-1}function l(az){var ay=w(az);ay.san=ar(ay);ay.to=m(ay.to);ay.from=m(ay.from);var ax="";for(var aw in D){if(D[aw]&ay.flags){ax+=aj[aw]}}ay.flags=ax;return ay}function w(ay){var aw=(ay instanceof Array)?[]:{};for(var ax in ay){if(typeof ax==="object"){aw[ax]=w(ay[ax])}else{aw[ax]=ay[ax]}}return aw}function ap(aw){return aw.replace(/^\s+|\s+$/g,"")}function ai(aC){var ax=b({legal:false});var az=0;var ay=t;for(var aA=0,aw=ax.length;aA0){var aB=ai(aC-1);az+=aB}else{az++}}G()}return az}return{WHITE:k,BLACK:p,PAWN:X,KNIGHT:S,BISHOP:s,ROOK:H,QUEEN:j,KING:ad,SQUARES:(function(){var ax=[];for(var aw=v.a8;aw<=v.h1;aw++){if(aw&136){aw+=7;continue}ax.push(m(aw))}return ax})(),FLAGS:aj,load:function(aw){return h(aw)},reset:function(){return Y()},moves:function(az){var ay=b(az);var ax=[];for(var aA=0,aw=ay.length;aA=100||ah()||A()||i()},insufficient_material:function(){return A()},in_threefold_repetition:function(){return i()},game_over:function(){return n>=100||I()||ah()||A()||i()},validate_fen:function(aw){return aa(aw)},fen:function(){return z()},pgn:function(aG){var az=(typeof aG==="object"&&typeof aG.newline_char==="string")?aG.newline_char:"\n";var aF=(typeof aG==="object"&&typeof aG.max_width==="number")?aG.max_width:0;var aH=[];var aD=false;for(var aB in ab){aH.push("["+aB+' "'+ab[aB]+'"]'+az);aD=true}if(aD&&Z.length){aH.push(az)}var aE=[];while(Z.length>0){aE.push(G())}var ax=[];var ay="";var aw=1;while(aE.length>0){var aA=aE.pop();if(aw===1&&aA.color==="b"){ay="1. ...";aw++}else{if(aA.color==="w"){if(ay.length){ax.push(ay)}ay=aw+".";aw++}}ay=ay+" "+ar(aA);al(aA)}if(ay.length){ax.push(ay)}if(typeof ab.Result!=="undefined"){ax.push(ab.Result)}if(aF===0){return aH.join("")+ax.join(" ")}var aC=0;for(var aB=0;aBaF&&aB!==0){if(aH[aH.length-1]===" "){aH.pop()}aH.push(az);aC=0}else{if(aB!==0){aH.push(" ");aC++}}aH.push(ax[aB]);aC+=ax[aB].length}return aH.join("")},load_pgn:function(aG,aK){function aL(aM){return aM.replace(/\n/g,"\\n").replace(/\r/g,"\\r")}function az(aO){var aV,aW,aN=D.NORMAL,aM;var aP=aO.match(/^([NBKRQ])?([abcdefgh12345678][12345678]?)?(x)?([abcdefgh][12345678])(=?[NBRQ])?/);if(aO.slice(0,5)==="O-O-O"){aW=ak[t];aV=aW-2;aN=D.QSIDE_CASTLE}else{if(aO.slice(0,3)==="O-O"){aW=ak[t];aV=aW+2;aN=D.KSIDE_CASTLE}else{if(aP&&aP[1]){var aY=aP[1].toLowerCase();if(aP[3]){aN=D.CAPTURE}aV=v[aP[4]];for(var aR=0,aS=J[aY].length;aR=0)){aW=aX}break}if(aY==="n"||aY==="k"){break}}}}else{if(aP){if(aP[3]){aV=v[aP[4]];for(var aR=2;aR<4;aR++){var aX=aV-u[t][aR];if(aX&136){continue}if(C[aX]!=null&&C[aX].color===t&&m(aX)[0]===aP[2]){aW=aX}}if(C[aV]){aN=D.CAPTURE}else{aN=D.EP_CAPTURE}}else{aV=v[aO.slice(0,2)];var aT=aV-u[t][0],aU=C[aT];if(aU&&aU.type===X&&aU.color===t){aW=aT}else{aT=aV-u[t][1];aU=C[aT];if(aU&&aU.type===X&&aU.color===t){aW=aT;aN=D.BIG_PAWN}}}if(aP[5]){if(typeof aP[5][1]=="undefined"){aM=aP[5][0].toLowerCase()}else{aM=aP[5][1].toLowerCase()}}}}}}if(aW>=0&&aV>=0&&aN){return am(C,aW,aV,aN,aM)}else{if(aO.length>0){}}}function aE(aM){return az(ap(aM))}function aw(aM){var aO=false;for(var aN in aM){aO=true}return aO}function aI(aT,aM){var aR=(typeof aM==="object"&&typeof aM.newline_char==="string")?aM.newline_char:"\r?\n";var aQ={};var aS=aT.split(aR);var aO="";var aP="";for(var aN=0;aN0){aQ[aO]=aP}}return aQ}var aB=(typeof aK==="object"&&typeof aK.newline_char==="string")?aK.newline_char:"\r?\n";var aH=new RegExp("^(\\[(.|"+aL(aB)+")*\\])("+aL(aB)+")*1.("+aL(aB)+"|.)*$","g");var aD=aG.replace(aH,"$1");if(aD[0]!=="["){aD=""}Y();var aA=aI(aD,aK);for(var aJ in aA){at([aJ,aA[aJ]])}var ay=aG.replace(aD,"").replace(new RegExp(aL(aB),"g")," ");ay=ay.replace(/(\{[^}]+\})+?/g,"");ay=ay.replace(/\d+\./g,"");var ax=ap(ay).split(new RegExp(/\s+/));ax=ax.join(",").replace(/,,+/g,",").split(",");var aC="";for(var aF=0;aF-1){if(aw(ab)&&typeof ab.Result==="undefined"){at(["Result",aC])}}else{aC=aE(aC);if(aC==null){return false}else{al(aC)}}return true},header:function(){return at(arguments)},ascii:function(){return a()},turn:function(){return t},move:function(aA){var az=null;var ay=b();if(typeof aA==="string"){for(var aB=0,ax=ay.length;aB0){aA.push(G())}while(aA.length>0){var aw=aA.pop();if(ax){az.push(l(aw))}else{az.push(ar(aw))}al(aw)}return az}}};if(typeof exports!=="undefined"){exports.Chess=Chess}; -------------------------------------------------------------------------------- /js/chessboard-0.3.0.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * chessboard.js v0.3.0 3 | * 4 | * Copyright 2013 Chris Oakman 5 | * Released under the MIT license 6 | * http://chessboardjs.com/license 7 | * 8 | * Date: 10 Aug 2013 9 | */ 10 | 11 | // start anonymous scope 12 | ;(function() { 13 | 'use strict'; 14 | 15 | //------------------------------------------------------------------------------ 16 | // Chess Util Functions 17 | //------------------------------------------------------------------------------ 18 | var COLUMNS = 'abcdefgh'.split(''); 19 | 20 | function validMove(move) { 21 | // move should be a string 22 | if (typeof move !== 'string') return false; 23 | 24 | // move should be in the form of "e2-e4", "f6-d5" 25 | var tmp = move.split('-'); 26 | if (tmp.length !== 2) return false; 27 | 28 | return (validSquare(tmp[0]) === true && validSquare(tmp[1]) === true); 29 | } 30 | 31 | function validSquare(square) { 32 | if (typeof square !== 'string') return false; 33 | return (square.search(/^[a-h][1-8]$/) !== -1); 34 | } 35 | 36 | function validPieceCode(code) { 37 | if (typeof code !== 'string') return false; 38 | return (code.search(/^[bw][KQRNBP]$/) !== -1); 39 | } 40 | 41 | // TODO: this whole function could probably be replaced with a single regex 42 | function validFen(fen) { 43 | if (typeof fen !== 'string') return false; 44 | 45 | // cut off any move, castling, etc info from the end 46 | // we're only interested in position information 47 | fen = fen.replace(/ .+$/, ''); 48 | 49 | // FEN should be 8 sections separated by slashes 50 | var chunks = fen.split('/'); 51 | if (chunks.length !== 8) return false; 52 | 53 | // check the piece sections 54 | for (var i = 0; i < 8; i++) { 55 | if (chunks[i] === '' || 56 | chunks[i].length > 8 || 57 | chunks[i].search(/[^kqrbnpKQRNBP1-8]/) !== -1) { 58 | return false; 59 | } 60 | } 61 | 62 | return true; 63 | } 64 | 65 | function validPositionObject(pos) { 66 | if (typeof pos !== 'object') return false; 67 | 68 | for (var i in pos) { 69 | if (pos.hasOwnProperty(i) !== true) continue; 70 | 71 | if (validSquare(i) !== true || validPieceCode(pos[i]) !== true) { 72 | return false; 73 | } 74 | } 75 | 76 | return true; 77 | } 78 | 79 | // convert FEN piece code to bP, wK, etc 80 | function fenToPieceCode(piece) { 81 | // black piece 82 | if (piece.toLowerCase() === piece) { 83 | return 'b' + piece.toUpperCase(); 84 | } 85 | 86 | // white piece 87 | return 'w' + piece.toUpperCase(); 88 | } 89 | 90 | // convert bP, wK, etc code to FEN structure 91 | function pieceCodeToFen(piece) { 92 | var tmp = piece.split(''); 93 | 94 | // white piece 95 | if (tmp[0] === 'w') { 96 | return tmp[1].toUpperCase(); 97 | } 98 | 99 | // black piece 100 | return tmp[1].toLowerCase(); 101 | } 102 | 103 | // convert FEN string to position object 104 | // returns false if the FEN string is invalid 105 | function fenToObj(fen) { 106 | if (validFen(fen) !== true) { 107 | return false; 108 | } 109 | 110 | // cut off any move, castling, etc info from the end 111 | // we're only interested in position information 112 | fen = fen.replace(/ .+$/, ''); 113 | 114 | var rows = fen.split('/'); 115 | var position = {}; 116 | 117 | var currentRow = 8; 118 | for (var i = 0; i < 8; i++) { 119 | var row = rows[i].split(''); 120 | var colIndex = 0; 121 | 122 | // loop through each character in the FEN section 123 | for (var j = 0; j < row.length; j++) { 124 | // number / empty squares 125 | if (row[j].search(/[1-8]/) !== -1) { 126 | var emptySquares = parseInt(row[j], 10); 127 | colIndex += emptySquares; 128 | } 129 | // piece 130 | else { 131 | var square = COLUMNS[colIndex] + currentRow; 132 | position[square] = fenToPieceCode(row[j]); 133 | colIndex++; 134 | } 135 | } 136 | 137 | currentRow--; 138 | } 139 | 140 | return position; 141 | } 142 | 143 | // position object to FEN string 144 | // returns false if the obj is not a valid position object 145 | function objToFen(obj) { 146 | if (validPositionObject(obj) !== true) { 147 | return false; 148 | } 149 | 150 | var fen = ''; 151 | 152 | var currentRow = 8; 153 | for (var i = 0; i < 8; i++) { 154 | for (var j = 0; j < 8; j++) { 155 | var square = COLUMNS[j] + currentRow; 156 | 157 | // piece exists 158 | if (obj.hasOwnProperty(square) === true) { 159 | fen += pieceCodeToFen(obj[square]); 160 | } 161 | 162 | // empty space 163 | else { 164 | fen += '1'; 165 | } 166 | } 167 | 168 | if (i !== 7) { 169 | fen += '/'; 170 | } 171 | 172 | currentRow--; 173 | } 174 | 175 | // squeeze the numbers together 176 | // haha, I love this solution... 177 | fen = fen.replace(/11111111/g, '8'); 178 | fen = fen.replace(/1111111/g, '7'); 179 | fen = fen.replace(/111111/g, '6'); 180 | fen = fen.replace(/11111/g, '5'); 181 | fen = fen.replace(/1111/g, '4'); 182 | fen = fen.replace(/111/g, '3'); 183 | fen = fen.replace(/11/g, '2'); 184 | 185 | return fen; 186 | } 187 | 188 | window['ChessBoard'] = window['ChessBoard'] || function(containerElOrId, cfg) { 189 | 'use strict'; 190 | 191 | cfg = cfg || {}; 192 | 193 | //------------------------------------------------------------------------------ 194 | // Constants 195 | //------------------------------------------------------------------------------ 196 | 197 | var MINIMUM_JQUERY_VERSION = '1.7.0', 198 | START_FEN = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR', 199 | START_POSITION = fenToObj(START_FEN); 200 | 201 | // use unique class names to prevent clashing with anything else on the page 202 | // and simplify selectors 203 | var CSS = { 204 | alpha: 'alpha-d2270', 205 | black: 'black-3c85d', 206 | board: 'board-b72b1', 207 | chessboard: 'chessboard-63f37', 208 | clearfix: 'clearfix-7da63', 209 | highlight1: 'highlight1-32417', 210 | highlight2: 'highlight2-9c5d2', 211 | notation: 'notation-322f9', 212 | numeric: 'numeric-fc462', 213 | piece: 'piece-417db', 214 | row: 'row-5277c', 215 | sparePieces: 'spare-pieces-7492f', 216 | sparePiecesBottom: 'spare-pieces-bottom-ae20f', 217 | sparePiecesTop: 'spare-pieces-top-4028b', 218 | square: 'square-55d63', 219 | white: 'white-1e1d7' 220 | }; 221 | 222 | //------------------------------------------------------------------------------ 223 | // Module Scope Variables 224 | //------------------------------------------------------------------------------ 225 | 226 | // DOM elements 227 | var containerEl, 228 | boardEl, 229 | draggedPieceEl, 230 | sparePiecesTopEl, 231 | sparePiecesBottomEl; 232 | 233 | // constructor return object 234 | var widget = {}; 235 | 236 | //------------------------------------------------------------------------------ 237 | // Stateful 238 | //------------------------------------------------------------------------------ 239 | 240 | var ANIMATION_HAPPENING = false, 241 | BOARD_BORDER_SIZE = 2, 242 | CURRENT_ORIENTATION = 'white', 243 | CURRENT_POSITION = {}, 244 | SQUARE_SIZE, 245 | DRAGGED_PIECE, 246 | DRAGGED_PIECE_LOCATION, 247 | DRAGGED_PIECE_SOURCE, 248 | DRAGGING_A_PIECE = false, 249 | SPARE_PIECE_ELS_IDS = {}, 250 | SQUARE_ELS_IDS = {}, 251 | SQUARE_ELS_OFFSETS; 252 | 253 | //------------------------------------------------------------------------------ 254 | // JS Util Functions 255 | //------------------------------------------------------------------------------ 256 | 257 | // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript 258 | function createId() { 259 | return 'xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx'.replace(/x/g, function(c) { 260 | var r = Math.random() * 16 | 0; 261 | return r.toString(16); 262 | }); 263 | } 264 | 265 | function deepCopy(thing) { 266 | return JSON.parse(JSON.stringify(thing)); 267 | } 268 | 269 | function parseSemVer(version) { 270 | var tmp = version.split('.'); 271 | return { 272 | major: parseInt(tmp[0], 10), 273 | minor: parseInt(tmp[1], 10), 274 | patch: parseInt(tmp[2], 10) 275 | }; 276 | } 277 | 278 | // returns true if version is >= minimum 279 | function compareSemVer(version, minimum) { 280 | version = parseSemVer(version); 281 | minimum = parseSemVer(minimum); 282 | 283 | var versionNum = (version.major * 10000 * 10000) + 284 | (version.minor * 10000) + version.patch; 285 | var minimumNum = (minimum.major * 10000 * 10000) + 286 | (minimum.minor * 10000) + minimum.patch; 287 | 288 | return (versionNum >= minimumNum); 289 | } 290 | 291 | //------------------------------------------------------------------------------ 292 | // Validation / Errors 293 | //------------------------------------------------------------------------------ 294 | 295 | function error(code, msg, obj) { 296 | // do nothing if showErrors is not set 297 | if (cfg.hasOwnProperty('showErrors') !== true || 298 | cfg.showErrors === false) { 299 | return; 300 | } 301 | 302 | var errorText = 'ChessBoard Error ' + code + ': ' + msg; 303 | 304 | // print to console 305 | if (cfg.showErrors === 'console' && 306 | typeof console === 'object' && 307 | typeof console.log === 'function') { 308 | console.log(errorText); 309 | if (arguments.length >= 2) { 310 | console.log(obj); 311 | } 312 | return; 313 | } 314 | 315 | // alert errors 316 | if (cfg.showErrors === 'alert') { 317 | if (obj) { 318 | errorText += '\n\n' + JSON.stringify(obj); 319 | } 320 | window.alert(errorText); 321 | return; 322 | } 323 | 324 | // custom function 325 | if (typeof cfg.showErrors === 'function') { 326 | cfg.showErrors(code, msg, obj); 327 | } 328 | } 329 | 330 | // check dependencies 331 | function checkDeps() { 332 | // if containerId is a string, it must be the ID of a DOM node 333 | if (typeof containerElOrId === 'string') { 334 | // cannot be empty 335 | if (containerElOrId === '') { 336 | window.alert('ChessBoard Error 1001: ' + 337 | 'The first argument to ChessBoard() cannot be an empty string.' + 338 | '\n\nExiting...'); 339 | return false; 340 | } 341 | 342 | // make sure the container element exists in the DOM 343 | var el = document.getElementById(containerElOrId); 344 | if (! el) { 345 | window.alert('ChessBoard Error 1002: Element with id "' + 346 | containerElOrId + '" does not exist in the DOM.' + 347 | '\n\nExiting...'); 348 | return false; 349 | } 350 | 351 | // set the containerEl 352 | containerEl = $(el); 353 | } 354 | 355 | // else it must be something that becomes a jQuery collection 356 | // with size 1 357 | // ie: a single DOM node or jQuery object 358 | else { 359 | containerEl = $(containerElOrId); 360 | 361 | if (containerEl.length !== 1) { 362 | window.alert('ChessBoard Error 1003: The first argument to ' + 363 | 'ChessBoard() must be an ID or a single DOM node.' + 364 | '\n\nExiting...'); 365 | return false; 366 | } 367 | } 368 | 369 | // JSON must exist 370 | if (! window.JSON || 371 | typeof JSON.stringify !== 'function' || 372 | typeof JSON.parse !== 'function') { 373 | window.alert('ChessBoard Error 1004: JSON does not exist. ' + 374 | 'Please include a JSON polyfill.\n\nExiting...'); 375 | return false; 376 | } 377 | 378 | // check for a compatible version of jQuery 379 | if (! (typeof window.$ && $.fn && $.fn.jquery && 380 | compareSemVer($.fn.jquery, MINIMUM_JQUERY_VERSION) === true)) { 381 | window.alert('ChessBoard Error 1005: Unable to find a valid version ' + 382 | 'of jQuery. Please include jQuery ' + MINIMUM_JQUERY_VERSION + ' or ' + 383 | 'higher on the page.\n\nExiting...'); 384 | return false; 385 | } 386 | 387 | return true; 388 | } 389 | 390 | function validAnimationSpeed(speed) { 391 | if (speed === 'fast' || speed === 'slow') { 392 | return true; 393 | } 394 | 395 | if ((parseInt(speed, 10) + '') !== (speed + '')) { 396 | return false; 397 | } 398 | 399 | return (speed >= 0); 400 | } 401 | 402 | // validate config / set default options 403 | function expandConfig() { 404 | if (typeof cfg === 'string' || validPositionObject(cfg) === true) { 405 | cfg = { 406 | position: cfg 407 | }; 408 | } 409 | 410 | // default for orientation is white 411 | if (cfg.orientation !== 'black') { 412 | cfg.orientation = 'white'; 413 | } 414 | CURRENT_ORIENTATION = cfg.orientation; 415 | 416 | // default for showNotation is true 417 | if (cfg.showNotation !== false) { 418 | cfg.showNotation = true; 419 | } 420 | 421 | // default for draggable is false 422 | if (cfg.draggable !== true) { 423 | cfg.draggable = false; 424 | } 425 | 426 | // default for dropOffBoard is 'snapback' 427 | if (cfg.dropOffBoard !== 'trash') { 428 | cfg.dropOffBoard = 'snapback'; 429 | } 430 | 431 | // default for sparePieces is false 432 | if (cfg.sparePieces !== true) { 433 | cfg.sparePieces = false; 434 | } 435 | 436 | // draggable must be true if sparePieces is enabled 437 | if (cfg.sparePieces === true) { 438 | cfg.draggable = true; 439 | } 440 | 441 | // default piece theme is wikipedia 442 | if (cfg.hasOwnProperty('pieceTheme') !== true || 443 | (typeof cfg.pieceTheme !== 'string' && 444 | typeof cfg.pieceTheme !== 'function')) { 445 | cfg.pieceTheme = 'img/chesspieces/wikipedia/{piece}.png'; 446 | } 447 | 448 | // animation speeds 449 | if (cfg.hasOwnProperty('appearSpeed') !== true || 450 | validAnimationSpeed(cfg.appearSpeed) !== true) { 451 | cfg.appearSpeed = 200; 452 | } 453 | if (cfg.hasOwnProperty('moveSpeed') !== true || 454 | validAnimationSpeed(cfg.moveSpeed) !== true) { 455 | cfg.moveSpeed = 200; 456 | } 457 | if (cfg.hasOwnProperty('snapbackSpeed') !== true || 458 | validAnimationSpeed(cfg.snapbackSpeed) !== true) { 459 | cfg.snapbackSpeed = 50; 460 | } 461 | if (cfg.hasOwnProperty('snapSpeed') !== true || 462 | validAnimationSpeed(cfg.snapSpeed) !== true) { 463 | cfg.snapSpeed = 25; 464 | } 465 | if (cfg.hasOwnProperty('trashSpeed') !== true || 466 | validAnimationSpeed(cfg.trashSpeed) !== true) { 467 | cfg.trashSpeed = 100; 468 | } 469 | 470 | // make sure position is valid 471 | if (cfg.hasOwnProperty('position') === true) { 472 | if (cfg.position === 'start') { 473 | CURRENT_POSITION = deepCopy(START_POSITION); 474 | } 475 | 476 | else if (validFen(cfg.position) === true) { 477 | CURRENT_POSITION = fenToObj(cfg.position); 478 | } 479 | 480 | else if (validPositionObject(cfg.position) === true) { 481 | CURRENT_POSITION = deepCopy(cfg.position); 482 | } 483 | 484 | else { 485 | error(7263, 'Invalid value passed to config.position.', cfg.position); 486 | } 487 | } 488 | 489 | return true; 490 | } 491 | 492 | //------------------------------------------------------------------------------ 493 | // DOM Misc 494 | //------------------------------------------------------------------------------ 495 | 496 | // calculates square size based on the width of the container 497 | // got a little CSS black magic here, so let me explain: 498 | // get the width of the container element (could be anything), reduce by 1 for 499 | // fudge factor, and then keep reducing until we find an exact mod 8 for 500 | // our square size 501 | function calculateSquareSize() { 502 | var containerWidth = parseInt(containerEl.css('width'), 10); 503 | 504 | // defensive, prevent infinite loop 505 | if (! containerWidth || containerWidth <= 0) { 506 | return 0; 507 | } 508 | 509 | // pad one pixel 510 | var boardWidth = containerWidth - 1; 511 | 512 | while (boardWidth % 8 !== 0 && boardWidth > 0) { 513 | boardWidth--; 514 | } 515 | 516 | return (boardWidth / 8); 517 | } 518 | 519 | // create random IDs for elements 520 | function createElIds() { 521 | // squares on the board 522 | for (var i = 0; i < COLUMNS.length; i++) { 523 | for (var j = 1; j <= 8; j++) { 524 | var square = COLUMNS[i] + j; 525 | SQUARE_ELS_IDS[square] = square + '-' + createId(); 526 | } 527 | } 528 | 529 | // spare pieces 530 | var pieces = 'KQRBNP'.split(''); 531 | for (var i = 0; i < pieces.length; i++) { 532 | var whitePiece = 'w' + pieces[i]; 533 | var blackPiece = 'b' + pieces[i]; 534 | SPARE_PIECE_ELS_IDS[whitePiece] = whitePiece + '-' + createId(); 535 | SPARE_PIECE_ELS_IDS[blackPiece] = blackPiece + '-' + createId(); 536 | } 537 | } 538 | 539 | //------------------------------------------------------------------------------ 540 | // Markup Building 541 | //------------------------------------------------------------------------------ 542 | 543 | function buildBoardContainer() { 544 | var html = '
'; 545 | 546 | if (cfg.sparePieces === true) { 547 | html += '
'; 549 | } 550 | 551 | html += '
'; 552 | 553 | if (cfg.sparePieces === true) { 554 | html += '
'; 556 | } 557 | 558 | html += '
'; 559 | 560 | return html; 561 | } 562 | 563 | /* 564 | var buildSquare = function(color, size, id) { 565 | var html = '
'; 568 | 569 | if (cfg.showNotation === true) { 570 | 571 | } 572 | 573 | html += '
'; 574 | 575 | return html; 576 | }; 577 | */ 578 | 579 | function buildBoard(orientation) { 580 | if (orientation !== 'black') { 581 | orientation = 'white'; 582 | } 583 | 584 | var html = ''; 585 | 586 | // algebraic notation / orientation 587 | var alpha = deepCopy(COLUMNS); 588 | var row = 8; 589 | if (orientation === 'black') { 590 | alpha.reverse(); 591 | row = 1; 592 | } 593 | 594 | var squareColor = 'white'; 595 | for (var i = 0; i < 8; i++) { 596 | html += '
'; 597 | for (var j = 0; j < 8; j++) { 598 | var square = alpha[j] + row; 599 | 600 | html += '
'; 605 | 606 | if (cfg.showNotation === true) { 607 | // alpha notation 608 | if ((orientation === 'white' && row === 1) || 609 | (orientation === 'black' && row === 8)) { 610 | html += '
' + 611 | alpha[j] + '
'; 612 | } 613 | 614 | // numeric notation 615 | if (j === 0) { 616 | html += '
' + 617 | row + '
'; 618 | } 619 | } 620 | 621 | html += '
'; // end .square 622 | 623 | squareColor = (squareColor === 'white' ? 'black' : 'white'); 624 | } 625 | html += '
'; 626 | 627 | squareColor = (squareColor === 'white' ? 'black' : 'white'); 628 | 629 | if (orientation === 'white') { 630 | row--; 631 | } 632 | else { 633 | row++; 634 | } 635 | } 636 | 637 | return html; 638 | } 639 | 640 | function buildPieceImgSrc(piece) { 641 | if (typeof cfg.pieceTheme === 'function') { 642 | return cfg.pieceTheme(piece); 643 | } 644 | 645 | if (typeof cfg.pieceTheme === 'string') { 646 | return cfg.pieceTheme.replace(/{piece}/g, piece); 647 | } 648 | 649 | // NOTE: this should never happen 650 | error(8272, 'Unable to build image source for cfg.pieceTheme.'); 651 | return ''; 652 | } 653 | 654 | function buildPiece(piece, hidden, id) { 655 | var html = ''; 668 | 669 | return html; 670 | } 671 | 672 | function buildSparePieces(color) { 673 | var pieces = ['wK', 'wQ', 'wR', 'wB', 'wN', 'wP']; 674 | if (color === 'black') { 675 | pieces = ['bK', 'bQ', 'bR', 'bB', 'bN', 'bP']; 676 | } 677 | 678 | var html = ''; 679 | for (var i = 0; i < pieces.length; i++) { 680 | html += buildPiece(pieces[i], false, SPARE_PIECE_ELS_IDS[pieces[i]]); 681 | } 682 | 683 | return html; 684 | } 685 | 686 | //------------------------------------------------------------------------------ 687 | // Animations 688 | //------------------------------------------------------------------------------ 689 | 690 | function animateSquareToSquare(src, dest, piece, completeFn) { 691 | // get information about the source and destination squares 692 | var srcSquareEl = $('#' + SQUARE_ELS_IDS[src]); 693 | var srcSquarePosition = srcSquareEl.offset(); 694 | var destSquareEl = $('#' + SQUARE_ELS_IDS[dest]); 695 | var destSquarePosition = destSquareEl.offset(); 696 | 697 | // create the animated piece and absolutely position it 698 | // over the source square 699 | var animatedPieceId = createId(); 700 | $('body').append(buildPiece(piece, true, animatedPieceId)); 701 | var animatedPieceEl = $('#' + animatedPieceId); 702 | animatedPieceEl.css({ 703 | display: '', 704 | position: 'absolute', 705 | top: srcSquarePosition.top, 706 | left: srcSquarePosition.left 707 | }); 708 | 709 | // remove original piece from source square 710 | srcSquareEl.find('.' + CSS.piece).remove(); 711 | 712 | // on complete 713 | var complete = function() { 714 | // add the "real" piece to the destination square 715 | destSquareEl.append(buildPiece(piece)); 716 | 717 | // remove the animated piece 718 | animatedPieceEl.remove(); 719 | 720 | // run complete function 721 | if (typeof completeFn === 'function') { 722 | completeFn(); 723 | } 724 | }; 725 | 726 | // animate the piece to the destination square 727 | var opts = { 728 | duration: cfg.moveSpeed, 729 | complete: complete 730 | }; 731 | animatedPieceEl.animate(destSquarePosition, opts); 732 | } 733 | 734 | function animateSparePieceToSquare(piece, dest, completeFn) { 735 | var srcOffset = $('#' + SPARE_PIECE_ELS_IDS[piece]).offset(); 736 | var destSquareEl = $('#' + SQUARE_ELS_IDS[dest]); 737 | var destOffset = destSquareEl.offset(); 738 | 739 | // create the animate piece 740 | var pieceId = createId(); 741 | $('body').append(buildPiece(piece, true, pieceId)); 742 | var animatedPieceEl = $('#' + pieceId); 743 | animatedPieceEl.css({ 744 | display: '', 745 | position: 'absolute', 746 | left: srcOffset.left, 747 | top: srcOffset.top 748 | }); 749 | 750 | // on complete 751 | var complete = function() { 752 | // add the "real" piece to the destination square 753 | destSquareEl.find('.' + CSS.piece).remove(); 754 | destSquareEl.append(buildPiece(piece)); 755 | 756 | // remove the animated piece 757 | animatedPieceEl.remove(); 758 | 759 | // run complete function 760 | if (typeof completeFn === 'function') { 761 | completeFn(); 762 | } 763 | }; 764 | 765 | // animate the piece to the destination square 766 | var opts = { 767 | duration: cfg.moveSpeed, 768 | complete: complete 769 | }; 770 | animatedPieceEl.animate(destOffset, opts); 771 | } 772 | 773 | // execute an array of animations 774 | function doAnimations(a, oldPos, newPos) { 775 | ANIMATION_HAPPENING = true; 776 | 777 | var numFinished = 0; 778 | function onFinish() { 779 | numFinished++; 780 | 781 | // exit if all the animations aren't finished 782 | if (numFinished !== a.length) return; 783 | 784 | drawPositionInstant(); 785 | ANIMATION_HAPPENING = false; 786 | 787 | // run their onMoveEnd function 788 | if (cfg.hasOwnProperty('onMoveEnd') === true && 789 | typeof cfg.onMoveEnd === 'function') { 790 | cfg.onMoveEnd(deepCopy(oldPos), deepCopy(newPos)); 791 | } 792 | } 793 | 794 | for (var i = 0; i < a.length; i++) { 795 | // clear a piece 796 | if (a[i].type === 'clear') { 797 | $('#' + SQUARE_ELS_IDS[a[i].square] + ' .' + CSS.piece) 798 | .fadeOut(cfg.trashSpeed, onFinish); 799 | } 800 | 801 | // add a piece (no spare pieces) 802 | if (a[i].type === 'add' && cfg.sparePieces !== true) { 803 | $('#' + SQUARE_ELS_IDS[a[i].square]) 804 | .append(buildPiece(a[i].piece, true)) 805 | .find('.' + CSS.piece) 806 | .fadeIn(cfg.appearSpeed, onFinish); 807 | } 808 | 809 | // add a piece from a spare piece 810 | if (a[i].type === 'add' && cfg.sparePieces === true) { 811 | animateSparePieceToSquare(a[i].piece, a[i].square, onFinish); 812 | } 813 | 814 | // move a piece 815 | if (a[i].type === 'move') { 816 | animateSquareToSquare(a[i].source, a[i].destination, a[i].piece, 817 | onFinish); 818 | } 819 | } 820 | } 821 | 822 | // returns the distance between two squares 823 | function squareDistance(s1, s2) { 824 | s1 = s1.split(''); 825 | var s1x = COLUMNS.indexOf(s1[0]) + 1; 826 | var s1y = parseInt(s1[1], 10); 827 | 828 | s2 = s2.split(''); 829 | var s2x = COLUMNS.indexOf(s2[0]) + 1; 830 | var s2y = parseInt(s2[1], 10); 831 | 832 | var xDelta = Math.abs(s1x - s2x); 833 | var yDelta = Math.abs(s1y - s2y); 834 | 835 | if (xDelta >= yDelta) return xDelta; 836 | return yDelta; 837 | } 838 | 839 | // returns an array of closest squares from square 840 | function createRadius(square) { 841 | var squares = []; 842 | 843 | // calculate distance of all squares 844 | for (var i = 0; i < 8; i++) { 845 | for (var j = 0; j < 8; j++) { 846 | var s = COLUMNS[i] + (j + 1); 847 | 848 | // skip the square we're starting from 849 | if (square === s) continue; 850 | 851 | squares.push({ 852 | square: s, 853 | distance: squareDistance(square, s) 854 | }); 855 | } 856 | } 857 | 858 | // sort by distance 859 | squares.sort(function(a, b) { 860 | return a.distance - b.distance; 861 | }); 862 | 863 | // just return the square code 864 | var squares2 = []; 865 | for (var i = 0; i < squares.length; i++) { 866 | squares2.push(squares[i].square); 867 | } 868 | 869 | return squares2; 870 | } 871 | 872 | // returns the square of the closest instance of piece 873 | // returns false if no instance of piece is found in position 874 | function findClosestPiece(position, piece, square) { 875 | // create array of closest squares from square 876 | var closestSquares = createRadius(square); 877 | 878 | // search through the position in order of distance for the piece 879 | for (var i = 0; i < closestSquares.length; i++) { 880 | var s = closestSquares[i]; 881 | 882 | if (position.hasOwnProperty(s) === true && position[s] === piece) { 883 | return s; 884 | } 885 | } 886 | 887 | return false; 888 | } 889 | 890 | // calculate an array of animations that need to happen in order to get 891 | // from pos1 to pos2 892 | function calculateAnimations(pos1, pos2) { 893 | // make copies of both 894 | pos1 = deepCopy(pos1); 895 | pos2 = deepCopy(pos2); 896 | 897 | var animations = []; 898 | var squaresMovedTo = {}; 899 | 900 | // remove pieces that are the same in both positions 901 | for (var i in pos2) { 902 | if (pos2.hasOwnProperty(i) !== true) continue; 903 | 904 | if (pos1.hasOwnProperty(i) === true && pos1[i] === pos2[i]) { 905 | delete pos1[i]; 906 | delete pos2[i]; 907 | } 908 | } 909 | 910 | // find all the "move" animations 911 | for (var i in pos2) { 912 | if (pos2.hasOwnProperty(i) !== true) continue; 913 | 914 | var closestPiece = findClosestPiece(pos1, pos2[i], i); 915 | if (closestPiece !== false) { 916 | animations.push({ 917 | type: 'move', 918 | source: closestPiece, 919 | destination: i, 920 | piece: pos2[i] 921 | }); 922 | 923 | delete pos1[closestPiece]; 924 | delete pos2[i]; 925 | squaresMovedTo[i] = true; 926 | } 927 | } 928 | 929 | // add pieces to pos2 930 | for (var i in pos2) { 931 | if (pos2.hasOwnProperty(i) !== true) continue; 932 | 933 | animations.push({ 934 | type: 'add', 935 | square: i, 936 | piece: pos2[i] 937 | }) 938 | 939 | delete pos2[i]; 940 | } 941 | 942 | // clear pieces from pos1 943 | for (var i in pos1) { 944 | if (pos1.hasOwnProperty(i) !== true) continue; 945 | 946 | // do not clear a piece if it is on a square that is the result 947 | // of a "move", ie: a piece capture 948 | if (squaresMovedTo.hasOwnProperty(i) === true) continue; 949 | 950 | animations.push({ 951 | type: 'clear', 952 | square: i, 953 | piece: pos1[i] 954 | }); 955 | 956 | delete pos1[i]; 957 | } 958 | 959 | return animations; 960 | } 961 | 962 | //------------------------------------------------------------------------------ 963 | // Control Flow 964 | //------------------------------------------------------------------------------ 965 | 966 | function drawPositionInstant() { 967 | // clear the board 968 | boardEl.find('.' + CSS.piece).remove(); 969 | 970 | // add the pieces 971 | for (var i in CURRENT_POSITION) { 972 | if (CURRENT_POSITION.hasOwnProperty(i) !== true) continue; 973 | 974 | $('#' + SQUARE_ELS_IDS[i]).append(buildPiece(CURRENT_POSITION[i])); 975 | } 976 | } 977 | 978 | function drawBoard() { 979 | boardEl.html(buildBoard(CURRENT_ORIENTATION)); 980 | drawPositionInstant(); 981 | 982 | if (cfg.sparePieces === true) { 983 | if (CURRENT_ORIENTATION === 'white') { 984 | sparePiecesTopEl.html(buildSparePieces('black')); 985 | sparePiecesBottomEl.html(buildSparePieces('white')); 986 | } 987 | else { 988 | sparePiecesTopEl.html(buildSparePieces('white')); 989 | sparePiecesBottomEl.html(buildSparePieces('black')); 990 | } 991 | } 992 | } 993 | 994 | // given a position and a set of moves, return a new position 995 | // with the moves executed 996 | function calculatePositionFromMoves(position, moves) { 997 | position = deepCopy(position); 998 | 999 | for (var i in moves) { 1000 | if (moves.hasOwnProperty(i) !== true) continue; 1001 | 1002 | // skip the move if the position doesn't have a piece on the source square 1003 | if (position.hasOwnProperty(i) !== true) continue; 1004 | 1005 | var piece = position[i]; 1006 | delete position[i]; 1007 | position[moves[i]] = piece; 1008 | } 1009 | 1010 | return position; 1011 | } 1012 | 1013 | function setCurrentPosition(position) { 1014 | var oldPos = deepCopy(CURRENT_POSITION); 1015 | var newPos = deepCopy(position); 1016 | var oldFen = objToFen(oldPos); 1017 | var newFen = objToFen(newPos); 1018 | 1019 | // do nothing if no change in position 1020 | if (oldFen === newFen) return; 1021 | 1022 | // run their onChange function 1023 | if (cfg.hasOwnProperty('onChange') === true && 1024 | typeof cfg.onChange === 'function') { 1025 | cfg.onChange(oldPos, newPos); 1026 | } 1027 | 1028 | // update state 1029 | CURRENT_POSITION = position; 1030 | } 1031 | 1032 | function isXYOnSquare(x, y) { 1033 | for (var i in SQUARE_ELS_OFFSETS) { 1034 | if (SQUARE_ELS_OFFSETS.hasOwnProperty(i) !== true) continue; 1035 | 1036 | var s = SQUARE_ELS_OFFSETS[i]; 1037 | if (x >= s.left && x < s.left + SQUARE_SIZE && 1038 | y >= s.top && y < s.top + SQUARE_SIZE) { 1039 | return i; 1040 | } 1041 | } 1042 | 1043 | return 'offboard'; 1044 | } 1045 | 1046 | // records the XY coords of every square into memory 1047 | function captureSquareOffsets() { 1048 | SQUARE_ELS_OFFSETS = {}; 1049 | 1050 | for (var i in SQUARE_ELS_IDS) { 1051 | if (SQUARE_ELS_IDS.hasOwnProperty(i) !== true) continue; 1052 | 1053 | SQUARE_ELS_OFFSETS[i] = $('#' + SQUARE_ELS_IDS[i]).offset(); 1054 | } 1055 | } 1056 | 1057 | function removeSquareHighlights() { 1058 | boardEl.find('.' + CSS.square) 1059 | .removeClass(CSS.highlight1 + ' ' + CSS.highlight2); 1060 | } 1061 | 1062 | function snapbackDraggedPiece() { 1063 | // there is no "snapback" for spare pieces 1064 | if (DRAGGED_PIECE_SOURCE === 'spare') { 1065 | trashDraggedPiece(); 1066 | return; 1067 | } 1068 | 1069 | removeSquareHighlights(); 1070 | 1071 | // animation complete 1072 | function complete() { 1073 | drawPositionInstant(); 1074 | draggedPieceEl.css('display', 'none'); 1075 | 1076 | // run their onSnapbackEnd function 1077 | if (cfg.hasOwnProperty('onSnapbackEnd') === true && 1078 | typeof cfg.onSnapbackEnd === 'function') { 1079 | cfg.onSnapbackEnd(DRAGGED_PIECE, DRAGGED_PIECE_SOURCE, 1080 | deepCopy(CURRENT_POSITION), CURRENT_ORIENTATION); 1081 | } 1082 | } 1083 | 1084 | // get source square position 1085 | var sourceSquarePosition = 1086 | $('#' + SQUARE_ELS_IDS[DRAGGED_PIECE_SOURCE]).offset(); 1087 | 1088 | // animate the piece to the target square 1089 | var opts = { 1090 | duration: cfg.snapbackSpeed, 1091 | complete: complete 1092 | }; 1093 | draggedPieceEl.animate(sourceSquarePosition, opts); 1094 | 1095 | // set state 1096 | DRAGGING_A_PIECE = false; 1097 | } 1098 | 1099 | function trashDraggedPiece() { 1100 | removeSquareHighlights(); 1101 | 1102 | // remove the source piece 1103 | var newPosition = deepCopy(CURRENT_POSITION); 1104 | delete newPosition[DRAGGED_PIECE_SOURCE]; 1105 | setCurrentPosition(newPosition); 1106 | 1107 | // redraw the position 1108 | drawPositionInstant(); 1109 | 1110 | // hide the dragged piece 1111 | draggedPieceEl.fadeOut(cfg.trashSpeed); 1112 | 1113 | // set state 1114 | DRAGGING_A_PIECE = false; 1115 | } 1116 | 1117 | function dropDraggedPieceOnSquare(square) { 1118 | removeSquareHighlights(); 1119 | 1120 | // update position 1121 | var newPosition = deepCopy(CURRENT_POSITION); 1122 | delete newPosition[DRAGGED_PIECE_SOURCE]; 1123 | newPosition[square] = DRAGGED_PIECE; 1124 | setCurrentPosition(newPosition); 1125 | 1126 | // get target square information 1127 | var targetSquarePosition = $('#' + SQUARE_ELS_IDS[square]).offset(); 1128 | 1129 | // animation complete 1130 | var complete = function() { 1131 | drawPositionInstant(); 1132 | draggedPieceEl.css('display', 'none'); 1133 | 1134 | // execute their onSnapEnd function 1135 | if (cfg.hasOwnProperty('onSnapEnd') === true && 1136 | typeof cfg.onSnapEnd === 'function') { 1137 | cfg.onSnapEnd(DRAGGED_PIECE_SOURCE, square, DRAGGED_PIECE); 1138 | } 1139 | }; 1140 | 1141 | // snap the piece to the target square 1142 | var opts = { 1143 | duration: cfg.snapSpeed, 1144 | complete: complete 1145 | }; 1146 | draggedPieceEl.animate(targetSquarePosition, opts); 1147 | 1148 | // set state 1149 | DRAGGING_A_PIECE = false; 1150 | } 1151 | 1152 | function beginDraggingPiece(source, piece, x, y) { 1153 | // run their custom onDragStart function 1154 | // their custom onDragStart function can cancel drag start 1155 | if (typeof cfg.onDragStart === 'function' && 1156 | cfg.onDragStart(source, piece, 1157 | deepCopy(CURRENT_POSITION), CURRENT_ORIENTATION) === false) { 1158 | return; 1159 | } 1160 | 1161 | // set state 1162 | DRAGGING_A_PIECE = true; 1163 | DRAGGED_PIECE = piece; 1164 | DRAGGED_PIECE_SOURCE = source; 1165 | 1166 | // if the piece came from spare pieces, location is offboard 1167 | if (source === 'spare') { 1168 | DRAGGED_PIECE_LOCATION = 'offboard'; 1169 | } 1170 | else { 1171 | DRAGGED_PIECE_LOCATION = source; 1172 | } 1173 | 1174 | // capture the x, y coords of all squares in memory 1175 | captureSquareOffsets(); 1176 | 1177 | // create the dragged piece 1178 | draggedPieceEl.attr('src', buildPieceImgSrc(piece)) 1179 | .css({ 1180 | display: '', 1181 | position: 'absolute', 1182 | left: x - (SQUARE_SIZE / 2), 1183 | top: y - (SQUARE_SIZE / 2) 1184 | }); 1185 | 1186 | if (source !== 'spare') { 1187 | // highlight the source square and hide the piece 1188 | $('#' + SQUARE_ELS_IDS[source]).addClass(CSS.highlight1) 1189 | .find('.' + CSS.piece).css('display', 'none'); 1190 | } 1191 | } 1192 | 1193 | function updateDraggedPiece(x, y) { 1194 | // put the dragged piece over the mouse cursor 1195 | draggedPieceEl.css({ 1196 | left: x - (SQUARE_SIZE / 2), 1197 | top: y - (SQUARE_SIZE / 2) 1198 | }); 1199 | 1200 | // get location 1201 | var location = isXYOnSquare(x, y); 1202 | 1203 | // do nothing if the location has not changed 1204 | if (location === DRAGGED_PIECE_LOCATION) return; 1205 | 1206 | // remove highlight from previous square 1207 | if (validSquare(DRAGGED_PIECE_LOCATION) === true) { 1208 | $('#' + SQUARE_ELS_IDS[DRAGGED_PIECE_LOCATION]) 1209 | .removeClass(CSS.highlight2); 1210 | } 1211 | 1212 | // add highlight to new square 1213 | if (validSquare(location) === true) { 1214 | $('#' + SQUARE_ELS_IDS[location]).addClass(CSS.highlight2); 1215 | } 1216 | 1217 | // run onDragMove 1218 | if (typeof cfg.onDragMove === 'function') { 1219 | cfg.onDragMove(location, DRAGGED_PIECE_LOCATION, 1220 | DRAGGED_PIECE_SOURCE, DRAGGED_PIECE, 1221 | deepCopy(CURRENT_POSITION), CURRENT_ORIENTATION); 1222 | } 1223 | 1224 | // update state 1225 | DRAGGED_PIECE_LOCATION = location; 1226 | } 1227 | 1228 | function stopDraggedPiece(location) { 1229 | // determine what the action should be 1230 | var action = 'drop'; 1231 | if (location === 'offboard' && cfg.dropOffBoard === 'snapback') { 1232 | action = 'snapback'; 1233 | } 1234 | if (location === 'offboard' && cfg.dropOffBoard === 'trash') { 1235 | action = 'trash'; 1236 | } 1237 | 1238 | // run their onDrop function, which can potentially change the drop action 1239 | if (cfg.hasOwnProperty('onDrop') === true && 1240 | typeof cfg.onDrop === 'function') { 1241 | var newPosition = deepCopy(CURRENT_POSITION); 1242 | 1243 | // source piece is a spare piece and position is off the board 1244 | //if (DRAGGED_PIECE_SOURCE === 'spare' && location === 'offboard') {...} 1245 | // position has not changed; do nothing 1246 | 1247 | // source piece is a spare piece and position is on the board 1248 | if (DRAGGED_PIECE_SOURCE === 'spare' && validSquare(location) === true) { 1249 | // add the piece to the board 1250 | newPosition[location] = DRAGGED_PIECE; 1251 | } 1252 | 1253 | // source piece was on the board and position is off the board 1254 | if (validSquare(DRAGGED_PIECE_SOURCE) === true && location === 'offboard') { 1255 | // remove the piece from the board 1256 | delete newPosition[DRAGGED_PIECE_SOURCE]; 1257 | } 1258 | 1259 | // source piece was on the board and position is on the board 1260 | if (validSquare(DRAGGED_PIECE_SOURCE) === true && 1261 | validSquare(location) === true) { 1262 | // move the piece 1263 | delete newPosition[DRAGGED_PIECE_SOURCE]; 1264 | newPosition[location] = DRAGGED_PIECE; 1265 | } 1266 | 1267 | var oldPosition = deepCopy(CURRENT_POSITION); 1268 | 1269 | var result = cfg.onDrop(DRAGGED_PIECE_SOURCE, location, DRAGGED_PIECE, 1270 | newPosition, oldPosition, CURRENT_ORIENTATION); 1271 | if (result === 'snapback' || result === 'trash') { 1272 | action = result; 1273 | } 1274 | } 1275 | 1276 | // do it! 1277 | if (action === 'snapback') { 1278 | snapbackDraggedPiece(); 1279 | } 1280 | else if (action === 'trash') { 1281 | trashDraggedPiece(); 1282 | } 1283 | else if (action === 'drop') { 1284 | dropDraggedPieceOnSquare(location); 1285 | } 1286 | } 1287 | 1288 | //------------------------------------------------------------------------------ 1289 | // Public Methods 1290 | //------------------------------------------------------------------------------ 1291 | 1292 | // clear the board 1293 | widget.clear = function(useAnimation) { 1294 | widget.position({}, useAnimation); 1295 | }; 1296 | 1297 | /* 1298 | // get or set config properties 1299 | // TODO: write this, GitHub Issue #1 1300 | widget.config = function(arg1, arg2) { 1301 | // get the current config 1302 | if (arguments.length === 0) { 1303 | return deepCopy(cfg); 1304 | } 1305 | }; 1306 | */ 1307 | 1308 | // remove the widget from the page 1309 | widget.destroy = function() { 1310 | // remove markup 1311 | containerEl.html(''); 1312 | draggedPieceEl.remove(); 1313 | 1314 | // remove event handlers 1315 | containerEl.unbind(); 1316 | }; 1317 | 1318 | // shorthand method to get the current FEN 1319 | widget.fen = function() { 1320 | return widget.position('fen'); 1321 | }; 1322 | 1323 | // flip orientation 1324 | widget.flip = function() { 1325 | widget.orientation('flip'); 1326 | }; 1327 | 1328 | /* 1329 | // TODO: write this, GitHub Issue #5 1330 | widget.highlight = function() { 1331 | 1332 | }; 1333 | */ 1334 | 1335 | // move pieces 1336 | widget.move = function() { 1337 | // no need to throw an error here; just do nothing 1338 | if (arguments.length === 0) return; 1339 | 1340 | var useAnimation = true; 1341 | 1342 | // collect the moves into an object 1343 | var moves = {}; 1344 | for (var i = 0; i < arguments.length; i++) { 1345 | // any "false" to this function means no animations 1346 | if (arguments[i] === false) { 1347 | useAnimation = false; 1348 | continue; 1349 | } 1350 | 1351 | // skip invalid arguments 1352 | if (validMove(arguments[i]) !== true) { 1353 | error(2826, 'Invalid move passed to the move method.', arguments[i]); 1354 | continue; 1355 | } 1356 | 1357 | var tmp = arguments[i].split('-'); 1358 | moves[tmp[0]] = tmp[1]; 1359 | } 1360 | 1361 | // calculate position from moves 1362 | var newPos = calculatePositionFromMoves(CURRENT_POSITION, moves); 1363 | 1364 | // update the board 1365 | widget.position(newPos, useAnimation); 1366 | 1367 | // return the new position object 1368 | return newPos; 1369 | }; 1370 | 1371 | widget.orientation = function(arg) { 1372 | // no arguments, return the current orientation 1373 | if (arguments.length === 0) { 1374 | return CURRENT_ORIENTATION; 1375 | } 1376 | 1377 | // set to white or black 1378 | if (arg === 'white' || arg === 'black') { 1379 | CURRENT_ORIENTATION = arg; 1380 | drawBoard(); 1381 | return; 1382 | } 1383 | 1384 | // flip orientation 1385 | if (arg === 'flip') { 1386 | CURRENT_ORIENTATION = (CURRENT_ORIENTATION === 'white') ? 'black' : 'white'; 1387 | drawBoard(); 1388 | return; 1389 | } 1390 | 1391 | error(5482, 'Invalid value passed to the orientation method.', arg); 1392 | }; 1393 | 1394 | widget.position = function(position, useAnimation) { 1395 | // no arguments, return the current position 1396 | if (arguments.length === 0) { 1397 | return deepCopy(CURRENT_POSITION); 1398 | } 1399 | 1400 | // get position as FEN 1401 | if (typeof position === 'string' && position.toLowerCase() === 'fen') { 1402 | return objToFen(CURRENT_POSITION); 1403 | } 1404 | 1405 | // default for useAnimations is true 1406 | if (useAnimation !== false) { 1407 | useAnimation = true; 1408 | } 1409 | 1410 | // start position 1411 | if (typeof position === 'string' && position.toLowerCase() === 'start') { 1412 | position = deepCopy(START_POSITION); 1413 | } 1414 | 1415 | // convert FEN to position object 1416 | if (validFen(position) === true) { 1417 | position = fenToObj(position); 1418 | } 1419 | 1420 | // validate position object 1421 | if (validPositionObject(position) !== true) { 1422 | error(6482, 'Invalid value passed to the position method.', position); 1423 | return; 1424 | } 1425 | 1426 | if (useAnimation === true) { 1427 | // start the animations 1428 | doAnimations(calculateAnimations(CURRENT_POSITION, position), 1429 | CURRENT_POSITION, position); 1430 | 1431 | // set the new position 1432 | setCurrentPosition(position); 1433 | } 1434 | // instant update 1435 | else { 1436 | setCurrentPosition(position); 1437 | drawPositionInstant(); 1438 | } 1439 | }; 1440 | 1441 | widget.resize = function() { 1442 | // calulate the new square size 1443 | SQUARE_SIZE = calculateSquareSize(); 1444 | 1445 | // set board width 1446 | boardEl.css('width', (SQUARE_SIZE * 8) + 'px'); 1447 | 1448 | // set drag piece size 1449 | draggedPieceEl.css({ 1450 | height: SQUARE_SIZE, 1451 | width: SQUARE_SIZE 1452 | }); 1453 | 1454 | // spare pieces 1455 | if (cfg.sparePieces === true) { 1456 | containerEl.find('.' + CSS.sparePieces) 1457 | .css('paddingLeft', (SQUARE_SIZE + BOARD_BORDER_SIZE) + 'px'); 1458 | } 1459 | 1460 | // redraw the board 1461 | drawBoard(); 1462 | }; 1463 | 1464 | // set the starting position 1465 | widget.start = function(useAnimation) { 1466 | widget.position('start', useAnimation); 1467 | }; 1468 | 1469 | //------------------------------------------------------------------------------ 1470 | // Browser Events 1471 | //------------------------------------------------------------------------------ 1472 | 1473 | function isTouchDevice() { 1474 | return ('ontouchstart' in document.documentElement); 1475 | } 1476 | 1477 | // reference: http://www.quirksmode.org/js/detect.html 1478 | function isMSIE() { 1479 | return (navigator && navigator.userAgent && 1480 | navigator.userAgent.search(/MSIE/) !== -1); 1481 | } 1482 | 1483 | function stopDefault(e) { 1484 | e.preventDefault(); 1485 | } 1486 | 1487 | function mousedownSquare(e) { 1488 | // do nothing if we're not draggable 1489 | if (cfg.draggable !== true) return; 1490 | 1491 | var square = $(this).attr('data-square'); 1492 | 1493 | // no piece on this square 1494 | if (validSquare(square) !== true || 1495 | CURRENT_POSITION.hasOwnProperty(square) !== true) { 1496 | return; 1497 | } 1498 | 1499 | beginDraggingPiece(square, CURRENT_POSITION[square], e.pageX, e.pageY); 1500 | } 1501 | 1502 | function touchstartSquare(e) { 1503 | // do nothing if we're not draggable 1504 | if (cfg.draggable !== true) return; 1505 | 1506 | var square = $(this).attr('data-square'); 1507 | 1508 | // no piece on this square 1509 | if (validSquare(square) !== true || 1510 | CURRENT_POSITION.hasOwnProperty(square) !== true) { 1511 | return; 1512 | } 1513 | 1514 | e = e.originalEvent; 1515 | beginDraggingPiece(square, CURRENT_POSITION[square], 1516 | e.changedTouches[0].pageX, e.changedTouches[0].pageY); 1517 | } 1518 | 1519 | function mousedownSparePiece(e) { 1520 | // do nothing if sparePieces is not enabled 1521 | if (cfg.sparePieces !== true) return; 1522 | 1523 | var piece = $(this).attr('data-piece'); 1524 | 1525 | beginDraggingPiece('spare', piece, e.pageX, e.pageY); 1526 | } 1527 | 1528 | function touchstartSparePiece(e) { 1529 | // do nothing if sparePieces is not enabled 1530 | if (cfg.sparePieces !== true) return; 1531 | 1532 | var piece = $(this).attr('data-piece'); 1533 | 1534 | e = e.originalEvent; 1535 | beginDraggingPiece('spare', piece, 1536 | e.changedTouches[0].pageX, e.changedTouches[0].pageY); 1537 | } 1538 | 1539 | function mousemoveWindow(e) { 1540 | // do nothing if we are not dragging a piece 1541 | if (DRAGGING_A_PIECE !== true) return; 1542 | 1543 | updateDraggedPiece(e.pageX, e.pageY); 1544 | } 1545 | 1546 | function touchmoveWindow(e) { 1547 | // do nothing if we are not dragging a piece 1548 | if (DRAGGING_A_PIECE !== true) return; 1549 | 1550 | // prevent screen from scrolling 1551 | e.preventDefault(); 1552 | 1553 | updateDraggedPiece(e.originalEvent.changedTouches[0].pageX, 1554 | e.originalEvent.changedTouches[0].pageY); 1555 | } 1556 | 1557 | function mouseupWindow(e) { 1558 | // do nothing if we are not dragging a piece 1559 | if (DRAGGING_A_PIECE !== true) return; 1560 | 1561 | // get the location 1562 | var location = isXYOnSquare(e.pageX, e.pageY); 1563 | 1564 | stopDraggedPiece(location); 1565 | } 1566 | 1567 | function touchendWindow(e) { 1568 | // do nothing if we are not dragging a piece 1569 | if (DRAGGING_A_PIECE !== true) return; 1570 | 1571 | // get the location 1572 | var location = isXYOnSquare(e.originalEvent.changedTouches[0].pageX, 1573 | e.originalEvent.changedTouches[0].pageY); 1574 | 1575 | stopDraggedPiece(location); 1576 | } 1577 | 1578 | function mouseenterSquare(e) { 1579 | // do not fire this event if we are dragging a piece 1580 | // NOTE: this should never happen, but it's a safeguard 1581 | if (DRAGGING_A_PIECE !== false) return; 1582 | 1583 | if (cfg.hasOwnProperty('onMouseoverSquare') !== true || 1584 | typeof cfg.onMouseoverSquare !== 'function') return; 1585 | 1586 | // get the square 1587 | var square = $(e.currentTarget).attr('data-square'); 1588 | 1589 | // NOTE: this should never happen; defensive 1590 | if (validSquare(square) !== true) return; 1591 | 1592 | // get the piece on this square 1593 | var piece = false; 1594 | if (CURRENT_POSITION.hasOwnProperty(square) === true) { 1595 | piece = CURRENT_POSITION[square]; 1596 | } 1597 | 1598 | // execute their function 1599 | cfg.onMouseoverSquare(square, piece, deepCopy(CURRENT_POSITION), 1600 | CURRENT_ORIENTATION); 1601 | } 1602 | 1603 | function mouseleaveSquare(e) { 1604 | // do not fire this event if we are dragging a piece 1605 | // NOTE: this should never happen, but it's a safeguard 1606 | if (DRAGGING_A_PIECE !== false) return; 1607 | 1608 | if (cfg.hasOwnProperty('onMouseoutSquare') !== true || 1609 | typeof cfg.onMouseoutSquare !== 'function') return; 1610 | 1611 | // get the square 1612 | var square = $(e.currentTarget).attr('data-square'); 1613 | 1614 | // NOTE: this should never happen; defensive 1615 | if (validSquare(square) !== true) return; 1616 | 1617 | // get the piece on this square 1618 | var piece = false; 1619 | if (CURRENT_POSITION.hasOwnProperty(square) === true) { 1620 | piece = CURRENT_POSITION[square]; 1621 | } 1622 | 1623 | // execute their function 1624 | cfg.onMouseoutSquare(square, piece, deepCopy(CURRENT_POSITION), 1625 | CURRENT_ORIENTATION); 1626 | } 1627 | 1628 | //------------------------------------------------------------------------------ 1629 | // Initialization 1630 | //------------------------------------------------------------------------------ 1631 | 1632 | function addEvents() { 1633 | // prevent browser "image drag" 1634 | $('body').on('mousedown mousemove', '.' + CSS.piece, stopDefault); 1635 | 1636 | // mouse drag pieces 1637 | boardEl.on('mousedown', '.' + CSS.square, mousedownSquare); 1638 | containerEl.on('mousedown', '.' + CSS.sparePieces + ' .' + CSS.piece, 1639 | mousedownSparePiece); 1640 | 1641 | // mouse enter / leave square 1642 | boardEl.on('mouseenter', '.' + CSS.square, mouseenterSquare); 1643 | boardEl.on('mouseleave', '.' + CSS.square, mouseleaveSquare); 1644 | 1645 | // IE doesn't like the events on the window object, but other browsers 1646 | // perform better that way 1647 | if (isMSIE() === true) { 1648 | // IE-specific prevent browser "image drag" 1649 | document.ondragstart = function() { return false; }; 1650 | 1651 | $('body').on('mousemove', mousemoveWindow); 1652 | $('body').on('mouseup', mouseupWindow); 1653 | } 1654 | else { 1655 | $(window).on('mousemove', mousemoveWindow); 1656 | $(window).on('mouseup', mouseupWindow); 1657 | } 1658 | 1659 | // touch drag pieces 1660 | if (isTouchDevice() === true) { 1661 | boardEl.on('touchstart', '.' + CSS.square, touchstartSquare); 1662 | containerEl.on('touchstart', '.' + CSS.sparePieces + ' .' + CSS.piece, 1663 | touchstartSparePiece); 1664 | $(window).on('touchmove', touchmoveWindow); 1665 | $(window).on('touchend', touchendWindow); 1666 | } 1667 | } 1668 | 1669 | function initDom() { 1670 | // build board and save it in memory 1671 | containerEl.html(buildBoardContainer()); 1672 | boardEl = containerEl.find('.' + CSS.board); 1673 | 1674 | if (cfg.sparePieces === true) { 1675 | sparePiecesTopEl = containerEl.find('.' + CSS.sparePiecesTop); 1676 | sparePiecesBottomEl = containerEl.find('.' + CSS.sparePiecesBottom); 1677 | } 1678 | 1679 | // create the drag piece 1680 | var draggedPieceId = createId(); 1681 | $('body').append(buildPiece('wP', true, draggedPieceId)); 1682 | draggedPieceEl = $('#' + draggedPieceId); 1683 | 1684 | // get the border size 1685 | BOARD_BORDER_SIZE = parseInt(boardEl.css('borderLeftWidth'), 10); 1686 | 1687 | // set the size and draw the board 1688 | widget.resize(); 1689 | } 1690 | 1691 | function init() { 1692 | if (checkDeps() !== true || 1693 | expandConfig() !== true) return; 1694 | 1695 | // create unique IDs for all the elements we will create 1696 | createElIds(); 1697 | 1698 | initDom(); 1699 | addEvents(); 1700 | } 1701 | 1702 | // go time 1703 | init(); 1704 | 1705 | // return the widget object 1706 | return widget; 1707 | 1708 | }; // end window.ChessBoard 1709 | 1710 | // expose util functions 1711 | window.ChessBoard.fenToObj = fenToObj; 1712 | window.ChessBoard.objToFen = objToFen; 1713 | 1714 | })(); // end anonymous wrapper 1715 | -------------------------------------------------------------------------------- /js/chess.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | * Copyright (c) 2013, Jeff Hlywa (jhlywa@gmail.com) 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * 1. Redistributions of source code must retain the above copyright notice, 11 | * this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright notice, 13 | * this list of conditions and the following disclaimer in the documentation 14 | * and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | * POSSIBILITY OF SUCH DAMAGE. 27 | * 28 | *----------------------------------------------------------------------------*/ 29 | 30 | /* minified license below */ 31 | 32 | /*! Copyright (c) 2013, Jeff Hlywa (jhlywa@gmail.com) 33 | * Released under the BSD license 34 | * https://github.com/jhlywa/chess.js/blob/master/LICENSE 35 | */ 36 | 37 | var Chess = function(fen) { 38 | 39 | /* jshint indent: false */ 40 | 41 | var BLACK = 'b'; 42 | var WHITE = 'w'; 43 | 44 | var EMPTY = -1; 45 | 46 | var PAWN = 'p'; 47 | var KNIGHT = 'n'; 48 | var BISHOP = 'b'; 49 | var ROOK = 'r'; 50 | var QUEEN = 'q'; 51 | var KING = 'k'; 52 | 53 | var SYMBOLS = 'pnbrqkPNBRQK'; 54 | 55 | var DEFAULT_POSITION = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'; 56 | 57 | var POSSIBLE_RESULTS = ['1-0', '0-1', '1/2-1/2', '*']; 58 | 59 | var PAWN_OFFSETS = { 60 | b: [16, 32, 17, 15], 61 | w: [-16, -32, -17, -15] 62 | }; 63 | 64 | var PIECE_OFFSETS = { 65 | n: [-18, -33, -31, -14, 18, 33, 31, 14], 66 | b: [-17, -15, 17, 15], 67 | r: [-16, 1, 16, -1], 68 | q: [-17, -16, -15, 1, 17, 16, 15, -1], 69 | k: [-17, -16, -15, 1, 17, 16, 15, -1] 70 | }; 71 | 72 | var ATTACKS = [ 73 | 20, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0,20, 0, 74 | 0,20, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0,20, 0, 0, 75 | 0, 0,20, 0, 0, 0, 0, 24, 0, 0, 0, 0,20, 0, 0, 0, 76 | 0, 0, 0,20, 0, 0, 0, 24, 0, 0, 0,20, 0, 0, 0, 0, 77 | 0, 0, 0, 0,20, 0, 0, 24, 0, 0,20, 0, 0, 0, 0, 0, 78 | 0, 0, 0, 0, 0,20, 2, 24, 2,20, 0, 0, 0, 0, 0, 0, 79 | 0, 0, 0, 0, 0, 2,53, 56, 53, 2, 0, 0, 0, 0, 0, 0, 80 | 24,24,24,24,24,24,56, 0, 56,24,24,24,24,24,24, 0, 81 | 0, 0, 0, 0, 0, 2,53, 56, 53, 2, 0, 0, 0, 0, 0, 0, 82 | 0, 0, 0, 0, 0,20, 2, 24, 2,20, 0, 0, 0, 0, 0, 0, 83 | 0, 0, 0, 0,20, 0, 0, 24, 0, 0,20, 0, 0, 0, 0, 0, 84 | 0, 0, 0,20, 0, 0, 0, 24, 0, 0, 0,20, 0, 0, 0, 0, 85 | 0, 0,20, 0, 0, 0, 0, 24, 0, 0, 0, 0,20, 0, 0, 0, 86 | 0,20, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0,20, 0, 0, 87 | 20, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0,20 88 | ]; 89 | 90 | var RAYS = [ 91 | 17, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 15, 0, 92 | 0, 17, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 15, 0, 0, 93 | 0, 0, 17, 0, 0, 0, 0, 16, 0, 0, 0, 0, 15, 0, 0, 0, 94 | 0, 0, 0, 17, 0, 0, 0, 16, 0, 0, 0, 15, 0, 0, 0, 0, 95 | 0, 0, 0, 0, 17, 0, 0, 16, 0, 0, 15, 0, 0, 0, 0, 0, 96 | 0, 0, 0, 0, 0, 17, 0, 16, 0, 15, 0, 0, 0, 0, 0, 0, 97 | 0, 0, 0, 0, 0, 0, 17, 16, 15, 0, 0, 0, 0, 0, 0, 0, 98 | 1, 1, 1, 1, 1, 1, 1, 0, -1, -1, -1,-1, -1, -1, -1, 0, 99 | 0, 0, 0, 0, 0, 0,-15,-16,-17, 0, 0, 0, 0, 0, 0, 0, 100 | 0, 0, 0, 0, 0,-15, 0,-16, 0,-17, 0, 0, 0, 0, 0, 0, 101 | 0, 0, 0, 0,-15, 0, 0,-16, 0, 0,-17, 0, 0, 0, 0, 0, 102 | 0, 0, 0,-15, 0, 0, 0,-16, 0, 0, 0,-17, 0, 0, 0, 0, 103 | 0, 0,-15, 0, 0, 0, 0,-16, 0, 0, 0, 0,-17, 0, 0, 0, 104 | 0,-15, 0, 0, 0, 0, 0,-16, 0, 0, 0, 0, 0,-17, 0, 0, 105 | -15, 0, 0, 0, 0, 0, 0,-16, 0, 0, 0, 0, 0, 0,-17 106 | ]; 107 | 108 | var SHIFTS = { p: 0, n: 1, b: 2, r: 3, q: 4, k: 5 }; 109 | 110 | var FLAGS = { 111 | NORMAL: 'n', 112 | CAPTURE: 'c', 113 | BIG_PAWN: 'b', 114 | EP_CAPTURE: 'e', 115 | PROMOTION: 'p', 116 | KSIDE_CASTLE: 'k', 117 | QSIDE_CASTLE: 'q' 118 | }; 119 | 120 | var BITS = { 121 | NORMAL: 1, 122 | CAPTURE: 2, 123 | BIG_PAWN: 4, 124 | EP_CAPTURE: 8, 125 | PROMOTION: 16, 126 | KSIDE_CASTLE: 32, 127 | QSIDE_CASTLE: 64 128 | }; 129 | 130 | var RANK_1 = 7; 131 | var RANK_2 = 6; 132 | var RANK_3 = 5; 133 | var RANK_4 = 4; 134 | var RANK_5 = 3; 135 | var RANK_6 = 2; 136 | var RANK_7 = 1; 137 | var RANK_8 = 0; 138 | 139 | var SQUARES = { 140 | a8: 0, b8: 1, c8: 2, d8: 3, e8: 4, f8: 5, g8: 6, h8: 7, 141 | a7: 16, b7: 17, c7: 18, d7: 19, e7: 20, f7: 21, g7: 22, h7: 23, 142 | a6: 32, b6: 33, c6: 34, d6: 35, e6: 36, f6: 37, g6: 38, h6: 39, 143 | a5: 48, b5: 49, c5: 50, d5: 51, e5: 52, f5: 53, g5: 54, h5: 55, 144 | a4: 64, b4: 65, c4: 66, d4: 67, e4: 68, f4: 69, g4: 70, h4: 71, 145 | a3: 80, b3: 81, c3: 82, d3: 83, e3: 84, f3: 85, g3: 86, h3: 87, 146 | a2: 96, b2: 97, c2: 98, d2: 99, e2: 100, f2: 101, g2: 102, h2: 103, 147 | a1: 112, b1: 113, c1: 114, d1: 115, e1: 116, f1: 117, g1: 118, h1: 119 148 | }; 149 | 150 | var ROOKS = { 151 | w: [{square: SQUARES.a1, flag: BITS.QSIDE_CASTLE}, 152 | {square: SQUARES.h1, flag: BITS.KSIDE_CASTLE}], 153 | b: [{square: SQUARES.a8, flag: BITS.QSIDE_CASTLE}, 154 | {square: SQUARES.h8, flag: BITS.KSIDE_CASTLE}] 155 | }; 156 | 157 | var board = new Array(128); 158 | var kings = {w: EMPTY, b: EMPTY}; 159 | var turn = WHITE; 160 | var castling = {w: 0, b: 0}; 161 | var ep_square = EMPTY; 162 | var half_moves = 0; 163 | var move_number = 1; 164 | var history = []; 165 | var header = {}; 166 | 167 | /* if the user passes in a fen string, load it, else default to 168 | * starting position 169 | */ 170 | if (typeof fen === 'undefined') { 171 | load(DEFAULT_POSITION); 172 | } else { 173 | load(fen); 174 | } 175 | 176 | function clear() { 177 | board = new Array(128); 178 | kings = {w: EMPTY, b: EMPTY}; 179 | turn = WHITE; 180 | castling = {w: 0, b: 0}; 181 | ep_square = EMPTY; 182 | half_moves = 0; 183 | move_number = 1; 184 | history = []; 185 | header = {}; 186 | update_setup(generate_fen()); 187 | } 188 | 189 | function reset() { 190 | load(DEFAULT_POSITION); 191 | } 192 | 193 | function load(fen) { 194 | var tokens = fen.split(/\s+/); 195 | var position = tokens[0]; 196 | var square = 0; 197 | var valid = SYMBOLS + '12345678/'; 198 | 199 | if (!validate_fen(fen).valid) { 200 | return false; 201 | } 202 | 203 | clear(); 204 | 205 | for (var i = 0; i < position.length; i++) { 206 | var piece = position.charAt(i); 207 | 208 | if (piece === '/') { 209 | square += 8; 210 | } else if (is_digit(piece)) { 211 | square += parseInt(piece, 10); 212 | } else { 213 | var color = (piece < 'a') ? WHITE : BLACK; 214 | put({type: piece.toLowerCase(), color: color}, algebraic(square)); 215 | square++; 216 | } 217 | } 218 | 219 | turn = tokens[1]; 220 | 221 | if (tokens[2].indexOf('K') > -1) { 222 | castling.w |= BITS.KSIDE_CASTLE; 223 | } 224 | if (tokens[2].indexOf('Q') > -1) { 225 | castling.w |= BITS.QSIDE_CASTLE; 226 | } 227 | if (tokens[2].indexOf('k') > -1) { 228 | castling.b |= BITS.KSIDE_CASTLE; 229 | } 230 | if (tokens[2].indexOf('q') > -1) { 231 | castling.b |= BITS.QSIDE_CASTLE; 232 | } 233 | 234 | ep_square = (tokens[3] === '-') ? EMPTY : SQUARES[tokens[3]]; 235 | half_moves = parseInt(tokens[4], 10); 236 | move_number = parseInt(tokens[5], 10); 237 | 238 | update_setup(generate_fen()); 239 | 240 | return true; 241 | } 242 | 243 | function validate_fen(fen) { 244 | var errors = { 245 | 0: 'No errors.', 246 | 1: 'FEN string must contain six space-delimited fields.', 247 | 2: '6th field (move number) must be a positive integer.', 248 | 3: '5th field (half move counter) must be a non-negative integer.', 249 | 4: '4th field (en-passant square) is invalid.', 250 | 5: '3rd field (castling availability) is invalid.', 251 | 6: '2nd field (side to move) is invalid.', 252 | 7: '1st field (piece positions) does not contain 8 \'/\'-delimited rows.', 253 | 8: '1st field (piece positions) is invalid [consecutive numbers].', 254 | 9: '1st field (piece positions) is invalid [invalid piece].', 255 | 10: '1st field (piece positions) is invalid [row too large].', 256 | }; 257 | 258 | /* 1st criterion: 6 space-seperated fields? */ 259 | var tokens = fen.split(/\s+/); 260 | if (tokens.length !== 6) { 261 | return {valid: false, error_number: 1, error: errors[1]}; 262 | } 263 | 264 | /* 2nd criterion: move number field is a integer value > 0? */ 265 | if (isNaN(tokens[5]) || (parseInt(tokens[5], 10) <= 0)) { 266 | return {valid: false, error_number: 2, error: errors[2]}; 267 | } 268 | 269 | /* 3rd criterion: half move counter is an integer >= 0? */ 270 | if (isNaN(tokens[4]) || (parseInt(tokens[4], 10) < 0)) { 271 | return {valid: false, error_number: 3, error: errors[3]}; 272 | } 273 | 274 | /* 4th criterion: 4th field is a valid e.p.-string? */ 275 | if (!/^(-|[abcdefgh][36])$/.test(tokens[3])) { 276 | return {valid: false, error_number: 4, error: errors[4]}; 277 | } 278 | 279 | /* 5th criterion: 3th field is a valid castle-string? */ 280 | if( !/^(KQ?k?q?|Qk?q?|kq?|q|-)$/.test(tokens[2])) { 281 | return {valid: false, error_number: 5, error: errors[5]}; 282 | } 283 | 284 | /* 6th criterion: 2nd field is "w" (white) or "b" (black)? */ 285 | if (!/^(w|b)$/.test(tokens[1])) { 286 | return {valid: false, error_number: 6, error: errors[6]}; 287 | } 288 | 289 | /* 7th criterion: 1st field contains 8 rows? */ 290 | var rows = tokens[0].split('/'); 291 | if (rows.length !== 8) { 292 | return {valid: false, error_number: 7, error: errors[7]}; 293 | } 294 | 295 | /* 8th criterion: every row is valid? */ 296 | for (var i = 0; i < rows.length; i++) { 297 | /* check for right sum of fields AND not two numbers in succession */ 298 | var sum_fields = 0; 299 | var previous_was_number = false; 300 | 301 | for (var k = 0; k < rows[i].length; k++) { 302 | if (!isNaN(rows[i][k])) { 303 | if (previous_was_number) { 304 | return {valid: false, error_number: 8, error: errors[8]}; 305 | } 306 | sum_fields += parseInt(rows[i][k], 10); 307 | previous_was_number = true; 308 | } else { 309 | if (!/^[prnbqkPRNBQK]$/.test(rows[i][k])) { 310 | return {valid: false, error_number: 9, error: errors[9]}; 311 | } 312 | sum_fields += 1; 313 | previous_was_number = false; 314 | } 315 | } 316 | if (sum_fields !== 8) { 317 | return {valid: false, error_number: 10, error: errors[10]}; 318 | } 319 | } 320 | 321 | /* everything's okay! */ 322 | return {valid: true, error_number: 0, error: errors[0]}; 323 | } 324 | 325 | function generate_fen() { 326 | var empty = 0; 327 | var fen = ''; 328 | 329 | for (var i = SQUARES.a8; i <= SQUARES.h1; i++) { 330 | if (board[i] == null) { 331 | empty++; 332 | } else { 333 | if (empty > 0) { 334 | fen += empty; 335 | empty = 0; 336 | } 337 | var color = board[i].color; 338 | var piece = board[i].type; 339 | 340 | fen += (color === WHITE) ? 341 | piece.toUpperCase() : piece.toLowerCase(); 342 | } 343 | 344 | if ((i + 1) & 0x88) { 345 | if (empty > 0) { 346 | fen += empty; 347 | } 348 | 349 | if (i !== SQUARES.h1) { 350 | fen += '/'; 351 | } 352 | 353 | empty = 0; 354 | i += 8; 355 | } 356 | } 357 | 358 | var cflags = ''; 359 | if (castling[WHITE] & BITS.KSIDE_CASTLE) { cflags += 'K'; } 360 | if (castling[WHITE] & BITS.QSIDE_CASTLE) { cflags += 'Q'; } 361 | if (castling[BLACK] & BITS.KSIDE_CASTLE) { cflags += 'k'; } 362 | if (castling[BLACK] & BITS.QSIDE_CASTLE) { cflags += 'q'; } 363 | 364 | /* do we have an empty castling flag? */ 365 | cflags = cflags || '-'; 366 | var epflags = (ep_square === EMPTY) ? '-' : algebraic(ep_square); 367 | 368 | return [fen, turn, cflags, epflags, half_moves, move_number].join(' '); 369 | } 370 | 371 | function set_header(args) { 372 | for (var i = 0; i < args.length; i += 2) { 373 | if (typeof args[i] === 'string' && 374 | typeof args[i + 1] === 'string') { 375 | header[args[i]] = args[i + 1]; 376 | } 377 | } 378 | return header; 379 | } 380 | 381 | /* called when the initial board setup is changed with put() or remove(). 382 | * modifies the SetUp and FEN properties of the header object. if the FEN is 383 | * equal to the default position, the SetUp and FEN are deleted 384 | * the setup is only updated if history.length is zero, ie moves haven't been 385 | * made. 386 | */ 387 | function update_setup(fen) { 388 | if (history.length > 0) return; 389 | 390 | if (fen !== DEFAULT_POSITION) { 391 | header['SetUp'] = fen; 392 | header['FEN'] = '1'; 393 | } else { 394 | delete header['SetUp']; 395 | delete header['FEN']; 396 | } 397 | } 398 | 399 | function get(square) { 400 | var piece = board[SQUARES[square]]; 401 | return (piece) ? {type: piece.type, color: piece.color} : null; 402 | } 403 | 404 | function put(piece, square) { 405 | /* check for valid piece object */ 406 | if (!('type' in piece && 'color' in piece)) { 407 | return false; 408 | } 409 | 410 | /* check for piece */ 411 | if (SYMBOLS.indexOf(piece.type.toLowerCase()) === -1) { 412 | return false; 413 | } 414 | 415 | /* check for valid square */ 416 | if (!(square in SQUARES)) { 417 | return false; 418 | } 419 | 420 | var sq = SQUARES[square]; 421 | board[sq] = {type: piece.type, color: piece.color}; 422 | if (piece.type === KING) { 423 | kings[piece.color] = sq; 424 | } 425 | 426 | update_setup(generate_fen()); 427 | 428 | return true; 429 | } 430 | 431 | function remove(square) { 432 | var piece = get(square); 433 | board[SQUARES[square]] = null; 434 | if (piece && piece.type === KING) { 435 | kings[piece.color] = EMPTY; 436 | } 437 | 438 | update_setup(generate_fen()); 439 | 440 | return piece; 441 | } 442 | 443 | function build_move(board, from, to, flags, promotion) { 444 | var move = { 445 | color: turn, 446 | from: from, 447 | to: to, 448 | flags: flags, 449 | piece: board[from].type 450 | }; 451 | 452 | if (promotion) { 453 | move.flags |= BITS.PROMOTION; 454 | move.promotion = promotion; 455 | } 456 | 457 | if (board[to]) { 458 | move.captured = board[to].type; 459 | } else if (flags & BITS.EP_CAPTURE) { 460 | move.captured = PAWN; 461 | } 462 | return move; 463 | } 464 | 465 | function generate_moves(options) { 466 | function add_move(board, moves, from, to, flags) { 467 | /* if pawn promotion */ 468 | if (board[from].type === PAWN && 469 | (rank(to) === RANK_8 || rank(to) === RANK_1)) { 470 | var pieces = [QUEEN, ROOK, BISHOP, KNIGHT]; 471 | for (var i = 0, len = pieces.length; i < len; i++) { 472 | moves.push(build_move(board, from, to, flags, pieces[i])); 473 | } 474 | } else { 475 | moves.push(build_move(board, from, to, flags)); 476 | } 477 | } 478 | 479 | var moves = []; 480 | var us = turn; 481 | var them = swap_color(us); 482 | var second_rank = {b: RANK_7, w: RANK_2}; 483 | 484 | var first_sq = SQUARES.a8; 485 | var last_sq = SQUARES.h1; 486 | var single_square = false; 487 | 488 | /* do we want legal moves? */ 489 | var legal = (typeof options !== 'undefined' && 'legal' in options) ? 490 | options.legal : true; 491 | 492 | /* are we generating moves for a single square? */ 493 | if (typeof options !== 'undefined' && 'square' in options) { 494 | if (options.square in SQUARES) { 495 | first_sq = last_sq = SQUARES[options.square]; 496 | single_square = true; 497 | } else { 498 | /* invalid square */ 499 | return []; 500 | } 501 | } 502 | 503 | for (var i = first_sq; i <= last_sq; i++) { 504 | /* did we run off the end of the board */ 505 | if (i & 0x88) { i += 7; continue; } 506 | 507 | var piece = board[i]; 508 | if (piece == null || piece.color !== us) { 509 | continue; 510 | } 511 | 512 | if (piece.type === PAWN) { 513 | /* single square, non-capturing */ 514 | var square = i + PAWN_OFFSETS[us][0]; 515 | if (board[square] == null) { 516 | add_move(board, moves, i, square, BITS.NORMAL); 517 | 518 | /* double square */ 519 | var square = i + PAWN_OFFSETS[us][1]; 520 | if (second_rank[us] === rank(i) && board[square] == null) { 521 | add_move(board, moves, i, square, BITS.BIG_PAWN); 522 | } 523 | } 524 | 525 | /* pawn captures */ 526 | for (j = 2; j < 4; j++) { 527 | var square = i + PAWN_OFFSETS[us][j]; 528 | if (square & 0x88) continue; 529 | 530 | if (board[square] != null && 531 | board[square].color === them) { 532 | add_move(board, moves, i, square, BITS.CAPTURE); 533 | } else if (square === ep_square) { 534 | add_move(board, moves, i, ep_square, BITS.EP_CAPTURE); 535 | } 536 | } 537 | } else { 538 | for (var j = 0, len = PIECE_OFFSETS[piece.type].length; j < len; j++) { 539 | var offset = PIECE_OFFSETS[piece.type][j]; 540 | var square = i; 541 | 542 | while (true) { 543 | square += offset; 544 | if (square & 0x88) break; 545 | 546 | if (board[square] == null) { 547 | add_move(board, moves, i, square, BITS.NORMAL); 548 | } else { 549 | if (board[square].color === us) break; 550 | add_move(board, moves, i, square, BITS.CAPTURE); 551 | break; 552 | } 553 | 554 | /* break, if knight or king */ 555 | if (piece.type === 'n' || piece.type === 'k') break; 556 | } 557 | } 558 | } 559 | } 560 | 561 | /* check for castling if: a) we're generating all moves, or b) we're doing 562 | * single square move generation on the king's square 563 | */ 564 | if ((!single_square) || last_sq === kings[us]) { 565 | /* king-side castling */ 566 | if (castling[us] & BITS.KSIDE_CASTLE) { 567 | var castling_from = kings[us]; 568 | var castling_to = castling_from + 2; 569 | 570 | if (board[castling_from + 1] == null && 571 | board[castling_to] == null && 572 | !attacked(them, kings[us]) && 573 | !attacked(them, castling_from + 1) && 574 | !attacked(them, castling_to)) { 575 | add_move(board, moves, kings[us] , castling_to, 576 | BITS.KSIDE_CASTLE); 577 | } 578 | } 579 | 580 | /* queen-side castling */ 581 | if (castling[us] & BITS.QSIDE_CASTLE) { 582 | var castling_from = kings[us]; 583 | var castling_to = castling_from - 2; 584 | 585 | if (board[castling_from - 1] == null && 586 | board[castling_from - 2] == null && 587 | board[castling_from - 3] == null && 588 | !attacked(them, kings[us]) && 589 | !attacked(them, castling_from - 1) && 590 | !attacked(them, castling_to)) { 591 | add_move(board, moves, kings[us], castling_to, 592 | BITS.QSIDE_CASTLE); 593 | } 594 | } 595 | } 596 | 597 | /* return all pseudo-legal moves (this includes moves that allow the king 598 | * to be captured) 599 | */ 600 | if (!legal) { 601 | return moves; 602 | } 603 | 604 | /* filter out illegal moves */ 605 | var legal_moves = []; 606 | for (var i = 0, len = moves.length; i < len; i++) { 607 | make_move(moves[i]); 608 | if (!king_attacked(us)) { 609 | legal_moves.push(moves[i]); 610 | } 611 | undo_move(); 612 | } 613 | 614 | return legal_moves; 615 | } 616 | 617 | /* convert a move from 0x88 coordinates to Standard Algebraic Notation 618 | * (SAN) 619 | */ 620 | function move_to_san(move) { 621 | var output = ''; 622 | 623 | if (move.flags & BITS.KSIDE_CASTLE) { 624 | output = 'O-O'; 625 | } else if (move.flags & BITS.QSIDE_CASTLE) { 626 | output = 'O-O-O'; 627 | } else { 628 | var disambiguator = get_disambiguator(move); 629 | 630 | if (move.piece !== PAWN) { 631 | output += move.piece.toUpperCase() + disambiguator; 632 | } 633 | 634 | if (move.flags & (BITS.CAPTURE | BITS.EP_CAPTURE)) { 635 | if (move.piece === PAWN) { 636 | output += algebraic(move.from)[0]; 637 | } 638 | output += 'x'; 639 | } 640 | 641 | output += algebraic(move.to); 642 | 643 | if (move.flags & BITS.PROMOTION) { 644 | output += '=' + move.promotion.toUpperCase(); 645 | } 646 | } 647 | 648 | make_move(move); 649 | if (in_check()) { 650 | if (in_checkmate()) { 651 | output += '#'; 652 | } else { 653 | output += '+'; 654 | } 655 | } 656 | undo_move(); 657 | 658 | return output; 659 | } 660 | 661 | function attacked(color, square) { 662 | for (var i = SQUARES.a8; i <= SQUARES.h1; i++) { 663 | /* did we run off the end of the board */ 664 | if (i & 0x88) { i += 7; continue; } 665 | 666 | /* if empty square or wrong color */ 667 | if (board[i] == null || board[i].color !== color) continue; 668 | 669 | var piece = board[i]; 670 | var difference = i - square; 671 | var index = difference + 119; 672 | 673 | if (ATTACKS[index] & (1 << SHIFTS[piece.type])) { 674 | if (piece.type === PAWN) { 675 | if (difference > 0) { 676 | if (piece.color === WHITE) return true; 677 | } else { 678 | if (piece.color === BLACK) return true; 679 | } 680 | continue; 681 | } 682 | 683 | /* if the piece is a knight or a king */ 684 | if (piece.type === 'n' || piece.type === 'k') return true; 685 | 686 | var offset = RAYS[index]; 687 | var j = i + offset; 688 | 689 | var blocked = false; 690 | while (j !== square) { 691 | if (board[j] != null) { blocked = true; break; } 692 | j += offset; 693 | } 694 | 695 | if (!blocked) return true; 696 | } 697 | } 698 | 699 | return false; 700 | } 701 | 702 | function king_attacked(color) { 703 | return attacked(swap_color(color), kings[color]); 704 | } 705 | 706 | function in_check() { 707 | return king_attacked(turn); 708 | } 709 | 710 | function in_checkmate() { 711 | return in_check() && generate_moves().length === 0; 712 | } 713 | 714 | function in_stalemate() { 715 | return !in_check() && generate_moves().length === 0; 716 | } 717 | 718 | function insufficient_material() { 719 | var pieces = {}; 720 | var bishops = []; 721 | var num_pieces = 0; 722 | var sq_color = 0; 723 | 724 | for (var i = SQUARES.a8; i<= SQUARES.h1; i++) { 725 | sq_color = (sq_color + 1) % 2; 726 | if (i & 0x88) { i += 7; continue; } 727 | 728 | var piece = board[i]; 729 | if (piece) { 730 | pieces[piece.type] = (piece.type in pieces) ? 731 | pieces[piece.type] + 1 : 1; 732 | if (piece.type === BISHOP) { 733 | bishops.push(sq_color); 734 | } 735 | num_pieces++; 736 | } 737 | } 738 | 739 | /* k vs. k */ 740 | if (num_pieces === 2) { return true; } 741 | 742 | /* k vs. kn .... or .... k vs. kb */ 743 | else if (num_pieces === 3 && (pieces[BISHOP] === 1 || 744 | pieces[KNIGHT] === 1)) { return true; } 745 | 746 | /* kb vs. kb where any number of bishops are all on the same color */ 747 | else if (num_pieces === pieces[BISHOP] + 2) { 748 | var sum = 0; 749 | var len = bishops.length; 750 | for (var i = 0; i < len; i++) { 751 | sum += bishops[i]; 752 | } 753 | if (sum === 0 || sum === len) { return true; } 754 | } 755 | 756 | return false; 757 | } 758 | 759 | function in_threefold_repetition() { 760 | /* TODO: while this function is fine for casual use, a better 761 | * implementation would use a Zobrist key (instead of FEN). the 762 | * Zobrist key would be maintained in the make_move/undo_move functions, 763 | * avoiding the costly that we do below. 764 | */ 765 | var moves = []; 766 | var positions = {}; 767 | var repetition = false; 768 | 769 | while (true) { 770 | var move = undo_move(); 771 | if (!move) break; 772 | moves.push(move); 773 | } 774 | 775 | while (true) { 776 | /* remove the last two fields in the FEN string, they're not needed 777 | * when checking for draw by rep */ 778 | var fen = generate_fen().split(' ').slice(0,4).join(' '); 779 | 780 | /* has the position occurred three or move times */ 781 | positions[fen] = (fen in positions) ? positions[fen] + 1 : 1; 782 | if (positions[fen] >= 3) { 783 | repetition = true; 784 | } 785 | 786 | if (!moves.length) { 787 | break; 788 | } 789 | make_move(moves.pop()); 790 | } 791 | 792 | return repetition; 793 | } 794 | 795 | function push(move) { 796 | history.push({ 797 | move: move, 798 | kings: {b: kings.b, w: kings.w}, 799 | turn: turn, 800 | castling: {b: castling.b, w: castling.w}, 801 | ep_square: ep_square, 802 | half_moves: half_moves, 803 | move_number: move_number 804 | }); 805 | } 806 | 807 | function make_move(move) { 808 | var us = turn; 809 | var them = swap_color(us); 810 | push(move); 811 | 812 | board[move.to] = board[move.from]; 813 | board[move.from] = null; 814 | 815 | /* if ep capture, remove the captured pawn */ 816 | if (move.flags & BITS.EP_CAPTURE) { 817 | if (turn === BLACK) { 818 | board[move.to - 16] = null; 819 | } else { 820 | board[move.to + 16] = null; 821 | } 822 | } 823 | 824 | /* if pawn promotion, replace with new piece */ 825 | if (move.flags & BITS.PROMOTION) { 826 | board[move.to] = {type: move.promotion, color: us}; 827 | } 828 | 829 | /* if we moved the king */ 830 | if (board[move.to].type === KING) { 831 | kings[board[move.to].color] = move.to; 832 | 833 | /* if we castled, move the rook next to the king */ 834 | if (move.flags & BITS.KSIDE_CASTLE) { 835 | var castling_to = move.to - 1; 836 | var castling_from = move.to + 1; 837 | board[castling_to] = board[castling_from]; 838 | board[castling_from] = null; 839 | } else if (move.flags & BITS.QSIDE_CASTLE) { 840 | var castling_to = move.to + 1; 841 | var castling_from = move.to - 2; 842 | board[castling_to] = board[castling_from]; 843 | board[castling_from] = null; 844 | } 845 | 846 | /* turn off castling */ 847 | castling[us] = ''; 848 | } 849 | 850 | /* turn off castling if we move a rook */ 851 | if (castling[us]) { 852 | for (var i = 0, len = ROOKS[us].length; i < len; i++) { 853 | if (move.from === ROOKS[us][i].square && 854 | castling[us] & ROOKS[us][i].flag) { 855 | castling[us] ^= ROOKS[us][i].flag; 856 | break; 857 | } 858 | } 859 | } 860 | 861 | /* turn off castling if we capture a rook */ 862 | if (castling[them]) { 863 | for (var i = 0, len = ROOKS[them].length; i < len; i++) { 864 | if (move.to === ROOKS[them][i].square && 865 | castling[them] & ROOKS[them][i].flag) { 866 | castling[them] ^= ROOKS[them][i].flag; 867 | break; 868 | } 869 | } 870 | } 871 | 872 | /* if big pawn move, update the en passant square */ 873 | if (move.flags & BITS.BIG_PAWN) { 874 | if (turn === 'b') { 875 | ep_square = move.to - 16; 876 | } else { 877 | ep_square = move.to + 16; 878 | } 879 | } else { 880 | ep_square = EMPTY; 881 | } 882 | 883 | /* reset the 50 move counter if a pawn is moved or a piece is captured */ 884 | if (move.piece === PAWN) { 885 | half_moves = 0; 886 | } else if (move.flags & (BITS.CAPTURE | BITS.EP_CAPTURE)) { 887 | half_moves = 0; 888 | } else { 889 | half_moves++; 890 | } 891 | 892 | if (turn === BLACK) { 893 | move_number++; 894 | } 895 | turn = swap_color(turn); 896 | } 897 | 898 | function undo_move() { 899 | var old = history.pop(); 900 | if (old == null) { return null; } 901 | 902 | var move = old.move; 903 | kings = old.kings; 904 | turn = old.turn; 905 | castling = old.castling; 906 | ep_square = old.ep_square; 907 | half_moves = old.half_moves; 908 | move_number = old.move_number; 909 | 910 | var us = turn; 911 | var them = swap_color(turn); 912 | 913 | board[move.from] = board[move.to]; 914 | board[move.from].type = move.piece; // to undo any promotions 915 | board[move.to] = null; 916 | 917 | if (move.flags & BITS.CAPTURE) { 918 | board[move.to] = {type: move.captured, color: them}; 919 | } else if (move.flags & BITS.EP_CAPTURE) { 920 | var index; 921 | if (us === BLACK) { 922 | index = move.to - 16; 923 | } else { 924 | index = move.to + 16; 925 | } 926 | board[index] = {type: PAWN, color: them}; 927 | } 928 | 929 | 930 | if (move.flags & (BITS.KSIDE_CASTLE | BITS.QSIDE_CASTLE)) { 931 | var castling_to, castling_from; 932 | if (move.flags & BITS.KSIDE_CASTLE) { 933 | castling_to = move.to + 1; 934 | castling_from = move.to - 1; 935 | } else if (move.flags & BITS.QSIDE_CASTLE) { 936 | castling_to = move.to - 2; 937 | castling_from = move.to + 1; 938 | } 939 | 940 | board[castling_to] = board[castling_from]; 941 | board[castling_from] = null; 942 | } 943 | 944 | return move; 945 | } 946 | 947 | /* this function is used to uniquely identify ambiguous moves */ 948 | function get_disambiguator(move) { 949 | var moves = generate_moves(); 950 | 951 | var from = move.from; 952 | var to = move.to; 953 | var piece = move.piece; 954 | 955 | var ambiguities = 0; 956 | var same_rank = 0; 957 | var same_file = 0; 958 | 959 | for (var i = 0, len = moves.length; i < len; i++) { 960 | var ambig_from = moves[i].from; 961 | var ambig_to = moves[i].to; 962 | var ambig_piece = moves[i].piece; 963 | 964 | /* if a move of the same piece type ends on the same to square, we'll 965 | * need to add a disambiguator to the algebraic notation 966 | */ 967 | if (piece === ambig_piece && from !== ambig_from && to === ambig_to) { 968 | ambiguities++; 969 | 970 | if (rank(from) === rank(ambig_from)) { 971 | same_rank++; 972 | } 973 | 974 | if (file(from) === file(ambig_from)) { 975 | same_file++; 976 | } 977 | } 978 | } 979 | 980 | if (ambiguities > 0) { 981 | /* if there exists a similar moving piece on the same rank and file as 982 | * the move in question, use the square as the disambiguator 983 | */ 984 | if (same_rank > 0 && same_file > 0) { 985 | return algebraic(from); 986 | } 987 | /* if the moving piece rests on the same file, use the rank symbol as the 988 | * disambiguator 989 | */ 990 | else if (same_file > 0) { 991 | return algebraic(from).charAt(1); 992 | } 993 | /* else use the file symbol */ 994 | else { 995 | return algebraic(from).charAt(0); 996 | } 997 | } 998 | 999 | return ''; 1000 | } 1001 | 1002 | function ascii() { 1003 | var s = ' +------------------------+\n'; 1004 | for (var i = SQUARES.a8; i <= SQUARES.h1; i++) { 1005 | /* display the rank */ 1006 | if (file(i) === 0) { 1007 | s += ' ' + '87654321'[rank(i)] + ' |'; 1008 | } 1009 | 1010 | /* empty piece */ 1011 | if (board[i] == null) { 1012 | s += ' . '; 1013 | } else { 1014 | var piece = board[i].type; 1015 | var color = board[i].color; 1016 | var symbol = (color === WHITE) ? 1017 | piece.toUpperCase() : piece.toLowerCase(); 1018 | s += ' ' + symbol + ' '; 1019 | } 1020 | 1021 | if ((i + 1) & 0x88) { 1022 | s += '|\n'; 1023 | i += 8; 1024 | } 1025 | } 1026 | s += ' +------------------------+\n'; 1027 | s += ' a b c d e f g h\n'; 1028 | 1029 | return s; 1030 | } 1031 | 1032 | /***************************************************************************** 1033 | * UTILITY FUNCTIONS 1034 | ****************************************************************************/ 1035 | function rank(i) { 1036 | return i >> 4; 1037 | } 1038 | 1039 | function file(i) { 1040 | return i & 15; 1041 | } 1042 | 1043 | function algebraic(i){ 1044 | var f = file(i), r = rank(i); 1045 | return 'abcdefgh'.substring(f,f+1) + '87654321'.substring(r,r+1); 1046 | } 1047 | 1048 | function swap_color(c) { 1049 | return c === WHITE ? BLACK : WHITE; 1050 | } 1051 | 1052 | function is_digit(c) { 1053 | return '0123456789'.indexOf(c) !== -1; 1054 | } 1055 | 1056 | /* pretty = external move object */ 1057 | function make_pretty(ugly_move) { 1058 | var move = clone(ugly_move); 1059 | move.san = move_to_san(move); 1060 | move.to = algebraic(move.to); 1061 | move.from = algebraic(move.from); 1062 | 1063 | var flags = ''; 1064 | 1065 | for (var flag in BITS) { 1066 | if (BITS[flag] & move.flags) { 1067 | flags += FLAGS[flag]; 1068 | } 1069 | } 1070 | move.flags = flags; 1071 | 1072 | return move; 1073 | } 1074 | 1075 | function clone(obj) { 1076 | var dupe = (obj instanceof Array) ? [] : {}; 1077 | 1078 | for (var property in obj) { 1079 | if (typeof property === 'object') { 1080 | dupe[property] = clone(obj[property]); 1081 | } else { 1082 | dupe[property] = obj[property]; 1083 | } 1084 | } 1085 | 1086 | return dupe; 1087 | } 1088 | 1089 | function trim(str) { 1090 | return str.replace(/^\s+|\s+$/g, ''); 1091 | } 1092 | 1093 | /***************************************************************************** 1094 | * DEBUGGING UTILITIES 1095 | ****************************************************************************/ 1096 | function perft(depth) { 1097 | var moves = generate_moves({legal: false}); 1098 | var nodes = 0; 1099 | var color = turn; 1100 | 1101 | for (var i = 0, len = moves.length; i < len; i++) { 1102 | make_move(moves[i]); 1103 | if (!king_attacked(color)) { 1104 | if (depth - 1 > 0) { 1105 | var child_nodes = perft(depth - 1); 1106 | nodes += child_nodes; 1107 | } else { 1108 | nodes++; 1109 | } 1110 | } 1111 | undo_move(); 1112 | } 1113 | 1114 | return nodes; 1115 | } 1116 | 1117 | return { 1118 | /*************************************************************************** 1119 | * PUBLIC CONSTANTS (is there a better way to do this?) 1120 | **************************************************************************/ 1121 | WHITE: WHITE, 1122 | BLACK: BLACK, 1123 | PAWN: PAWN, 1124 | KNIGHT: KNIGHT, 1125 | BISHOP: BISHOP, 1126 | ROOK: ROOK, 1127 | QUEEN: QUEEN, 1128 | KING: KING, 1129 | SQUARES: (function() { 1130 | /* from the ECMA-262 spec (section 12.6.4): 1131 | * "The mechanics of enumerating the properties ... is 1132 | * implementation dependent" 1133 | * so: for (var sq in SQUARES) { keys.push(sq); } might not be 1134 | * ordered correctly 1135 | */ 1136 | var keys = []; 1137 | for (var i = SQUARES.a8; i <= SQUARES.h1; i++) { 1138 | if (i & 0x88) { i += 7; continue; } 1139 | keys.push(algebraic(i)); 1140 | } 1141 | return keys; 1142 | })(), 1143 | FLAGS: FLAGS, 1144 | 1145 | /*************************************************************************** 1146 | * PUBLIC API 1147 | **************************************************************************/ 1148 | load: function(fen) { 1149 | return load(fen); 1150 | }, 1151 | 1152 | reset: function() { 1153 | return reset(); 1154 | }, 1155 | 1156 | moves: function(options) { 1157 | /* The internal representation of a chess move is in 0x88 format, and 1158 | * not meant to be human-readable. The code below converts the 0x88 1159 | * square coordinates to algebraic coordinates. It also prunes an 1160 | * unnecessary move keys resulting from a verbose call. 1161 | */ 1162 | 1163 | var ugly_moves = generate_moves(options); 1164 | var moves = []; 1165 | 1166 | for (var i = 0, len = ugly_moves.length; i < len; i++) { 1167 | 1168 | /* does the user want a full move object (most likely not), or just 1169 | * SAN 1170 | */ 1171 | if (typeof options !== 'undefined' && 'verbose' in options && 1172 | options.verbose) { 1173 | moves.push(make_pretty(ugly_moves[i])); 1174 | } else { 1175 | moves.push(move_to_san(ugly_moves[i])); 1176 | } 1177 | } 1178 | 1179 | return moves; 1180 | }, 1181 | 1182 | in_check: function() { 1183 | return in_check(); 1184 | }, 1185 | 1186 | in_checkmate: function() { 1187 | return in_checkmate(); 1188 | }, 1189 | 1190 | in_stalemate: function() { 1191 | return in_stalemate(); 1192 | }, 1193 | 1194 | in_draw: function() { 1195 | return half_moves >= 100 || 1196 | in_stalemate() || 1197 | insufficient_material() || 1198 | in_threefold_repetition(); 1199 | }, 1200 | 1201 | insufficient_material: function() { 1202 | return insufficient_material(); 1203 | }, 1204 | 1205 | in_threefold_repetition: function() { 1206 | return in_threefold_repetition(); 1207 | }, 1208 | 1209 | game_over: function() { 1210 | return half_moves >= 100 || 1211 | in_checkmate() || 1212 | in_stalemate() || 1213 | insufficient_material() || 1214 | in_threefold_repetition(); 1215 | }, 1216 | 1217 | validate_fen: function(fen) { 1218 | return validate_fen(fen); 1219 | }, 1220 | 1221 | fen: function() { 1222 | return generate_fen(); 1223 | }, 1224 | 1225 | pgn: function(options) { 1226 | /* using the specification from http://www.chessclub.com/help/PGN-spec 1227 | * example for html usage: .pgn({ max_width: 72, newline_char: "
" }) 1228 | */ 1229 | var newline = (typeof options === 'object' && 1230 | typeof options.newline_char === 'string') ? 1231 | options.newline_char : '\n'; 1232 | var max_width = (typeof options === 'object' && 1233 | typeof options.max_width === 'number') ? 1234 | options.max_width : 0; 1235 | var result = []; 1236 | var header_exists = false; 1237 | 1238 | /* add the PGN header headerrmation */ 1239 | for (var i in header) { 1240 | /* TODO: order of enumerated properties in header object is not 1241 | * guaranteed, see ECMA-262 spec (section 12.6.4) 1242 | */ 1243 | result.push('[' + i + ' \"' + header[i] + '\"]' + newline); 1244 | header_exists = true; 1245 | } 1246 | 1247 | if (header_exists && history.length) { 1248 | result.push(newline); 1249 | } 1250 | 1251 | /* pop all of history onto reversed_history */ 1252 | var reversed_history = []; 1253 | while (history.length > 0) { 1254 | reversed_history.push(undo_move()); 1255 | } 1256 | 1257 | var moves = []; 1258 | var move_string = ''; 1259 | var pgn_move_number = 1; 1260 | 1261 | /* build the list of moves. a move_string looks like: "3. e3 e6" */ 1262 | while (reversed_history.length > 0) { 1263 | var move = reversed_history.pop(); 1264 | 1265 | /* if the position started with black to move, start PGN with 1. ... */ 1266 | if (pgn_move_number === 1 && move.color === 'b') { 1267 | move_string = '1. ...'; 1268 | pgn_move_number++; 1269 | } else if (move.color === 'w') { 1270 | /* store the previous generated move_string if we have one */ 1271 | if (move_string.length) { 1272 | moves.push(move_string); 1273 | } 1274 | move_string = pgn_move_number + '.'; 1275 | pgn_move_number++; 1276 | } 1277 | 1278 | move_string = move_string + ' ' + move_to_san(move); 1279 | make_move(move); 1280 | } 1281 | 1282 | /* are there any other leftover moves? */ 1283 | if (move_string.length) { 1284 | moves.push(move_string); 1285 | } 1286 | 1287 | /* is there a result? */ 1288 | if (typeof header.Result !== 'undefined') { 1289 | moves.push(header.Result); 1290 | } 1291 | 1292 | /* history should be back to what is was before we started generating PGN, 1293 | * so join together moves 1294 | */ 1295 | if (max_width === 0) { 1296 | return result.join('') + moves.join(' '); 1297 | } 1298 | 1299 | /* wrap the PGN output at max_width */ 1300 | var current_width = 0; 1301 | for (var i = 0; i < moves.length; i++) { 1302 | /* if the current move will push past max_width */ 1303 | if (current_width + moves[i].length > max_width && i !== 0) { 1304 | 1305 | /* don't end the line with whitespace */ 1306 | if (result[result.length - 1] === ' ') { 1307 | result.pop(); 1308 | } 1309 | 1310 | result.push(newline); 1311 | current_width = 0; 1312 | } else if (i !== 0) { 1313 | result.push(' '); 1314 | current_width++; 1315 | } 1316 | result.push(moves[i]); 1317 | current_width += moves[i].length; 1318 | } 1319 | 1320 | return result.join(''); 1321 | }, 1322 | 1323 | load_pgn: function(pgn, options) { 1324 | function mask(str) { 1325 | return str.replace(/\n/g, '\\n').replace(/\r/g, '\\r'); 1326 | } 1327 | 1328 | /* convert a move from Standard Algebraic Notation (SAN) to 0x88 1329 | * coordinates 1330 | */ 1331 | function move_from_san(move) { 1332 | var to, from, flags = BITS.NORMAL, promotion; 1333 | var parse = move.match(/^([NBKRQ])?([abcdefgh12345678][12345678]?)?(x)?([abcdefgh][12345678])(=?[NBRQ])?/); 1334 | if (move.slice(0, 5) === 'O-O-O') { 1335 | from = kings[turn]; 1336 | to = from - 2; 1337 | flags = BITS.QSIDE_CASTLE; 1338 | } else if (move.slice(0, 3) === 'O-O') { 1339 | from = kings[turn]; 1340 | to = from + 2; 1341 | flags = BITS.KSIDE_CASTLE; 1342 | } else if (parse && parse[1]) { 1343 | // regular moves 1344 | var piece = parse[1].toLowerCase(); 1345 | if (parse[3]) { 1346 | // capture 1347 | flags = BITS.CAPTURE; 1348 | } 1349 | to = SQUARES[parse[4]]; 1350 | for (var j = 0, len = PIECE_OFFSETS[piece].length; j < len; j++) { 1351 | var offset = PIECE_OFFSETS[piece][j]; 1352 | var square = to; 1353 | 1354 | while (true) { 1355 | square += offset; 1356 | if (square & 0x88) break; 1357 | 1358 | var b = board[square]; 1359 | if (b) { 1360 | if (b.color === turn && b.type === piece && (!parse[2] || algebraic(square).indexOf(parse[2]) >= 0)) { 1361 | from = square; 1362 | } 1363 | break; 1364 | } 1365 | 1366 | /* break, if knight or king */ 1367 | if (piece === 'n' || piece === 'k') break; 1368 | } 1369 | } 1370 | } else if (parse) { 1371 | // pawn move 1372 | if (parse[3]) { 1373 | // capture 1374 | to = SQUARES[parse[4]]; 1375 | for (var j = 2; j < 4; j++) { 1376 | var square = to - PAWN_OFFSETS[turn][j]; 1377 | if (square & 0x88) continue; 1378 | 1379 | if (board[square] != null && 1380 | board[square].color === turn && 1381 | algebraic(square)[0] === parse[2]) { 1382 | from = square; 1383 | } 1384 | } 1385 | if (board[to]) { 1386 | flags = BITS.CAPTURE; 1387 | } else { 1388 | flags = BITS.EP_CAPTURE; 1389 | } 1390 | } else { 1391 | // normal move 1392 | to = SQUARES[move.slice(0,2)]; 1393 | var c = to - PAWN_OFFSETS[turn][0], 1394 | b = board[c]; 1395 | if (b && b.type === PAWN && b.color === turn) { 1396 | from = c; 1397 | } else { 1398 | c = to - PAWN_OFFSETS[turn][1]; 1399 | b = board[c]; 1400 | if (b && b.type === PAWN && b.color === turn) { 1401 | from = c; 1402 | flags = BITS.BIG_PAWN; 1403 | } 1404 | } 1405 | } 1406 | // promotion? 1407 | if (parse[5]) { 1408 | if(typeof parse[5][1] == 'undefined') { 1409 | promotion = parse[5][0].toLowerCase(); 1410 | } else { 1411 | promotion = parse[5][1].toLowerCase(); 1412 | } 1413 | } 1414 | } 1415 | if (from >=0 && to >=0 && flags) { 1416 | return build_move(board, from, to, flags, promotion); 1417 | } else if (move.length > 0) { 1418 | /* alert(move); // error in PGN, or in parsing. */ 1419 | } 1420 | } 1421 | 1422 | function get_move_obj(move) { 1423 | return move_from_san(trim(move)); 1424 | } 1425 | 1426 | function has_keys(object) { 1427 | var has_keys = false; 1428 | for (var key in object) { 1429 | has_keys = true; 1430 | } 1431 | return has_keys; 1432 | } 1433 | 1434 | function parse_pgn_header(header, options) { 1435 | var newline_char = (typeof options === 'object' && 1436 | typeof options.newline_char === 'string') ? 1437 | options.newline_char : '\r?\n'; 1438 | var header_obj = {}; 1439 | var headers = header.split(newline_char); 1440 | var key = ''; 1441 | var value = ''; 1442 | 1443 | for (var i = 0; i < headers.length; i++) { 1444 | key = headers[i].replace(/^\[([A-Z][A-Za-z]*)\s.*\]$/, '$1'); 1445 | value = headers[i].replace(/^\[[A-Za-z]+\s"(.*)"\]$/, '$1'); 1446 | if (trim(key).length > 0) { 1447 | header_obj[key] = value; 1448 | } 1449 | } 1450 | 1451 | return header_obj; 1452 | } 1453 | 1454 | var newline_char = (typeof options === 'object' && 1455 | typeof options.newline_char === 'string') ? 1456 | options.newline_char : '\r?\n'; 1457 | var regex = new RegExp('^(\\[(.|' + mask(newline_char) + ')*\\])' + 1458 | '(' + mask(newline_char) + ')*' + 1459 | '1.(' + mask(newline_char) + '|.)*$', 'g'); 1460 | 1461 | /* get header part of the PGN file */ 1462 | var header_string = pgn.replace(regex, '$1'); 1463 | 1464 | /* no info part given, begins with moves */ 1465 | if (header_string[0] !== '[') { 1466 | header_string = ''; 1467 | } 1468 | 1469 | reset(); 1470 | 1471 | /* parse PGN header */ 1472 | var headers = parse_pgn_header(header_string, options); 1473 | for (var key in headers) { 1474 | set_header([key, headers[key]]); 1475 | } 1476 | 1477 | /* delete header to get the moves */ 1478 | var ms = pgn.replace(header_string, '').replace(new RegExp(mask(newline_char), 'g'), ' '); 1479 | 1480 | /* delete comments */ 1481 | ms = ms.replace(/(\{[^}]+\})+?/g, ''); 1482 | 1483 | /* delete move numbers */ 1484 | ms = ms.replace(/\d+\./g, ''); 1485 | 1486 | 1487 | /* trim and get array of moves */ 1488 | var moves = trim(ms).split(new RegExp(/\s+/)); 1489 | 1490 | /* delete empty entries */ 1491 | moves = moves.join(',').replace(/,,+/g, ',').split(','); 1492 | var move = ''; 1493 | 1494 | for (var half_move = 0; half_move < moves.length - 1; half_move++) { 1495 | move = get_move_obj(moves[half_move]); 1496 | 1497 | /* move not possible! (don't clear the board to examine to show the 1498 | * latest valid position) 1499 | */ 1500 | if (move == null) { 1501 | return false; 1502 | } else { 1503 | make_move(move); 1504 | } 1505 | } 1506 | 1507 | /* examine last move */ 1508 | move = moves[moves.length - 1]; 1509 | if (POSSIBLE_RESULTS.indexOf(move) > -1) { 1510 | if (has_keys(header) && typeof header.Result === 'undefined') { 1511 | set_header(['Result', move]); 1512 | } 1513 | } 1514 | else { 1515 | move = get_move_obj(move); 1516 | if (move == null) { 1517 | return false; 1518 | } else { 1519 | make_move(move); 1520 | } 1521 | } 1522 | return true; 1523 | }, 1524 | 1525 | header: function() { 1526 | return set_header(arguments); 1527 | }, 1528 | 1529 | ascii: function() { 1530 | return ascii(); 1531 | }, 1532 | 1533 | turn: function() { 1534 | return turn; 1535 | }, 1536 | 1537 | move: function(move) { 1538 | /* The move function can be called with in the following parameters: 1539 | * 1540 | * .move('Nxb7') <- where 'move' is a case-sensitive SAN string 1541 | * 1542 | * .move({ from: 'h7', <- where the 'move' is a move object (additional 1543 | * to :'h8', fields are ignored) 1544 | * promotion: 'q', 1545 | * }) 1546 | */ 1547 | var move_obj = null; 1548 | var moves = generate_moves(); 1549 | 1550 | if (typeof move === 'string') { 1551 | /* convert the move string to a move object */ 1552 | for (var i = 0, len = moves.length; i < len; i++) { 1553 | if (move === move_to_san(moves[i])) { 1554 | move_obj = moves[i]; 1555 | break; 1556 | } 1557 | } 1558 | } else if (typeof move === 'object') { 1559 | /* convert the pretty move object to an ugly move object */ 1560 | for (var i = 0, len = moves.length; i < len; i++) { 1561 | if (move.from === algebraic(moves[i].from) && 1562 | move.to === algebraic(moves[i].to) && 1563 | (!('promotion' in moves[i]) || 1564 | move.promotion === moves[i].promotion)) { 1565 | move_obj = moves[i]; 1566 | break; 1567 | } 1568 | } 1569 | } 1570 | 1571 | /* failed to find move */ 1572 | if (!move_obj) { 1573 | return null; 1574 | } 1575 | 1576 | /* need to make a copy of move because we can't generate SAN after the 1577 | * move is made 1578 | */ 1579 | var pretty_move = make_pretty(move_obj); 1580 | 1581 | make_move(move_obj); 1582 | 1583 | return pretty_move; 1584 | }, 1585 | 1586 | undo: function() { 1587 | var move = undo_move(); 1588 | return (move) ? make_pretty(move) : null; 1589 | }, 1590 | 1591 | clear: function() { 1592 | return clear(); 1593 | }, 1594 | 1595 | put: function(piece, square) { 1596 | return put(piece, square); 1597 | }, 1598 | 1599 | get: function(square) { 1600 | return get(square); 1601 | }, 1602 | 1603 | remove: function(square) { 1604 | return remove(square); 1605 | }, 1606 | 1607 | perft: function(depth) { 1608 | return perft(depth); 1609 | }, 1610 | 1611 | square_color: function(square) { 1612 | if (square in SQUARES) { 1613 | var sq_0x88 = SQUARES[square]; 1614 | return ((rank(sq_0x88) + file(sq_0x88)) % 2 === 0) ? 'light' : 'dark'; 1615 | } 1616 | 1617 | return null; 1618 | }, 1619 | 1620 | history: function(options) { 1621 | var reversed_history = []; 1622 | var move_history = []; 1623 | var verbose = (typeof options !== 'undefined' && 'verbose' in options && 1624 | options.verbose); 1625 | 1626 | while (history.length > 0) { 1627 | reversed_history.push(undo_move()); 1628 | } 1629 | 1630 | while (reversed_history.length > 0) { 1631 | var move = reversed_history.pop(); 1632 | if (verbose) { 1633 | move_history.push(make_pretty(move)); 1634 | } else { 1635 | move_history.push(move_to_san(move)); 1636 | } 1637 | make_move(move); 1638 | } 1639 | 1640 | return move_history; 1641 | } 1642 | 1643 | }; 1644 | }; 1645 | 1646 | /* export Chess object if using node or any other CommonJS compatible 1647 | * environment */ 1648 | if (typeof exports !== 'undefined') exports.Chess = Chess; 1649 | /* export Chess object for any RequireJS compatible environment */ 1650 | if (typeof define !== 'undefined') define( function () { return Chess; }); 1651 | --------------------------------------------------------------------------------