1/* 2 * pgn4web javascript chessboard 3 * copyright (C) 2009-2016 Paolo Casaschi 4 * see README file and http://pgn4web.casaschi.net 5 * for credits, license and more details 6 */ 7 8"use strict"; 9 10var pgn4web_version = '3.00'; 11 12var pgn4web_project_url = "http://pgn4web.casaschi.net"; 13var pgn4web_project_author = "Paolo Casaschi"; 14var pgn4web_project_email = "pgn4web@casaschi.net"; 15 16var helpWin; 17function displayHelp(section) { 18 if (helpWin && !helpWin.closed) { helpWin.close(); } 19 helpWin = window.open(detectHelpLocation() + (section ? "?" + section : ""), "pgn4web_help", "resizable=yes,scrollbars=yes,toolbar=no,location=no,menubar=no,status=no"); 20 if (helpWin && window.focus) { helpWin.focus(); } 21} 22 23 24// empty event APIs to be redefined 25 26function customFunctionOnPgnTextLoad() {} 27function customFunctionOnPgnGameLoad() {} 28function customFunctionOnMove() {} 29function customFunctionOnAlert(msg) {} 30function customFunctionOnCheckLiveBroadcastStatus() {} 31 32// custom header tags APIs for customFunctionOnPgnGameLoad 33 34function customPgnHeaderTag(customTag, htmlElementId, gameNum) { 35 var matches, tag = ""; 36 customTag = customTag.replace(/\W+/g, ""); 37 if (gameNum === undefined) { gameNum = currentGame; } 38 if ((pgnHeader[gameNum]) && (matches = pgnHeader[gameNum].match('\\[\\s*' + customTag + '\\s*\"([^\"]+)\"\\s*\\]'))) { tag = matches[1]; } 39 if (htmlElementId) { 40 var theObj = document.getElementById(htmlElementId); 41 if ((theObj) && (typeof(theObj.innerHTML) == "string")) { theObj.innerHTML = tag; } 42 } 43 return tag; 44} 45 46// custom comment tags API for customFunctionOnMove 47 48function customPgnCommentTag(customTag, htmlElementId, plyNum, varId) { 49 var matches, tag = "", theObj; 50 customTag = customTag.replace(/\W+/g, ""); 51 if (typeof(varId) == "undefined") { varId = 0; } 52 if (typeof(plyNum) == "undefined") { plyNum = CurrentPly; } 53 if ((MoveCommentsVar[varId][plyNum]) && (matches = MoveCommentsVar[varId][plyNum].match('\\[%' + customTag + '\\s+((?:,?(?:"[^"]*"|[^,\\]]*))*)\\s*\\]'))) { tag = matches[1].replace(/\s+$/, ""); } 54 if ((htmlElementId) && (theObj = document.getElementById(htmlElementId)) && (typeof(theObj.innerHTML) == "string")) { theObj.innerHTML = tag; } 55 return tag; 56} 57 58 59function simpleAddEvent(obj, evt, cbk) { 60 if (obj.addEventListener) { obj.addEventListener(evt, cbk, false); } 61 else if (obj.attachEvent) { obj.attachEvent("on" + evt, cbk); } // bugfix: IE8 and below specific 62} 63 64simpleAddEvent(document, "keydown", pgn4web_handleKey_event); 65simpleAddEvent(window, "load", pgn4web_onload_event); 66 67 68function pgn4web_onload_event(e) { 69 pgn4web_onload(e); 70} 71 72function pgn4web_onload(e) { 73 start_pgn4web(); 74} 75 76function start_pgn4web() { 77 // keep startup logs at the very first run, otherwise reset 78 if (alertFirstResetLoadingPgn) { alertFirstResetLoadingPgn = false; } 79 else { resetAlert(); } 80 InitImages(); 81 createBoard(); 82 if (LiveBroadcastDelay > 0) { restartLiveBroadcastTimeout(); } 83 pgn4web_initTouchEvents(); 84} 85 86var alertLog; 87var alertLast; 88var alertNum; 89var alertNumSinceReset; 90var fatalErrorNumSinceReset; 91var alertPromptInterval = null; 92var alertPromptOn = false; 93var alertFirstResetLoadingPgn = true; 94 95resetAlert(); 96 97function resetAlert() { 98 alertLog = new Array(5); 99 alertLast = alertLog.length - 1; 100 alertNum = alertNumSinceReset = fatalErrorNumSinceReset = 0; 101 stopAlertPrompt(); 102 if (!alertFirstResetLoadingPgn) { 103 if (boardIsDefault(debugShortcutSquare)) { boardShortcut(debugShortcutSquare, "pgn4web v" + pgn4web_version + " debug info", null, true); } 104 } 105} 106 107function myAlert(msg, fatalError, doNotPrompt) { 108 alertNum++; 109 alertNumSinceReset++; 110 if (fatalError) { fatalErrorNumSinceReset++; } 111 alertLast = (alertLast + 1) % alertLog.length; 112 alertLog[alertLast] = msg + "\n" + (new Date()).toLocaleString(); 113 if (boardIsDefault(debugShortcutSquare)) { boardShortcut(debugShortcutSquare, "pgn4web v" + pgn4web_version + " debug info\n" + alertNum + " alert" + (alertNum > 1 ? "s" : ""), null, true); } 114 if ((!doNotPrompt) && ((LiveBroadcastDelay === 0) || (LiveBroadcastAlert === true)) && (boardIsDefault(debugShortcutSquare))) { startAlertPrompt(); } 115 customFunctionOnAlert(msg); 116} 117 118function startAlertPrompt() { 119 if (alertPromptOn) { return; } // dont start flashing twice 120 if (alertPromptInterval) { clearTimeout(alertPromptInterval); } 121 alertPromptInterval = setTimeout("alertPromptTick(true);", 500); 122} 123 124function stopAlertPrompt() { 125 if (alertPromptInterval) { 126 clearTimeout(alertPromptInterval); 127 alertPromptInterval = null; 128 } 129 if (alertPromptOn) { alertPromptTick(false); } 130} 131 132function alertPromptTick(restart) { 133 if (alertPromptInterval) { 134 clearTimeout(alertPromptInterval); 135 alertPromptInterval = null; 136 } 137 var colRow = colRowFromSquare(debugShortcutSquare); 138 if (!colRow) { return; } 139 140 var alertPromptDelay = 1500; // for alerts before the board is printed 141 var theObj = document.getElementById('tcol' + colRow.col + 'trow' + colRow.row); 142 if (theObj) { 143 if (alertPromptOn) { 144 if ((highlightOption) && ((colFromHighlighted === 0 && rowFromHighlighted === 7) || (colToHighlighted === 0 && rowToHighlighted === 7))) { 145 theObj.className = 'highlightWhiteSquare'; 146 } else { theObj.className = 'whiteSquare'; } 147 } else { theObj.className = 'blackSquare'; } 148 149 alertPromptOn = !alertPromptOn; 150 if (alertPromptOn) { alertPromptDelay = 500; } 151 else { alertPromptDelay = 3000; } 152 } 153 if (restart) { alertPromptInterval = setTimeout("alertPromptTick(true);", alertPromptDelay); } 154} 155 156 157function stopEvProp(e) { 158 e.cancelBubble = true; 159 if (e.stopPropagation) { e.stopPropagation(); } 160 if (e.preventDefault) { e.preventDefault(); } 161 return false; 162} 163 164// for onFocus/onBlur textbox events, allowing text typing 165var shortcutKeysWereEnabled = false; 166function disableShortcutKeysAndStoreStatus() { 167 if ((shortcutKeysWereEnabled = shortcutKeysEnabled) === true) { 168 SetShortcutKeysEnabled(false); 169 } 170} 171function restoreShortcutKeysStatus() { 172 if (shortcutKeysWereEnabled === true) { SetShortcutKeysEnabled(true); } 173 shortcutKeysWereEnabled = false; 174} 175 176function customShortcutKey_Shift_0() {} 177function customShortcutKey_Shift_1() {} 178function customShortcutKey_Shift_2() {} 179function customShortcutKey_Shift_3() {} 180function customShortcutKey_Shift_4() {} 181function customShortcutKey_Shift_5() {} 182function customShortcutKey_Shift_6() {} 183function customShortcutKey_Shift_7() {} 184function customShortcutKey_Shift_8() {} 185function customShortcutKey_Shift_9() {} 186 187function pgn4web_handleKey_event(e) { 188 pgn4web_handleKey(e); 189} 190 191var shortcutKeysEnabled = false; 192function pgn4web_handleKey(e) { 193 var keycode, oldPly, oldVar, colRow, colRowList; 194 195 if (!e) { e = window.event; } 196 197 if (e.altKey || e.ctrlKey || e.metaKey) { return true; } 198 199 keycode = e.keyCode; 200 201 // shift-escape always enabled: toggle shortcut keys 202 if (!shortcutKeysEnabled && !(keycode == 27 && e.shiftKey)) { return true; } 203 204 switch (keycode) { 205 206 case 8: // backspace 207 case 9: // tab 208 case 16: // shift 209 case 17: // ctrl 210 case 18: // alt 211 case 32: // space 212 case 33: // page-up 213 case 34: // page-down 214 case 35: // end 215 case 36: // home 216 case 92: // super 217 case 93: // menu 218 case 188: // comma 219 return true; 220 221 case 27: // escape 222 if (e.shiftKey) { interactivelyToggleShortcutKeys(); } 223 else { displayHelp(); } 224 break; 225 226 case 189: // dash 227 if (colRowList = prompt("Enter shortcut square coordinates to click:", "")) { 228 colRowList = colRowList.toUpperCase().replace(/[^A-Z0-9]/g,""); 229 while (colRow = colRowFromSquare(colRowList)) { 230 boardOnClick[colRow.col][colRow.row]({"id": "img_tcol" + colRow.col + "trow" + colRow.row}, e); 231 colRowList = colRowList.substr(2); 232 } 233 } 234 break; 235 236 case 90: // z 237 if (e.shiftKey) { window.open(pgn4web_project_url); } 238 else { displayDebugInfo(); } 239 break; 240 241 case 37: // left-arrow 242 case 74: // j 243 backButton(e); 244 break; 245 246 case 38: // up-arrow 247 case 72: // h 248 startButton(e); 249 break; 250 251 case 39: // right-arrow 252 case 75: // k 253 forwardButton(e); 254 break; 255 256 case 40: // down-arrow 257 case 76: // l 258 endButton(e); 259 break; 260 261 case 73: // i 262 MoveToPrevComment(e.shiftKey); 263 break; 264 265 case 79: // o 266 MoveToNextComment(e.shiftKey); 267 break; 268 269 case 190: // dot 270 if (e.shiftKey) { goToFirstChild(); } 271 else { goToNextVariationSibling(); } 272 break; 273 274 case 85: // u 275 if (e.shiftKey) { undoStackRedo(); } 276 else { undoStackUndo(); } 277 break; 278 279 case 45: // insert 280 undoStackRedo(); 281 break; 282 283 case 46: // delete 284 undoStackUndo(); 285 break; 286 287 case 83: // s 288 if (e.shiftKey) { searchPgnGame(""); } 289 else { searchPgnGamePrompt(); } 290 break; 291 292 case 13: // enter 293 if (e.shiftKey) { searchPgnGame(lastSearchPgnExpression, true); } 294 else { searchPgnGame(lastSearchPgnExpression); } 295 break; 296 297 case 68: // d 298 if (e.shiftKey) { displayFenData(); } 299 else { displayPgnData(true); } 300 break; 301 302 case 187: // equal 303 SwitchAutoPlay(); 304 break; 305 306 case 65: // a 307 GoToMove(CurrentPly + 1); 308 SetAutoPlay(true); 309 break; 310 311 case 48: // 0 312 if (e.shiftKey) { customShortcutKey_Shift_0(); } 313 else { SetAutoPlay(false); } 314 break; 315 316 case 49: // 1 317 if (e.shiftKey) { customShortcutKey_Shift_1(); } 318 else { SetAutoplayDelayAndStart( 1*1000); } 319 break; 320 321 case 50: // 2 322 if (e.shiftKey) { customShortcutKey_Shift_2(); } 323 else { SetAutoplayDelayAndStart( 2*1000); } 324 break; 325 326 case 51: // 3 327 if (e.shiftKey) { customShortcutKey_Shift_3(); } 328 else { SetAutoplayDelayAndStart( 3*1000); } 329 break; 330 331 case 52: // 4 332 if (e.shiftKey) { customShortcutKey_Shift_4(); } 333 else { SetAutoplayDelayAndStart( 4*1000); } 334 break; 335 336 case 53: // 5 337 if (e.shiftKey) { customShortcutKey_Shift_5(); } 338 else { SetAutoplayDelayAndStart( 5*1000); } 339 break; 340 341 case 54: // 6 342 if (e.shiftKey) { customShortcutKey_Shift_6(); } 343 else { SetAutoplayDelayAndStart( 6*1000); } 344 break; 345 346 case 55: // 7 347 if (e.shiftKey) { customShortcutKey_Shift_7(); } 348 else { SetAutoplayDelayAndStart( 7*1000); } 349 break; 350 351 case 56: // 8 352 if (e.shiftKey) { customShortcutKey_Shift_8(); } 353 else { SetAutoplayDelayAndStart( 8*1000); } 354 break; 355 356 case 57: // 9 357 if (e.shiftKey) { customShortcutKey_Shift_9(); } 358 else { setCustomAutoplayDelay(); } 359 break; 360 361 case 81: // q 362 SetAutoplayDelayAndStart(10*1000); 363 break; 364 365 case 87: // w 366 SetAutoplayDelayAndStart(20*1000); 367 break; 368 369 case 69: // e 370 SetAutoplayDelayAndStart(30*1000); 371 break; 372 373 case 82: // r 374 pauseLiveBroadcast(); 375 break; 376 377 case 84: // t 378 if (e.shiftKey) { LiveBroadcastSteppingMode = !LiveBroadcastSteppingMode; } 379 else { refreshPgnSource(); } 380 break; 381 382 case 89: // y 383 restartLiveBroadcast(); 384 break; 385 386 case 70: // f 387 if (!e.shiftKey || IsRotated) { FlipBoard(); } 388 break; 389 390 case 71: // g 391 SetHighlight(!highlightOption); 392 break; 393 394 case 88: // x 395 randomGameRandomPly(); 396 break; 397 398 case 67: // c 399 if (numberOfGames > 1) { Init(Math.floor(Math.random()*numberOfGames)); } 400 break; 401 402 case 86: // v 403 if (numberOfGames > 1) { Init(0); } 404 break; 405 406 case 66: // b 407 Init(currentGame - 1); 408 break; 409 410 case 78: // n 411 Init(currentGame + 1); 412 break; 413 414 case 77: // m 415 if (numberOfGames > 1) { Init(numberOfGames - 1); } 416 break; 417 418 case 80: // p 419 if (e.shiftKey) { SetCommentsOnSeparateLines(!commentsOnSeparateLines); } 420 else { SetCommentsIntoMoveText(!commentsIntoMoveText); } 421 oldPly = CurrentPly; 422 oldVar = CurrentVar; 423 Init(); 424 GoToMove(oldPly, oldVar); 425 break; 426 427 default: 428 return true; 429 } 430 return stopEvProp(e); 431} 432 433var boardOnClick = new Array(8); 434var boardTitle = new Array(8); 435var boardDefault = new Array(8); 436for (var col=0; col<8; col++) { 437 boardOnClick[col] = new Array(8); 438 boardTitle[col] = new Array(8); 439 boardDefault[col] = new Array(8); 440} 441clearShortcutSquares("ABCDEFGH", "12345678"); 442 443function colRowFromSquare(square) { 444 if ((typeof(square) != "string") || (!square)) { return null; } 445 var col = square.charCodeAt(0) - 65; // 65="A" 446 if ((col < 0) || (col > 7)) { return null; } 447 var row = 56 - square.charCodeAt(1); // 56="8" 448 if ((row < 0) || (row > 7)) { return null; } 449 return { "col": col, "row": row }; 450} 451 452function clearShortcutSquares(cols, rows) { 453 if ((typeof(cols) != "string") || (typeof(rows) != "string")) { return; } 454 for (var c=0; c<cols.length; c++) { for (var r=0; r<rows.length; r++) { 455 boardShortcut(cols.charAt(c).toUpperCase()+rows.charAt(r), "", function(t,e){}); 456 } } 457} 458 459function boardIsDefault(square) { 460 var colRow = colRowFromSquare(square); 461 if (!colRow) { return false; } 462 return boardDefault[colRow.col][colRow.row]; 463} 464 465function boardShortcut(square, title, functionPointer, defaultSetting) { 466 var theObj, colRow = colRowFromSquare(square); 467 if (!colRow) { return; } 468 else { var col = colRow.col; var row = colRow.row; } 469 boardTitle[col][row] = title; 470 if (functionPointer) { boardOnClick[col][row] = functionPointer; } 471 boardDefault[col][row] = defaultSetting ? true : false; 472 if (theObj = document.getElementById('img_tcol' + col + 'trow' + row)) { 473 if (IsRotated) { square = String.fromCharCode(72-col,49+row); } 474 theObj.title = square + (boardTitle[col][row] ? ': ' + boardTitle[col][row] : ''); 475 } 476} 477 478// boardShortcut() always assumes 'square' defined as with white on bottom 479 480var debugShortcutSquare = "A8"; 481 482 483boardShortcut("A8", "pgn4web v" + pgn4web_version + " debug info", function(t,e){ displayDebugInfo(); }, true); 484 485boardShortcut("B8", "show current position FEN string", function(t,e){ displayFenData(); }, true); 486 487boardShortcut("C8", "show current game PGN source data", function(t,e){ if (e.shiftKey) { savePgnData(true); } else { displayPgnData(true); } }, true); 488 489boardShortcut("D8", "show all games PGN source data", function(t,e){ if (e.shiftKey) { savePgnData(); } else { displayPgnData(); } }, true); 490 491boardShortcut("E8", "search help", function(t,e){ displayHelp("search_tool"); }, true); 492 493boardShortcut("F8", "shortcut keys help", function(t,e){ displayHelp("shortcut_keys"); }, true); 494 495boardShortcut("G8", "shortcut squares help", function(t,e){ displayHelp(e.shiftKey ? "informant_symbols" : "shortcut_squares"); }, true); 496 497boardShortcut("H8", "pgn4web help", function(t,e){ displayHelp(e.shiftKey ? "credits_and_license" : ""); }, true); 498 499boardShortcut("A7", "pgn4web website", function(t,e){ window.open(pgn4web_project_url); }, true); 500 501boardShortcut("B7", "undo last chessboard position update", function(t,e){ undoStackUndo(); }, true); 502 503boardShortcut("C7", "redo last undo", function(t,e){ undoStackRedo(); }, true); 504 505boardShortcut("D7", "toggle highlight last move", function(t,e){ SetHighlight(!highlightOption); }, true); 506 507boardShortcut("E7", "flip board", function(t,e){ if (!e.shiftKey || IsRotated) { FlipBoard(); } }, true); 508 509boardShortcut("F7", "toggle show comments in game text", function(t,e){ if (e.shiftKey) { SetCommentsOnSeparateLines(!commentsOnSeparateLines); } else { SetCommentsIntoMoveText(!commentsIntoMoveText); } var oldPly = CurrentPly; var oldVar = CurrentVar; Init(); GoToMove(oldPly, oldVar); }, true); 510 511boardShortcut("G7", "", function(t,e){}, true); // see setG7A6B6H6... 512 513boardShortcut("H7", "toggle enabling shortcut keys", function(t,e){ interactivelyToggleShortcutKeys(); }, true); 514 515boardShortcut("A6", "", function(t,e){}, true); // see setG7A6B6H6... 516 517boardShortcut("B6", "", function(t,e){}, true); // see setG7A6B6H6... 518 519boardShortcut("C6", "search previous finished game", function(t,e){ searchPgnGame('\\[\\s*Result\\s*"(?!\\*"\\s*\\])', true); }); 520 521boardShortcut("D6", "search previous unfinished game", function(t,e){ searchPgnGame('\\[\\s*Result\\s*"\\*"\\s*\\]', true); }); 522 523boardShortcut("E6", "search next unfinished game", function(t,e){ searchPgnGame('\\[\\s*Result\\s*"\\*"\\s*\\]', false); }, true); 524 525boardShortcut("F6", "search next finished game", function(t,e){ searchPgnGame('\\[\\s*Result\\s*"(?!\\*"\\s*\\])', false); }, true); 526 527boardShortcut("G6", "", function(t,e){}, true); 528 529boardShortcut("H6", "", function(t,e){}, true); // see setG7A6B6H6... 530 531boardShortcut("A5", "repeat last search backward", function(t,e){ searchPgnGame(lastSearchPgnExpression, true); }, true); 532 533boardShortcut("B5", "search prompt", function(t,e){ if (e.shiftKey) { searchPgnGame(""); } else { searchPgnGamePrompt(); } }, true); 534 535boardShortcut("C5", "repeat last search", function(t,e){ searchPgnGame(lastSearchPgnExpression); }, true); 536 537boardShortcut("D5", "search previous win result", function(t,e){ searchPgnGame('\\[\\s*Result\\s*"(1-0|0-1)"\\s*\\]', true); }, true); 538 539boardShortcut("E5", "search next win result", function(t,e){ searchPgnGame('\\[\\s*Result\\s*"(1-0|0-1)"\\s*\\]', false); }, true); 540 541boardShortcut("F5", "", function(t,e){}, true); 542 543boardShortcut("G5", "", function(t,e){}, true); 544 545boardShortcut("H5", "", function(t,e){}, true); 546 547boardShortcut("A4", "search previous event", function(t,e){ searchPgnGame('\\[\\s*Event\\s*"(?!' + fixRegExp(gameEvent[currentGame]) + '"\\s*\\])', true); }, true); 548 549boardShortcut("B4", "search previous round of same event", function(t,e){ searchPgnGame('\\[\\s*Event\\s*"' + fixRegExp(gameEvent[currentGame]) + '"\\s*\\].*\\[\\s*Round\\s*"(?!' + fixRegExp(gameRound[currentGame]) + '"\\s*\\])|\\[\\s*Round\\s*"(?!' + fixRegExp(gameRound[currentGame]) + '"\\s*\\]).*\\[\\s*Event\\s*"' + fixRegExp(gameEvent[currentGame]) + '"\\s*\\]', true); }, true); 550 551boardShortcut("C4", "search previous game of same black player", function(t,e){ searchPgnGame('\\[\\s*' + (e.shiftKey ? 'White' : 'Black') + '\\s*"' + fixRegExp(gameBlack[currentGame]) + '"\\s*\\]', true); }, true); 552 553boardShortcut("D4", "search previous game of same white player", function(t,e){ searchPgnGame('\\[\\s*' + (e.shiftKey ? 'Black' : 'White') + '\\s*"' + fixRegExp(gameWhite[currentGame]) + '"\\s*\\]', true); }, true); 554 555boardShortcut("E4", "search next game of same white player", function(t,e){ searchPgnGame('\\[\\s*' + (e.shiftKey ? 'Black' : 'White') + '\\s*"' + fixRegExp(gameWhite[currentGame]) + '"\\s*\\]', false); }, true); 556 557boardShortcut("F4", "search next game of same black player", function(t,e){ searchPgnGame('\\[\\s*' + (e.shiftKey ? 'White' : 'Black') + '\\s*"' + fixRegExp(gameBlack[currentGame]) + '"\\s*\\]', false); }, true); 558 559boardShortcut("G4", "search next round of same event", function(t,e){ searchPgnGame('\\[\\s*Event\\s*"' + fixRegExp(gameEvent[currentGame]) + '"\\s*\\].*\\[\\s*Round\\s*"(?!' + fixRegExp(gameRound[currentGame]) + '"\\s*\\])|\\[\\s*Round\\s*"(?!' + fixRegExp(gameRound[currentGame]) + '"\\s*\\]).*\\[\\s*Event\\s*"' + fixRegExp(gameEvent[currentGame]) + '"\\s*\\]', false); }, true); 560 561boardShortcut("H4", "search next event", function(t,e){ searchPgnGame('\\[\\s*Event\\s*"(?!' + fixRegExp(gameEvent[currentGame]) + '"\\s*\\])', false); }, true); 562 563boardShortcut("A3", "load first game", function(t,e){ if (numberOfGames > 1) { Init(0); } }, true); 564 565boardShortcut("B3", "jump to previous games decile", function(t,e){ if (currentGame > 0) { calculateDeciles(); for (var ii=(deciles.length-2); ii>=0; ii--) { if (currentGame > deciles[ii]) { Init(deciles[ii]); break; } } } }, true); 566 567boardShortcut("C3", "load previous game", function(t,e){ Init(currentGame - 1); }, true); 568 569boardShortcut("D3", "load random game", function(t,e){ if (numberOfGames > 1) { Init(Math.floor(Math.random()*numberOfGames)); } }, true); 570 571boardShortcut("E3", "load random game at random position", function(t,e){ randomGameRandomPly(); }, true); 572 573boardShortcut("F3", "load next game", function(t,e){ Init(currentGame + 1); }, true); 574 575boardShortcut("G3", "jump to next games decile", function(t,e){ if (currentGame < numberOfGames - 1) { calculateDeciles(); for (var ii=1; ii<deciles.length; ii++) { if (currentGame < deciles[ii]) { Init(deciles[ii]); break; } } } }, true); 576 577boardShortcut("H3", "load last game", function(t,e){ if (numberOfGames > 1) { Init(numberOfGames - 1); } }, true); 578 579boardShortcut("A2", "stop autoplay", function(t,e){ SetAutoPlay(e.shiftKey); }, true); 580 581boardShortcut("B2", "toggle autoplay", function(t,e){ SwitchAutoPlay(); }, true); 582 583boardShortcut("C2", "autoplay 1 second", function(t,e){ SetAutoplayDelayAndStart((e.shiftKey ? 10 : 1)*1000); }, true); 584 585boardShortcut("D2", "autoplay 2 seconds", function(t,e){ SetAutoplayDelayAndStart((e.shiftKey ? 20 : 2)*1000); }, true); 586 587boardShortcut("E2", "autoplay 5 seconds", function(t,e){ SetAutoplayDelayAndStart((e.shiftKey ? 50 : 5)*1000); }, true); 588 589boardShortcut("F2", "autoplay custom delay", function(t,e){ setCustomAutoplayDelay(); }, true); 590 591boardShortcut("G2", "replay up to 6 previous half-moves, then autoplay forward", function(t,e){ replayPreviousMoves(e.shiftKey ? 10 : 6); }, true); 592 593boardShortcut("H2", "replay the previous half-move, then autoplay forward", function(t,e){ replayPreviousMoves(e.shiftKey ? 3 : 1); }, true); 594 595boardShortcut("A1", "go to game start", function(t,e){ startButton(e); }, true); 596 597boardShortcut("B1", "", function(t,e){}, true); // see setB1C1F1G1... 598 599boardShortcut("C1", "", function(t,e){}, true); // see setB1C1F1G1... 600 601boardShortcut("D1", "move backward", function(t,e){ backButton(e); }, true); 602 603boardShortcut("E1", "move forward", function(t,e){ forwardButton(e); }, true); 604 605boardShortcut("F1", "", function(t,e){}, true); // see setB1C1F1G1... 606 607boardShortcut("G1", "", function(t,e){}, true); // see setB1C1F1G1... 608 609boardShortcut("H1", "go to game end", function(t,e){ endButton(e); }, true); 610 611 612setG7A6B6H7boardShortcuts(); 613 614function setG7A6B6H7boardShortcuts() { 615 if (LiveBroadcastDelay > 0) { 616 if (boardIsDefault("G7")) { boardShortcut("G7", "", function(t,e){}, true); } 617 if (boardIsDefault("A6")) { boardShortcut("A6", "pause live broadcast automatic games refresh", function(t,e){ pauseLiveBroadcast(); }, true); } 618 if (boardIsDefault("B6")) { boardShortcut("B6", "restart live broadcast automatic games refresh", function(t,e){ restartLiveBroadcast(); }, true); } 619 if (boardIsDefault("H6")) { boardShortcut("H6", "force live broadcast games refresh", function(t,e){ refreshPgnSource(); }, true); } 620 } else { 621 if (boardIsDefault("G7")) { boardShortcut("G7", "toggle autoplay next game", function(t,e){ SetAutoplayNextGame(!autoplayNextGame); }, true); } 622 if (boardIsDefault("A6")) { boardShortcut("A6", "", function(t,e){}, true); } 623 if (boardIsDefault("B6")) { boardShortcut("B6", "", function(t,e){}, true); } 624 if (boardIsDefault("H6")) { boardShortcut("H6", "", function(t,e){}, true); } 625 } 626} 627 628setB1C1F1G1boardShortcuts(); 629 630function setB1C1F1G1boardShortcuts() { 631 if (commentsIntoMoveText && GameHasComments) { 632 if (boardIsDefault("B1")) { boardShortcut("B1", "find previous comment or variation", function(t,e){ if (e.shiftKey) { GoToMove(CurrentPly - 10); } else { MoveToPrevComment(); } }, true); } 633 if (boardIsDefault("G1")) { boardShortcut("G1", "find next comment or variation", function(t,e){ if (e.shiftKey) { GoToMove(CurrentPly + 10); } else { MoveToNextComment(); } }, true); } 634 } else { 635 if (boardIsDefault("B1")) { boardShortcut("B1", "move 10 half-moves backward", function(t,e){ GoToMove(CurrentPly - 10); }, true); } 636 if (boardIsDefault("G1")) { boardShortcut("G1", "move 10 half-moves forward", function(t,e){ GoToMove(CurrentPly + 10); }, true); } 637 } 638 if (commentsIntoMoveText && GameHasVariations) { 639 if (boardIsDefault("C1")) { boardShortcut("C1", "go to parent variation", function(t,e){ if (e.shiftKey) { GoToMove(CurrentPly - 6); } else { GoToMove(StartPlyVar[CurrentVar]); } }, true); } 640 if (boardIsDefault("F1")) { boardShortcut("F1", "cycle through alternative variations, if any, otherwise move forward", function(t,e){ if (e.shiftKey) { GoToMove(CurrentPly + 6); } else { if (!goToNextVariationSibling()) { GoToMove(CurrentPly + 1); } } }, true); } 641 } else { 642 if (boardIsDefault("C1")) { boardShortcut("C1", "move 6 half-moves backward", function(t,e){ GoToMove(CurrentPly - 6); }, true); } 643 if (boardIsDefault("F1")) { boardShortcut("F1", "move 6 half-moves forward", function(t,e){ GoToMove(CurrentPly + 6); }, true); } 644 } 645} 646 647 648var deciles = new Array(11); 649function calculateDeciles() { 650 for (var ii=0; ii<deciles.length; ii++) { 651 deciles[ii] = Math.round((numberOfGames - 1) * ii / (deciles.length - 1)); 652 } 653} 654 655function replayPreviousMoves(numPlies) { 656 var thisPly = numPlies ? CurrentPly - numPlies : StartPly; 657 if (thisPly < StartPlyVar[CurrentVar]) { 658 thisPly = StartPlyVar[CurrentVar] + (CurrentVar === 0 ? 0 : 1); 659 } 660 if (thisPly !== CurrentPly) { GoToMove(thisPly); } 661 SetAutoPlay(true); 662} 663 664function detectJavascriptLocation(jsre) { 665 if (typeof(jsre) == "undefined") { jsre = new RegExp("pgn4web(|-compacted|\.min)\.js$", ""); } 666 var e = document.getElementsByTagName("script"); 667 for (var i=0; i<e.length; i++) { 668 if ((e[i].src) && (e[i].src.match(jsre))) { 669 return e[i].src; 670 } 671 } 672 return ""; 673} 674 675function detectHelpLocation() { 676 return detectJavascriptLocation().replace(/pgn4web(|-compacted|\.min)\.js$/, "pgn4web-help.html"); 677} 678 679function detectBaseLocation() { 680 var e = document.getElementsByTagName("base"); 681 for (var i=0; i<e.length; i++) { 682 if (e[i].href) { return e[i].href; } 683 } 684 return ""; 685} 686 687 688var debugWin; 689function displayDebugInfo() { 690 var theObj; 691 var bu = detectBaseLocation(); 692 var ju = detectJavascriptLocation(); 693 stopAlertPrompt(); 694 var dbg1 = 'pgn4web: version=' + pgn4web_version + ' homepage=' + pgn4web_project_url + '\n\nHTMLURL: length=' + location.href.length + ' url='; 695 var dbg2 = location.href.length < 100 ? location.href : (location.href.substring(0,99) + '...'); 696 var dbg3 = (bu ? '\nBASEURL: url=' + bu : '') + (ju != 'pgn4web.js' ? '\nJSURL: url=' + ju : ''); 697 if (pgnUrl) { 698 dbg3 += '\nPGNURL: url=' + pgnUrl; 699 } else if (theObj = document.getElementById("pgnText")) { 700 dbg3 += '\nPGNTEXT: length=' + (theObj.tagName.toLowerCase() == 'textarea' ? theObj.value.length : '?'); 701 } 702 dbg3 += '\n' + (numberOfGames > 1 ? '\nGAME: current=' + (currentGame+1) + ' number=' + numberOfGames : '') + (numberOfVars > 1 ? '\nVARIATION: current=' + CurrentVar + ' number=' + (numberOfVars-1) : '') + (PlyNumber > 0 ? '\nPLY: start=' + StartPly + ' current=' + CurrentPly + ' number=' + PlyNumber : '') + '\nAUTOPLAY: status=' + (isAutoPlayOn ? 'on' : 'off') + ' delay=' + Delay + 'ms' + ' next=' + autoplayNextGame; 703 if ((typeof(gameVariant[currentGame]) !== "undefined") && (gameVariant[currentGame].match(/^\s*(|chess|normal|standard)\s*$/i) === null)) { 704 dbg3 += '\nVARIANT: ' + gameVariant[currentGame]; 705 } 706 if (LiveBroadcastDelay > 0) { 707 dbg3 += '\n\nLIVEBROADCAST: status=' + liveStatusDebug() + (LiveBroadcastEndlessMode ? '/endless ' : '') + ' ticker=' + LiveBroadcastTicker + ' delay=' + LiveBroadcastDelay + 'm' + '\n' + 'refreshed: ' + LiveBroadcastLastRefreshedLocal + '\n' + 'received: ' + LiveBroadcastLastReceivedLocal + '\n' + 'modified (server time): ' + LiveBroadcastLastModified_ServerTime(); 708 } 709 if (typeof(engineWinCheck) == "function") { 710 dbg3 += '\n\nANALYSIS: ' + (engineWinCheck() ? 'board=connected ' + engineWin.customDebugInfo() : 'board=disconnected'); 711 } 712 var thisInfo = customDebugInfo(); 713 if (thisInfo) { dbg3 += '\n\nCUSTOM: ' + thisInfo; } 714 dbg3 += '\n\nALERTLOG: fatalnew=' + fatalErrorNumSinceReset + ' new=' + alertNumSinceReset + ' shown=' + Math.min(alertNum, alertLog.length) + ' total=' + alertNum + '\n--'; 715 if (alertNum > 0) { 716 for (var ii = 0; ii<alertLog.length; ii++) { 717 if (alertLog[(alertNum - 1 - ii) % alertLog.length] === undefined) { break; } 718 else { dbg3 += "\n" + alertLog[(alertNum - 1 - ii) % alertLog.length] + "\n--"; } 719 } 720 } 721 if (confirm(dbg1 + dbg2 + dbg3 + '\n\nclick OK to show this debug info in a browser window for cut and paste')) { 722 if (debugWin && !debugWin.closed) { debugWin.close(); } 723 debugWin = window.open("", "pgn4web_debug_data", "resizable=yes,scrollbars=yes,toolbar=no,location=no,menubar=no,status=no"); 724 if (debugWin) { 725 debugWin.document.open("text/html", "replace"); 726 debugWin.document.write("<html><head><title>pgn4web debug info</title><link rel='icon' sizes='16x16' href='pawn.ico' /></head><body>\n<pre>\n" + dbg1 + location.href + " " + dbg3 + "\n</pre>\n</body></html>"); 727 debugWin.document.close(); 728 if (window.focus) { debugWin.focus(); } 729 } 730 } 731 alertNumSinceReset = fatalErrorNumSinceReset = 0; 732} 733 734function liveStatusDebug() { 735 if (LiveBroadcastEnded) { return "ended"; } 736 if (LiveBroadcastPaused) { return "paused"; } 737 if (LiveBroadcastStarted) { return "started"; } 738 return "waiting"; 739} 740 741function customDebugInfo() { return ""; } 742 743var pgnWin; 744function displayPgnData(oneGameOnly) { 745 if (pgnWin && !pgnWin.closed) { pgnWin.close(); } 746 pgnWin = window.open("", "pgn4web_pgn_data", "resizable=yes,scrollbars=yes,toolbar=no,location=no,menubar=no,status=no"); 747 if (pgnWin) { 748 var text = "<html><head><title>pgn4web PGN source</title><link rel='icon' sizes='16x16' href='pawn.ico' /></head><body>\n<pre>\n"; 749 if (oneGameOnly) { text += fullPgnGame(currentGame) + "\n\n"; } 750 else { for (var ii = 0; ii < numberOfGames; ++ii) { text += fullPgnGame(ii) + "\n\n"; } } 751 text += "\n</pre>\n</body></html>"; 752 pgnWin.document.open("text/html", "replace"); 753 pgnWin.document.write(text); 754 pgnWin.document.close(); 755 if (window.focus) { pgnWin.focus(); } 756 } 757} 758 759function savePgnData(oneGameOnly) { 760 if (pgnUrl && !oneGameOnly) { location.href = pgnUrl; } 761 else { 762 displayPgnData(oneGameOnly); // fallback on displayPgnData for now 763 } 764} 765 766function CurrentFEN() { 767 var thisFEN = ""; 768 769 var emptySquares = 0; 770 for (var row=7; row>=0; row--) { 771 for (var col=0; col<=7; col++) { 772 if (Board[col][row] === 0) { emptySquares++; } 773 else { 774 if (emptySquares) { 775 thisFEN += emptySquares; 776 emptySquares = 0; 777 } 778 if (Board[col][row] > 0) { thisFEN += PiecesArr[Board[col][row]-1].toUpperCase(); } 779 else if (Board[col][row] < 0) { thisFEN += PiecesArr[-Board[col][row]-1].toLowerCase(); } 780 } 781 } 782 if (emptySquares) { 783 thisFEN += emptySquares; 784 emptySquares = 0; 785 } 786 if (row>0) { thisFEN += "/"; } 787 } 788 789 thisFEN += CurrentPly%2 ? " b" : " w"; 790 791 // castling availability: always in the KQkq form 792 // note: wrong FEN for Chess960 positions with inner castling rook 793 var CastlingFEN = ""; 794 if (RookForOOCastling(0) !== null) { CastlingFEN += PiecesArr[0].toUpperCase(); } 795 if (RookForOOOCastling(0) !== null) { CastlingFEN += PiecesArr[1].toUpperCase(); } 796 if (RookForOOCastling(1) !== null) { CastlingFEN += PiecesArr[0].toLowerCase(); } 797 if (RookForOOOCastling(1) !== null) { CastlingFEN += PiecesArr[1].toLowerCase(); } 798 thisFEN += " " + (CastlingFEN || "-"); 799 800 if (HistEnPassant[CurrentPly]) { 801 thisFEN += " " + String.fromCharCode(HistEnPassantCol[CurrentPly] + 97); 802 thisFEN += CurrentPly%2 ? "3" : "6"; 803 } else { thisFEN += " -"; } 804 805 var HalfMoveClock = InitialHalfMoveClock; 806 for (var thisPly = StartPly; thisPly < CurrentPly; thisPly++) { 807 if ((HistType[0][thisPly] == 6) || (HistPieceId[1][thisPly] >= 16)) { HalfMoveClock = 0; } 808 else { HalfMoveClock++; } 809 } 810 thisFEN += " " + HalfMoveClock; 811 812 thisFEN += " " + (Math.floor(CurrentPly/2)+1); 813 814 return thisFEN; 815} 816 817var fenWin; 818function displayFenData(addGametext) { 819 if (fenWin && !fenWin.closed) { fenWin.close(); } 820 821 var thisFEN = CurrentFEN(); 822 823 var movesStr = ""; 824 var lineStart = 0; 825 if (addGametext) { 826 for (var thisPly = CurrentPly; thisPly <= StartPly + PlyNumber; thisPly++) { 827 var addStr = ""; 828 if (thisPly == StartPly + PlyNumber) { 829 addStr = (CurrentVar ? "*" : gameResult[currentGame] || "*"); 830 } else { 831 if (thisPly%2 === 0) { addStr = (Math.floor(thisPly/2)+1) + ". "; } 832 else if (thisPly == CurrentPly) { addStr = (Math.floor(thisPly/2)+1) + "... "; } 833 addStr += Moves[thisPly]; 834 } 835 if (movesStr.length + addStr.length + 1 > lineStart + 80) { 836 lineStart = movesStr.length; 837 movesStr += "\n" + addStr; 838 } else { 839 if (movesStr.length > 0) { movesStr += " "; } 840 movesStr += addStr; 841 } 842 } 843 } 844 845 fenWin = window.open("", "pgn4web_fen_data", "resizable=yes,scrollbars=yes,toolbar=no,location=no,menubar=no,status=no"); 846 if (fenWin) { 847 var text = "<html><head><title>pgn4web FEN string</title><link rel='icon' sizes='16x16' href='pawn.ico' /></head><body>\n<b><pre>\n\n" + thisFEN + "\n\n</pre></b>\n<hr>\n<pre>\n\n"; 848 if (addGametext) { 849 text += "[Event \"" + ((CurrentVar ? "" : gameEvent[currentGame]) || "?") + "\"]\n"; 850 text += "[Site \"" + ((CurrentVar ? "" : gameSite[currentGame]) || "?") + "\"]\n"; 851 text += "[Date \"" + ((CurrentVar ? "" : gameDate[currentGame]) || "????.??.??") + "\"]\n"; 852 text += "[Round \"" + ((CurrentVar ? "" : gameRound[currentGame]) || "?") + "\"]\n"; 853 text += "[White \"" + ((CurrentVar ? "" : gameWhite[currentGame]) || "?") + "\"]\n"; 854 text += "[Black \"" + ((CurrentVar ? "" : gameBlack[currentGame]) || "?") + "\"]\n"; 855 text += "[Result \"" + ((CurrentVar ? "" : gameResult[currentGame]) || "*") + "\"]\n"; 856 } 857 if ((thisFEN != FenStringStart) || (!addGametext)) { 858 text += "[SetUp \"1\"]\n" + "[FEN \"" + thisFEN + "\"]\n"; 859 } 860 if (gameVariant[currentGame] !== "") { text += "[Variant \"" + gameVariant[currentGame] + "\"]\n"; } 861 if (addGametext) { text += "\n" + movesStr + "\n"; } 862 text += "</pre>\n</body></html>"; 863 fenWin.document.open("text/html", "replace"); 864 fenWin.document.write(text); 865 fenWin.document.close(); 866 if (window.focus) { fenWin.focus(); } 867 } 868} 869 870 871var pgnHeader = new Array(); 872var pgnGame = new Array(); 873var numberOfGames = -1; 874var currentGame = -1; 875 876var firstStart = true; 877 878var gameDate = new Array(); 879var gameWhite = new Array(); 880var gameBlack = new Array(); 881var gameEvent = new Array(); 882var gameSite = new Array(); 883var gameRound = new Array(); 884var gameResult = new Array(); 885var gameSetUp = new Array(); 886var gameFEN = new Array(); 887var gameInitialWhiteClock = new Array(); 888var gameInitialBlackClock = new Array(); 889var gameVariant = new Array(); 890 891var highlightedMoveId = ""; 892 893var isAutoPlayOn = false; 894var AutoPlayInterval = null; 895var Delay = 1000; // milliseconds 896var autostartAutoplay = false; 897var autoplayNextGame = false; 898 899var initialGame = 1; 900var initialVariation = 0; 901var initialHalfmove = 0; 902var alwaysInitialHalfmove = false; 903 904var LiveBroadcastInterval = null; 905var LiveBroadcastDelay = 0; // minutes 906var LiveBroadcastAlert = false; 907var LiveBroadcastDemo = false; 908var LiveBroadcastStarted = false; 909var LiveBroadcastEnded = false; 910var LiveBroadcastPaused = false; 911var LiveBroadcastTicker = 0; 912var LiveBroadcastGamesRunning = 0; 913var LiveBroadcastLastModified = new Date(0); // default to epoch start 914var LiveBroadcastLastModifiedHeader = LiveBroadcastLastModified.toUTCString(); 915var LiveBroadcastLastReceivedLocal = 'unavailable'; 916var LiveBroadcastLastRefreshedLocal = 'unavailable'; 917var LiveBroadcastPlaceholderEvent = 'live chess broadcast'; 918var LiveBroadcastPlaceholderPgn = '[Event "' + LiveBroadcastPlaceholderEvent + '"]'; 919var gameDemoMaxPly = new Array(); 920var gameDemoLength = new Array(); 921var LiveBroadcastSteppingMode = false; 922var LiveBroadcastEndlessMode = false; 923 924var ParseLastMoveError = false; 925 926var castleRook = -1; 927var mvCapture = 0; 928var mvIsCastling = 0; 929var mvIsPromotion = 0; 930var mvFromCol = -1; 931var mvFromRow = -1; 932var mvToCol = -1; 933var mvToRow = -1; 934var mvPiece = -1; 935var mvPieceId = -1; 936var mvPieceOnTo = -1; 937var mvCaptured = -1; 938var mvCapturedId = -1; 939var mvIsNull = 0; 940 941var Board = new Array(8); 942for (var i=0; i<8; ++i) { Board[i] = new Array(8); } 943 944// HistCol, HistRow: move history up to last replayed ply 945// HistCol[0], HistRow[0]: "square from"; 0..7, 0..7 from A1 946// HistCol[1], HistRow[1]: castling/capture 947// HistCol[2], HistRow[2]: "square to"; 0..7, 0..7 from A1 948 949var HistCol = new Array(3); 950var HistRow = new Array(3); 951var HistPieceId = new Array(2); 952var HistType = new Array(2); 953var HistVar = new Array(); 954 955var PieceCol = new Array(2); 956var PieceRow = new Array(2); 957var PieceType = new Array(2); 958var PieceMoveCounter = new Array(2); 959 960for (i=0; i<2; ++i) { 961 PieceCol[i] = new Array(16); 962 PieceRow[i] = new Array(16); 963 PieceType[i] = new Array(16); 964 PieceMoveCounter[i] = new Array(16); 965 HistType[i] = new Array(); 966 HistPieceId[i] = new Array(); 967} 968 969for (i=0; i<3; ++i) { 970 HistCol[i] = new Array(); 971 HistRow[i] = new Array(); 972} 973 974var HistEnPassant = new Array(); 975HistEnPassant[0] = false; 976var HistEnPassantCol = new Array(); 977HistEnPassantCol[0] = -1; 978 979var HistNull = new Array(); 980HistNull[0] = 0; 981 982var PiecesArr = "KQRBNP".split(""); 983var FenStringStart = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; 984var columnsLetters = "ABCDEFGH"; 985var InitialHalfMoveClock = 0; 986 987var PieceImg = new Array(new Array(6), new Array(6)); 988var ClearImg; 989 990var ImagePath = 'images'; 991var ImagePathOld = null; 992var imageType = 'png'; 993var defaultImagesSize = 40; 994 995var highlightOption = true; 996 997var commentsIntoMoveText = true; 998var commentsOnSeparateLines = false; 999 1000var pgnUrl = ''; 1001 1002var CastlingLong = new Array(2); 1003var CastlingShort = new Array(2); 1004var Moves = new Array(); 1005var MoveComments = new Array(); 1006 1007var MoveColor; 1008var MoveCount; 1009var PlyNumber; 1010var StartPly; 1011var CurrentPly; 1012 1013var IsRotated = false; 1014 1015var pgnHeaderTagRegExp = /\[\s*(\w+)\s*"([^"]*)"\s*\]/; 1016var pgnHeaderTagRegExpGlobal = /\[\s*(\w+)\s*"([^"]*)"\s*\]/g; 1017var pgnHeaderBlockRegExp = /\s*(\[\s*\w+\s*"[^"]*"\s*\]\s*)+/; 1018 1019var emptyPgnHeader = '[Event ""]\n[Site ""]\n[Date ""]\n[Round ""]\n[White ""]\n[Black ""]\n[Result ""]\n'; 1020var alertPgn = emptyPgnHeader + "\n{error: click on the top left chessboard square for debug info}"; 1021 1022var pgn4webVariationRegExp = /\[%pgn4web_variation (\d+)\]/; 1023var pgn4webVariationRegExpGlobal = /\[%pgn4web_variation (\d+)\]/g; 1024 1025var gameSelectorHead = ' ···'; 1026var gameSelectorMono = true; 1027var gameSelectorNum = false; 1028var gameSelectorNumLenght = 0; 1029var gameSelectorChEvent = 0; 1030var gameSelectorChSite = 0; 1031var gameSelectorChRound = 0; 1032var gameSelectorChWhite = 15; 1033var gameSelectorChBlack = 15; 1034var gameSelectorChResult = 0; 1035var gameSelectorChDate = 10; 1036 1037function CheckLegality(what, plyCount) { 1038 var retVal, thisCol; 1039 1040 if (what == '--') { 1041 StoreMove(plyCount); 1042 return true; 1043 } 1044 1045 // castling 1046 if (what == 'O-O') { 1047 if (!CheckLegalityOO()) { return false; } 1048 for (thisCol = PieceCol[MoveColor][0]; thisCol < 7; thisCol++) { 1049 if (IsCheck(thisCol, MoveColor*7, MoveColor)) { return false; } 1050 } 1051 StoreMove(plyCount); 1052 return true; 1053 } else if (what == 'O-O-O') { 1054 if (!CheckLegalityOOO()) { return false; } 1055 for (thisCol = PieceCol[MoveColor][0]; thisCol > 1; thisCol--) { 1056 if (IsCheck(thisCol, MoveColor*7, MoveColor)) { return false; } 1057 } 1058 StoreMove(plyCount); 1059 return true; 1060 } 1061 1062 // capture: "square to" occupied by opposite color piece (except en-passant) 1063 // promotion: "square to" moved piece different from piece 1064 if (!mvCapture) { 1065 if (Board[mvToCol][mvToRow] !== 0) { return false; } 1066 } 1067 if ((mvCapture) && (Color(Board[mvToCol][mvToRow]) != 1-MoveColor)) { 1068 if ((mvPiece != 6) || (!HistEnPassant[plyCount]) || (HistEnPassantCol[plyCount] != mvToCol) || (mvToRow != 5-3*MoveColor)) { return false; } 1069 } 1070 if (mvIsPromotion) { 1071 if (mvPiece != 6) { return false; } 1072 if (mvPieceOnTo >= 6) { return false; } 1073 if (mvToRow != 7*(1-MoveColor)) { return false; } 1074 } 1075 1076 // piece move: which same type piece could move there? 1077 for (var pieceId = 0; pieceId < 16; ++pieceId) { 1078 if (PieceType[MoveColor][pieceId] == mvPiece) { 1079 if (mvPiece == 1) { retVal = CheckLegalityKing(pieceId); } 1080 else if (mvPiece == 2) { retVal = CheckLegalityQueen(pieceId); } 1081 else if (mvPiece == 3) { retVal = CheckLegalityRook(pieceId); } 1082 else if (mvPiece == 4) { retVal = CheckLegalityBishop(pieceId); } 1083 else if (mvPiece == 5) { retVal = CheckLegalityKnight(pieceId); } 1084 else if (mvPiece == 6) { retVal = CheckLegalityPawn(pieceId); } 1085 if (retVal) { 1086 mvPieceId = pieceId; 1087 // board updated: king check? 1088 StoreMove(plyCount); 1089 if (!IsCheck(PieceCol[MoveColor][0], PieceRow[MoveColor][0], MoveColor)) { return true; } 1090 else { UndoMove(plyCount); } 1091 } 1092 } 1093 } 1094 return false; 1095} 1096 1097function CheckLegalityKing(thisKing) { 1098 if ((mvFromCol >= 0) && (mvFromCol != PieceCol[MoveColor][thisKing])) { return false; } 1099 if ((mvFromRow >= 0) && (mvFromRow != PieceRow[MoveColor][thisKing])) { return false; } 1100 if (Math.abs(PieceCol[MoveColor][thisKing]-mvToCol) > 1) { return false; } 1101 if (Math.abs(PieceRow[MoveColor][thisKing]-mvToRow) > 1) { return false; } 1102 return true; 1103} 1104 1105function CheckLegalityQueen(thisQueen) { 1106 if ((mvFromCol >= 0) && (mvFromCol != PieceCol[MoveColor][thisQueen])) { return false; } 1107 if ((mvFromRow >= 0) && (mvFromRow != PieceRow[MoveColor][thisQueen])) { return false; } 1108 if (((PieceCol[MoveColor][thisQueen]-mvToCol) * (PieceRow[MoveColor][thisQueen]-mvToRow) !== 0) && (Math.abs(PieceCol[MoveColor][thisQueen]-mvToCol) != Math.abs(PieceRow[MoveColor][thisQueen]-mvToRow))) { return false; } 1109 if (!CheckClearWay(thisQueen)) { return false; } 1110 return true; 1111} 1112 1113function CheckLegalityRook(thisRook) { 1114 if ((mvFromCol >= 0) && (mvFromCol != PieceCol[MoveColor][thisRook])) { return false; } 1115 if ((mvFromRow >= 0) && (mvFromRow != PieceRow[MoveColor][thisRook])) { return false; } 1116 if ((PieceCol[MoveColor][thisRook]-mvToCol) * (PieceRow[MoveColor][thisRook]-mvToRow) !== 0) { return false; } 1117 if (!CheckClearWay(thisRook)) { return false; } 1118 return true; 1119} 1120 1121function CheckLegalityBishop(thisBishop) { 1122 if ((mvFromCol >= 0) && (mvFromCol != PieceCol[MoveColor][thisBishop])) { return false; } 1123 if ((mvFromRow >= 0) && (mvFromRow != PieceRow[MoveColor][thisBishop])) { return false; } 1124 if (Math.abs(PieceCol[MoveColor][thisBishop]-mvToCol) != Math.abs(PieceRow[MoveColor][thisBishop]-mvToRow)) { return false; } 1125 if (!CheckClearWay(thisBishop)) { return false; } 1126 return true; 1127} 1128 1129function CheckLegalityKnight(thisKnight) { 1130 if ((mvFromCol >= 0) && (mvFromCol != PieceCol[MoveColor][thisKnight])) { return false; } 1131 if ((mvFromRow >= 0) && (mvFromRow != PieceRow[MoveColor][thisKnight])) { return false; } 1132 if (Math.abs(PieceCol[MoveColor][thisKnight]-mvToCol) * Math.abs(PieceRow[MoveColor][thisKnight]-mvToRow) != 2) { return false; } 1133 return true; 1134} 1135 1136function CheckLegalityPawn(thisPawn) { 1137 if ((mvFromCol >= 0) && (mvFromCol != PieceCol[MoveColor][thisPawn])) { return false; } 1138 if ((mvFromRow >= 0) && (mvFromRow != PieceRow[MoveColor][thisPawn])) { return false; } 1139 if (Math.abs(PieceCol[MoveColor][thisPawn]-mvToCol) != mvCapture) { return false; } 1140 if (mvCapture) { 1141 if (PieceRow[MoveColor][thisPawn]-mvToRow != 2*MoveColor-1) { return false; } 1142 } else { 1143 if (PieceRow[MoveColor][thisPawn]-mvToRow == 4*MoveColor-2) { 1144 if (PieceRow[MoveColor][thisPawn] != 1+5*MoveColor) { return false; } 1145 if (Board[mvToCol][mvToRow+2*MoveColor-1] !== 0) { return false; } 1146 } else { 1147 if (PieceRow[MoveColor][thisPawn]-mvToRow != 2*MoveColor-1) { return false; } 1148 } 1149 } 1150 return true; 1151} 1152 1153function RookForOOCastling(color) { 1154 if (CastlingShort[color] < 0) { return null; } 1155 if (PieceMoveCounter[color][0] > 0) { return null; } 1156 1157 var legal = false; 1158 for (var thisRook = 0; thisRook < 16; thisRook++) { 1159 if ((PieceCol[color][thisRook] == CastlingShort[color]) && (PieceCol[color][thisRook] > PieceCol[color][0]) && (PieceRow[color][thisRook] == color*7) && (PieceType[color][thisRook] == 3)) { 1160 legal = true; 1161 break; 1162 } 1163 } 1164 if (!legal) { return null; } 1165 if (PieceMoveCounter[color][thisRook] > 0) { return null; } 1166 1167 return thisRook; 1168} 1169 1170function CheckLegalityOO() { 1171 1172 var thisRook = RookForOOCastling(MoveColor); 1173 if (thisRook === null) { return false; } 1174 1175 // check no piece between king and rook 1176 // clear king/rook squares for Chess960 1177 Board[PieceCol[MoveColor][0]][MoveColor*7] = 0; 1178 Board[PieceCol[MoveColor][thisRook]][MoveColor*7] = 0; 1179 var col = PieceCol[MoveColor][thisRook]; 1180 if (col < 6) { col = 6; } 1181 while ((col > PieceCol[MoveColor][0]) || (col >= 5)) { 1182 if (Board[col][MoveColor*7] !== 0) { return false; } 1183 --col; 1184 } 1185 castleRook = thisRook; 1186 return true; 1187} 1188 1189function RookForOOOCastling(color) { 1190 if (CastlingLong[color] < 0) { return null; } 1191 if (PieceMoveCounter[color][0] > 0) { return null; } 1192 1193 var legal = false; 1194 for (var thisRook = 0; thisRook < 16; thisRook++) { 1195 if ((PieceCol[color][thisRook] == CastlingLong[color]) && (PieceCol[color][thisRook] < PieceCol[color][0]) && (PieceRow[color][thisRook] == color*7) && (PieceType[color][thisRook] == 3)) { 1196 legal = true; 1197 break; 1198 } 1199 } 1200 if (!legal) { return null; } 1201 if (PieceMoveCounter[color][thisRook] > 0) { return null; } 1202 1203 return thisRook; 1204} 1205 1206function CheckLegalityOOO() { 1207 1208 var thisRook = RookForOOOCastling(MoveColor); 1209 if (thisRook === null) { return false; } 1210 1211 // check no piece between king and rook 1212 // clear king/rook squares for Chess960 1213 Board[PieceCol[MoveColor][0]][MoveColor*7] = 0; 1214 Board[PieceCol[MoveColor][thisRook]][MoveColor*7] = 0; 1215 var col = PieceCol[MoveColor][thisRook]; 1216 if (col > 2) { col = 2; } 1217 while ((col < PieceCol[MoveColor][0]) || (col <= 3)) { 1218 if (Board[col][MoveColor*7] !== 0) { return false; } 1219 ++col; 1220 } 1221 castleRook = thisRook; 1222 return true; 1223} 1224 1225function CheckClearWay(thisPiece) { 1226 var stepCol = sign(mvToCol-PieceCol[MoveColor][thisPiece]); 1227 var stepRow = sign(mvToRow-PieceRow[MoveColor][thisPiece]); 1228 var startCol = PieceCol[MoveColor][thisPiece]+stepCol; 1229 var startRow = PieceRow[MoveColor][thisPiece]+stepRow; 1230 while ((startCol != mvToCol) || (startRow != mvToRow)) { 1231 if (Board[startCol][startRow] !== 0) { return false; } 1232 startCol += stepCol; 1233 startRow += stepRow; 1234 } 1235 return true; 1236} 1237 1238function CleanMove(move) { 1239 move = move.replace(/[^a-wyzA-WYZ0-9#-]*/g, ''); // patch: pgn notation: remove/add '+' 'x' '=' chars for full chess informant style or pgn style for the game text 1240 if (move.match(/^[Oo0]/)) { move = move.replace(/[o0]/g, 'O').replace(/O(?=O)/g, 'O-'); } 1241 move = move.replace(/ep/i, ''); 1242 return move; 1243} 1244 1245function GoToMove(thisPly, thisVar) { 1246 SetAutoPlay(false); 1247 if (typeof(thisVar) == "undefined") { thisVar = CurrentVar; } 1248 else { 1249 if (thisVar < 0) { thisVar = 0; } 1250 else if (thisVar >= numberOfVars) { thisVar = numberOfVars - 1; } 1251 } 1252 if (thisPly < 0) { thisPly = 0; } 1253 else if (thisPly >= StartPlyVar[thisVar] + PlyNumberVar[thisVar]) { 1254 thisPly = StartPlyVar[thisVar] + PlyNumberVar[thisVar]; 1255 } 1256 1257 if (thisVar === CurrentVar) { 1258 var diff = thisPly - CurrentPly; 1259 if (diff > 0) { MoveForward(diff); } 1260 else { MoveBackward(-diff); } 1261 } else { 1262 var backStart = StartPly; 1263loopCommonPredecessor: 1264 for (var ii = PredecessorsVars[CurrentVar].length - 1; ii >= 0; ii--) { 1265 for (var jj = PredecessorsVars[thisVar].length - 1; jj >= 0; jj--) { 1266 if (PredecessorsVars[CurrentVar][ii] === PredecessorsVars[thisVar][jj]) { 1267 backStart = Math.min(PredecessorsVars[CurrentVar][ii+1] ? StartPlyVar[PredecessorsVars[CurrentVar][ii+1]] : CurrentPly, PredecessorsVars[thisVar][jj+1] ? StartPlyVar[PredecessorsVars[thisVar][jj+1]] : thisPly); 1268 break loopCommonPredecessor; 1269 } 1270 } 1271 } 1272 MoveBackward(CurrentPly - backStart, true); 1273 MoveForward(thisPly - backStart, thisVar); 1274 } 1275} 1276 1277 1278function SetShortcutKeysEnabled(onOff) { 1279 shortcutKeysEnabled = onOff; 1280} 1281 1282function interactivelyToggleShortcutKeys() { 1283 if (confirm("Shortcut keys currently " + (shortcutKeysEnabled ? "enabled" : "disabled") + ".\nToggle shortcut keys to " + (shortcutKeysEnabled ? "DISABLED" : "ENABLED") + "?")) { 1284 SetShortcutKeysEnabled(!shortcutKeysEnabled); 1285 } 1286} 1287 1288function SetCommentsIntoMoveText(onOff) { 1289 commentsIntoMoveText = onOff; 1290} 1291 1292function SetCommentsOnSeparateLines(onOff) { 1293 commentsOnSeparateLines = onOff; 1294} 1295 1296function SetAutostartAutoplay(onOff) { 1297 autostartAutoplay = onOff; 1298} 1299 1300function SetAutoplayNextGame(onOff) { 1301 autoplayNextGame = onOff; 1302} 1303 1304function SetInitialHalfmove(number_or_string, always) { 1305 alwaysInitialHalfmove = (always === true); 1306 initialHalfmove = typeof(number_or_string) == "undefined" ? 0 : number_or_string; 1307} 1308 1309function SetInitialVariation(number) { 1310 initialVariation = isNaN(number = parseInt(number, 10)) ? 0 : number; 1311} 1312 1313function SetInitialGame(number_or_string) { 1314 initialGame = typeof(number_or_string) == "undefined" ? 1 : number_or_string; 1315} 1316 1317function randomGameRandomPly() { 1318 if (numberOfGames > 1) { 1319 var oldInitialHalfmove = initialHalfmove; 1320 var oldAlwaysInitialHalfmove = alwaysInitialHalfmove; 1321 SetInitialHalfmove("random", true); 1322 Init(Math.floor(Math.random()*numberOfGames)); 1323 SetInitialHalfmove(oldInitialHalfmove, oldAlwaysInitialHalfmove); 1324 } 1325} 1326 1327 1328// clock detection as [%clk 01:02] 1329 1330function clockFromComment(plyNum) { 1331 return customPgnCommentTag("clk", null, plyNum); 1332} 1333 1334function clockFromHeader(whiteToMove) { 1335 var clockString = customPgnHeaderTag("Clock") + ""; 1336 var matches = clockString.match("^" + (whiteToMove ? "W" : "B") + "/(.*)$"); 1337 if (matches) { return matches[1]; } 1338 else { return null; } 1339} 1340 1341function HighlightLastMove() { 1342 var theObj, moveId, text, ii, clockString, clockRegExp, clockMatch; 1343 1344 undoStackStore(); 1345 1346 // remove old move highlighting 1347 if (highlightedMoveId) { 1348 if (theObj = document.getElementById(highlightedMoveId)) { 1349 theObj.className = (highlightedMoveId.match(/Var0Mv/) ? 'move' : 'variation') + ' notranslate'; 1350 } 1351 } 1352 1353 // halfmove to be highlighted, negative for starting position 1354 var showThisMove = CurrentPly - 1; 1355 if (showThisMove > StartPlyVar[CurrentVar] + PlyNumberVar[CurrentVar]) { showThisMove = StartPlyVar[CurrentVar] + PlyNumberVar[CurrentVar]; } 1356 1357 if (theObj = document.getElementById("GameLastComment")) { 1358 if (commentsIntoMoveText) { 1359 variationTextDepth = CurrentVar === 0 ? 0 : 1; 1360 text = '<SPAN CLASS="comment">' + strippedMoveComment(showThisMove+1, CurrentVar, true).replace(/\sID="[^"]*"/g, '') + '</SPAN>'; 1361 } else { text = ''; } 1362 theObj.innerHTML = text; 1363 } 1364 1365 // side to move 1366 var whiteToMove = ((showThisMove+1)%2 === 0); 1367 text = whiteToMove ? 'white' : 'black'; 1368 1369 if (theObj = document.getElementById("GameSideToMove")) { theObj.innerHTML = text; } 1370 1371 // clock 1372 var lastMoverClockObj = document.getElementById(whiteToMove ? "GameBlackClock" : "GameWhiteClock"); 1373 var initialLastMoverClock = whiteToMove ? gameInitialBlackClock[currentGame] : gameInitialWhiteClock[currentGame]; 1374 var beforeLastMoverClockObj = document.getElementById(whiteToMove ? "GameWhiteClock" : "GameBlackClock"); 1375 var initialBeforeLastMoverClock = whiteToMove ? gameInitialWhiteClock[currentGame] : gameInitialBlackClock[currentGame]; 1376 1377 if (lastMoverClockObj) { 1378 clockString = ((showThisMove+1 === StartPly+PlyNumber) && ((!LiveBroadcastDemo) || (gameResult[currentGame] !== "*"))) ? clockFromHeader(!whiteToMove) : null; 1379 if (clockString === null) { 1380 clockString = showThisMove+1 > StartPly ? clockFromComment(showThisMove+1) : initialLastMoverClock; 1381 if (!clockString && (CurrentPly === StartPly+PlyNumber)) { 1382 // support for time info in the last comment as { White Time: 0h:12min Black Time: 1h:23min } 1383 clockRegExp = new RegExp((whiteToMove ? "Black" : "White") + "\\s+Time:\\s*(\\S+)", "i"); 1384 if (clockMatch = strippedMoveComment(StartPly+PlyNumber).match(clockRegExp)) { 1385 clockString = clockMatch[1]; 1386 } 1387 } 1388 } 1389 lastMoverClockObj.innerHTML = clockString; 1390 } 1391 if (beforeLastMoverClockObj) { 1392 clockString = ((showThisMove+1 === StartPly+PlyNumber) && ((!LiveBroadcastDemo) || (gameResult[currentGame] !== "*"))) ? clockFromHeader(whiteToMove) : null; 1393 if (clockString === null) { 1394 clockString = showThisMove > StartPly ? clockFromComment(showThisMove) : initialBeforeLastMoverClock; 1395 if (!clockString && (CurrentPly === StartPly+PlyNumber)) { 1396 // see comment above 1397 clockRegExp = new RegExp((whiteToMove ? "White" : "Black") + "\\s+Time:\\s*(\\S+)", "i"); 1398 if (clockMatch = strippedMoveComment(StartPly+PlyNumber).match(clockRegExp)) { 1399 clockString = clockMatch[1]; 1400 } 1401 } 1402 } 1403 beforeLastMoverClockObj.innerHTML = clockString; 1404 } 1405 1406 if (lastMoverClockObj && beforeLastMoverClockObj) { 1407 if (lastMoverClockObj.innerHTML && !beforeLastMoverClockObj.innerHTML) { 1408 beforeLastMoverClockObj.innerHTML = "-"; 1409 } else if (!lastMoverClockObj.innerHTML && beforeLastMoverClockObj.innerHTML) { 1410 lastMoverClockObj.innerHTML = "-"; 1411 } 1412 } 1413 1414 // next move 1415 if (theObj = document.getElementById("GameNextMove")) { 1416 if (CurrentVar === 0 && showThisMove + 1 >= StartPly + PlyNumber) { 1417 text = '<SPAN CLASS="move notranslate">' + gameResult[currentGame] + '</SPAN>'; 1418 } else if (typeof(Moves[showThisMove+1]) == "undefined") { 1419 text = ""; 1420 } else { 1421 text = printMoveText(showThisMove+1, CurrentVar, (CurrentVar !== 0), true, false); 1422 } 1423 theObj.innerHTML = text; 1424 } 1425 1426 // next variations 1427 if (theObj = document.getElementById("GameNextVariations")) { 1428 text = ''; 1429 if (commentsIntoMoveText) { 1430 var children = childrenVars(showThisMove+1, CurrentVar); 1431 for (ii = 0; ii < children.length; ii++) { 1432 if (children[ii] !== CurrentVar) { 1433 text += ' ' + printMoveText(showThisMove+1, children[ii], (children[ii] !== 0), true, false); 1434 } 1435 } 1436 } 1437 theObj.innerHTML = text; 1438 } 1439 1440 // last move 1441 if (theObj = document.getElementById("GameLastMove")) { 1442 if ((showThisMove >= StartPly) && Moves[showThisMove]) { 1443 text = printMoveText(showThisMove, CurrentVar, (CurrentVar !== 0), true, false); 1444 } else if (showThisMove === StartPly - 1) { 1445 text = '<SPAN CLASS="move notranslate">' + (Math.floor((showThisMove+1)/2) + 1) + (((showThisMove+1) % 2) ? "..." : ".") + '</SPAN>'; 1446 } else { text = ''; } 1447 theObj.innerHTML = text; 1448 } 1449 1450 // last variations 1451 if (theObj = document.getElementById("GameLastVariations")) { 1452 text = ''; 1453 if (commentsIntoMoveText) { 1454 var siblings = childrenVars(showThisMove, HistVar[showThisMove]); 1455 for (ii = 0; ii < siblings.length; ii++) { 1456 if (siblings[ii] !== CurrentVar) { 1457 text += ' ' + printMoveText(showThisMove, siblings[ii], (siblings[ii] !== 0), true, false); 1458 } 1459 } 1460 } 1461 theObj.innerHTML = text; 1462 } 1463 1464 if (showThisMove >= (StartPlyVar[CurrentVar]-1)) { 1465 moveId = 'Var' + CurrentVar + 'Mv' + (showThisMove + 1); 1466 if (theObj = document.getElementById(moveId)) { 1467 theObj.className = (CurrentVar ? 'variation variationOn' : 'move moveOn') + ' notranslate'; 1468 } 1469 highlightedMoveId = moveId; 1470 1471 if (highlightOption) { 1472 var colFrom, rowFrom, colTo, rowTo; 1473 if ((showThisMove < StartPly) || HistNull[showThisMove]) { 1474 colFrom = rowFrom = -1; 1475 colTo = rowTo = -1; 1476 } else { 1477 colFrom = HistCol[0][showThisMove] === undefined ? -1 : HistCol[0][showThisMove]; 1478 rowFrom = HistRow[0][showThisMove] === undefined ? -1 : HistRow[0][showThisMove]; 1479 colTo = HistCol[2][showThisMove] === undefined ? -1 : HistCol[2][showThisMove]; 1480 rowTo = HistRow[2][showThisMove] === undefined ? -1 : HistRow[2][showThisMove]; 1481 } 1482 highlightMove(colFrom, rowFrom, colTo, rowTo); 1483 } 1484 } 1485} 1486 1487function SetHighlightOption(on) { 1488 highlightOption = on; 1489} 1490 1491function SetHighlight(on) { 1492 SetHighlightOption(on); 1493 if (on) { HighlightLastMove(); } 1494 else { highlightMove(-1, -1, -1, -1); } 1495} 1496 1497var colFromHighlighted = -1; 1498var rowFromHighlighted = -1; 1499var colToHighlighted = -1; 1500var rowToHighlighted = -1; 1501function highlightMove(colFrom, rowFrom, colTo, rowTo) { 1502 highlightSquare(colFromHighlighted, rowFromHighlighted, false); 1503 highlightSquare(colToHighlighted, rowToHighlighted, false); 1504 if ( highlightSquare(colFrom, rowFrom, true) ) { 1505 colFromHighlighted = colFrom; 1506 rowFromHighlighted = rowFrom; 1507 } else { colFromHighlighted = rowFromHighlighted = -1; } 1508 if ( highlightSquare(colTo, rowTo, true) ) { 1509 colToHighlighted = colTo; 1510 rowToHighlighted = rowTo; 1511 } else { colToHighlighted = rowToHighlighted = -1; } 1512} 1513 1514function highlightSquare(col, row, on) { 1515 if ((col === undefined) || (row === undefined)) { return false; } 1516 if (!SquareOnBoard(col, row)) { return false; } 1517 var trow = IsRotated ? row : 7 - row; 1518 var tcol = IsRotated ? 7 - col : col; 1519 var theObj = document.getElementById('tcol' + tcol + 'trow' + trow); 1520 if (!theObj) { return false; } 1521 if (on) { theObj.className = (trow+tcol)%2 === 0 ? "highlightWhiteSquare" : "highlightBlackSquare"; } 1522 else { theObj.className = (trow+tcol)%2 === 0 ? "whiteSquare" : "blackSquare"; } 1523 return true; 1524} 1525 1526var undoStackMax = 1000; 1527var undoStackGame = new Array(undoStackMax); 1528var undoStackVar = new Array(undoStackMax); 1529var undoStackPly = new Array(undoStackMax); 1530var undoStackStart = 0; 1531var undoStackCurrent = 0; 1532var undoStackEnd = 0; 1533var undoRedoInProgress = false; 1534 1535function undoStackReset() { 1536 undoStackGame = new Array(undoStackMax); 1537 undoStackVar = new Array(undoStackMax); 1538 undoStackPly = new Array(undoStackMax); 1539 undoStackStart = undoStackCurrent = undoStackEnd = 0; 1540} 1541 1542function undoStackStore() { 1543 if (undoRedoInProgress) { return false; } 1544 if ((undoStackStart === undoStackCurrent) || (currentGame !== undoStackGame[undoStackCurrent]) || (CurrentVar !== undoStackVar[undoStackCurrent]) || (CurrentPly !== undoStackPly[undoStackCurrent])) { 1545 undoStackCurrent = (undoStackCurrent + 1) % undoStackMax; 1546 undoStackGame[undoStackCurrent] = currentGame; 1547 undoStackVar[undoStackCurrent] = CurrentVar; 1548 undoStackPly[undoStackCurrent] = CurrentPly; 1549 undoStackEnd = undoStackCurrent; 1550 if (undoStackStart === undoStackCurrent) { undoStackStart = (undoStackStart + 1) % undoStackMax; } 1551 } 1552 return true; 1553} 1554 1555function undoStackUndo() { 1556 if ((undoStackCurrent - 1 + undoStackMax) % undoStackMax === undoStackStart) { return false; } 1557 undoRedoInProgress = true; 1558 undoStackCurrent = (undoStackCurrent - 1 + undoStackMax) % undoStackMax; 1559 if (undoStackGame[undoStackCurrent] !== currentGame) { Init(undoStackGame[undoStackCurrent]); } 1560 GoToMove(undoStackPly[undoStackCurrent], undoStackVar[undoStackCurrent]); 1561 undoRedoInProgress = false; 1562 return true; 1563} 1564 1565function undoStackRedo() { 1566 if (undoStackCurrent === undoStackEnd) { return false; } 1567 undoRedoInProgress = true; 1568 undoStackCurrent = (undoStackCurrent + 1) % undoStackMax; 1569 if (undoStackGame[undoStackCurrent] !== currentGame) { Init(undoStackGame[undoStackCurrent]); } 1570 GoToMove(undoStackPly[undoStackCurrent], undoStackVar[undoStackCurrent]); 1571 undoRedoInProgress = false; 1572 return true; 1573} 1574 1575 1576function fixCommonPgnMistakes(text) { 1577 text = text.replace(/[\u00A0\u180E\u2000-\u200A\u202F\u205F\u3000]/g," "); // some spaces to plain space 1578 text = text.replace(/\u00BD/g,"1/2"); // "half fraction" to "1/2" 1579 text = text.replace(/[\u2010-\u2015]/g,"-"); // "hyphens" to "-" 1580 text = text.replace(/\u2024/g,"."); // "one dot leader" to "." 1581 text = text.replace(/[\u2025-\u2026]/g,"..."); // "two dot leader" and "ellipsis" to "..." 1582 text = text.replace(/\\"/g,"'"); // fix [Opening "Queen\"s Gambit"] 1583 return text; 1584} 1585 1586function fullPgnGame(gameNum) { 1587 var res = pgnHeader[gameNum] ? pgnHeader[gameNum].replace(/^[^[]*/g, "") : ""; 1588 res = res.replace(/\[\s*(\w+)\s*"([^"]*)"\s*\][^[]*/g, '[$1 "$2"]\n'); 1589 res += "\n"; 1590 res += pgnGame[gameNum] ? pgnGame[gameNum].replace(/(^[\s]*|[\s]*$)/g, "") : ""; 1591 return res; 1592} 1593 1594function pgnGameFromPgnText(pgnText) { 1595 1596 var newNumGames, headMatch, prevHead, newHead, startNew, afterNew, lastOpen, checkedGame, validHead; 1597 1598 pgnText = simpleHtmlentities(fixCommonPgnMistakes(pgnText)); 1599 1600 // PGN standard: ignore lines starting with % 1601 pgnText = pgnText.replace(/(^|\n)%.*(\n|$)/g, "\n"); 1602 1603 newNumGames = 0; 1604 checkedGame = ""; 1605 while (headMatch = pgnHeaderBlockRegExp.exec(pgnText)) { 1606 newHead = headMatch[0]; 1607 startNew = pgnText.indexOf(newHead); 1608 afterNew = startNew + newHead.length; 1609 if (prevHead) { 1610 checkedGame += pgnText.slice(0, startNew); 1611 validHead = ((lastOpen = checkedGame.lastIndexOf("{")) < 0) || (checkedGame.lastIndexOf("}")) > lastOpen; 1612 if (validHead) { 1613 pgnHeader[newNumGames] = prevHead; 1614 pgnGame[newNumGames++] = checkedGame; 1615 checkedGame = ""; 1616 } else { 1617 checkedGame += newHead; 1618 } 1619 } else { 1620 validHead = true; 1621 } 1622 if (validHead) { prevHead = newHead; } 1623 pgnText = pgnText.slice(afterNew); 1624 } 1625 if (prevHead) { 1626 pgnHeader[newNumGames] = prevHead; 1627 checkedGame += pgnText; 1628 pgnGame[newNumGames++] = checkedGame; 1629 } 1630 1631 if (newNumGames === 0) { return false; } 1632 numberOfGames = newNumGames; 1633 return true; 1634} 1635 1636 1637function pgnGameFromHttpRequest(httpResponseData) { 1638 1639 // process here any special file types, for instance zipfiles: 1640 // if (pgnUrl && pgnUrl.replace(/[?#].*/, "").match(/\.zip$/i)) { return pgnGameFromPgnText(unzipPgnFiles(httpResponseData)); } 1641 // remember to fix function loadPgnFromPgnUrl() for binary data 1642 1643 return pgnGameFromPgnText(httpResponseData); 1644} 1645 1646var http_request_last_processed_id = 0; 1647function updatePgnFromHttpRequest(this_http_request, this_http_request_id) { 1648 var res = LOAD_PGN_FAIL; 1649 1650 if (this_http_request.readyState != 4) { return; } 1651 1652 if (this_http_request_id < http_request_last_processed_id) { return; } 1653 else { http_request_last_processed_id = this_http_request_id; } 1654 1655 if ((this_http_request.status == 200) || (this_http_request.status == 304)) { 1656 1657 if (this_http_request.status == 304) { 1658 if (LiveBroadcastDelay > 0) { 1659 res = LOAD_PGN_UNMODIFIED; 1660 } else { 1661 myAlert('error: unmodified PGN URL when not in live mode'); 1662 } 1663 } else if (!this_http_request.responseText) { 1664 myAlert('error: no data received from PGN URL\n' + pgnUrl, true); 1665 } else if (!pgnGameFromHttpRequest(this_http_request.responseText)) { 1666 myAlert('error: no games found at PGN URL\n' + pgnUrl, true); 1667 } else { 1668 if (LiveBroadcastDelay > 0) { 1669 LiveBroadcastLastReceivedLocal = (new Date()).toLocaleString(); 1670 if (LiveBroadcastLastModifiedHeader = this_http_request.getResponseHeader("Last-Modified")) { 1671 LiveBroadcastLastModified = new Date(LiveBroadcastLastModifiedHeader); 1672 } else { LiveBroadcastLastModified_Reset(); } 1673 } 1674 res = LOAD_PGN_OK; 1675 } 1676 1677 } else { 1678 myAlert('error: failed reading PGN URL\n' + pgnUrl, true); 1679 } 1680 1681 if (LiveBroadcastDemo && (res == LOAD_PGN_UNMODIFIED)) { 1682 res = LOAD_PGN_OK; 1683 } 1684 1685 loadPgnCheckingLiveStatus(res); 1686} 1687 1688var LOAD_PGN_FAIL = 0; 1689var LOAD_PGN_OK = 1; 1690var LOAD_PGN_UNMODIFIED = 2; 1691function loadPgnCheckingLiveStatus(res) { 1692 1693 switch (res) { 1694 1695 case LOAD_PGN_OK: 1696 if (LiveBroadcastDelay > 0) { 1697 firstStart = true; 1698 var oldParseLastMoveError = ParseLastMoveError; 1699 if (!LiveBroadcastStarted) { 1700 LiveBroadcastStarted = true; 1701 } else { 1702 var oldWhite = gameWhite[currentGame]; 1703 var oldBlack = gameBlack[currentGame]; 1704 var oldEvent = gameEvent[currentGame]; 1705 var oldRound = gameRound[currentGame]; 1706 var oldSite = gameSite[currentGame]; 1707 var oldDate = gameDate[currentGame]; 1708 1709 initialGame = currentGame + 1; 1710 1711 LiveBroadcastOldCurrentVar = CurrentVar; 1712 LiveBroadcastOldCurrentPly = CurrentPly; 1713 LiveBroadcastOldCurrentPlyLast = (CurrentVar === 0 && CurrentPly === StartPlyVar[0] + PlyNumberVar[0]); 1714 1715 var oldAutoplay = isAutoPlayOn; 1716 if (isAutoPlayOn) { SetAutoPlay(false); } 1717 1718 LoadGameHeaders(); 1719 LiveBroadcastFoundOldGame = false; 1720 for (var ii=0; ii<numberOfGames; ii++) { 1721 LiveBroadcastFoundOldGame = (gameWhite[ii]==oldWhite) && (gameBlack[ii]==oldBlack) && (gameEvent[ii]==oldEvent) && (gameRound[ii]==oldRound) && (gameSite[ii] ==oldSite ); // && (gameDate[ii] ==oldDate ); 1722 if (LiveBroadcastFoundOldGame) { break; } 1723 } 1724 if (LiveBroadcastFoundOldGame) { initialGame = ii + 1; } 1725 1726 if (LiveBroadcastFoundOldGame) { 1727 var oldInitialVariation = initialVariation; 1728 var oldInitialHalfmove = initialHalfmove; 1729 initialVariation = CurrentVar; 1730 if (LiveBroadcastSteppingMode) { 1731 initialHalfmove = (LiveBroadcastOldCurrentPlyLast || oldParseLastMoveError) ? LiveBroadcastOldCurrentPly+1 : LiveBroadcastOldCurrentPly; 1732 } else { 1733 initialHalfmove = (LiveBroadcastOldCurrentPlyLast || oldParseLastMoveError) ? "end" : LiveBroadcastOldCurrentPly; 1734 } 1735 } 1736 } 1737 } 1738 1739 undoStackReset(); 1740 Init(); 1741 1742 if (LiveBroadcastDelay > 0) { 1743 if (LiveBroadcastFoundOldGame) { 1744 initialHalfmove = oldInitialHalfmove; 1745 initialVariation = oldInitialVariation; 1746 } 1747 checkLiveBroadcastStatus(); 1748 } 1749 1750 customFunctionOnPgnTextLoad(); 1751 1752 if (LiveBroadcastDelay > 0) { 1753 if (LiveBroadcastFoundOldGame) { 1754 if (LiveBroadcastSteppingMode) { 1755 if (oldAutoplay || LiveBroadcastOldCurrentPlyLast || oldParseLastMoveError) { SetAutoPlay(true); } 1756 } else { 1757 if (oldAutoplay) { SetAutoPlay(true); } 1758 } 1759 } 1760 } 1761 1762 break; 1763 1764 case LOAD_PGN_UNMODIFIED: 1765 if (LiveBroadcastDelay > 0) { 1766 checkLiveBroadcastStatus(); 1767 } 1768 break; 1769 1770 case LOAD_PGN_FAIL: 1771 default: 1772 if (LiveBroadcastDelay === 0) { 1773 pgnGameFromPgnText(alertPgn); 1774 undoStackReset(); 1775 Init(); 1776 customFunctionOnPgnTextLoad(); 1777 } else { // live broadcast: wait for live show start 1778 if (!LiveBroadcastStarted) { 1779 pgnGameFromPgnText(LiveBroadcastPlaceholderPgn); 1780 firstStart = true; 1781 undoStackReset(); 1782 Init(); 1783 checkLiveBroadcastStatus(); 1784 customFunctionOnPgnTextLoad(); 1785 } else { checkLiveBroadcastStatus(); } 1786 } 1787 break; 1788 1789 } 1790 1791 if (LiveBroadcastDelay > 0) { restartLiveBroadcastTimeout(); } 1792} 1793 1794var http_request_last_id = 0; 1795function loadPgnFromPgnUrl(pgnUrl) { 1796 1797 LiveBroadcastLastRefreshedLocal = (new Date()).toLocaleString(); 1798 1799 try { 1800 var http_request = new XMLHttpRequest(); 1801 if (http_request.overrideMimeType) { 1802 // patch: pgn encoding: amend the following line to match the expected encoding of the PGN file if charachters beyond the basic 128 ascii set are not displayed correctly 1803 http_request.overrideMimeType("text/plain"); // this assumes the PGN file is encoded as unicode UTF-8 1804 // http_request.overrideMimeType("text/plain; charset=ISO-8859-15"); // this has been reported to work with some files created by the DGT live boards software 1805 // http_request.overrideMimeType("text/plain; charset=x-user-defined"); // this works with a binary file, such as a zipfile, but would need further processing 1806 } 1807 } catch(e) { 1808 myAlert('error: failed creating XMLHttpRequest for PGN URL\n' + pgnUrl, true); 1809 loadPgnCheckingLiveStatus(LOAD_PGN_FAIL); 1810 return false; 1811 } 1812 1813 var http_request_id = http_request_last_id++; 1814 http_request.onreadystatechange = function () { updatePgnFromHttpRequest(http_request, http_request_id); }; 1815 1816 try { 1817 var randomizer = ""; 1818 // anti-caching #1 1819 if ((LiveBroadcastDelay > 0) && (pgnUrl.indexOf("?") == -1) && (pgnUrl.indexOf("#") == -1)) { 1820 randomizer = "?noCache=" + (0x1000000000 + Math.floor((Math.random() * 0xF000000000))).toString(16).toUpperCase(); 1821 } 1822 http_request.open("GET", pgnUrl + randomizer); 1823 // anti-caching #2 1824 if (LiveBroadcastDelay > 0) { 1825 http_request.setRequestHeader( "If-Modified-Since", LiveBroadcastLastModifiedHeader ); 1826 } 1827 http_request.send(null); 1828 } catch(e) { 1829 myAlert('error: failed sending XMLHttpRequest for PGN URL\n' + pgnUrl, true); 1830 return false; 1831 } 1832 1833 return true; 1834} 1835 1836function SetPgnUrl(url) { 1837 pgnUrl = url; 1838} 1839 1840 1841function LiveBroadcastLastModified_Reset() { 1842 LiveBroadcastLastModified = new Date(0); 1843 LiveBroadcastLastModifiedHeader = LiveBroadcastLastModified.toUTCString(); 1844} 1845 1846function LiveBroadcastLastReceivedLocal_Reset() { 1847 LiveBroadcastLastReceivedLocal = 'unavailable'; 1848} 1849 1850function LiveBroadcastLastModified_ServerTime() { 1851 return LiveBroadcastLastModified.getTime() === 0 ? 'unavailable' : LiveBroadcastLastModifiedHeader; 1852} 1853 1854function pauseLiveBroadcast() { 1855 if ((LiveBroadcastDelay === 0) || (LiveBroadcastPaused)) { return; } 1856 LiveBroadcastPaused = true; 1857 clearTimeout(LiveBroadcastInterval); 1858 LiveBroadcastInterval = null; 1859 LiveBroadcastTicker--; 1860 checkLiveBroadcastStatus(); 1861 LiveBroadcastTicker++; 1862} 1863 1864function restartLiveBroadcast() { 1865 if (LiveBroadcastDelay === 0) { return; } 1866 LiveBroadcastPaused = false; 1867 refreshPgnSource(); 1868} 1869 1870function checkLiveBroadcastStatus() { 1871 if (LiveBroadcastDelay === 0) { return; } 1872 1873 var theTitle, theHTML, theObj, ii; 1874 1875 // broadcast started yet? 1876 if (LiveBroadcastStarted === false || typeof(pgnHeader) == "undefined" || (numberOfGames == 1 && gameEvent[0] == LiveBroadcastPlaceholderEvent)) { 1877 // no 1878 LiveBroadcastEnded = false; 1879 LiveBroadcastGamesRunning = 0; 1880 theTitle = "live broadcast yet to start"; 1881 } else { 1882 // yes 1883 var lbgr = 0, lbga = 0; 1884 for (ii=0; ii<numberOfGames; ii++) { if (gameResult[ii].indexOf('*') >= 0) { lbga++; if (!pgnGame[ii].match(/^\s*\*?\s*$/)) { lbgr++; } } } 1885 LiveBroadcastEnded = ((lbga === 0) && (!LiveBroadcastEndlessMode)); 1886 LiveBroadcastGamesRunning = lbgr; 1887 theTitle = LiveBroadcastEnded ? "live broadcast ended" : LiveBroadcastPaused ? "live broadcast paused" : lbgr + " live game" + (lbgr == 1 ? "" : "s") + " out of " + numberOfGames; 1888 } 1889 theHTML = LiveBroadcastEnded ? "#" : LiveBroadcastPaused ? "+" : "="; 1890 theHTML = (LiveBroadcastTicker % 4 === 0 ? theHTML : " ") + (LiveBroadcastTicker % 2 === 1 ? theHTML : " ") + (LiveBroadcastTicker % 4 === 2 ? theHTML : " "); 1891 theHTML = LiveBroadcastGamesRunning + "<span style='display:inline-block; min-width:3em; text-align:center;'>" + theHTML + "</span>" + numberOfGames; 1892 theHTML = "<span onclick='" + (LiveBroadcastPaused ? "restartLiveBroadcast();" : "refreshPgnSource();") + " this.blur();'>" + theHTML + "</span>"; 1893 1894 if (theObj = document.getElementById("GameLiveStatus")) { 1895 theObj.innerHTML = theHTML; 1896 theObj.title = theTitle; 1897 } 1898 1899 if (theObj = document.getElementById("GameLiveLastRefreshed")) { theObj.innerHTML = LiveBroadcastLastRefreshedLocal; } 1900 if (theObj = document.getElementById("GameLiveLastReceived")) { theObj.innerHTML = LiveBroadcastLastReceivedLocal; } 1901 if (theObj = document.getElementById("GameLiveLastModifiedServer")) { theObj.innerHTML = LiveBroadcastLastModified_ServerTime(); } 1902 1903 customFunctionOnCheckLiveBroadcastStatus(); 1904} 1905 1906function restartLiveBroadcastTimeout() { 1907 if (LiveBroadcastDelay === 0) { return; } 1908 if (LiveBroadcastInterval) { clearTimeout(LiveBroadcastInterval); LiveBroadcastInterval = null; } 1909 if ((!LiveBroadcastEnded) && (!LiveBroadcastPaused)) { 1910 LiveBroadcastInterval = setTimeout("refreshPgnSource()", LiveBroadcastDelay * 60000); 1911 } 1912 LiveBroadcastTicker++; 1913} 1914 1915var LiveBroadcastFoundOldGame = false; 1916var LiveBroadcastOldCurrentVar; 1917var LiveBroadcastOldCurrentPly; 1918var LiveBroadcastOldCurrentPlyLast = false; 1919function refreshPgnSource() { 1920 if (LiveBroadcastDelay === 0) { return; } 1921 if (LiveBroadcastInterval) { clearTimeout(LiveBroadcastInterval); LiveBroadcastInterval = null; } 1922 if (LiveBroadcastDemo) { 1923 var newPly, addedPly = 0; 1924 for (var ii=0; ii<numberOfGames; ii++) { 1925 // 5% 15% 40% 1926 newPly = [3, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1][Math.floor(20 * Math.random())] || 0; 1927 if (gameDemoMaxPly[ii] <= gameDemoLength[ii]) { 1928 gameDemoMaxPly[ii] += newPly; 1929 addedPly += newPly; 1930 } 1931 } 1932 if (addedPly > 0) { LiveBroadcastLastReceivedLocal = (new Date()).toLocaleString(); } 1933 } 1934 1935 if (pgnUrl) { 1936 loadPgnFromPgnUrl(pgnUrl); 1937 } else if ( document.getElementById("pgnText") ) { 1938 loadPgnFromTextarea("pgnText"); 1939 } else { 1940 pgnGameFromPgnText(alertPgn); 1941 undoStackReset(); 1942 Init(); 1943 customFunctionOnPgnTextLoad(); 1944 myAlert('error: missing PGN URL location and pgnText object in the HTML file', true); 1945 } 1946} 1947 1948function loadPgnFromTextarea(textareaId) { 1949 var res = LOAD_PGN_FAIL, text, theObj; 1950 1951 LiveBroadcastLastRefreshedLocal = (new Date()).toLocaleString(); 1952 1953 if (!(theObj = document.getElementById(textareaId))) { 1954 myAlert('error: missing ' + textareaId + ' textarea object in the HTML file', true); 1955 } else { 1956 if (document.getElementById(textareaId).tagName.toLowerCase() == "textarea") { 1957 text = document.getElementById(textareaId).value; 1958 } else { // compatibility with pgn4web up to 1.77: <span> used for pgnText 1959 text = document.getElementById(textareaId).innerHTML; 1960 // fixes browser issue removing \n from innerHTML 1961 if (text.indexOf('\n') < 0) { text = text.replace(/((\[[^\[\]]*\]\s*)+)/g, "\n$1\n"); } 1962 // fixes browser issue replacing quotes with " 1963 if (text.indexOf('"') < 0) { text = text.replace(/(")/g, '"'); } 1964 } 1965 1966 // no header: add emptyPgnHeader 1967 if (pgnHeaderTagRegExp.test(text) === false) { text = emptyPgnHeader + "\n" + text; } 1968 1969 if ( pgnGameFromPgnText(text) ) { 1970 res = LOAD_PGN_OK; 1971 LiveBroadcastLastReceivedLocal = (new Date()).toLocaleString(); 1972 } else { 1973 myAlert('error: no games found in ' + textareaId + ' object in the HTML file'); 1974 } 1975 } 1976 1977 loadPgnCheckingLiveStatus(res); 1978} 1979 1980function createBoard() { 1981 if (pgnUrl) { 1982 loadPgnFromPgnUrl(pgnUrl); 1983 } else if ( document.getElementById("pgnText") ) { 1984 loadPgnFromTextarea("pgnText"); 1985 } else { 1986 pgnGameFromPgnText(alertPgn); 1987 undoStackReset(); 1988 Init(); 1989 customFunctionOnPgnTextLoad(); 1990 myAlert('error: missing PGN URL location or pgnText in the HTML file', true); 1991 } 1992} 1993 1994function setCurrentGameFromInitialGame() { 1995 switch (initialGame) { 1996 case "first": 1997 currentGame = 0; 1998 break; 1999 case "last": 2000 currentGame = numberOfGames - 1; 2001 break; 2002 case "random": 2003 currentGame = Math.floor(Math.random()*numberOfGames); 2004 break; 2005 default: 2006 if (isNaN(parseInt(initialGame,10))) { 2007 currentGame = gameNumberSearchPgn(initialGame, false, true); 2008 if (!currentGame) { currentGame = 0; } 2009 } else { 2010 initialGame = parseInt(initialGame,10); 2011 initialGame = initialGame < 0 ? -Math.floor(-initialGame) : Math.floor(initialGame); 2012 if (initialGame < -numberOfGames) { currentGame = 0; } 2013 else if (initialGame < 0) { currentGame = numberOfGames + initialGame; } 2014 else if (initialGame === 0) { currentGame = Math.floor(Math.random()*numberOfGames); } 2015 else if (initialGame <= numberOfGames) { currentGame = (initialGame - 1); } 2016 else { currentGame = numberOfGames - 1; } 2017 } 2018 break; 2019 } 2020} 2021 2022function GoToInitialHalfmove() { 2023 var iv, ih; 2024 if (initialVariation < 0) { iv = Math.max(numberOfVars + initialVariations, 0); } 2025 else { iv = Math.min(initialVariation, numberOfVars - 1); } 2026 2027 switch (initialHalfmove) { 2028 case "start": 2029 GoToMove((StartPlyVar[iv] + (iv ? 1 : 0)), iv); 2030 break; 2031 case "end": 2032 GoToMove(StartPlyVar[iv] + PlyNumberVar[iv], iv); 2033 break; 2034 case "random": 2035 GoToMove((StartPlyVar[iv] + (iv ? 1 : 0)) + Math.floor(Math.random()*(StartPlyVar[iv] + PlyNumberVar[iv])), iv); 2036 break; 2037 case "comment": 2038 case "variation": 2039 GoToMove((StartPlyVar[iv] + (iv ? 1 : 0)), iv); 2040 MoveToNextComment(initialHalfmove == "variation"); 2041 break; 2042 default: 2043 if (isNaN(initialHalfmove = parseInt(initialHalfmove, 10))) { initialHalfmove = 0; } 2044 if (initialHalfmove < 0) { ih = Math.max(StartPlyVar[iv] + PlyNumberVar[iv] + 1 + initialHalfmove, StartPly); } 2045 else { ih = Math.min(initialHalfmove, StartPlyVar[iv] + PlyNumberVar[iv]); } 2046 GoToMove(ih, iv); 2047 break; 2048 } 2049} 2050 2051function Init(nextGame) { 2052 2053 if (nextGame !== undefined) { 2054 if ((!isNaN(nextGame)) && (nextGame >= 0) && (nextGame < numberOfGames)) { 2055 currentGame = parseInt(nextGame,10); 2056 } else { return; } 2057 } 2058 2059 if (isAutoPlayOn) { SetAutoPlay(false); } 2060 2061 InitImages(); 2062 if (firstStart) { 2063 LoadGameHeaders(); 2064 setCurrentGameFromInitialGame(); 2065 } 2066 2067 if ((gameSetUp[currentGame] !== undefined) && (gameSetUp[currentGame] != "1")) { InitFEN(); } 2068 else { InitFEN(gameFEN[currentGame]); } 2069 2070 OpenGame(currentGame); 2071 2072 CurrentPly = StartPly; 2073 if (firstStart || alwaysInitialHalfmove) { 2074 GoToInitialHalfmove(); 2075 setTimeout("autoScrollToCurrentMoveIfEnabled();", Math.min(666, 0.9 * Delay)); 2076 } else { 2077 synchMoves(); 2078 RefreshBoard(); 2079 HighlightLastMove(); 2080 autoScrollToCurrentMoveIfEnabled(); 2081 // customFunctionOnMove here for consistency: null move starting new game 2082 customFunctionOnMove(); 2083 if (typeof(engineWinOnMove) == "function") { engineWinOnMove(); } 2084 } 2085 2086 if ((firstStart) && (autostartAutoplay)) { SetAutoPlay(true); } 2087 2088 customFunctionOnPgnGameLoad(); 2089 2090 initialVariation = 0; 2091 firstStart = false; 2092} 2093 2094function myAlertFEN(FenString, text) { 2095 myAlert("error: invalid FEN in game " + (currentGame+1) + ": " + text + "\n" + FenString, true); 2096} 2097 2098function InitFEN(startingFEN) { 2099 var ii, jj, cc, color, castlingRookCol, fullMoveNumber; 2100 2101 var FenString = typeof(startingFEN) != "string" ? FenStringStart : startingFEN.replace(/\\/g, "/").replace(/[^a-zA-Z0-9\s\/-]/g, " ").replace(/(^\s*|\s*$)/g, "").replace(/\s+/g, " "); 2102 2103 for (ii = 0; ii < 8; ++ii) { 2104 for (jj = 0; jj < 8; ++jj) { 2105 Board[ii][jj] = 0; 2106 } 2107 } 2108 2109 StartPly = 0; 2110 MoveCount = StartPly; 2111 MoveColor = StartPly % 2; 2112 2113 var newEnPassant = false; 2114 var newEnPassantCol; 2115 CastlingLong = [0, 0]; 2116 CastlingShort = [7, 7]; 2117 InitialHalfMoveClock = 0; 2118 2119 HistVar[StartPly] = 0; 2120 HistNull[StartPly] = 0; 2121 2122 if (FenString == FenStringStart) { 2123 for (color = 0; color < 2; color++) { 2124 // K Q N B R p 2125 PieceType[color] = [1, 2, 5, 5, 4, 4, 3, 3, 6, 6, 6, 6, 6, 6, 6, 6]; 2126 PieceCol[color] = [4, 3, 1, 6, 2, 5, 0, 7, 0, 1, 2, 3, 4, 5, 6, 7]; 2127 PieceMoveCounter[color] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; 2128 PieceRow[color] = color ? [7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6]: 2129 [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1]; 2130 for (ii = 0; ii < 16; ii++) { 2131 var col = PieceCol[color][ii]; 2132 var row = PieceRow[color][ii]; 2133 Board[col][row] = (1-2*color)*PieceType[color][ii]; 2134 } 2135 } 2136 } else { 2137 var kk, ll, nn, mm; 2138 for (ii = 0; ii < 2; ii++) { 2139 for (jj = 0; jj < 16; jj++) { 2140 PieceType[ii][jj] = -1; 2141 PieceCol[ii][jj] = 0; 2142 PieceRow[ii][jj] = 0; 2143 PieceMoveCounter[ii][jj] = 0; 2144 } 2145 } 2146 2147 ii = 0; jj = 7; ll = 0; nn = 1; mm = 1; cc = FenString.charAt(ll++); 2148 while (cc != " ") { 2149 if (cc == "/") { 2150 if (ii != 8) { 2151 myAlertFEN(FenString, "char " + ll); 2152 InitFEN(); 2153 return; 2154 } 2155 ii = 0; 2156 jj--; 2157 } 2158 if (ii == 8) { 2159 myAlertFEN(FenString, "char " + ll); 2160 InitFEN(); 2161 return; 2162 } 2163 if (!isNaN(cc)) { 2164 ii += parseInt(cc,10); 2165 if ((ii < 0) || (ii > 8)) { 2166 myAlertFEN(FenString, "char " + ll); 2167 InitFEN(); 2168 return; 2169 } 2170 } 2171 if (cc === PiecesArr[0].toUpperCase()) { 2172 if (PieceType[0][0] != -1) { 2173 myAlertFEN(FenString, "char " + ll); 2174 InitFEN(); 2175 return; 2176 } 2177 PieceType[0][0] = 1; 2178 PieceCol[0][0] = ii; 2179 PieceRow[0][0] = jj; 2180 ii++; 2181 } else if (cc === PiecesArr[0].toLowerCase()) { 2182 if (PieceType[1][0] != -1) { 2183 myAlertFEN(FenString, "char " + ll); 2184 InitFEN(); 2185 return; 2186 } 2187 PieceType[1][0] = 1; 2188 PieceCol[1][0] = ii; 2189 PieceRow[1][0] = jj; 2190 ii++; 2191 } 2192 for (kk = 1; kk < 6; kk++) { 2193 if (cc === PiecesArr[kk].toUpperCase()) { 2194 if (nn == 16) { 2195 myAlertFEN(FenString, "char " + ll); 2196 InitFEN(); 2197 return; 2198 } 2199 PieceType[0][nn] = kk+1; 2200 PieceCol[0][nn] = ii; 2201 PieceRow[0][nn] = jj; 2202 nn++; 2203 ii++; 2204 } else if (cc === PiecesArr[kk].toLowerCase()) { 2205 if (mm==16) { 2206 myAlertFEN(FenString, "char " + ll); 2207 InitFEN(); 2208 return; 2209 } 2210 PieceType[1][mm] = kk+1; 2211 PieceCol[1][mm] = ii; 2212 PieceRow[1][mm] = jj; 2213 mm++; 2214 ii++; 2215 } 2216 } 2217 cc = ll < FenString.length ? FenString.charAt(ll++) : " "; 2218 } 2219 if ((ii != 8) || (jj !== 0)) { 2220 myAlertFEN(FenString, "char " + ll); 2221 InitFEN(); 2222 return; 2223 } 2224 if ((PieceType[0][0] == -1) || (PieceType[1][0] == -1)) { 2225 myAlertFEN(FenString, "missing King"); 2226 InitFEN(); 2227 return; 2228 } 2229 if (ll == FenString.length) { 2230 FenString += "w " + assumedCastleRights() + " - 0 1"; 2231 } 2232 cc = FenString.charAt(ll++); 2233 if ((cc == "w") || (cc == "b")) { 2234 if (cc == "b") { 2235 StartPly += 1; 2236 MoveColor = 1; 2237 } 2238 } else { 2239 myAlertFEN(FenString, "invalid active color"); 2240 } 2241 2242 // set board 2243 for (color = 0; color < 2; ++color) { 2244 for (ii = 0; ii < 16; ii++) { 2245 if (PieceType[color][ii] != -1) { 2246 col = PieceCol[color][ii]; 2247 row = PieceRow[color][ii]; 2248 Board[col][row] = (1-2*color)*(PieceType[color][ii]); 2249 } 2250 } 2251 } 2252 2253 ll++; 2254 if (ll >= FenString.length) { 2255 myAlertFEN(FenString, "missing castling availability"); 2256 FenString += " " + assumedCastleRights() + " - 0 1"; 2257 ll++; 2258 } 2259 CastlingLong = [-1, -1]; 2260 CastlingShort = [-1, -1]; 2261 cc = FenString.charAt(ll++); 2262 while (cc!=" ") { 2263 if (cc === PiecesArr[0].toUpperCase()) { 2264 for (CastlingShort[0] = 7; CastlingShort[0] > PieceCol[0][0]; CastlingShort[0]--) { 2265 if (Board[CastlingShort[0]][0] == 3) { break; } 2266 } 2267 if (CastlingShort[0] <= PieceCol[0][0]) { 2268 myAlertFEN(FenString, "missing castling Rook " + cc); 2269 CastlingShort[0] = -1; 2270 } 2271 } else if (cc === PiecesArr[1].toUpperCase()) { 2272 for (CastlingLong[0] = 0; CastlingLong[0] < PieceCol[0][0]; CastlingLong[0]++) { 2273 if (Board[CastlingLong[0]][0] == 3) { break; } 2274 } 2275 if (CastlingLong[0] >= PieceCol[0][0]) { 2276 myAlertFEN(FenString, "missing castling Rook " + cc); 2277 CastlingLong[0] = -1; 2278 } 2279 } else if (cc === PiecesArr[0].toLowerCase()) { 2280 for (CastlingShort[1] = 7; CastlingShort[1] > PieceCol[1][0]; CastlingShort[1]--) { 2281 if (Board[CastlingShort[1]][7] == -3) { break; } 2282 } 2283 if (CastlingShort[1] <= PieceCol[1][0]) { 2284 myAlertFEN(FenString, "missing castling Rook " + cc); 2285 CastlingShort[1] = -1; 2286 } 2287 } else if (cc === PiecesArr[1].toLowerCase()) { 2288 for (CastlingLong[1] = 0; CastlingLong[1] < PieceCol[1][0]; CastlingLong[1]++) { 2289 if (Board[CastlingLong[1]][7] == -3) { break; } 2290 } 2291 if (CastlingLong[1] >= PieceCol[1][0]) { 2292 myAlertFEN(FenString, "missing castling Rook " + cc); 2293 CastlingLong[1] = -1; 2294 } 2295 } 2296 castlingRookCol = columnsLetters.toUpperCase().indexOf(cc); 2297 if (castlingRookCol >= 0) { color = 0; } 2298 else { 2299 castlingRookCol = columnsLetters.toLowerCase().indexOf(cc); 2300 if (castlingRookCol >= 0) { color = 1; } 2301 } 2302 if (castlingRookCol >= 0) { 2303 if (Board[castlingRookCol][color*7] == (1-2*color) * 3) { 2304 if (castlingRookCol > PieceCol[color][0]) { CastlingShort[color] = castlingRookCol; } 2305 if (castlingRookCol < PieceCol[color][0]) { CastlingLong[color] = castlingRookCol; } 2306 } else { 2307 myAlertFEN(FenString, "missing castling Rook " + cc); 2308 } 2309 } 2310 cc = ll<FenString.length ? FenString.charAt(ll++) : " "; 2311 } 2312 2313 if (ll >= FenString.length) { 2314 myAlertFEN(FenString, "missing en passant square"); 2315 FenString += " - 0 1"; 2316 ll++; 2317 } 2318 cc = FenString.charAt(ll++); 2319 while (cc != " ") { 2320 if ((cc.charCodeAt(0)-97 >= 0) && (cc.charCodeAt(0)-97 <= 7)) { 2321 newEnPassant = true; 2322 newEnPassantCol = cc.charCodeAt(0)-97; 2323 } 2324 cc = ll<FenString.length ? FenString.charAt(ll++) : " "; 2325 } 2326 if (ll >= FenString.length) { 2327 myAlertFEN(FenString, "missing halfmove clock"); 2328 FenString += " 0 1"; 2329 ll++; 2330 } 2331 InitialHalfMoveClock = 0; 2332 cc = FenString.charAt(ll++); 2333 while (cc != " ") { 2334 if (isNaN(cc)) { 2335 myAlertFEN(FenString, "invalid halfmove clock"); 2336 break; 2337 } 2338 InitialHalfMoveClock=InitialHalfMoveClock*10+parseInt(cc,10); 2339 cc = ll<FenString.length ? FenString.charAt(ll++) : " "; 2340 } 2341 if (ll >= FenString.length) { 2342 myAlertFEN(FenString, "missing fullmove number"); 2343 FenString += " 1"; 2344 ll++; 2345 } 2346 2347 fullMoveNumber = 0; 2348 cc = FenString.charAt(ll++); 2349 while (cc != " ") { 2350 if (isNaN(cc)) { 2351 myAlertFEN(FenString, "invalid fullmove number"); 2352 fullMoveNumber = 1; 2353 break; 2354 } 2355 fullMoveNumber = fullMoveNumber*10+parseInt(cc,10); 2356 cc = ll<FenString.length ? FenString.charAt(ll++) : " "; 2357 } 2358 if (fullMoveNumber === 0) { 2359 myAlertFEN(FenString, "invalid fullmove 0 set to 1"); 2360 fullMoveNumber = 1; 2361 } 2362 StartPly += 2*(fullMoveNumber-1); 2363 2364 HistEnPassant[StartPly] = newEnPassant; 2365 HistEnPassantCol[StartPly] = newEnPassantCol; 2366 HistNull[StartPly] = 0; 2367 HistVar[StartPly] = 0; 2368 } 2369} 2370 2371// castling rights assuming Kings and Rooks starting positions as in normal chess 2372function assumedCastleRights() { 2373 var ii, rights = ""; 2374 if ((PieceRow[0][0] === 0) && (PieceCol[0][0] === 4)) { 2375 for (ii = 0; ii < PieceType[0].length; ii++) { 2376 if ((PieceType[0][ii] === 3) && (PieceRow[0][ii] === 0) && (PieceCol[0][ii] === 7)) { 2377 rights += PiecesArr[0].toUpperCase(); 2378 } else if ((PieceType[0][ii] === 3) && (PieceRow[0][ii] === 0) && (PieceCol[0][ii] === 0)) { 2379 rights += PiecesArr[1].toUpperCase(); 2380 } 2381 } 2382 } 2383 if ((PieceRow[1][0] === 7) && (PieceCol[1][0] === 4)) { 2384 for (ii = 0; ii < PieceType[1].length; ii++) { 2385 if ((PieceType[1][ii] === 3) && (PieceRow[1][ii] === 7) && (PieceCol[1][ii] === 7)) { 2386 rights += PiecesArr[0].toLowerCase(); 2387 } else if ((PieceType[1][ii] === 3) && (PieceRow[1][ii] === 7) && (PieceCol[1][ii] === 0)) { 2388 rights += PiecesArr[1].toLowerCase(); 2389 } 2390 } 2391 } 2392 return rights || "-"; 2393} 2394 2395 2396function SetImageType(extension) { 2397 imageType = extension; 2398} 2399 2400function InitImages() { 2401 if (ImagePathOld === ImagePath) { return; } 2402 2403 if ((ImagePath.length > 0) && (ImagePath[ImagePath.length-1] != '/')) { 2404 ImagePath += '/'; 2405 } 2406 2407 ClearImg = new Image(); 2408 ClearImg.src = ImagePath + 'clear.' + imageType; 2409 2410 var ColorName = new Array ("w", "b"); 2411 var PiecePrefix = new Array ("k", "q", "r", "b", "n", "p"); 2412 for (var c=0; c<2; ++c) { 2413 for (var p=1; p<7; p++) { 2414 PieceImg[c][p] = new Image(); 2415 PieceImg[c][p].src = ImagePath + ColorName[c] + PiecePrefix[p-1] + '.' + imageType; 2416 } 2417 } 2418 ImagePathOld = ImagePath; 2419} 2420 2421 2422function IsCheck(col, row, color) { 2423 var ii, jj; 2424 var sign = 2*color-1; // white or black 2425 2426 // other king giving check? 2427 if ((Math.abs(PieceCol[1-color][0]-col) <= 1) && (Math.abs(PieceRow[1-color][0]-row) <= 1)) { return true; } 2428 2429 // knight? 2430 for (ii = -2; ii <= 2; ii += 4) { 2431 for (jj = -1; jj <= 1; jj += 2) { 2432 if (SquareOnBoard(col+ii, row+jj)) { 2433 if (Board[col+ii][row+jj] == sign*5) { return true; } 2434 } 2435 if (SquareOnBoard(col+jj, row+ii)) { 2436 if (Board[col+jj][row+ii] == sign*5) { return true; } 2437 } 2438 } 2439 } 2440 2441 // pawn? 2442 for (ii = -1; ii <= 1; ii += 2) { 2443 if (SquareOnBoard(col+ii, row-sign)) { 2444 if (Board[col+ii][row-sign] == sign*6) { return true; } 2445 } 2446 } 2447 2448 // queens, rooks, bishops? 2449 for (ii = -1; ii <= 1; ++ii) { 2450 for (jj = -1; jj <= 1; ++jj) { 2451 if ((ii !== 0) || (jj !== 0)) { 2452 var checkCol = col+ii; 2453 var checkRow = row+jj; 2454 var thisPiece = 0; 2455 2456 while (SquareOnBoard(checkCol, checkRow) && (thisPiece === 0)) { 2457 thisPiece = Board[checkCol][checkRow]; 2458 if (thisPiece === 0) { 2459 checkCol += ii; 2460 checkRow += jj; 2461 } else { 2462 if (thisPiece == sign*2) { return true; } 2463 if ((thisPiece == sign*3) && ((ii === 0) || (jj === 0))) { return true; } 2464 if ((thisPiece == sign*4) && ((ii !== 0) && (jj !== 0))) { return true; } 2465 } 2466 } 2467 } 2468 } 2469 } 2470 return false; 2471} 2472 2473 2474function fixRegExp(exp) { 2475 return exp.replace(/([\[\]\(\)\{\}\.\*\+\^\$\|\?\\])/g, "\\$1"); 2476} 2477 2478function LoadGameHeaders() { 2479 var ii; 2480 var parse; 2481 2482 gameEvent.length = gameSite.length = gameRound.length = gameDate.length = 0; 2483 gameWhite.length = gameBlack.length = gameResult.length = 0; 2484 gameSetUp.length = gameFEN.length = 0; 2485 gameInitialWhiteClock.length = gameInitialBlackClock.length = 0; 2486 gameVariant.length = 0; 2487 2488 pgnHeaderTagRegExpGlobal.lastIndex = 0; // resets global regular expression 2489 for (ii = 0; ii < numberOfGames; ++ii) { 2490 var ss = pgnHeader[ii]; 2491 gameEvent[ii] = gameSite[ii] = gameRound[ii] = gameDate[ii] = ""; 2492 gameWhite[ii] = gameBlack[ii] = gameResult[ii] = ""; 2493 gameInitialWhiteClock[ii] = gameInitialBlackClock[ii] = ""; 2494 gameVariant[ii] = ""; 2495 while (parse = pgnHeaderTagRegExpGlobal.exec(ss)) { 2496 switch (parse[1]) { 2497 case 'Event': gameEvent[ii] = parse[2]; break; 2498 case 'Site': gameSite[ii] = parse[2]; break; 2499 case 'Round': gameRound[ii] = parse[2]; break; 2500 case 'Date': gameDate[ii] = parse[2]; break; 2501 case 'White': gameWhite[ii] = parse[2]; break; 2502 case 'Black': gameBlack[ii] = parse[2]; break; 2503 case 'Result': gameResult[ii] = parse[2]; break; 2504 case 'SetUp': gameSetUp[ii] = parse[2]; break; 2505 case 'FEN': gameFEN[ii] = parse[2]; break; 2506 case 'WhiteClock': gameInitialWhiteClock[ii] = parse[2]; break; 2507 case 'BlackClock': gameInitialBlackClock[ii] = parse[2]; break; 2508 case 'Variant': gameVariant[ii] = parse[2]; break; 2509 default: break; 2510 } 2511 } 2512 } 2513 if ((LiveBroadcastDemo) && (numberOfGames > 0)) { 2514 for (ii = 0; ii < numberOfGames; ++ii) { 2515 if ((gameDemoLength[ii] === undefined) || (gameDemoLength[ii] === 0)) { 2516 InitFEN(gameFEN[ii]); 2517 ParsePGNGameString(pgnGame[ii]); 2518 gameDemoLength[ii] = PlyNumber; 2519 } 2520 if (gameDemoMaxPly[ii] === undefined) { gameDemoMaxPly[ii] = 0; } 2521 if ((gameDemoMaxPly[ii] <= gameDemoLength[ii]) && (gameDemoLength[ii] > 0)) { gameResult[ii] = '*'; } 2522 } 2523 } 2524 return; 2525} 2526 2527 2528function MoveBackward(diff, scanOnly) { 2529 2530 // CurrentPly counts from 1, starting position 0 2531 var goFromPly = CurrentPly - 1; 2532 var goToPly = goFromPly - diff; 2533 if (goToPly < StartPly) { goToPly = StartPly-1; } 2534 2535 // reconstruct old position 2536 for (var thisPly = goFromPly; thisPly > goToPly; --thisPly) { 2537 CurrentPly--; 2538 MoveColor = 1-MoveColor; 2539 CurrentVar = HistVar[thisPly]; 2540 UndoMove(thisPly); 2541 } 2542 2543 if (scanOnly) { return; } 2544 2545 synchMoves(); 2546 2547 // old position reconstructed: refresh board 2548 RefreshBoard(); 2549 HighlightLastMove(); 2550 2551 autoScrollToCurrentMoveIfEnabled(); 2552 2553 // autoplay: restart timeout 2554 if (AutoPlayInterval) { clearTimeout(AutoPlayInterval); AutoPlayInterval = null; } 2555 if (isAutoPlayOn) { 2556 if (goToPly >= StartPlyVar[CurrentVar]) { AutoPlayInterval=setTimeout("MoveBackward(1)", Delay); } 2557 else { SetAutoPlay(false); } 2558 } 2559 2560 customFunctionOnMove(); 2561 if (typeof(engineWinOnMove) == "function") { engineWinOnMove(); } 2562} 2563 2564function MoveForward(diff, targetVar, scanOnly) { 2565 var nextVar, nextVarStartPly, move, text; 2566 var oldVar = -1; 2567 2568 if (typeof(targetVar) == "undefined") { targetVar = CurrentVar; } 2569 2570 // CurrentPly counts from 1, starting position 0 2571 var goToPly = CurrentPly + parseInt(diff,10); 2572 2573 if (goToPly > StartPlyVar[targetVar] + PlyNumberVar[targetVar]) { 2574 goToPly = StartPlyVar[targetVar] + PlyNumberVar[targetVar]; 2575 } 2576 2577 // reach to selected move checking legality 2578 for (var thisPly = CurrentPly; thisPly < goToPly; ++thisPly) { 2579 2580 if (targetVar !== CurrentVar) { 2581 for (var ii = 0; ii < PredecessorsVars[targetVar].length; ii++) { 2582 if (PredecessorsVars[targetVar][ii] === CurrentVar) { break; } 2583 } 2584 if (ii === PredecessorsVars[targetVar].length) { 2585 myAlert("error: unknown path to variation " + targetVar + " from " + CurrentVar + " in game " + (currentGame+1), true); 2586 return; 2587 } else { 2588 nextVarStartPly = StartPlyVar[PredecessorsVars[targetVar][ii + 1]]; 2589 for (ii = ii+1; ii < PredecessorsVars[targetVar].length - 1; ii++) { 2590 if (StartPlyVar[PredecessorsVars[targetVar][ii+1]] !== StartPlyVar[PredecessorsVars[targetVar][ii]] ) { break; } 2591 } 2592 nextVar = PredecessorsVars[targetVar][ii]; 2593 } 2594 } else { nextVar = nextVarStartPly = -1; } 2595 2596 if (thisPly === nextVarStartPly) { 2597 oldVar = CurrentVar; 2598 CurrentVar = nextVar; 2599 } 2600 2601 if (typeof(move = MovesVar[CurrentVar][thisPly]) == "undefined") { break; } 2602 2603 if (ParseLastMoveError = !ParseMove(move, thisPly)) { 2604 text = (Math.floor(thisPly / 2) + 1) + ((thisPly % 2) === 0 ? '. ' : '... '); 2605 myAlert('error: invalid ply ' + text + move + ' in game ' + (currentGame+1) + ' variation ' + CurrentVar, true); 2606 if (thisPly === nextVarStartPly) { CurrentVar = oldVar; } 2607 break; 2608 } 2609 MoveColor = 1-MoveColor; 2610 2611 } 2612 2613 // new position: update ply count, then refresh board 2614 CurrentPly = thisPly; 2615 2616 if (scanOnly) { return; } 2617 2618 synchMoves(); 2619 2620 RefreshBoard(); 2621 HighlightLastMove(); 2622 2623 autoScrollToCurrentMoveIfEnabled(); 2624 2625 // autoplay: restart timeout 2626 if (AutoPlayInterval) { clearTimeout(AutoPlayInterval); AutoPlayInterval = null; } 2627 if (ParseLastMoveError) { SetAutoPlay(false); } 2628 else if (thisPly == goToPly) { 2629 if (isAutoPlayOn) { 2630 if (goToPly < StartPlyVar[CurrentVar] + PlyNumberVar[CurrentVar]) { 2631 AutoPlayInterval=setTimeout("MoveForward(1)", Delay); 2632 } else { 2633 if (autoplayNextGame && (CurrentVar === 0)) { AutoPlayInterval=setTimeout("AutoplayNextGame()", Delay); } 2634 else { SetAutoPlay(false); } 2635 } 2636 } 2637 } 2638 2639 customFunctionOnMove(); 2640 if (typeof(engineWinOnMove) == "function") { engineWinOnMove(); } 2641} 2642 2643var lastSynchCurrentVar = -1; 2644function synchMoves() { 2645 var start, end; 2646 if (CurrentVar === lastSynchCurrentVar) { return; } 2647 Moves = new Array(); 2648 MoveComments = new Array(); 2649 for (var ii = 0; ii < PredecessorsVars[CurrentVar].length; ii++) { 2650 start = StartPlyVar[PredecessorsVars[CurrentVar][ii]]; 2651 if (ii < PredecessorsVars[CurrentVar].length - 1) { 2652 end = StartPlyVar[PredecessorsVars[CurrentVar][ii+1]]; 2653 } else { 2654 end = StartPlyVar[PredecessorsVars[CurrentVar][ii]] + PlyNumberVar[PredecessorsVars[CurrentVar][ii]]; 2655 } 2656 for (var jj = start; jj < end; jj++) { 2657 Moves[jj] = MovesVar[PredecessorsVars[CurrentVar][ii]][jj]; 2658 MoveComments[jj] = MoveCommentsVar[PredecessorsVars[CurrentVar][ii]][jj] || ""; 2659 } 2660 } 2661 MoveComments[jj] = MoveCommentsVar[PredecessorsVars[CurrentVar][ii-1]][jj] || ""; 2662 PlyNumber = StartPlyVar[CurrentVar] + PlyNumberVar[CurrentVar] - StartPly; 2663 lastSynchCurrentVar = CurrentVar; 2664} 2665 2666 2667function AutoplayNextGame() { 2668 if (fatalErrorNumSinceReset === 0) { 2669 if (numberOfGames > 0) { 2670 Init((currentGame + 1) % numberOfGames); 2671 if ((numberOfGames > 1) || (PlyNumber > 0)) { 2672 SetAutoPlay(true); 2673 return; 2674 } 2675 } 2676 } 2677 SetAutoPlay(false); 2678} 2679 2680function MoveToNextComment(varOnly) { 2681 for (var ii=CurrentPly+1; ii<=StartPlyVar[CurrentVar] + PlyNumberVar[CurrentVar]; ii++) { 2682 if (MoveComments[ii].match(pgn4webVariationRegExp) || (!varOnly && strippedMoveComment(ii))) { GoToMove(ii); break; } 2683 } 2684} 2685 2686function MoveToPrevComment(varOnly) { 2687 for (var ii=(CurrentPly-1); ii>=StartPly; ii--) { 2688 if ((ii > 0 || CurrentVar > 0) && ii === StartPlyVar[HistVar[ii+1]]) { GoToMove(ii+1, HistVar[ii]); break; } 2689 if (MoveComments[ii].match(pgn4webVariationRegExp) || (!varOnly && strippedMoveComment(ii))) { GoToMove(ii); break; } 2690 } 2691} 2692 2693 2694function OpenGame(gameId) { 2695 ParsePGNGameString(pgnGame[gameId]); 2696 currentGame = gameId; 2697 ParseLastMoveError = false; 2698 2699 if (LiveBroadcastDemo) { 2700 if (gameDemoMaxPly[gameId] <= PlyNumber) { PlyNumber = PlyNumberVar[0] = gameDemoMaxPly[gameId]; } 2701 } 2702 2703 PrintHTML(); 2704} 2705 2706var CurrentVar = -1; 2707var lastVarWithNoMoves; 2708var numberOfVars; 2709var MovesVar; 2710var MoveCommentsVar; 2711var GameHasComments; 2712var GameHasVariations; 2713var StartPlyVar; 2714var PlyNumberVar; 2715var CurrentVarStack; 2716var PlyNumberStack; 2717var PredecessorsVars; 2718 2719function initVar () { 2720 MovesVar = new Array(); 2721 MoveCommentsVar = new Array(); 2722 GameHasComments = false; 2723 GameHasVariations = false; 2724 StartPlyVar = new Array(); 2725 PlyNumberVar = new Array(); 2726 CurrentVar = -1; 2727 lastVarWithNoMoves = [false]; 2728 numberOfVars = 0; 2729 CurrentVarStack = new Array(); 2730 PlyNumber = 1; 2731 PlyNumberStack = new Array(); 2732 PredecessorsVars = new Array(); 2733 startVar(false); 2734} 2735 2736function startVar(isContinuation) { 2737 if (CurrentVar >= 0) { 2738 CurrentVarStack.push(CurrentVar); 2739 PlyNumberStack.push(PlyNumber); 2740 } 2741 CurrentVar = numberOfVars++; 2742 PredecessorsVars[CurrentVar] = CurrentVarStack.slice(0); 2743 PredecessorsVars[CurrentVar].push(CurrentVar); 2744 MovesVar[CurrentVar] = new Array(); 2745 MoveCommentsVar[CurrentVar] = new Array(); 2746 if (!isContinuation) { 2747 if (lastVarWithNoMoves[lastVarWithNoMoves.length - 1]) { 2748 myAlert("warning: malformed PGN data in game " + (currentGame+1) + ": variation " + CurrentVar + " starting before parent", true); 2749 } else { 2750 PlyNumber -= 1; 2751 } 2752 } 2753 lastVarWithNoMoves.push(true); 2754 MoveCommentsVar[CurrentVar][StartPly + PlyNumber] = ""; 2755 StartPlyVar[CurrentVar] = StartPly + PlyNumber; 2756} 2757 2758function closeVar() { 2759 if (StartPly + PlyNumber === StartPlyVar[CurrentVar]) { 2760 myAlert("warning: empty variation " + CurrentVar + " in game " + (currentGame+1), false); 2761 } else { 2762 GameHasVariations = true; 2763 } 2764 lastVarWithNoMoves.pop(); 2765 PlyNumberVar[CurrentVar] = StartPly + PlyNumber - StartPlyVar[CurrentVar]; 2766 for (var ii=StartPlyVar[CurrentVar]; ii<=StartPlyVar[CurrentVar]+PlyNumberVar[CurrentVar]; ii++) { 2767 if (MoveCommentsVar[CurrentVar][ii]) { 2768 MoveCommentsVar[CurrentVar][ii] = MoveCommentsVar[CurrentVar][ii].replace(/\s+/g, ' '); 2769 MoveCommentsVar[CurrentVar][ii] = translateNAGs(MoveCommentsVar[CurrentVar][ii]); 2770 MoveCommentsVar[CurrentVar][ii] = MoveCommentsVar[CurrentVar][ii].replace(/\s+$/g, ''); 2771 } else { 2772 MoveCommentsVar[CurrentVar][ii] = ''; 2773 } 2774 } 2775 if (CurrentVarStack.length) { 2776 CurrentVar = CurrentVarStack.pop(); 2777 PlyNumber = PlyNumberStack.pop(); 2778 } else { 2779 myAlert("error: closeVar error" + " in game " + (currentGame+1), true); 2780 } 2781} 2782 2783function childrenVars(thisPly, thisVar) { 2784 if (typeof(thisVar) == "undefined") { thisVar = CurrentVar; } 2785 if (typeof(thisPly) == "undefined") { thisPly = CurrentPly; } 2786 var children = new Array(); 2787 for (var ii = thisVar; ii < numberOfVars; ii++) { 2788 if ((ii === thisVar && StartPlyVar[ii] + PlyNumberVar[ii] > thisPly) || (realParentVar(ii) === thisVar && StartPlyVar[ii] === thisPly && PlyNumberVar[ii] > 0)) { 2789 children.push(ii); 2790 } 2791 } 2792 return children; 2793} 2794 2795function realParentVar(childVar) { 2796 for (var ii = PredecessorsVars[childVar].length - 1; ii > 0; ii--) { 2797 if (StartPlyVar[PredecessorsVars[childVar][ii]] !== StartPlyVar[PredecessorsVars[childVar][ii-1]]) { 2798 return PredecessorsVars[childVar][ii-1]; 2799 } 2800 } 2801 return PredecessorsVars[childVar][ii]; 2802} 2803 2804function goToNextVariationSibling() { 2805 if (CurrentPly === StartPly) { return false; } 2806 var siblings = childrenVars(CurrentPly - 1, HistVar[CurrentPly - 1]); 2807 if (siblings.length < 2) { return false; } 2808 for (var ii = 0; ii < siblings.length; ii++) { 2809 if (siblings[ii] === CurrentVar) { break; } 2810 } 2811 if (siblings[ii] !== CurrentVar) { return false; } 2812 GoToMove(CurrentPly, siblings[(ii + 1) % siblings.length]); 2813 return true; 2814} 2815 2816function goToFirstChild() { 2817 var children = childrenVars(CurrentPly, CurrentVar); 2818 if (children.length < 1) { return false; } 2819 if (children[0] === CurrentVar) { 2820 if (children.length < 2) { return false; } 2821 GoToMove(CurrentPly + 1, children[1]); 2822 } else { 2823 GoToMove(CurrentPly + 1, children[0]); 2824 } 2825 return true; 2826} 2827 2828function ParsePGNGameString(gameString) { 2829 var ii, start, end, move, moveCount, needle, commentStart, commentEnd, isContinuation; 2830 2831 var ssRep, ss = gameString, ssComm; 2832 ss = ss.replace(pgn4webVariationRegExpGlobal, "[%_pgn4web_variation_ $1]"); 2833 // empty variations to comments 2834 while ((ssRep = ss.replace(/\((([\?!+#\s]|\$\d+|{[^}]*})*)\)/g, ' $1 ')) !== ss) { ss = ssRep; } 2835 ss = ss.replace(/^\s/, ''); 2836 ss = ss.replace(/\s$/, ''); 2837 2838 initVar (); 2839 2840 PlyNumber = 0; 2841 2842 for (start=0; start<ss.length; start++) { 2843 2844 switch (ss.charAt(start)) { 2845 2846 case ' ': 2847 case '\b': 2848 case '\f': 2849 case '\n': 2850 case '\r': 2851 case '\t': 2852 break; 2853 2854 case '$': 2855 commentStart = start; 2856 commentEnd = commentStart + 1; 2857 while ('0123456789'.indexOf(ss.charAt(commentEnd)) >= 0) { 2858 commentEnd++; 2859 if (commentEnd >= ss.length) { break; } 2860 } 2861 if (MoveCommentsVar[CurrentVar][StartPly+PlyNumber]) { MoveCommentsVar[CurrentVar][StartPly+PlyNumber] += ' '; } 2862 MoveCommentsVar[CurrentVar][StartPly+PlyNumber] += translateNAGs(ss.substring(commentStart, commentEnd).replace(/(^\s*|\s*$)/, '')); 2863 start = commentEnd - 1; 2864 break; 2865 2866 case '!': 2867 case '?': 2868 commentStart = start; 2869 commentEnd = commentStart + 1; 2870 while ('!?'.indexOf(ss.charAt(commentEnd)) >= 0) { 2871 commentEnd++; 2872 if (commentEnd >= ss.length) { break; } 2873 } 2874 if (MoveCommentsVar[CurrentVar][StartPly+PlyNumber]) { MoveCommentsVar[CurrentVar][StartPly+PlyNumber] += ' '; } 2875 MoveCommentsVar[CurrentVar][StartPly+PlyNumber] += ss.substring(commentStart, commentEnd); 2876 start = commentEnd - 1; 2877 break; 2878 2879 case '{': 2880 commentStart = start+1; 2881 commentEnd = ss.indexOf('}',start+1); 2882 if (commentEnd < 0) { 2883 myAlert('error: missing end comment } in game ' + (currentGame+1), true); 2884 commentEnd = ss.length; 2885 } 2886 if (MoveCommentsVar[CurrentVar][StartPly+PlyNumber]) { MoveCommentsVar[CurrentVar][StartPly+PlyNumber] += ' '; } 2887 ssComm = translateNAGs(ss.substring(commentStart, commentEnd).replace(/(^\s*|\s*$)/, '')); 2888 MoveCommentsVar[CurrentVar][StartPly+PlyNumber] += ssComm; 2889 GameHasComments = GameHasComments || ssComm.replace(/\[%[^\]]*\]\s*/g,'').replace(basicNAGs, '').replace(/^\s+$/,'') !== ''; 2890 start = commentEnd; 2891 break; 2892 2893 case '%': 2894 // % must be first char of the line 2895 if ((start > 0) && (ss.charAt(start-1) != '\n')) { break; } 2896 commentStart = start+1; 2897 commentEnd = ss.indexOf('\n',start+1); 2898 if (commentEnd < 0) { commentEnd = ss.length; } 2899 start = commentEnd; 2900 break; 2901 2902 case ';': 2903 commentStart = start+1; 2904 commentEnd = ss.indexOf('\n',start+1); 2905 if (commentEnd < 0) { commentEnd = ss.length; } 2906 if (MoveCommentsVar[CurrentVar][StartPly+PlyNumber]) { MoveCommentsVar[CurrentVar][StartPly+PlyNumber] += ' '; } 2907 ssComm = translateNAGs(ss.substring(commentStart, commentEnd).replace(/(^\s*|\s*$)/, '')); 2908 MoveCommentsVar[CurrentVar][StartPly+PlyNumber] += ssComm; 2909 GameHasComments = GameHasComments || ssComm.replace(/\[%[^\]]*\]\s*/g,'').replace(basicNAGs, '').replace(/^\s+$/,'') !== ''; 2910 start = commentEnd; 2911 break; 2912 2913 case '(': 2914 if (isContinuation = (ss.charAt(start+1) == '*')) { start += 1; } 2915 MoveCommentsVar[CurrentVar][StartPly+PlyNumber] += ' [%pgn4web_variation ' + numberOfVars + '] '; 2916 startVar(isContinuation); 2917 break; 2918 2919 case ')': 2920 closeVar(); 2921 break; 2922 2923 default: 2924 2925 needle = new Array('1-0', '0-1', '1/2-1/2', '*'); 2926 for (ii=0; ii<needle.length; ii++) { 2927 if (ss.indexOf(needle[ii],start)==start) { 2928 if (CurrentVar === 0) { end = ss.length; } 2929 else { 2930 end = start + needle[ii].length; 2931 if (MoveCommentsVar[CurrentVar][StartPly+PlyNumber]) { MoveCommentsVar[CurrentVar][StartPly+PlyNumber] += ' '; } 2932 MoveCommentsVar[CurrentVar][StartPly+PlyNumber] += needle[ii]; 2933 } 2934 start = end; 2935 break; 2936 } 2937 } 2938 if (start == ss.length) { break; } 2939 2940 moveCount = Math.floor((StartPly+PlyNumber)/2)+1; 2941 needle = moveCount.toString(); 2942 if (ss.indexOf(needle,start)==start) { 2943 start += needle.length; 2944 while (' .\n\r'.indexOf(ss.charAt(start)) != -1) { start++; } 2945 } 2946 2947 if ((end = start + ss.substr(start).search(/[\s${;!?()]/)) < start) { end = ss.length; } 2948 move = ss.substring(start,end); 2949 MovesVar[CurrentVar][StartPly+PlyNumber] = CleanMove(move); 2950 lastVarWithNoMoves[lastVarWithNoMoves.length - 1] = false; 2951 if (ss.charAt(end) == ' ') { start = end; } 2952 else { start = end - 1; } 2953 if (!MovesVar[CurrentVar][StartPly+PlyNumber].match(/^[\s+#]*$/)) { // to cope with malsformed PGN data 2954 PlyNumber++; 2955 MoveCommentsVar[CurrentVar][StartPly+PlyNumber] = ''; 2956 } 2957 break; 2958 } 2959 } 2960 2961 if (CurrentVar !== 0) { 2962 myAlert("error: ParsePGNGameString ends with CurrentVar " + CurrentVar + " in game " + (currentGame+1), true); 2963 while (CurrentVar > 0) { closeVar(); } 2964 } 2965 2966 StartPlyVar[0] = StartPly; 2967 PlyNumberVar[0] = PlyNumber; 2968 2969 GameHasComments = GameHasComments || GameHasVariations; 2970 2971 lastSynchCurrentVar = -1; 2972} 2973 2974var NAGstyle = 'default'; 2975var NAG = new Array(); 2976NAG[0] = ''; 2977NAG[1] = '!'; // 'good move'; 2978NAG[2] = '?'; // 'bad move'; 2979NAG[3] = '!!'; // 'very good move'; 2980NAG[4] = '??'; // 'very bad move'; 2981NAG[5] = '!?'; // 'speculative move'; 2982NAG[6] = '?!'; // 'questionable move'; 2983NAG[7] = 'forced move'; // '[]'; 2984NAG[8] = 'singular move'; // '[]'; 2985NAG[9] = 'worst move'; // '??'; 2986NAG[10] = 'drawish position'; // '='; 2987NAG[11] = 'equal chances, quiet position'; // '='; 2988NAG[12] = 'equal chances, active position'; // '='; 2989NAG[13] = 'unclear position'; // '~~'; 2990NAG[14] = 'White has a slight advantage'; // NAG[15] = '+/='; 2991NAG[16] = 'White has a moderate advantage'; // NAG[17] = '+/-'; 2992NAG[18] = 'White has a decisive advantage'; // NAG[19] = '+-'; 2993NAG[20] = 'White has a crushing advantage'; // NAG[21] = '+-'; 2994NAG[22] = 'White is in zugzwang'; // NAG[23] = '(.)'; 2995NAG[24] = 'White has a slight space advantage'; // NAG[25] = '()'; 2996NAG[26] = 'White has a moderate space advantage'; // NAG[27] = '()'; 2997NAG[28] = 'White has a decisive space advantage'; // NAG[29] = '()'; 2998NAG[30] = 'White has a slight time (development) advantage'; // NAG[31] = '@'; 2999NAG[32] = 'White has a moderate time (development) advantage'; // NAG[33] = '@'; 3000NAG[34] = 'White has a decisive time (development) advantage'; // NAG[35] = '@'; 3001NAG[36] = 'White has the initiative'; // NAG[37] = '|^'; 3002NAG[38] = 'White has a lasting initiative'; // NAG[39] = '|^'; 3003NAG[40] = 'White has the attack'; // NAG[41] = '->'; 3004NAG[42] = 'White has insufficient compensation for material deficit'; 3005NAG[44] = 'White has sufficient compensation for material deficit'; // NAG[45] = '=/~'; 3006NAG[46] = 'White has more than adequate compensation for material deficit'; // NAG[47] = '=/~'; 3007NAG[48] = 'White has a slight center control advantage'; // NAG[49] = '[+]'; 3008NAG[50] = 'White has a moderate center control advantage'; // NAG[51] = '[+]'; 3009NAG[52] = 'White has a decisive center control advantage'; // NAG[53] = '[+]'; 3010NAG[54] = 'White has a slight kingside control advantage'; // NAG[55] = '>>'; 3011NAG[56] = 'White has a moderate kingside control advantage'; // NAG[57] = '>>'; 3012NAG[58] = 'White has a decisive kingside control advantage'; // NAG[59] = '>>'; 3013NAG[60] = 'White has a slight queenside control advantage'; // NAG[61] = '<<'; 3014NAG[62] = 'White has a moderate queenside control advantage'; // NAG[63] = '<<'; 3015NAG[64] = 'White has a decisive queenside control advantage'; // NAG[65] = '<<'; 3016NAG[66] = 'White has a vulnerable first rank'; 3017NAG[68] = 'White has a well protected first rank'; 3018NAG[70] = 'White has a poorly protected king'; 3019NAG[72] = 'White has a well protected king'; 3020NAG[74] = 'White has a poorly placed king'; 3021NAG[76] = 'White has a well placed king'; 3022NAG[78] = 'White has a very weak pawn structure'; 3023NAG[80] = 'White has a moderately weak pawn structure'; 3024NAG[82] = 'White has a moderately strong pawn structure'; 3025NAG[84] = 'White has a very strong pawn structure'; 3026NAG[86] = 'White has poor knight placement'; 3027NAG[88] = 'White has good knight placement'; 3028NAG[90] = 'White has poor bishop placement'; 3029NAG[92] = 'White has good bishop placement'; 3030NAG[94] = 'White has poor rook placement'; 3031NAG[96] = 'White has good rook placement'; 3032NAG[98] = 'White has poor queen placement'; 3033NAG[100] = 'White has good queen placement'; 3034NAG[102] = 'White has poor piece coordination'; 3035NAG[104] = 'White has good piece coordination'; 3036NAG[106] = 'White has played the opening very poorly'; 3037NAG[108] = 'White has played the opening poorly'; 3038NAG[110] = 'White has played the opening well'; 3039NAG[112] = 'White has played the opening very well'; 3040NAG[114] = 'White has played the middlegame very poorly'; 3041NAG[116] = 'White has played the middlegame poorly'; 3042NAG[118] = 'White has played the middlegame well'; 3043NAG[120] = 'White has played the middlegame very well'; 3044NAG[122] = 'White has played the ending very poorly'; 3045NAG[124] = 'White has played the ending poorly'; 3046NAG[126] = 'White has played the ending well'; 3047NAG[128] = 'White has played the ending very well'; 3048NAG[130] = 'White has slight counterplay'; // NAG[131] = '<=>'; 3049NAG[132] = 'White has moderate counterplay'; // NAG[133] = '<=>'; 3050NAG[134] = 'White has decisive counterplay'; // NAG[135] = '<=>'; 3051NAG[136] = 'White has moderate time control pressure'; // NAG[137] = '(+)'; 3052NAG[138] = 'White has severe time control pressure'; // NAG[139] = '(+)'; 3053 3054for (i=14; i<139; i+=2) { NAG[i+1] = NAG[i].replace("White", "Black"); } 3055 3056function translateNAGs(comment) { 3057 var matches = comment.match(/\$+[0-9]+/g); 3058 if (matches) { 3059 for (var ii = 0; ii < matches.length; ii++) { 3060 var nag = matches[ii].substr(1); 3061 if (NAG[nag] !== undefined) { 3062 comment = comment.replace(new RegExp("\\$+" + nag + "(?!\\d)"), NAG[nag]); 3063 } 3064 } 3065 } 3066 return comment; 3067} 3068 3069function ParseMove(move, plyCount) { 3070 var ii, ll; 3071 var rem; 3072 var toRowMarker = -1; 3073 3074 castleRook = -1; 3075 mvIsCastling = 0; 3076 mvIsPromotion = 0; 3077 mvCapture = 0; 3078 mvFromCol = -1; 3079 mvFromRow = -1; 3080 mvToCol = -1; 3081 mvToRow = -1; 3082 mvPiece = -1; 3083 mvPieceId = -1; 3084 mvPieceOnTo = -1; 3085 mvCaptured = -1; 3086 mvCapturedId = -1; 3087 mvIsNull = 0; 3088 3089 if (typeof(move) == "undefined") { return false; } 3090 3091 HistEnPassant[plyCount+1] = false; 3092 HistEnPassantCol[plyCount+1] = -1; 3093 3094 if (move.indexOf('--') === 0) { 3095 mvIsNull = 1; 3096 CheckLegality('--', plyCount); 3097 return true; 3098 } 3099 3100 // get destination column/row remembering what's left e.g. Rdxc3 exf8=Q# 3101 for (ii = move.length-1; ii > 0; ii--) { 3102 if (!isNaN(move.charAt(ii))) { 3103 mvToCol = move.charCodeAt(ii-1) - 97; 3104 mvToRow = move.charAt(ii) - 1; 3105 rem = move.substring(0, ii-1); 3106 toRowMarker = ii; 3107 break; 3108 } 3109 } 3110 3111 // final square did not make sense: maybe a castle? 3112 if ((mvToCol < 0) || (mvToCol > 7) || (mvToRow < 0) || (mvToRow > 7)) { 3113 // long castling first: looking for o-o will get o-o-o too 3114 if (move.indexOf('O-O-O') === 0) { 3115 mvIsCastling = 1; 3116 mvPiece = 1; 3117 mvPieceId = 0; 3118 mvPieceOnTo = 1; 3119 mvFromCol = 4; 3120 mvToCol = 2; 3121 mvFromRow = 7*MoveColor; 3122 mvToRow = 7*MoveColor; 3123 return CheckLegality('O-O-O', plyCount); 3124 } else if (move.indexOf('O-O') === 0) { 3125 mvIsCastling = 1; 3126 mvPiece = 1; 3127 mvPieceId = 0; 3128 mvPieceOnTo = 1; 3129 mvFromCol = 4; 3130 mvToCol = 6; 3131 mvFromRow = 7*MoveColor; 3132 mvToRow = 7*MoveColor; 3133 return CheckLegality('O-O', plyCount); 3134 } else { return false; } 3135 } 3136 3137 rem = rem.replace(/-/g, ''); 3138 // get piece and origin square: mark captures ('x' is there) 3139 ll = rem.length; 3140 if (ll > 4) { return false; } 3141 mvPiece = -1; // make sure mvPiece is properly assigned later 3142 if (ll === 0) { mvPiece = 6; } 3143 else { 3144 for (ii = 5; ii > 0; ii--) { if (rem.charAt(0) == PiecesArr[ii-1]) { mvPiece = ii; break; } } 3145 if (mvPiece == -1) { if (columnsLetters.toLowerCase().indexOf(rem.charAt(0)) >= 0) { mvPiece = 6; } } 3146 if (mvPiece == -1) { return false; } 3147 if (rem.charAt(ll-1) == 'x') { mvCapture = 1; } 3148 if (isNaN(move.charAt(ll-1-mvCapture))) { 3149 mvFromCol = move.charCodeAt(ll-1-mvCapture) - 97; 3150 if ((mvFromCol < 0) || (mvFromCol > 7)) { mvFromCol = -1; } 3151 } else { 3152 mvFromRow = move.charAt(ll-1-mvCapture) - 1; 3153 if ((mvFromRow < 0) || (mvFromRow > 7)) { mvFromRow = -1; } 3154 else { 3155 mvFromCol = move.charCodeAt(ll-2-mvCapture) - 97; 3156 if ((mvFromCol < 0) || (mvFromCol > 7)) { mvFromCol = -1; } 3157 } 3158 } 3159 3160 if ( (ll > 1) && (!mvCapture) && (mvFromCol == -1) && (mvFromRow == -1) ) { return false; } 3161 if ( (mvPiece == 6) && (!mvCapture) && (mvFromCol == -1) && (mvFromRow == -1) ) { return false; } 3162 } 3163 3164 // "square to" occupied: capture (note en-passant case) 3165 if ((Board[mvToCol][mvToRow] !== 0) || ((mvPiece == 6) && (HistEnPassant[plyCount]) && (mvToCol == HistEnPassantCol[plyCount]) && (mvToRow == 5-3*MoveColor))) { mvCapture = 1; } 3166 3167 mvPieceOnTo = mvPiece; 3168 if (mvPiece == 6) { 3169 // move contains '=' or char after destination row: might be a promotion 3170 ii = move.indexOf('='); 3171 if (ii < 0) { ii = toRowMarker; } 3172 if ((ii > 0) && (ii < move.length-1)) { 3173 var newPiece = move.charAt(ii+1); 3174 if (newPiece == PiecesArr[1]) { mvPieceOnTo = 2; } 3175 else if (newPiece == PiecesArr[2]) { mvPieceOnTo = 3; } 3176 else if (newPiece == PiecesArr[3]) { mvPieceOnTo = 4; } 3177 else if (newPiece == PiecesArr[4]) { mvPieceOnTo = 5; } 3178 if (mvPieceOnTo != mvPiece) { mvIsPromotion = 1; } 3179 } 3180 if ((mvToRow == 7 * (1-MoveColor)) ? !mvIsPromotion : mvIsPromotion) { return false; } 3181 } 3182 3183 // which captured piece: if nothing found must be en-passant 3184 if (mvCapture) { 3185 for (mvCapturedId = 15; mvCapturedId >= 0; mvCapturedId--) { 3186 if ((PieceType[1-MoveColor][mvCapturedId] > 0) && (PieceCol[1-MoveColor][mvCapturedId] == mvToCol) && (PieceRow[1-MoveColor][mvCapturedId] == mvToRow)) { 3187 mvCaptured = PieceType[1-MoveColor][mvCapturedId]; 3188 if (mvCaptured == 1) { return false; } 3189 break; 3190 } 3191 } 3192 if ((mvPiece == 6) && (mvCapturedId < 1) && (HistEnPassant[plyCount])) { 3193 for (mvCapturedId = 15; mvCapturedId >= 0; mvCapturedId--) { 3194 if ((PieceType[1-MoveColor][mvCapturedId] == 6) && (PieceCol[1-MoveColor][mvCapturedId] == mvToCol) && (PieceRow[1-MoveColor][mvCapturedId] == 4-MoveColor)) { 3195 mvCaptured = PieceType[1-MoveColor][mvCapturedId]; 3196 break; 3197 } 3198 } 3199 } 3200 } 3201 3202 // check move legality 3203 if (!CheckLegality(PiecesArr[mvPiece-1], plyCount)) { return false; } 3204 3205 // pawn moved: check en-passant possibility 3206 if (mvPiece == 6) { 3207 if (Math.abs(HistRow[0][plyCount]-mvToRow) == 2) { 3208 HistEnPassant[plyCount+1] = true; 3209 HistEnPassantCol[plyCount+1] = mvToCol; 3210 } 3211 } 3212 return true; 3213} 3214 3215function SetGameSelectorOptions(head, num, chEvent, chSite, chRound, chWhite, chBlack, chResult, chDate) { 3216 if (typeof(head) == "string") { gameSelectorHead = head; } 3217 gameSelectorNum = (num === true); 3218 gameSelectorChEvent = Math.max(Math.min(chEvent , 32) || 0, 0) || 0; 3219 gameSelectorChSite = Math.max(Math.min(chSite , 32) || 0, 0) || 0; 3220 gameSelectorChRound = Math.max(Math.min(chRound , 32) || 0, 0) || 0; 3221 gameSelectorChWhite = Math.max(Math.min(chWhite , 32) || 0, 0) || 0; 3222 gameSelectorChBlack = Math.max(Math.min(chBlack , 32) || 0, 0) || 0; 3223 gameSelectorChResult = Math.max(Math.min(chResult, 32) || 0, 0) || 0; 3224 gameSelectorChDate = Math.max(Math.min(chDate , 32) || 0, 0) || 0; 3225} 3226 3227var clickedSquareInterval = null; 3228function clickedSquare(ii, jj) { 3229 if (clickedSquareInterval) { return; } // dont trigger twice 3230 var squareId = 'tcol' + jj + 'trow' + ii; 3231 var theObj = document.getElementById(squareId); 3232 if (theObj) { 3233 var oldClass = theObj.className; 3234 theObj.className = (ii+jj)%2 === 0 ? "blackSquare" : "whiteSquare"; 3235 clickedSquareInterval = setTimeout("reset_after_click(" + ii + "," + jj + ",'" + oldClass + "','" + theObj.className + "')", 66); 3236 clearSelectedText(); 3237 } 3238} 3239 3240function reset_after_click (ii, jj, oldClass, newClass) { 3241 var theObj = document.getElementById('tcol' + jj + 'trow' + ii); 3242 if (theObj) { 3243 // square class changed again by pgn4web already: dont touch it anymore e.g. autoplay 3244 if (theObj.className == newClass) { theObj.className = oldClass; } 3245 clickedSquareInterval = null; 3246 } 3247} 3248 3249 3250var lastSearchPgnExpression = ""; 3251function gameNumberSearchPgn(searchExpression, backward, includeCurrent) { 3252 lastSearchPgnExpression = searchExpression; 3253 if (searchExpression === "") { return false; } 3254 // replace newline with spaces so that we can use regexp "." on whole game 3255 var newlinesRegExp = new RegExp("[\n\r]", "gm"); 3256 var searchExpressionRegExp = new RegExp(searchExpression, "im"); 3257 // at start currentGame might still be -1 3258 var thisCurrentGame = (currentGame < 0) || (currentGame >= numberOfGames) ? 0 : currentGame; 3259 var needle = fullPgnGame(thisCurrentGame); 3260 if (includeCurrent && needle.replace(newlinesRegExp, " ").match(searchExpressionRegExp)) { 3261 return thisCurrentGame; 3262 } 3263 var delta = backward ? -1 : +1; 3264 for (var thisGame = (thisCurrentGame + delta + numberOfGames) % numberOfGames; thisGame != thisCurrentGame; thisGame = (thisGame + delta + numberOfGames) % numberOfGames) { 3265 needle = fullPgnGame(thisGame); 3266 if (needle.replace(newlinesRegExp, " ").match(searchExpressionRegExp)) { 3267 return thisGame; 3268 } 3269 } 3270 return false; 3271} 3272 3273function searchPgnGame(searchExpression, backward) { 3274 if (typeof(searchExpression) == "undefined") { searchExpression = ""; } 3275 lastSearchPgnExpression = searchExpression; 3276 var theObj = document.getElementById('searchPgnExpression'); 3277 if (theObj) { theObj.value = searchExpression; } 3278 if ((searchExpression === "") || (numberOfGames < 2)) { return; } 3279 var thisGame = gameNumberSearchPgn(searchExpression, backward, false); 3280 if ((thisGame !== false) && (thisGame != currentGame)) { Init(thisGame); } 3281} 3282 3283function searchPgnGamePrompt() { 3284 if (numberOfGames < 2) { 3285 alert("info: search prompt disabled with less than 2 games"); 3286 return; 3287 } 3288 var searchExpression = prompt("Please enter search pattern for PGN games:", lastSearchPgnExpression); 3289 if (searchExpression) { searchPgnGame(searchExpression); } 3290} 3291 3292function searchPgnGameForm() { 3293 var theObj = document.getElementById('searchPgnExpression'); 3294 if (theObj) { searchPgnGame(document.getElementById('searchPgnExpression').value); } 3295} 3296 3297var chessMovesRegExp = new RegExp("\\b((\\d+(\\.{1,3}|\\s)\\s*)?((([KQRBN][a-h1-8]?)|[a-h])?x?[a-h][1-8](=[QRNB])?|O-O-O|O-O)\\b[!?+#]*)", "g"); 3298function fixCommentForDisplay(comment) { 3299 return comment.replace(chessMovesRegExp, '<SPAN CLASS="commentMove">$1</SPAN>'); 3300} 3301 3302var tableSize = 0; 3303var textSelectOptions = ''; 3304function PrintHTML() { 3305 var ii, jj, text, theObj, squareId, imageId, squareCoord, squareTitle, numText, textSO; 3306 3307 // chessboard 3308 3309 if (theObj = document.getElementById("GameBoard")) { 3310 text = '<TABLE CLASS="boardTable" ID="boardTable" CELLSPACING=0 CELLPADDING=0'; 3311 text += (tableSize > 0) ? ' STYLE="width: ' + tableSize + 'px; height: ' + tableSize + 'px;">' : '>'; 3312 for (ii = 0; ii < 8; ++ii) { 3313 text += '<TR>'; 3314 for (jj = 0; jj < 8; ++jj) { 3315 squareId = 'tcol' + jj + 'trow' + ii; 3316 imageId = 'img_' + squareId; 3317 text += (ii+jj)%2 === 0 ? '<TD CLASS="whiteSquare" ID="' + squareId + '" BGCOLOR="#FFFFFF"' : '<TD CLASS="blackSquare" ID="' + squareId + '" BGCOLOR="#D3D3D3"'; 3318 text += ' ALIGN="center" VALIGN="middle" ONCLICK="clickedSquare(' + ii + ',' + jj + ')">'; 3319 squareCoord = IsRotated ? String.fromCharCode(72-jj,49+ii) : String.fromCharCode(jj+65,56-ii); 3320 squareTitle = squareCoord; 3321 if (boardTitle[jj][ii] !== '') { squareTitle += ': ' + boardTitle[jj][ii]; } 3322 text += '<IMG SRC="'+ ClearImg.src + '" CLASS="pieceImage" STYLE="border: none; display: block; vertical-align: middle;" ONCLICK="boardOnClick[' + jj + '][' + ii + '](this, event);" ID="' + imageId + '" TITLE="' + squareTitle + '" ' + 'ONFOCUS="this.blur()" /></TD>'; 3323 } 3324 text += '</TR>'; 3325 } 3326 text += '</TABLE>'; 3327 3328 theObj.innerHTML = text; 3329 } 3330 3331 if (theObj = document.getElementById("boardTable")) { 3332 tableSize = theObj.offsetWidth; 3333 if (tableSize > 0) { // coping with browser always returning 0 to offsetWidth 3334 theObj.style.height = tableSize + "px"; 3335 } 3336 } 3337 3338 // control buttons 3339 3340 if (theObj = document.getElementById("GameButtons")) { 3341 var numButtons = 5; 3342 var spaceSize = 3; 3343 var buttonSize = (tableSize - spaceSize*(numButtons - 1)) / numButtons; 3344 text = '<FORM NAME="GameButtonsForm" STYLE="display:inline;"><TABLE BORDER="0" CELLPADDING="0" CELLSPACING="0"><TR><TD><INPUT ID="startButton" TYPE="BUTTON" VALUE="<<" STYLE="'; 3345 if (buttonSize > 0) { text += 'width: ' + buttonSize + 'px;'; } 3346 text += '"; CLASS="buttonControl" TITLE="go to game start" ID="btnGoToStart" onClick="clickedBbtn(this,event);" ONFOCUS="this.blur();"></TD><TD CLASS="buttonControlSpace" WIDTH="' + spaceSize + '"></TD><TD><INPUT ID="backButton" TYPE="BUTTON" VALUE="<" STYLE="'; 3347 if (buttonSize > 0) { text += 'width: ' + buttonSize + 'px;'; } 3348 text += '"; CLASS="buttonControl" TITLE="move backward" ID="btnMoveBackward1" onClick="clickedBbtn(this,event);" ONFOCUS="this.blur();"></TD><TD CLASS="buttonControlSpace" WIDTH="' + spaceSize + '"></TD><TD>'; 3349 text += '<INPUT ID="autoplayButton" TYPE="BUTTON" VALUE=' + (isAutoPlayOn ? "=" : "\u2192") + ' STYLE="'; 3350 if (buttonSize > 0) { text += 'width: ' + buttonSize + 'px;'; } 3351 text += isAutoPlayOn ? '"; CLASS="buttonControlStop" TITLE="toggle autoplay (stop)" ' : '"; CLASS="buttonControlPlay" TITLE="toggle autoplay (start)" '; 3352 text += ' ID="btnPlay" NAME="AutoPlay" onClick="clickedBbtn(this,event);" ONFOCUS="this.blur();"></TD><TD CLASS="buttonControlSpace" WIDTH="' + spaceSize + '"></TD><TD><INPUT ID="forwardButton" TYPE="BUTTON" VALUE=">" STYLE="'; 3353 if (buttonSize > 0) { text += 'width: ' + buttonSize + 'px;'; } 3354 text += '"; CLASS="buttonControl" TITLE="move forward" ID="btnMoveForward1" onClick="clickedBbtn(this,event);" ONFOCUS="this.blur();"></TD><TD CLASS="buttonControlSpace" WIDTH="' + spaceSize + '"></TD><TD><INPUT ID="endButton" TYPE="BUTTON" VALUE=">>" STYLE="'; 3355 if (buttonSize > 0) { text += 'width: ' + buttonSize + 'px;'; } 3356 text += '"; CLASS="buttonControl" TITLE="go to game end" ID="btnGoToEnd" onClick="clickedBbtn(this,event);" ONFOCUS="this.blur();"></TD></TR></TABLE></FORM>'; 3357 3358 theObj.innerHTML = text; 3359 } 3360 3361 // game selector 3362 3363 if (theObj = document.getElementById("GameSelector")) { 3364 if (firstStart) { textSelectOptions=''; } 3365 if (numberOfGames < 2) { 3366 while (theObj.firstChild) { theObj.removeChild(theObj.firstChild); } 3367 textSelectOptions = ''; 3368 } else { 3369 if (textSelectOptions === '') { 3370 if (gameSelectorNum) { gameSelectorNumLenght = Math.floor(Math.log(numberOfGames)/Math.log(10)) + 1; } 3371 text = '<FORM NAME="GameSel" STYLE="display:inline;"><SELECT ID="GameSelSelect" NAME="GameSelSelect" STYLE="'; 3372 if (tableSize > 0) { text += 'width: ' + tableSize + 'px; '; } 3373 text += 'font-family: monospace;" CLASS="selectControl" TITLE="select a game" ONCHANGE="this.blur(); if (this.value >= 0) { Init(this.value); this.value = -1; }" ONFOCUS="disableShortcutKeysAndStoreStatus();" ONBLUR="restoreShortcutKeysStatus();"><OPTION CLASS="optionSelectControl" value=-1>'; 3374 3375 var blanks = ''; for (ii=0; ii<32; ii++) { blanks += ' '; } 3376 var headDisplay = (gameSelectorNum ? blanks.substring(0, gameSelectorNumLenght) + ' ' : '') + gameSelectorHead; 3377 text += headDisplay.replace(/ /g, ' '); 3378 3379 for (ii=0; ii<numberOfGames; ii++) { 3380 textSelectOptions += '<OPTION CLASS="optionSelectControl" value=' + ii + '>'; 3381 textSO = ''; 3382 if (gameSelectorNum) { 3383 numText = ' ' + (ii+1); 3384 textSO += blanks.substr(0, gameSelectorNumLenght - (numText.length - 1)) + numText + ' '; 3385 } 3386 if (gameSelectorChEvent > 0) { 3387 textSO += ' ' + gameEvent[ii].substring(0, gameSelectorChEvent) + blanks.substr(0, gameSelectorChEvent - gameEvent[ii].length) + ' '; 3388 } 3389 if (gameSelectorChSite > 0) { 3390 textSO += ' ' + gameSite[ii].substring(0, gameSelectorChSite) + blanks.substr(0, gameSelectorChSite - gameSite[ii].length) + ' '; 3391 } 3392 if (gameSelectorChRound > 0) { 3393 textSO += ' ' + blanks.substr(0, gameSelectorChRound - gameRound[ii].length) + gameRound[ii].substring(0, gameSelectorChRound) + ' '; 3394 } 3395 if (gameSelectorChWhite > 0) { 3396 textSO += ' ' + gameWhite[ii].substring(0, gameSelectorChWhite) + blanks.substr(0, gameSelectorChWhite - gameWhite[ii].length) + ' '; 3397 } 3398 if (gameSelectorChBlack > 0) { 3399 textSO += ' ' + gameBlack[ii].substring(0, gameSelectorChBlack) + blanks.substr(0, gameSelectorChBlack - gameBlack[ii].length) + ' '; 3400 } 3401 if (gameSelectorChResult > 0) { 3402 textSO += ' ' + gameResult[ii].substring(0, gameSelectorChResult) + blanks.substr(0, gameSelectorChResult - gameResult[ii].length) + ' '; 3403 } 3404 if (gameSelectorChDate > 0) { 3405 textSO += ' ' + gameDate[ii].substring(0, gameSelectorChDate) + blanks.substr(0, gameSelectorChDate - gameDate[ii].length) + ' '; 3406 } 3407 textSelectOptions += textSO.replace(/ /g, ' '); 3408 } 3409 text += textSelectOptions.replace(/&(amp|lt|gt);/g, '&$1;') + '</SELECT></FORM>'; // see function simpleHtmlentities() 3410 theObj.innerHTML = text; 3411 } 3412 } 3413 } 3414 3415 // game event 3416 3417 if (theObj = document.getElementById("GameEvent")) { theObj.innerHTML = gameEvent[currentGame]; } 3418 3419 // game round 3420 3421 if (theObj = document.getElementById("GameRound")) { theObj.innerHTML = gameRound[currentGame]; } 3422 3423 // game site 3424 3425 if (theObj = document.getElementById("GameSite")) { theObj.innerHTML = gameSite[currentGame]; } 3426 3427 // game date 3428 3429 if (theObj = document.getElementById("GameDate")) { 3430 theObj.innerHTML = gameDate[currentGame]; 3431 theObj.style.whiteSpace = "nowrap"; 3432 } 3433 3434 // game white 3435 3436 if (theObj = document.getElementById("GameWhite")) { theObj.innerHTML = gameWhite[currentGame]; } 3437 3438 // game black 3439 3440 if (theObj = document.getElementById("GameBlack")) { theObj.innerHTML = gameBlack[currentGame]; } 3441 3442 // game result 3443 3444 if (theObj = document.getElementById("GameResult")) { 3445 theObj.innerHTML = gameResult[currentGame]; 3446 theObj.style.whiteSpace = "nowrap"; 3447 } 3448 3449 // game text 3450 3451 if (theObj = document.getElementById("GameText")) { 3452 variationTextDepth = -1; 3453 text = '<SPAN ID="ShowPgnText">' + variationTextFromId(0); + '</SPAN>'; 3454 theObj.innerHTML = text; 3455 } 3456 3457 setB1C1F1G1boardShortcuts(); // depend on presence of comments 3458 3459 // game searchbox 3460 3461 if ((theObj = document.getElementById("GameSearch")) && firstStart) { 3462 if (numberOfGames < 2) { 3463 while (theObj.firstChild) { theObj.removeChild(theObj.firstChild); } 3464 } else { 3465 text = '<FORM ID="searchPgnForm" STYLE="display: inline;" ACTION="javascript:searchPgnGameForm();"><INPUT ID="searchPgnButton" CLASS="searchPgnButton" STYLE="display: inline; '; 3466 if (tableSize > 0) { text += 'width: ' + (tableSize/4) + 'px; '; } 3467 text += '" TITLE="find games matching the search string (regular expression)" TYPE="submit" VALUE="?"><INPUT ID="searchPgnExpression" CLASS="searchPgnExpression" TITLE="find games matching the search string (regular expression)" TYPE="input" VALUE="" STYLE="display: inline; box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box;'; 3468 if (tableSize > 0) { text += 'width: ' + (3*tableSize/4) + 'px; '; } 3469 text += '" ONFOCUS="disableShortcutKeysAndStoreStatus();" ONBLUR="restoreShortcutKeysStatus();"></FORM>'; 3470 theObj.innerHTML = text; 3471 theObj = document.getElementById('searchPgnExpression'); 3472 if (theObj) { theObj.value = lastSearchPgnExpression; } 3473 } 3474 } 3475} 3476 3477function startButton(e) { 3478 if (e.shiftKey) { 3479 GoToMove(StartPlyVar[CurrentVar] + (CurrentPly <= StartPlyVar[CurrentVar] + 1 ? 0 : 1)); 3480 } else { GoToMove(StartPlyVar[0], 0); } 3481} 3482 3483function backButton(e) { 3484 if (e.shiftKey) { GoToMove(StartPlyVar[CurrentVar]); } 3485 else { GoToMove(CurrentPly - 1); } 3486} 3487 3488function forwardButton(e) { 3489 if (e.shiftKey) { if (!goToNextVariationSibling()) { GoToMove(CurrentPly + 1); } } 3490 else { GoToMove(CurrentPly + 1); } 3491} 3492 3493function endButton(e) { 3494 if (e.shiftKey) { 3495 if (CurrentPly === StartPlyVar[CurrentVar] + PlyNumberVar[CurrentVar]) { goToFirstChild(); } 3496 else { GoToMove(StartPlyVar[CurrentVar] + PlyNumberVar[CurrentVar]); } 3497 } else { GoToMove(StartPlyVar[0] + PlyNumberVar[0], 0); } 3498} 3499 3500function clickedBbtn(t,e) { 3501 switch (t.id) { 3502 case "startButton": 3503 startButton(e); 3504 break; 3505 case "backButton": 3506 backButton(e); 3507 break; 3508 case "autoplayButton": 3509 if (e.shiftKey) { goToNextVariationSibling(); } 3510 else { SwitchAutoPlay(); } 3511 break; 3512 case "forwardButton": 3513 forwardButton(e); 3514 break; 3515 case "endButton": 3516 endButton(e); 3517 break; 3518 default: 3519 break; 3520 } 3521} 3522 3523var basicNAGs = /^[\?!+#\s]+(\s|$)/; 3524function strippedMoveComment(plyNum, varId, addHtmlTags) { 3525 if (typeof(addHtmlTags) == "undefined") { 3526 addHtmlTags = false; 3527 if (typeof(varId) == "undefined") { varId = CurrentVar; } 3528 } 3529 if (!MoveCommentsVar[varId][plyNum]) { return ""; } 3530 return fixCommentForDisplay(MoveCommentsVar[varId][plyNum]).replace(pgn4webVariationRegExpGlobal, function (m) { return variationTextFromTag(m, addHtmlTags); }).replace(/\[%[^\]]*\]\s*/g,'').replace(plyNum === StartPlyVar[varId] ? '' : basicNAGs, '').replace(/^\s+$/,''); 3531} 3532 3533function basicNAGsMoveComment(plyNum, varId) { 3534 if (typeof(varId) == "undefined") { varId = CurrentVar; } 3535 if (!MoveCommentsVar[varId][plyNum]) { return ""; } 3536 var thisBasicNAGs = MoveCommentsVar[varId][plyNum].replace(/\[%[^\]]*\]\s*/g,'').match(basicNAGs, ''); 3537 return thisBasicNAGs ? thisBasicNAGs[0].replace(/\s+(?!class=)/gi,'') : ''; 3538} 3539 3540function variationTextFromTag(variationTag, addHtmlTags) { 3541 if (typeof(addHtmlTags) == "undefined") { addHtmlTags = false; } 3542 var varId = variationTag.replace(pgn4webVariationRegExp, "$1"); 3543 if (isNaN(varId)) { 3544 myAlert("error: issue parsing variation tag " + variationTag + " in game " + (currentGame+1), true); 3545 return ""; 3546 } 3547 var text = variationTextFromId(varId); 3548 if (text) { 3549 if (addHtmlTags) { text = '</SPAN>' + text + '<SPAN CLASS="comment">'; } 3550 } else { text = ''; } 3551 return text; 3552} 3553 3554var variationTextDepth, printedComment, printedVariation; 3555function variationTextFromId(varId) { 3556 var punctChars = ",.;:!?", thisComment; 3557 3558 if (isNaN(varId) || varId < 0 || varId >= numberOfVars || typeof(StartPlyVar[varId]) == "undefined" || typeof(PlyNumberVar[varId]) == "undefined") { 3559 myAlert("error: issue parsing variation id " + varId + " in game " + (currentGame+1), true); 3560 return ""; 3561 } 3562 var text = ++variationTextDepth ? ('<SPAN CLASS="variation">' + (printedVariation ? ' ' : '') + (variationTextDepth > 1 ? '(' : '[')) + '</SPAN>' : ''; 3563 printedVariation = false; 3564 for (var ii = StartPlyVar[varId]; ii < StartPlyVar[varId] + PlyNumberVar[varId]; ii++) { 3565 printedComment = false; 3566 if (commentsIntoMoveText && (thisComment = strippedMoveComment(ii, varId, true))) { 3567 if (commentsOnSeparateLines && variationTextDepth === 0 && ii > StartPlyVar[varId]) { 3568 text += '<DIV CLASS="comment" STYLE="line-height: 33%;"> </DIV>'; 3569 } 3570 if (printedVariation) { if (punctChars.indexOf(thisComment.charAt(0)) == -1) { text += '<SPAN CLASS="variation"> </SPAN>'; } } 3571 else { printedVariation = variationTextDepth > 0; } 3572 text += '<SPAN CLASS="comment">' + thisComment + '</SPAN>'; 3573 if (commentsOnSeparateLines && variationTextDepth === 0) { 3574 text += '<DIV CLASS="comment" STYLE="line-height: 33%;"> </DIV>'; 3575 } 3576 printedComment = true; 3577 } 3578 if (printedComment || printedVariation) { text += '<SPAN CLASS="variation"> </SPAN>'; } 3579 printedVariation = true; 3580 3581 text += printMoveText(ii, varId, (variationTextDepth > 0), ((printedComment) || (ii == StartPlyVar[varId])), true); 3582 } 3583 if (commentsIntoMoveText && (thisComment = strippedMoveComment(StartPlyVar[varId] + PlyNumberVar[varId], varId, true))) { 3584 if (commentsOnSeparateLines && variationTextDepth === 0) { 3585 text += '<DIV CLASS="comment" STYLE="line-height: 33%;"> </DIV>'; 3586 } 3587 if (printedVariation && (punctChars.indexOf(thisComment.charAt(0)) == -1)) { text += '<SPAN CLASS="comment notranslate"> </SPAN>'; } 3588 text += '<SPAN CLASS="comment">' + thisComment + '</SPAN>'; 3589 printedComment = true; 3590 } 3591 text += variationTextDepth-- ? ('<SPAN CLASS="variation">' + (variationTextDepth ? ')' : ']') + '</SPAN>') : ''; 3592 printedVariation = true; 3593 return text; 3594} 3595 3596function printMoveText(thisPly, thisVar, isVar, hasLeadingNum, hasId) { 3597 if (typeof(thisVar) == "undefined") { thisVar = CurrentVar; } 3598 if (typeof(thisPly) == "undefined") { thisPly = CurrentPly; } 3599 var text = ''; 3600 3601 if (thisVar >= numberOfVars || thisPly < StartPlyVar[thisVar] || thisPly > StartPlyVar[thisVar] + PlyNumberVar[thisVar]) { 3602 return text; 3603 } 3604 3605 var moveCount = Math.floor(thisPly/2)+1; 3606 if (thisPly%2 === 0) { 3607 text += '<SPAN CLASS="' + (isVar ? 'variation' : 'move') + ' notranslate">' + moveCount + '. </SPAN>'; 3608 } else { 3609 if (hasLeadingNum) { 3610 text += '<SPAN CLASS="' + (isVar ? 'variation' : 'move') + ' notranslate">' + moveCount + '... </SPAN>'; 3611 } 3612 } 3613 var jj = thisPly+1; 3614 text += '<A HREF="javascript:void(0);" ONCLICK="GoToMove(' + jj + ', ' + thisVar + ');" CLASS="' + (isVar ? 'variation' : 'move') + ' notranslate" '; 3615 if (hasId) { text += 'ID="Var' + thisVar + 'Mv' + jj + '" '; } 3616 text += 'ONFOCUS="this.blur();">' + MovesVar[thisVar][thisPly]; 3617 if (commentsIntoMoveText) { text += basicNAGsMoveComment(jj, thisVar); } 3618 text += '</A>'; 3619 3620 return text; 3621} 3622 3623 3624// undocumented API to autoscroll gane text to show current move 3625// enable with "enableAutoScrollToCurrentMove(objId)", with objId as the game text container object id 3626// add onresize="autoScrollToCurrentMoveIfEnabled" to the body tag for autoscrolling on page resize 3627 3628function enableAutoScrollToCurrentMove(objId) { autoScrollToCurrentMove_objId = objId; } 3629function disableAutoScrollToCurrentMove() { autoScrollToCurrentMove_objId = ""; } 3630function toggleAutoScrollToCurrentMove(objId) { autoScrollToCurrentMove_objId = autoScrollToCurrentMove_objId ? "" : objId; } 3631 3632var autoScrollToCurrentMove_objId = ""; 3633function autoScrollToCurrentMoveIfEnabled() { autoScrollToCurrentMove(autoScrollToCurrentMove_objId); } 3634 3635function objOffsetVeryTop(obj) { 3636 for (var offset = obj.offsetTop; obj = obj.offsetParent; /* */) { offset += obj.offsetTop + obj.clientTop; } 3637 return offset; 3638} 3639 3640function autoScrollToCurrentMove(objId) { 3641 if (!objId) { return; } 3642 var theContainerObj = document.getElementById(objId); 3643 if (theContainerObj) { 3644 if (CurrentPly == StartPly) { theContainerObj.scrollTop = 0; } 3645 else { 3646 var theMoveObj = document.getElementById('Var' + CurrentVar + 'Mv' + CurrentPly); 3647 if (theMoveObj) { 3648 var theContainerObjOffsetVeryTop = objOffsetVeryTop(theContainerObj); 3649 var theMoveObjOffsetVeryTop = objOffsetVeryTop(theMoveObj); 3650 if ((theMoveObjOffsetVeryTop + theMoveObj.offsetHeight > theContainerObjOffsetVeryTop + theContainerObj.scrollTop + theContainerObj.clientHeight) || (theMoveObjOffsetVeryTop < theContainerObjOffsetVeryTop + theContainerObj.scrollTop)) { 3651 theContainerObj.scrollTop = theMoveObjOffsetVeryTop - theContainerObjOffsetVeryTop; 3652 } 3653 } 3654 } 3655 } 3656} 3657 3658 3659function FlipBoard() { 3660 var oldHighlightOption = highlightOption; 3661 if (oldHighlightOption) { SetHighlight(false); } 3662 IsRotated = !IsRotated; 3663 PrintHTML(); 3664 RefreshBoard(); 3665 if (oldHighlightOption) { SetHighlight(true); } 3666} 3667 3668function RefreshBoard() { 3669 for (var jj = 0; jj < 8; ++jj) { 3670 for (var ii = 0; ii < 8; ++ii) { 3671 if (Board[jj][ii] === 0) { SetImage(jj, ii, ClearImg.src); } 3672 } 3673 } 3674 for (jj = 0; jj < 2; ++jj) { 3675 for (ii = 0; ii < 16; ++ii) { 3676 if (PieceType[jj][ii] > 0) { 3677 SetImage(PieceCol[jj][ii], PieceRow[jj][ii], PieceImg[jj][PieceType[jj][ii]].src); 3678 } 3679 } 3680 } 3681} 3682 3683function SetAutoPlay(vv) { 3684 isAutoPlayOn = vv; 3685 3686 if (AutoPlayInterval) { clearTimeout(AutoPlayInterval); AutoPlayInterval = null; } 3687 3688 if (isAutoPlayOn) { 3689 if (document.GameButtonsForm) { 3690 if (document.GameButtonsForm.AutoPlay) { 3691 document.GameButtonsForm.AutoPlay.value = "||"; 3692 document.GameButtonsForm.AutoPlay.title = "toggle autoplay (stop)"; 3693 document.GameButtonsForm.AutoPlay.className = "buttonControlStop"; 3694 } 3695 } 3696 if (CurrentPly < StartPlyVar[CurrentVar] + PlyNumberVar[CurrentVar]) { AutoPlayInterval=setTimeout("MoveForward(1)", Delay); } 3697 else { 3698 if (autoplayNextGame && (CurrentVar === 0)) { AutoPlayInterval=setTimeout("AutoplayNextGame()", Delay); } 3699 else { SetAutoPlay(false); } 3700 } 3701 } else { 3702 if (document.GameButtonsForm) { 3703 if (document.GameButtonsForm.AutoPlay) { 3704 document.GameButtonsForm.AutoPlay.value = "\u2192"; 3705 document.GameButtonsForm.AutoPlay.title = "toggle autoplay (start)"; 3706 document.GameButtonsForm.AutoPlay.className = "buttonControlPlay"; 3707 } 3708 } 3709 } 3710} 3711 3712 3713var minAutoplayDelay = 500; 3714var maxAutoplayDelay = 300000; 3715function setCustomAutoplayDelay() { 3716 var newDelaySec = prompt("Enter custom autoplay delay, in seconds, between " + (minAutoplayDelay/1000) + " and " + (maxAutoplayDelay/1000) + ":", Math.floor(Delay / 100) / 10); 3717 if (!isNaN(newDelaySec = parseInt(newDelaySec, 10))) { 3718 SetAutoplayDelayAndStart(newDelaySec * 1000); 3719 } 3720} 3721 3722function SetAutoplayDelay(vv) { 3723 if (isNaN(vv = parseInt(vv, 10))) { return; } 3724 Delay = Math.min(Math.max(vv, minAutoplayDelay), maxAutoplayDelay); 3725} 3726 3727function SetAutoplayDelayAndStart(vv) { 3728 MoveForward(1); 3729 SetAutoplayDelay(vv); 3730 SetAutoPlay(true); 3731} 3732 3733function SetLiveBroadcast(delay, alertFlag, demoFlag, stepFlag, endlessFlag) { 3734 LiveBroadcastDelay = delay; // zero delay: no live broadcast 3735 LiveBroadcastAlert = (alertFlag === true); 3736 LiveBroadcastDemo = (demoFlag === true); 3737 LiveBroadcastSteppingMode = (stepFlag === true); 3738 LiveBroadcastEndlessMode = (endlessFlag === true); 3739 setG7A6B6H7boardShortcuts(); 3740} 3741 3742function SetImage(col, row, image) { 3743 var trow = IsRotated ? row : 7 - row; 3744 var tcol = IsRotated ? 7 - col : col; 3745 var theObj = document.getElementById('img_' + 'tcol' + tcol + 'trow' + trow); 3746 if ((theObj) && (theObj.src != image)) { theObj.src = image; } 3747} 3748 3749function SetImagePath(path) { 3750 ImagePath = path; 3751} 3752 3753function SwitchAutoPlay() { 3754 if (!isAutoPlayOn) { MoveForward(1); } 3755 SetAutoPlay(!isAutoPlayOn); 3756} 3757 3758function StoreMove(thisPly) { 3759 3760 HistVar[thisPly+1] = CurrentVar; 3761 3762 if (HistNull[thisPly] = mvIsNull) { return; } 3763 3764 // "square from" history 3765 HistPieceId[0][thisPly] = mvPieceId; 3766 HistCol[0][thisPly] = PieceCol[MoveColor][mvPieceId]; 3767 HistRow[0][thisPly] = PieceRow[MoveColor][mvPieceId]; 3768 HistType[0][thisPly] = PieceType[MoveColor][mvPieceId]; 3769 3770 // "square to" history 3771 HistCol[2][thisPly] = mvToCol; 3772 HistRow[2][thisPly] = mvToRow; 3773 3774 if (mvIsCastling) { 3775 HistPieceId[1][thisPly] = castleRook; 3776 HistCol[1][thisPly] = PieceCol[MoveColor][castleRook]; 3777 HistRow[1][thisPly] = PieceRow[MoveColor][castleRook]; 3778 HistType[1][thisPly] = PieceType[MoveColor][castleRook]; 3779 } else if (mvCapturedId >= 0) { 3780 HistPieceId[1][thisPly] = mvCapturedId+16; 3781 HistCol[1][thisPly] = PieceCol[1-MoveColor][mvCapturedId]; 3782 HistRow[1][thisPly] = PieceRow[1-MoveColor][mvCapturedId]; 3783 HistType[1][thisPly] = PieceType[1-MoveColor][mvCapturedId]; 3784 } else { 3785 HistPieceId[1][thisPly] = -1; 3786 } 3787 3788 // update "square from" and captured square (not "square to" for en-passant) 3789 Board[PieceCol[MoveColor][mvPieceId]][PieceRow[MoveColor][mvPieceId]] = 0; 3790 3791 // mark captured piece 3792 if (mvCapturedId >= 0) { 3793 PieceType[1-MoveColor][mvCapturedId] = -1; 3794 PieceMoveCounter[1-MoveColor][mvCapturedId]++; 3795 Board[PieceCol[1-MoveColor][mvCapturedId]][PieceRow[1-MoveColor][mvCapturedId]] = 0; 3796 } 3797 3798 // update piece arrays: promotion changes piece type 3799 PieceType[MoveColor][mvPieceId] = mvPieceOnTo; 3800 PieceMoveCounter[MoveColor][mvPieceId]++; 3801 PieceCol[MoveColor][mvPieceId] = mvToCol; 3802 PieceRow[MoveColor][mvPieceId] = mvToRow; 3803 if (mvIsCastling) { 3804 PieceMoveCounter[MoveColor][castleRook]++; 3805 PieceCol[MoveColor][castleRook] = mvToCol == 2 ? 3 : 5; 3806 PieceRow[MoveColor][castleRook] = mvToRow; 3807 } 3808 3809 // update board 3810 Board[mvToCol][mvToRow] = PieceType[MoveColor][mvPieceId]*(1-2*MoveColor); 3811 if (mvIsCastling) { 3812 Board[PieceCol[MoveColor][castleRook]][PieceRow[MoveColor][castleRook]] = PieceType[MoveColor][castleRook]*(1-2*MoveColor); 3813 } 3814 return; 3815} 3816 3817function UndoMove(thisPly) { 3818 3819 if (HistNull[thisPly]) { return; } 3820 3821 // moved piece back to original square 3822 var chgPiece = HistPieceId[0][thisPly]; 3823 Board[PieceCol[MoveColor][chgPiece]][PieceRow[MoveColor][chgPiece]] = 0; 3824 3825 Board[HistCol[0][thisPly]][HistRow[0][thisPly]] = HistType[0][thisPly] * (1-2*MoveColor); 3826 PieceType[MoveColor][chgPiece] = HistType[0][thisPly]; 3827 PieceCol[MoveColor][chgPiece] = HistCol[0][thisPly]; 3828 PieceRow[MoveColor][chgPiece] = HistRow[0][thisPly]; 3829 PieceMoveCounter[MoveColor][chgPiece]--; 3830 3831 // castling: rook back to original square 3832 chgPiece = HistPieceId[1][thisPly]; 3833 if ((chgPiece >= 0) && (chgPiece < 16)) { 3834 Board[PieceCol[MoveColor][chgPiece]][PieceRow[MoveColor][chgPiece]] = 0; 3835 Board[HistCol[1][thisPly]][HistRow[1][thisPly]] = HistType[1][thisPly] * (1-2*MoveColor); 3836 PieceType[MoveColor][chgPiece] = HistType[1][thisPly]; 3837 PieceCol[MoveColor][chgPiece] = HistCol[1][thisPly]; 3838 PieceRow[MoveColor][chgPiece] = HistRow[1][thisPly]; 3839 PieceMoveCounter[MoveColor][chgPiece]--; 3840 } 3841 3842 // capture: captured piece back to original square 3843 chgPiece -= 16; 3844 if ((chgPiece >= 0) && (chgPiece < 16)) { 3845 Board[PieceCol[1-MoveColor][chgPiece]][PieceRow[1-MoveColor][chgPiece]] = 0; 3846 Board[HistCol[1][thisPly]][HistRow[1][thisPly]] = HistType[1][thisPly] * (2*MoveColor-1); 3847 PieceType[1-MoveColor][chgPiece] = HistType[1][thisPly]; 3848 PieceCol[1-MoveColor][chgPiece] = HistCol[1][thisPly]; 3849 PieceRow[1-MoveColor][chgPiece] = HistRow[1][thisPly]; 3850 PieceMoveCounter[1-MoveColor][chgPiece]--; 3851 } 3852} 3853 3854function Color(nn) { 3855 if (nn < 0) { return 1; } 3856 if (nn > 0) { return 0; } 3857 return 2; 3858} 3859 3860function sign(nn) { 3861 if (nn > 0) { return 1; } 3862 if (nn < 0) { return -1; } 3863 return 0; 3864} 3865 3866function SquareOnBoard(col, row) { 3867 return col >= 0 && col <= 7 && row >= 0 && row <= 7; 3868} 3869 3870var pgn4webMaxTouches = 0; 3871var pgn4webOngoingTouches = new Array(); 3872function pgn4webOngoingTouchIndexById(needle) { 3873 var id; 3874 for (var ii = 0; ii < pgn4webOngoingTouches.length; ii++) { 3875 id = pgn4webOngoingTouches[ii].identifier; 3876 if (pgn4webOngoingTouches[ii].identifier === needle) { return ii; } 3877 } 3878 return -1; 3879} 3880 3881function pgn4web_handleTouchStart(e) { 3882 e.stopPropagation(); 3883 for (var ii = 0; ii < e.changedTouches.length; ii++) { 3884 pgn4webMaxTouches++; 3885 pgn4webOngoingTouches.push({ identifier: e.changedTouches[ii].identifier, clientX: e.changedTouches[ii].clientX, clientY: e.changedTouches[ii].clientY }); 3886 } 3887} 3888 3889function pgn4web_handleTouchMove(e) { 3890 e.stopPropagation(); 3891 e.preventDefault(); 3892} 3893 3894function pgn4web_handleTouchEnd(e) { 3895 e.stopPropagation(); 3896 var jj; 3897 for (var ii = 0; ii < e.changedTouches.length; ii++) { 3898 if ((jj = pgn4webOngoingTouchIndexById(e.changedTouches[ii].identifier)) != -1) { 3899 if (pgn4webOngoingTouches.length == 1) { 3900 customFunctionOnTouch(e.changedTouches[ii].clientX - pgn4webOngoingTouches[jj].clientX, e.changedTouches[ii].clientY - pgn4webOngoingTouches[jj].clientY); 3901 pgn4webMaxTouches = 0; 3902 } 3903 pgn4webOngoingTouches.splice(jj, 1); 3904 } 3905 } 3906 clearSelectedText(); 3907} 3908 3909function pgn4web_handleTouchCancel(e) { 3910 e.stopPropagation(); 3911 var jj; 3912 for (var ii = 0; ii < e.changedTouches.length; ii++) { 3913 if ((jj = pgn4webOngoingTouchIndexById(e.changedTouches[ii].identifier)) != -1) { 3914 pgn4webOngoingTouches.splice(jj, 1); 3915 if (pgn4webOngoingTouches.length === 0) { pgn4webMaxTouches = 0; } 3916 } 3917 } 3918 clearSelectedText(); 3919} 3920 3921function pgn4web_initTouchEvents() { 3922 var theObj = document.getElementById("GameBoard"); 3923 if (theObj && touchEventEnabled) { 3924 simpleAddEvent(theObj, "touchstart", pgn4web_handleTouchStart); 3925 simpleAddEvent(theObj, "touchmove", pgn4web_handleTouchMove); 3926 simpleAddEvent(theObj, "touchend", pgn4web_handleTouchEnd); 3927 simpleAddEvent(theObj, "touchleave", pgn4web_handleTouchEnd); 3928 simpleAddEvent(theObj, "touchcancel", pgn4web_handleTouchCancel); 3929 } 3930} 3931 3932var waitForDoubleLeftTouchTimer = null; 3933function customFunctionOnTouch(deltaX, deltaY) { 3934 if (Math.max(Math.abs(deltaX), Math.abs(deltaY)) < 13) { return; } 3935 if (Math.abs(deltaY) > 1.5 * Math.abs(deltaX)) { // vertical up or down 3936 if (numberOfGames > 1) { 3937 if ((currentGame === 0) && (deltaY < 0)) { Init(numberOfGames - 1); } 3938 else if ((currentGame === numberOfGames - 1) && (deltaY > 0)) { Init(0); } 3939 else { Init(currentGame + sign(deltaY)); } 3940 } 3941 } else if (Math.abs(deltaX) > 1.5 * Math.abs(deltaY)) { 3942 if (deltaX > 0) { // horizontal right 3943 if (isAutoPlayOn) { GoToMove(StartPlyVar[CurrentVar] + PlyNumberVar[CurrentVar]); } 3944 else { SwitchAutoPlay(); } 3945 } else { // horizontal left 3946 if (isAutoPlayOn && !waitForDoubleLeftTouchTimer) { SwitchAutoPlay(); } 3947 else { 3948 if (waitForDoubleLeftTouchTimer) { 3949 clearTimeout(waitForDoubleLeftTouchTimer); 3950 waitForDoubleLeftTouchTimer = null; 3951 } 3952 if ((LiveBroadcastDelay > 0) && (CurrentVar === 0) && (CurrentPly === StartPly + PlyNumber)) { 3953 waitForDoubleLeftTouchTimer = setTimeout("waitForDoubleLeftTouchTimer = null;", 900); 3954 replayPreviousMoves(6); 3955 } else { GoToMove(StartPlyVar[CurrentVar] + (((CurrentPly <= StartPlyVar[CurrentVar] + 1) || (CurrentVar === 0)) ? 0 : 1)); } 3956 } 3957 } 3958 } 3959} 3960 3961var touchGestures_helpActions = [ "chessboard top-down swipe", "chessboard bottom-up swipe", "chessboard left-right swipe", "chessboard right-left swipe" ]; 3962var touchGestures_helpText = [ "load next game, cycling through", "load previous game, cyclig through", "start autoplay; if autoplay already active: go to variation end or to game end", "stop autoplay; if autoplay not active: go to variation start, then to parent variation, then to game start; if at last move of live broadcast: replay up to 6 previous half-moves, then autoplay forward" ]; 3963 3964var touchEventEnabled = true; 3965function SetTouchEventEnabled(onOff) { 3966 touchEventEnabled = onOff; 3967} 3968 3969function clearSelectedText() { 3970 if (window.getSelection) { 3971 if (window.getSelection().empty) { window.getSelection().empty(); } 3972 else if (window.getSelection().removeAllRanges) { window.getSelection().removeAllRanges(); } 3973 } else if (document.selection && document.selection.empty) { document.selection.empty(); } 3974} 3975 3976function simpleHtmlentities(text) { 3977 return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); 3978} 3979 3980function simpleHtmlentitiesDecode(text) { 3981 return text.replace(/>/g, ">").replace(/</g, "<").replace(/&/g, "&"); 3982} 3983 3984