/* * pgn4web javascript chessboard * copyright (C) 2009-2025 Paolo Casaschi * see README file and http://pgn4web.casaschi.net * for credits, license and more details */ "use strict"; var pgn4web_version = '3.08'; var pgn4web_project_url = "http://pgn4web.casaschi.net"; var pgn4web_project_author = "Paolo Casaschi"; var pgn4web_project_email = "pgn4web@casaschi.net"; var helpWin; function displayHelp(section) { if (helpWin && !helpWin.closed) { helpWin.close(); } helpWin = window.open(detectHelpLocation() + (section ? "?" + section : ""), "pgn4web_help", "resizable=yes,scrollbars=yes,toolbar=no,location=no,menubar=no,status=no"); if (helpWin && window.focus) { helpWin.focus(); } } // empty event APIs to be redefined function customFunctionOnPgnTextLoad() { } function customFunctionOnPgnGameLoad() { } function customFunctionOnMove() { } function customFunctionOnAlert(msg) { } function customFunctionOnCheckLiveBroadcastStatus() { } // custom header tags APIs for customFunctionOnPgnGameLoad function customPgnHeaderTag(customTag, htmlElementId, gameNum) { var matches, tag = ""; customTag = customTag.replace(/\W+/g, ""); if (gameNum === undefined) { gameNum = currentGame; } if ((pgnHeader[gameNum]) && (matches = pgnHeader[gameNum].match('\\[\\s*' + customTag + '\\s*\"([^\"]+)\"\\s*\\]'))) { tag = matches[1]; } if (htmlElementId) { var theObj = document.getElementById(htmlElementId); if ((theObj) && (typeof (theObj.innerHTML) == "string")) { theObj.innerHTML = tag; } } return tag; } // custom comment tags API for customFunctionOnMove function customPgnCommentTag(customTag, htmlElementId, plyNum, varId) { var matches, tag = "", theObj; customTag = customTag.replace(/\W+/g, ""); if (typeof (varId) == "undefined") { varId = 0; } if (typeof (plyNum) == "undefined") { plyNum = CurrentPly; } if ((MoveCommentsVar[varId][plyNum]) && (matches = MoveCommentsVar[varId][plyNum].match('\\[%' + customTag + '\\s+((?:,?(?:"[^"]*"|[^,\\]]*))*)\\s*\\]'))) { tag = matches[1].replace(/\s+$/, ""); } if ((htmlElementId) && (theObj = document.getElementById(htmlElementId)) && (typeof (theObj.innerHTML) == "string")) { theObj.innerHTML = tag; } return tag; } function simpleAddEvent(obj, evt, cbk) { if (obj.addEventListener) { obj.addEventListener(evt, cbk, false); } else if (obj.attachEvent) { obj.attachEvent("on" + evt, cbk); } // bugfix: IE8 and below specific } simpleAddEvent(document, "keydown", pgn4web_handleKey_event); simpleAddEvent(window, "load", pgn4web_onload_event); function pgn4web_onload_event(e) { pgn4web_onload(e); } function pgn4web_onload(e) { start_pgn4web(); } function start_pgn4web() { // keep startup logs at the very first run, otherwise reset if (alertFirstResetLoadingPgn) { alertFirstResetLoadingPgn = false; } else { resetAlert(); } InitImages(); createBoard(); pgn4web_initTouchEvents(); } var alertLog; var alertLast; var alertNum; var alertNumSinceReset; var fatalErrorNumSinceReset; var alertPromptInterval = null; var alertPromptOn = false; var alertFirstResetLoadingPgn = true; resetAlert(); function resetAlert() { alertLog = new Array(5); alertLast = alertLog.length - 1; alertNum = alertNumSinceReset = fatalErrorNumSinceReset = 0; stopAlertPrompt(); if (!alertFirstResetLoadingPgn) { if (boardIsDefault(debugShortcutSquare)) { boardShortcut(debugShortcutSquare, "pgn4web v" + pgn4web_version + " debug info", null, true); } } } function myAlert(msg, fatalError, doNotPrompt) { alertNum++; alertNumSinceReset++; if (fatalError) { fatalErrorNumSinceReset++; } alertLast = (alertLast + 1) % alertLog.length; alertLog[alertLast] = msg + "\n" + (new Date()).toLocaleString(); if (boardIsDefault(debugShortcutSquare)) { boardShortcut(debugShortcutSquare, "pgn4web v" + pgn4web_version + " debug info\n" + alertNum + " alert" + (alertNum > 1 ? "s" : ""), null, true); } if ((!doNotPrompt) && ((LiveBroadcastDelay === 0) || (LiveBroadcastAlert === true)) && (boardIsDefault(debugShortcutSquare))) { startAlertPrompt(); } customFunctionOnAlert(msg); } function startAlertPrompt() { if (alertPromptOn) { return; } // dont start flashing twice if (alertPromptInterval) { clearTimeout(alertPromptInterval); } alertPromptInterval = setTimeout("alertPromptTick(true);", 500); } function stopAlertPrompt() { if (alertPromptInterval) { clearTimeout(alertPromptInterval); alertPromptInterval = null; } if (alertPromptOn) { alertPromptTick(false); } } function alertPromptTick(restart) { if (alertPromptInterval) { clearTimeout(alertPromptInterval); alertPromptInterval = null; } var colRow = colRowFromSquare(debugShortcutSquare); if (!colRow) { return; } var alertPromptDelay = 1500; // for alerts before the board is printed var theObj = document.getElementById('tcol' + colRow.col + 'trow' + colRow.row); if (theObj) { if (alertPromptOn) { if ((highlightOption) && ((colFromHighlighted === 0 && rowFromHighlighted === 7) || (colToHighlighted === 0 && rowToHighlighted === 7))) { theObj.className = 'highlightWhiteSquare'; } else { theObj.className = 'whiteSquare'; } } else { theObj.className = 'blackSquare'; } alertPromptOn = !alertPromptOn; if (alertPromptOn) { alertPromptDelay = 500; } else { alertPromptDelay = 3000; } } if (restart) { alertPromptInterval = setTimeout("alertPromptTick(true);", alertPromptDelay); } } function stopEvProp(e) { e.cancelBubble = true; if (e.stopPropagation) { e.stopPropagation(); } if (e.preventDefault) { e.preventDefault(); } return false; } // for onFocus/onBlur textbox events, allowing text typing var shortcutKeysWereEnabled = false; function disableShortcutKeysAndStoreStatus() { if ((shortcutKeysWereEnabled = shortcutKeysEnabled) === true) { SetShortcutKeysEnabled(false); } } function restoreShortcutKeysStatus() { if (shortcutKeysWereEnabled === true) { SetShortcutKeysEnabled(true); } shortcutKeysWereEnabled = false; } function customShortcutKey_Shift_0() { } function customShortcutKey_Shift_1() { } function customShortcutKey_Shift_2() { } function customShortcutKey_Shift_3() { } function customShortcutKey_Shift_4() { } function customShortcutKey_Shift_5() { } function customShortcutKey_Shift_6() { } function customShortcutKey_Shift_7() { } function customShortcutKey_Shift_8() { } function customShortcutKey_Shift_9() { } function pgn4web_handleKey_event(e) { pgn4web_handleKey(e); } var shortcutKeysEnabled = false; function pgn4web_handleKey(e) { var keycode, oldPly, oldVar, colRow, colRowList; if (!e) { e = window.event; } if (e.altKey || e.ctrlKey || e.metaKey) { return true; } keycode = e.keyCode; // shift-escape always enabled: toggle shortcut keys if (!shortcutKeysEnabled && !(keycode == 27 && e.shiftKey)) { return true; } switch (keycode) { case 8: // backspace case 9: // tab case 16: // shift case 17: // ctrl case 18: // alt case 32: // space case 33: // page-up case 34: // page-down case 35: // end case 36: // home case 92: // super case 93: // menu case 188: // comma return true; case 27: // escape if (e.shiftKey) { interactivelyToggleShortcutKeys(); } else { displayHelp(); } break; case 189: // dash if (colRowList = prompt("Enter shortcut square coordinates to click:", "")) { colRowList = colRowList.toUpperCase().replace(/[^A-Z0-9]/g, ""); while (colRow = colRowFromSquare(colRowList)) { boardOnClick[colRow.col][colRow.row]({"id": "img_tcol" + colRow.col + "trow" + colRow.row}, e); colRowList = colRowList.substr(2); } } break; case 90: // z if (e.shiftKey) { window.open(pgn4web_project_url); } else { displayDebugInfo(); } break; case 37: // left-arrow case 74: // j backButton(e); break; case 38: // up-arrow case 72: // h startButton(e); break; case 39: // right-arrow case 75: // k forwardButton(e); break; case 40: // down-arrow case 76: // l endButton(e); break; case 73: // i MoveToPrevComment(e.shiftKey); break; case 79: // o MoveToNextComment(e.shiftKey); break; case 190: // dot if (e.shiftKey) { goToFirstChild(); } else { goToNextVariationSibling(); } break; case 85: // u if (e.shiftKey) { undoStackRedo(); } else { undoStackUndo(); } break; case 45: // insert undoStackRedo(); break; case 46: // delete undoStackUndo(); break; case 83: // s if (e.shiftKey) { searchPgnGame(""); } else { searchPgnGamePrompt(); } break; case 13: // enter if (e.shiftKey) { searchPgnGame(lastSearchPgnExpression, true); } else { searchPgnGame(lastSearchPgnExpression); } break; case 68: // d if (e.shiftKey) { displayFenData(); } else { displayPgnData(true); } break; case 187: // equal SwitchAutoPlay(); break; case 65: // a GoToMove(CurrentPly + 1); SetAutoPlay(true); break; case 48: // 0 if (e.shiftKey) { customShortcutKey_Shift_0(); } else { SetAutoPlay(false); } break; case 49: // 1 if (e.shiftKey) { customShortcutKey_Shift_1(); } else { SetAutoplayDelayAndStart(1 * 1000); } break; case 50: // 2 if (e.shiftKey) { customShortcutKey_Shift_2(); } else { SetAutoplayDelayAndStart(2 * 1000); } break; case 51: // 3 if (e.shiftKey) { customShortcutKey_Shift_3(); } else { SetAutoplayDelayAndStart(3 * 1000); } break; case 52: // 4 if (e.shiftKey) { customShortcutKey_Shift_4(); } else { SetAutoplayDelayAndStart(4 * 1000); } break; case 53: // 5 if (e.shiftKey) { customShortcutKey_Shift_5(); } else { SetAutoplayDelayAndStart(5 * 1000); } break; case 54: // 6 if (e.shiftKey) { customShortcutKey_Shift_6(); } else { SetAutoplayDelayAndStart(6 * 1000); } break; case 55: // 7 if (e.shiftKey) { customShortcutKey_Shift_7(); } else { SetAutoplayDelayAndStart(7 * 1000); } break; case 56: // 8 if (e.shiftKey) { customShortcutKey_Shift_8(); } else { SetAutoplayDelayAndStart(8 * 1000); } break; case 57: // 9 if (e.shiftKey) { customShortcutKey_Shift_9(); } else { setCustomAutoplayDelay(); } break; case 81: // q SetAutoplayDelayAndStart(10 * 1000); break; case 87: // w SetAutoplayDelayAndStart(20 * 1000); break; case 69: // e SetAutoplayDelayAndStart(30 * 1000); break; case 82: // r pauseLiveBroadcast(); break; case 84: // t if (e.shiftKey) { LiveBroadcastSteppingMode = !LiveBroadcastSteppingMode; } else { refreshPgnSource(); } break; case 89: // y restartLiveBroadcast(); break; case 70: // f if (!e.shiftKey || IsRotated) { FlipBoard(); } break; case 71: // g SetHighlight(!highlightOption); break; case 88: // x randomGameRandomPly(); break; case 67: // c if (numberOfGames > 1) { Init(Math.floor(Math.random() * numberOfGames)); } break; case 86: // v if (numberOfGames > 1) { Init(0); } break; case 66: // b Init(currentGame - 1); break; case 78: // n Init(currentGame + 1); break; case 77: // m if (numberOfGames > 1) { Init(numberOfGames - 1); } break; case 80: // p if (e.shiftKey) { SetCommentsOnSeparateLines(!commentsOnSeparateLines); } else { SetCommentsIntoMoveText(!commentsIntoMoveText); } oldPly = CurrentPly; oldVar = CurrentVar; Init(); GoToMove(oldPly, oldVar); break; default: return true; } return stopEvProp(e); } var boardOnClick = new Array(8); var boardTitle = new Array(8); var boardDefault = new Array(8); for (var col = 0; col < 8; col++) { boardOnClick[col] = new Array(8); boardTitle[col] = new Array(8); boardDefault[col] = new Array(8); } clearShortcutSquares("ABCDEFGH", "12345678"); function colRowFromSquare(square) { if ((typeof (square) != "string") || (!square)) { return null; } var col = square.charCodeAt(0) - 65; // 65="A" if ((col < 0) || (col > 7)) { return null; } var row = 56 - square.charCodeAt(1); // 56="8" if ((row < 0) || (row > 7)) { return null; } return {"col": col, "row": row}; } function clearShortcutSquares(cols, rows) { if ((typeof (cols) != "string") || (typeof (rows) != "string")) { return; } for (var c = 0; c < cols.length; c++) { for (var r = 0; r < rows.length; r++) { boardShortcut(cols.charAt(c).toUpperCase() + rows.charAt(r), "", function (t, e) { }); } } } function boardIsDefault(square) { var colRow = colRowFromSquare(square); if (!colRow) { return false; } return boardDefault[colRow.col][colRow.row]; } function boardShortcut(square, title, functionPointer, defaultSetting) { var theObj, colRow = colRowFromSquare(square); if (!colRow) { return; } else { var col = colRow.col; var row = colRow.row; } boardTitle[col][row] = title; if (functionPointer) { boardOnClick[col][row] = functionPointer; } boardDefault[col][row] = defaultSetting ? true : false; if (theObj = document.getElementById('img_tcol' + col + 'trow' + row)) { if (IsRotated) { square = String.fromCharCode(72 - col, 49 + row); } theObj.title = square + (boardTitle[col][row] ? ': ' + boardTitle[col][row] : ''); } } // boardShortcut() always assumes 'square' defined as with white on bottom var debugShortcutSquare = "A8"; boardShortcut("A8", "pgn4web v" + pgn4web_version + " debug info", function (t, e) { displayDebugInfo(); }, true); boardShortcut("B8", "show current position FEN string", function (t, e) { displayFenData(); }, true); boardShortcut("C8", "show current game PGN source data", function (t, e) { if (e.shiftKey) { savePgnData(true); } else { displayPgnData(true); } }, true); boardShortcut("D8", "show all games PGN source data", function (t, e) { if (e.shiftKey) { savePgnData(); } else { displayPgnData(); } }, true); boardShortcut("E8", "search help", function (t, e) { displayHelp("search_tool"); }, true); boardShortcut("F8", "shortcut keys help", function (t, e) { displayHelp("shortcut_keys"); }, true); boardShortcut("G8", "shortcut squares help", function (t, e) { displayHelp(e.shiftKey ? "informant_symbols" : "shortcut_squares"); }, true); boardShortcut("H8", "pgn4web help", function (t, e) { displayHelp(e.shiftKey ? "credits_and_license" : ""); }, true); boardShortcut("A7", "pgn4web website", function (t, e) { window.open(pgn4web_project_url); }, true); boardShortcut("B7", "undo last chessboard position update", function (t, e) { undoStackUndo(); }, true); boardShortcut("C7", "redo last undo", function (t, e) { undoStackRedo(); }, true); boardShortcut("D7", "toggle highlight last move", function (t, e) { SetHighlight(!highlightOption); }, true); boardShortcut("E7", "flip board", function (t, e) { if (!e.shiftKey || IsRotated) { FlipBoard(); } }, true); boardShortcut("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); boardShortcut("G7", "", function (t, e) { }, true); // see setG7A6B6H6... boardShortcut("H7", "toggle enabling shortcut keys", function (t, e) { interactivelyToggleShortcutKeys(); }, true); boardShortcut("A6", "", function (t, e) { }, true); // see setG7A6B6H6... boardShortcut("B6", "", function (t, e) { }, true); // see setG7A6B6H6... boardShortcut("C6", "search previous finished game", function (t, e) { searchPgnGame('\\[\\s*Result\\s*"(?!\\*"\\s*\\])', true); }); boardShortcut("D6", "search previous unfinished game", function (t, e) { searchPgnGame('\\[\\s*Result\\s*"\\*"\\s*\\]', true); }); boardShortcut("E6", "search next unfinished game", function (t, e) { searchPgnGame('\\[\\s*Result\\s*"\\*"\\s*\\]', false); }, true); boardShortcut("F6", "search next finished game", function (t, e) { searchPgnGame('\\[\\s*Result\\s*"(?!\\*"\\s*\\])', false); }, true); boardShortcut("G6", "", function (t, e) { }, true); boardShortcut("H6", "", function (t, e) { }, true); // see setG7A6B6H6... boardShortcut("A5", "repeat last search backward", function (t, e) { searchPgnGame(lastSearchPgnExpression, true); }, true); boardShortcut("B5", "search prompt", function (t, e) { if (e.shiftKey) { searchPgnGame(""); } else { searchPgnGamePrompt(); } }, true); boardShortcut("C5", "repeat last search", function (t, e) { searchPgnGame(lastSearchPgnExpression); }, true); boardShortcut("D5", "search previous win result", function (t, e) { searchPgnGame('\\[\\s*Result\\s*"(1-0|0-1)"\\s*\\]', true); }, true); boardShortcut("E5", "search next win result", function (t, e) { searchPgnGame('\\[\\s*Result\\s*"(1-0|0-1)"\\s*\\]', false); }, true); boardShortcut("F5", "", function (t, e) { }, true); boardShortcut("G5", "", function (t, e) { }, true); boardShortcut("H5", "", function (t, e) { }, true); boardShortcut("A4", "search previous event", function (t, e) { searchPgnGame('\\[\\s*Event\\s*"(?!' + fixRegExp(gameEvent[currentGame]) + '"\\s*\\])', true); }, true); boardShortcut("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); boardShortcut("C4", "search previous game of same black player", function (t, e) { searchPgnGame('\\[\\s*' + (e.shiftKey ? 'White' : 'Black') + '\\s*"' + fixRegExp(gameBlack[currentGame]) + '"\\s*\\]', true); }, true); boardShortcut("D4", "search previous game of same white player", function (t, e) { searchPgnGame('\\[\\s*' + (e.shiftKey ? 'Black' : 'White') + '\\s*"' + fixRegExp(gameWhite[currentGame]) + '"\\s*\\]', true); }, true); boardShortcut("E4", "search next game of same white player", function (t, e) { searchPgnGame('\\[\\s*' + (e.shiftKey ? 'Black' : 'White') + '\\s*"' + fixRegExp(gameWhite[currentGame]) + '"\\s*\\]', false); }, true); boardShortcut("F4", "search next game of same black player", function (t, e) { searchPgnGame('\\[\\s*' + (e.shiftKey ? 'White' : 'Black') + '\\s*"' + fixRegExp(gameBlack[currentGame]) + '"\\s*\\]', false); }, true); boardShortcut("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); boardShortcut("H4", "search next event", function (t, e) { searchPgnGame('\\[\\s*Event\\s*"(?!' + fixRegExp(gameEvent[currentGame]) + '"\\s*\\])', false); }, true); boardShortcut("A3", "load first game", function (t, e) { if (numberOfGames > 1) { Init(0); } }, true); boardShortcut("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); boardShortcut("C3", "load previous game", function (t, e) { Init(currentGame - 1); }, true); boardShortcut("D3", "load random game", function (t, e) { if (numberOfGames > 1) { Init(Math.floor(Math.random() * numberOfGames)); } }, true); boardShortcut("E3", "load random game at random position", function (t, e) { randomGameRandomPly(); }, true); boardShortcut("F3", "load next game", function (t, e) { Init(currentGame + 1); }, true); boardShortcut("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); boardShortcut("H3", "load last game", function (t, e) { if (numberOfGames > 1) { Init(numberOfGames - 1); } }, true); boardShortcut("A2", "stop autoplay", function (t, e) { SetAutoPlay(e.shiftKey); }, true); boardShortcut("B2", "toggle autoplay", function (t, e) { SwitchAutoPlay(); }, true); boardShortcut("C2", "autoplay 1 second", function (t, e) { SetAutoplayDelayAndStart((e.shiftKey ? 10 : 1) * 1000); }, true); boardShortcut("D2", "autoplay 2 seconds", function (t, e) { SetAutoplayDelayAndStart((e.shiftKey ? 20 : 2) * 1000); }, true); boardShortcut("E2", "autoplay 5 seconds", function (t, e) { SetAutoplayDelayAndStart((e.shiftKey ? 50 : 5) * 1000); }, true); boardShortcut("F2", "autoplay custom delay", function (t, e) { setCustomAutoplayDelay(); }, true); boardShortcut("G2", "replay up to 6 previous half-moves, then autoplay forward", function (t, e) { replayPreviousMoves(e.shiftKey ? 10 : 6); }, true); boardShortcut("H2", "replay the previous half-move, then autoplay forward", function (t, e) { replayPreviousMoves(e.shiftKey ? 3 : 1); }, true); boardShortcut("A1", "go to game start", function (t, e) { startButton(e); }, true); boardShortcut("B1", "", function (t, e) { }, true); // see setB1C1F1G1... boardShortcut("C1", "", function (t, e) { }, true); // see setB1C1F1G1... boardShortcut("D1", "move backward", function (t, e) { backButton(e); }, true); boardShortcut("E1", "move forward", function (t, e) { forwardButton(e); }, true); boardShortcut("F1", "", function (t, e) { }, true); // see setB1C1F1G1... boardShortcut("G1", "", function (t, e) { }, true); // see setB1C1F1G1... boardShortcut("H1", "go to game end", function (t, e) { endButton(e); }, true); setG7A6B6H7boardShortcuts(); function setG7A6B6H7boardShortcuts() { if (LiveBroadcastDelay > 0) { if (boardIsDefault("G7")) { boardShortcut("G7", "", function (t, e) { }, true); } if (boardIsDefault("A6")) { boardShortcut("A6", "pause live broadcast automatic games refresh", function (t, e) { pauseLiveBroadcast(); }, true); } if (boardIsDefault("B6")) { boardShortcut("B6", "restart live broadcast automatic games refresh", function (t, e) { restartLiveBroadcast(); }, true); } if (boardIsDefault("H6")) { boardShortcut("H6", "force live broadcast games refresh", function (t, e) { refreshPgnSource(); }, true); } } else { if (boardIsDefault("G7")) { boardShortcut("G7", "toggle autoplay next game", function (t, e) { SetAutoplayNextGame(!autoplayNextGame); }, true); } if (boardIsDefault("A6")) { boardShortcut("A6", "", function (t, e) { }, true); } if (boardIsDefault("B6")) { boardShortcut("B6", "", function (t, e) { }, true); } if (boardIsDefault("H6")) { boardShortcut("H6", "", function (t, e) { }, true); } } } setB1C1F1G1boardShortcuts(); function setB1C1F1G1boardShortcuts() { if (commentsIntoMoveText && GameHasComments) { if (boardIsDefault("B1")) { boardShortcut("B1", "find previous comment or variation", function (t, e) { if (e.shiftKey) { GoToMove(CurrentPly - 10); } else { MoveToPrevComment(); } }, true); } if (boardIsDefault("G1")) { boardShortcut("G1", "find next comment or variation", function (t, e) { if (e.shiftKey) { GoToMove(CurrentPly + 10); } else { MoveToNextComment(); } }, true); } } else { if (boardIsDefault("B1")) { boardShortcut("B1", "move 10 half-moves backward", function (t, e) { GoToMove(CurrentPly - 10); }, true); } if (boardIsDefault("G1")) { boardShortcut("G1", "move 10 half-moves forward", function (t, e) { GoToMove(CurrentPly + 10); }, true); } } if (commentsIntoMoveText && GameHasVariations) { if (boardIsDefault("C1")) { boardShortcut("C1", "go to parent variation", function (t, e) { if (e.shiftKey) { GoToMove(CurrentPly - 6); } else { GoToMove(StartPlyVar[CurrentVar]); } }, true); } 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); } } else { if (boardIsDefault("C1")) { boardShortcut("C1", "move 6 half-moves backward", function (t, e) { GoToMove(CurrentPly - 6); }, true); } if (boardIsDefault("F1")) { boardShortcut("F1", "move 6 half-moves forward", function (t, e) { GoToMove(CurrentPly + 6); }, true); } } } var deciles = new Array(11); function calculateDeciles() { for (var ii = 0; ii < deciles.length; ii++) { deciles[ii] = Math.round((numberOfGames - 1) * ii / (deciles.length - 1)); } } function replayPreviousMoves(numPlies) { var thisPly = numPlies ? CurrentPly - numPlies : StartPly; if (thisPly < StartPlyVar[CurrentVar]) { thisPly = StartPlyVar[CurrentVar] + (CurrentVar === 0 ? 0 : 1); } if (thisPly !== CurrentPly) { GoToMove(thisPly); } SetAutoPlay(true); } function detectJavascriptLocation(jsre) { if (typeof (jsre) == "undefined") { jsre = new RegExp("pgn4web(|-compacted|\.min)\.js$", ""); } var e = document.getElementsByTagName("script"); for (var i = 0; i < e.length; i++) { if ((e[i].src) && (e[i].src.match(jsre))) { return e[i].src; } } return ""; } function detectHelpLocation() { return detectJavascriptLocation().replace(/pgn4web(|-compacted|\.min)\.js$/, "pgn4web-help.html"); } function detectBaseLocation() { var e = document.getElementsByTagName("base"); for (var i = 0; i < e.length; i++) { if (e[i].href) { return e[i].href; } } return ""; } var debugWin; function displayDebugInfo() { var theObj; var bu = detectBaseLocation(); var ju = detectJavascriptLocation(); stopAlertPrompt(); var dbg1 = 'pgn4web: version=' + pgn4web_version + ' homepage=' + pgn4web_project_url + '\n\nHTMLURL: length=' + location.href.length + ' url='; var dbg2 = location.href.length < 100 ? location.href : (location.href.substring(0, 99) + '...'); var dbg3 = (bu ? '\nBASEURL: url=' + bu : '') + (ju != 'pgn4web.js' ? '\nJSURL: url=' + ju : ''); if (pgnUrl) { dbg3 += '\nPGNURL: url=' + pgnUrl; } else if (theObj = document.getElementById("pgnText")) { dbg3 += '\nPGNTEXT: length=' + (theObj.tagName.toLowerCase() == 'textarea' ? theObj.value.length : '?'); } 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; if ((typeof (gameVariant[currentGame]) !== "undefined") && (gameVariant[currentGame].match(/^\s*(|chess|normal|standard)\s*$/i) === null)) { dbg3 += '\nVARIANT: ' + gameVariant[currentGame]; } if (LiveBroadcastDelay > 0) { dbg3 += '\n\nLIVEBROADCAST: status=' + liveStatusDebug() + (LiveBroadcastEndlessMode ? '/endless ' : '') + ' ticker=' + LiveBroadcastTicker + ' delay=' + LiveBroadcastDelay + 'm' + '\n' + 'refreshed: ' + LiveBroadcastLastRefreshedLocal + '\n' + 'received: ' + LiveBroadcastLastReceivedLocal + '\n' + 'modified (server time): ' + LiveBroadcastLastModified_ServerTime(); } if (typeof (engineWinCheck) == "function") { dbg3 += '\n\nANALYSIS: ' + (engineWinCheck() ? 'board=connected ' + engineWin.customDebugInfo() : 'board=disconnected'); } var thisInfo = customDebugInfo(); if (thisInfo) { dbg3 += '\n\nCUSTOM: ' + thisInfo; } dbg3 += '\n\nALERTLOG: fatalnew=' + fatalErrorNumSinceReset + ' new=' + alertNumSinceReset + ' shown=' + Math.min(alertNum, alertLog.length) + ' total=' + alertNum + '\n--'; if (alertNum > 0) { for (var ii = 0; ii < alertLog.length; ii++) { if (alertLog[(alertNum - 1 - ii) % alertLog.length] === undefined) { break; } else { dbg3 += "\n" + alertLog[(alertNum - 1 - ii) % alertLog.length] + "\n--"; } } } if (confirm(dbg1 + dbg2 + dbg3 + '\n\nclick OK to show this debug info in a browser window for cut and paste')) { if (debugWin && !debugWin.closed) { debugWin.close(); } debugWin = window.open("", "pgn4web_debug_data", "resizable=yes,scrollbars=yes,toolbar=no,location=no,menubar=no,status=no"); if (debugWin) { debugWin.document.open("text/html", "replace"); debugWin.document.write("
\n" + dbg1 + location.href + " " + dbg3 + "\n\n"); debugWin.document.close(); if (window.focus) { debugWin.focus(); } } } alertNumSinceReset = fatalErrorNumSinceReset = 0; } function liveStatusDebug() { if (LiveBroadcastEnded) { return "ended"; } if (LiveBroadcastPaused) { return "paused"; } if (LiveBroadcastStarted) { return "started"; } return "waiting"; } function customDebugInfo() { return ""; } var pgnWin; function displayPgnData(oneGameOnly) { if (pgnWin && !pgnWin.closed) { pgnWin.close(); } pgnWin = window.open("", "pgn4web_pgn_data", "resizable=yes,scrollbars=yes,toolbar=no,location=no,menubar=no,status=no"); if (pgnWin) { var text = "
\n";
if (oneGameOnly) {
text += fullPgnGame(currentGame) + "\n\n";
} else {
for (var ii = 0; ii < numberOfGames; ++ii) {
text += fullPgnGame(ii) + "\n\n";
}
}
text += "\n\n";
pgnWin.document.open("text/html", "replace");
pgnWin.document.write(text);
pgnWin.document.close();
if (window.focus) {
pgnWin.focus();
}
}
}
function savePgnData(oneGameOnly) {
if (pgnUrl && !oneGameOnly) {
location.href = pgnUrl;
} else {
displayPgnData(oneGameOnly); // fallback on displayPgnData for now
}
}
function CurrentFEN() {
var thisFEN = "";
var emptySquares = 0;
for (var row = 7; row >= 0; row--) {
for (var col = 0; col <= 7; col++) {
if (Board[col][row] === 0) {
emptySquares++;
} else {
if (emptySquares) {
thisFEN += emptySquares;
emptySquares = 0;
}
if (Board[col][row] > 0) {
thisFEN += PiecesArr[Board[col][row] - 1].toUpperCase();
} else if (Board[col][row] < 0) {
thisFEN += PiecesArr[-Board[col][row] - 1].toLowerCase();
}
}
}
if (emptySquares) {
thisFEN += emptySquares;
emptySquares = 0;
}
if (row > 0) {
thisFEN += "/";
}
}
thisFEN += CurrentPly % 2 ? " b" : " w";
// castling availability: always in the KQkq form
// knownbug: wrong FEN for Chess960 positions with inner castling rook
var CastlingFEN = "";
if (RookForOOCastling(0) !== null) {
CastlingFEN += PiecesArr[0].toUpperCase();
}
if (RookForOOOCastling(0) !== null) {
CastlingFEN += PiecesArr[1].toUpperCase();
}
if (RookForOOCastling(1) !== null) {
CastlingFEN += PiecesArr[0].toLowerCase();
}
if (RookForOOOCastling(1) !== null) {
CastlingFEN += PiecesArr[1].toLowerCase();
}
thisFEN += " " + (CastlingFEN || "-");
if (HistEnPassant[CurrentPly]) {
thisFEN += " " + String.fromCharCode(HistEnPassantCol[CurrentPly] + 97);
thisFEN += CurrentPly % 2 ? "3" : "6";
} else {
thisFEN += " -";
}
var HalfMoveClock = InitialHalfMoveClock;
for (var thisPly = StartPly; thisPly < CurrentPly; thisPly++) {
if ((HistType[0][thisPly] == 6) || (HistPieceId[1][thisPly] >= 16)) {
HalfMoveClock = 0;
} else {
HalfMoveClock++;
}
}
thisFEN += " " + HalfMoveClock;
thisFEN += " " + (Math.floor(CurrentPly / 2) + 1);
return thisFEN;
}
var fenWin;
function displayFenData(addGametext) {
if (fenWin && !fenWin.closed) {
fenWin.close();
}
var thisFEN = CurrentFEN();
var movesStr = "";
var lineStart = 0;
if (addGametext) {
for (var thisPly = CurrentPly; thisPly <= StartPly + PlyNumber; thisPly++) {
var addStr = "";
if (thisPly == StartPly + PlyNumber) {
addStr = (CurrentVar ? "*" : gameResult[currentGame] || "*");
} else {
if (thisPly % 2 === 0) {
addStr = (Math.floor(thisPly / 2) + 1) + ". ";
} else if (thisPly == CurrentPly) {
addStr = (Math.floor(thisPly / 2) + 1) + "... ";
}
addStr += Moves[thisPly];
}
if (movesStr.length + addStr.length + 1 > lineStart + 80) {
lineStart = movesStr.length;
movesStr += "\n" + addStr;
} else {
if (movesStr.length > 0) {
movesStr += " ";
}
movesStr += addStr;
}
}
}
fenWin = window.open("", "pgn4web_fen_data", "resizable=yes,scrollbars=yes,toolbar=no,location=no,menubar=no,status=no");
if (fenWin) {
var text = "\n\n" + thisFEN + "\n\n\n
\n\n";
if (addGametext) {
text += "[Event \"" + ((CurrentVar ? "" : gameEvent[currentGame]) || "?") + "\"]\n";
text += "[Site \"" + ((CurrentVar ? "" : gameSite[currentGame]) || "?") + "\"]\n";
text += "[Date \"" + ((CurrentVar ? "" : gameDate[currentGame]) || "????.??.??") + "\"]\n";
text += "[Round \"" + ((CurrentVar ? "" : gameRound[currentGame]) || "?") + "\"]\n";
text += "[White \"" + ((CurrentVar ? "" : gameWhite[currentGame]) || "?") + "\"]\n";
text += "[Black \"" + ((CurrentVar ? "" : gameBlack[currentGame]) || "?") + "\"]\n";
text += "[Result \"" + ((CurrentVar ? "" : gameResult[currentGame]) || "*") + "\"]\n";
}
if ((thisFEN != FenStringStart) || (!addGametext)) {
text += "[SetUp \"1\"]\n" + "[FEN \"" + thisFEN + "\"]\n";
}
if (gameVariant[currentGame] !== "") {
text += "[Variant \"" + gameVariant[currentGame] + "\"]\n";
}
if (addGametext) {
text += "\n" + movesStr + "\n";
}
text += "\n";
fenWin.document.open("text/html", "replace");
fenWin.document.write(text);
fenWin.document.close();
if (window.focus) {
fenWin.focus();
}
}
}
var pgnHeader = new Array();
var pgnGame = new Array();
var numberOfGames = -1;
var currentGame = -1;
var firstStart = true;
var gameDate = new Array();
var gameWhite = new Array();
var gameBlack = new Array();
var gameEvent = new Array();
var gameSite = new Array();
var gameRound = new Array();
var gameResult = new Array();
var gameSetUp = new Array();
var gameFEN = new Array();
var gameInitialWhiteClock = new Array();
var gameInitialBlackClock = new Array();
var gameVariant = new Array();
var highlightedMoveId = "";
var isAutoPlayOn = false;
var AutoPlayInterval = null;
var Delay = 1000; // milliseconds
var autostartAutoplay = false;
var autoplayNextGame = false;
var initialGame = 1;
var initialVariation = 0;
var initialHalfmove = 0;
var alwaysInitialHalfmove = false;
var LiveBroadcastInterval = null;
var LiveBroadcastDelay = 0; // minutes
var LiveBroadcastAlert = false;
var LiveBroadcastDemo = false;
var LiveBroadcastStarted = false;
var LiveBroadcastEnded = false;
var LiveBroadcastPaused = false;
var LiveBroadcastTicker = 0;
var LiveBroadcastGamesRunning = 0;
var LiveBroadcastLastModified = new Date(0); // default to epoch start
var LiveBroadcastLastModifiedHeader = LiveBroadcastLastModified.toUTCString();
var LiveBroadcastLastReceivedLocal = 'unavailable';
var LiveBroadcastLastRefreshedLocal = 'unavailable';
var LiveBroadcastPlaceholderEvent = 'live chess broadcast';
var LiveBroadcastPlaceholderPgn = '[Event "' + LiveBroadcastPlaceholderEvent + '"]';
var gameDemoMaxPly = new Array();
var gameDemoLength = new Array();
var LiveBroadcastSteppingMode = false;
var LiveBroadcastEndlessMode = false;
var ParseLastMoveError = false;
var castleRook = -1;
var mvCapture = 0;
var mvIsCastling = 0;
var mvIsPromotion = 0;
var mvFromCol = -1;
var mvFromRow = -1;
var mvToCol = -1;
var mvToRow = -1;
var mvPiece = -1;
var mvPieceId = -1;
var mvPieceOnTo = -1;
var mvCaptured = -1;
var mvCapturedId = -1;
var mvIsNull = 0;
var Board = new Array(8);
for (var i = 0; i < 8; ++i) {
Board[i] = new Array(8);
}
// HistCol, HistRow: move history up to last replayed ply
// HistCol[0], HistRow[0]: "square from"; 0..7, 0..7 from A1
// HistCol[1], HistRow[1]: castling/capture
// HistCol[2], HistRow[2]: "square to"; 0..7, 0..7 from A1
var HistCol = new Array(3);
var HistRow = new Array(3);
var HistPieceId = new Array(2);
var HistType = new Array(2);
var HistVar = new Array();
var PieceCol = new Array(2);
var PieceRow = new Array(2);
var PieceType = new Array(2);
var PieceMoveCounter = new Array(2);
for (i = 0; i < 2; ++i) {
PieceCol[i] = new Array(16);
PieceRow[i] = new Array(16);
PieceType[i] = new Array(16);
PieceMoveCounter[i] = new Array(16);
HistType[i] = new Array();
HistPieceId[i] = new Array();
}
for (i = 0; i < 3; ++i) {
HistCol[i] = new Array();
HistRow[i] = new Array();
}
var HistEnPassant = new Array();
HistEnPassant[0] = false;
var HistEnPassantCol = new Array();
HistEnPassantCol[0] = -1;
var HistNull = new Array();
HistNull[0] = 0;
var PiecesArr = "KQRBNP".split("");
var FenStringStart = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
var columnsLetters = "ABCDEFGH";
var InitialHalfMoveClock = 0;
var PieceImg = new Array(new Array(6), new Array(6));
var ClearImg;
var ImagePath = '/images';
var ImagePathOld = null;
var imageType = 'png';
var defaultImagesSize = 40;
var highlightOption = true;
var commentsIntoMoveText = true;
var commentsOnSeparateLines = false;
var pgnUrl = '';
var CastlingLong = new Array(2);
var CastlingShort = new Array(2);
var Moves = new Array();
var MoveComments = new Array();
var MoveColor;
var MoveCount;
var PlyNumber;
var StartPly;
var CurrentPly;
var IsRotated = false;
var pgnHeaderTagRegExp = /\[\s*(\w+)\s*"([^"]*)"\s*\]/;
var pgnHeaderTagRegExpGlobal = /\[\s*(\w+)\s*"([^"]*)"\s*\]/g;
var pgnHeaderBlockRegExp = /\s*(\[\s*\w+\s*"[^"]*"\s*\]\s*)+/;
var emptyPgnHeader = '[Event ""]\n[Site ""]\n[Date ""]\n[Round ""]\n[White ""]\n[Black ""]\n[Result ""]\n';
var alertPgn = emptyPgnHeader + "\n{error: click on the top left chessboard square for debug info}";
var pgn4webVariationRegExp = /\[%pgn4web_variation (\d+)\]/;
var pgn4webVariationRegExpGlobal = /\[%pgn4web_variation (\d+)\]/g;
var gameSelectorHead = ' ···';
var gameSelectorMono = true;
var gameSelectorNum = false;
var gameSelectorNumLenght = 0;
var gameSelectorChEvent = 0;
var gameSelectorChSite = 0;
var gameSelectorChRound = 0;
var gameSelectorChWhite = 15;
var gameSelectorChBlack = 15;
var gameSelectorChResult = 0;
var gameSelectorChDate = 10;
function CheckLegality(what, plyCount) {
var retVal, thisCol;
if (what == '--') {
StoreMove(plyCount);
return true;
}
// castling
if (what == 'O-O') {
if (!CheckLegalityOO()) {
return false;
}
for (thisCol = PieceCol[MoveColor][0]; thisCol < 7; thisCol++) {
if (IsCheck(thisCol, MoveColor * 7, MoveColor)) {
return false;
}
}
StoreMove(plyCount);
return true;
} else if (what == 'O-O-O') {
if (!CheckLegalityOOO()) {
return false;
}
for (thisCol = PieceCol[MoveColor][0]; thisCol > 1; thisCol--) {
if (IsCheck(thisCol, MoveColor * 7, MoveColor)) {
return false;
}
}
StoreMove(plyCount);
return true;
}
// capture: "square to" occupied by opposite color piece (except en-passant)
if (!mvCapture) {
if (Board[mvToCol][mvToRow] !== 0) {
return false;
}
}
if ((mvCapture) && (Color(Board[mvToCol][mvToRow]) != 1 - MoveColor)) {
if ((mvPiece != 6) || (!HistEnPassant[plyCount]) || (HistEnPassantCol[plyCount] != mvToCol) || (mvToRow != 5 - 3 * MoveColor)) {
return false;
}
}
// promotion: "square to" moved piece different from piece
if (mvIsPromotion) {
if (mvPiece != 6) {
return false;
}
if (mvPieceOnTo >= 6) {
return false;
}
if (mvToRow != 7 * (1 - MoveColor)) {
return false;
}
}
// piece move: which same type piece could move there?
var howManyCandidates = 0;
var candidatePieceId = -1;
for (var pieceId = 15; pieceId >= 0; --pieceId) {
if (PieceType[MoveColor][pieceId] == mvPiece) {
if (mvPiece == 1) {
retVal = CheckLegalityKing(pieceId);
} else if (mvPiece == 2) {
retVal = CheckLegalityQueen(pieceId);
} else if (mvPiece == 3) {
retVal = CheckLegalityRook(pieceId);
} else if (mvPiece == 4) {
retVal = CheckLegalityBishop(pieceId);
} else if (mvPiece == 5) {
retVal = CheckLegalityKnight(pieceId);
} else if (mvPiece == 6) {
retVal = CheckLegalityPawn(pieceId);
}
if (retVal) {
// board updated: king in check?
mvPieceId = pieceId;
StoreMove(plyCount);
if (!IsCheck(PieceCol[MoveColor][0], PieceRow[MoveColor][0], MoveColor)) {
howManyCandidates++;
candidatePieceId = pieceId;
}
UndoMove(plyCount);
}
}
}
// patch: for rejecting rather than guessing ambiguous move replace the code till the end of this function with the following line
// if (howManyCandidates == 1) { mvPieceId = candidatePieceId; StoreMove(plyCount); return true; } else { return false; }
if (howManyCandidates > 0) {
mvPieceId = candidatePieceId;
StoreMove(plyCount);
if (howManyCandidates > 1) {
var text = (Math.floor(plyCount / 2) + 1) + ((plyCount % 2) === 0 ? '. ' : '... ');
myAlert('error: guessed ambiguous ply ' + text + MovesVar[CurrentVar][plyCount] + ' in game ' + (currentGame + 1) + ' variation ' + CurrentVar);
}
return true;
}
return false;
}
function CheckLegalityKing(thisKing) {
if ((mvFromCol >= 0) && (mvFromCol != PieceCol[MoveColor][thisKing])) {
return false;
}
if ((mvFromRow >= 0) && (mvFromRow != PieceRow[MoveColor][thisKing])) {
return false;
}
if (Math.abs(PieceCol[MoveColor][thisKing] - mvToCol) > 1) {
return false;
}
if (Math.abs(PieceRow[MoveColor][thisKing] - mvToRow) > 1) {
return false;
}
return true;
}
function CheckLegalityQueen(thisQueen) {
if ((mvFromCol >= 0) && (mvFromCol != PieceCol[MoveColor][thisQueen])) {
return false;
}
if ((mvFromRow >= 0) && (mvFromRow != PieceRow[MoveColor][thisQueen])) {
return false;
}
if (((PieceCol[MoveColor][thisQueen] - mvToCol) * (PieceRow[MoveColor][thisQueen] - mvToRow) !== 0) && (Math.abs(PieceCol[MoveColor][thisQueen] - mvToCol) != Math.abs(PieceRow[MoveColor][thisQueen] - mvToRow))) {
return false;
}
if (!CheckClearWay(thisQueen)) {
return false;
}
return true;
}
function CheckLegalityRook(thisRook) {
if ((mvFromCol >= 0) && (mvFromCol != PieceCol[MoveColor][thisRook])) {
return false;
}
if ((mvFromRow >= 0) && (mvFromRow != PieceRow[MoveColor][thisRook])) {
return false;
}
if ((PieceCol[MoveColor][thisRook] - mvToCol) * (PieceRow[MoveColor][thisRook] - mvToRow) !== 0) {
return false;
}
if (!CheckClearWay(thisRook)) {
return false;
}
return true;
}
function CheckLegalityBishop(thisBishop) {
if ((mvFromCol >= 0) && (mvFromCol != PieceCol[MoveColor][thisBishop])) {
return false;
}
if ((mvFromRow >= 0) && (mvFromRow != PieceRow[MoveColor][thisBishop])) {
return false;
}
if (Math.abs(PieceCol[MoveColor][thisBishop] - mvToCol) != Math.abs(PieceRow[MoveColor][thisBishop] - mvToRow)) {
return false;
}
if (!CheckClearWay(thisBishop)) {
return false;
}
return true;
}
function CheckLegalityKnight(thisKnight) {
if ((mvFromCol >= 0) && (mvFromCol != PieceCol[MoveColor][thisKnight])) {
return false;
}
if ((mvFromRow >= 0) && (mvFromRow != PieceRow[MoveColor][thisKnight])) {
return false;
}
if (Math.abs(PieceCol[MoveColor][thisKnight] - mvToCol) * Math.abs(PieceRow[MoveColor][thisKnight] - mvToRow) != 2) {
return false;
}
return true;
}
function CheckLegalityPawn(thisPawn) {
if ((mvFromCol >= 0) && (mvFromCol != PieceCol[MoveColor][thisPawn])) {
return false;
}
if ((mvFromRow >= 0) && (mvFromRow != PieceRow[MoveColor][thisPawn])) {
return false;
}
if (Math.abs(PieceCol[MoveColor][thisPawn] - mvToCol) != mvCapture) {
return false;
}
if (mvCapture) {
if (PieceRow[MoveColor][thisPawn] - mvToRow != 2 * MoveColor - 1) {
return false;
}
} else {
if (PieceRow[MoveColor][thisPawn] - mvToRow == 4 * MoveColor - 2) {
if (PieceRow[MoveColor][thisPawn] != 1 + 5 * MoveColor) {
return false;
}
if (Board[mvToCol][mvToRow + 2 * MoveColor - 1] !== 0) {
return false;
}
} else {
if (PieceRow[MoveColor][thisPawn] - mvToRow != 2 * MoveColor - 1) {
return false;
}
}
}
return true;
}
function RookForOOCastling(color) {
if (CastlingShort[color] < 0) {
return null;
}
if (PieceMoveCounter[color][0] > 0) {
return null;
}
var legal = false;
for (var thisRook = 0; thisRook < 16; thisRook++) {
if ((PieceCol[color][thisRook] == CastlingShort[color]) && (PieceCol[color][thisRook] > PieceCol[color][0]) && (PieceRow[color][thisRook] == color * 7) && (PieceType[color][thisRook] == 3)) {
legal = true;
break;
}
}
if (!legal) {
return null;
}
if (PieceMoveCounter[color][thisRook] > 0) {
return null;
}
return thisRook;
}
function CheckLegalityOO() {
var thisRook = RookForOOCastling(MoveColor);
if (thisRook === null) {
return false;
}
// check no piece between king and rook
// clear king/rook squares for Chess960
Board[PieceCol[MoveColor][0]][MoveColor * 7] = 0;
Board[PieceCol[MoveColor][thisRook]][MoveColor * 7] = 0;
var col = PieceCol[MoveColor][thisRook];
if (col < 6) {
col = 6;
}
while ((col > PieceCol[MoveColor][0]) || (col >= 5)) {
if (Board[col][MoveColor * 7] !== 0) {
return false;
}
--col;
}
castleRook = thisRook;
return true;
}
function RookForOOOCastling(color) {
if (CastlingLong[color] < 0) {
return null;
}
if (PieceMoveCounter[color][0] > 0) {
return null;
}
var legal = false;
for (var thisRook = 0; thisRook < 16; thisRook++) {
if ((PieceCol[color][thisRook] == CastlingLong[color]) && (PieceCol[color][thisRook] < PieceCol[color][0]) && (PieceRow[color][thisRook] == color * 7) && (PieceType[color][thisRook] == 3)) {
legal = true;
break;
}
}
if (!legal) {
return null;
}
if (PieceMoveCounter[color][thisRook] > 0) {
return null;
}
return thisRook;
}
function CheckLegalityOOO() {
var thisRook = RookForOOOCastling(MoveColor);
if (thisRook === null) {
return false;
}
// check no piece between king and rook
// clear king/rook squares for Chess960
Board[PieceCol[MoveColor][0]][MoveColor * 7] = 0;
Board[PieceCol[MoveColor][thisRook]][MoveColor * 7] = 0;
var col = PieceCol[MoveColor][thisRook];
if (col > 2) {
col = 2;
}
while ((col < PieceCol[MoveColor][0]) || (col <= 3)) {
if (Board[col][MoveColor * 7] !== 0) {
return false;
}
++col;
}
castleRook = thisRook;
return true;
}
function CheckClearWay(thisPiece) {
var stepCol = sign(mvToCol - PieceCol[MoveColor][thisPiece]);
var stepRow = sign(mvToRow - PieceRow[MoveColor][thisPiece]);
var startCol = PieceCol[MoveColor][thisPiece] + stepCol;
var startRow = PieceRow[MoveColor][thisPiece] + stepRow;
while ((startCol != mvToCol) || (startRow != mvToRow)) {
if (Board[startCol][startRow] !== 0) {
return false;
}
startCol += stepCol;
startRow += stepRow;
}
return true;
}
function CleanMove(move) {
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
if (move.match(/^[Oo0]/)) {
move = move.replace(/[o0]/g, 'O').replace(/O(?=O)/g, 'O-');
}
move = move.replace(/ep/i, '');
return move;
}
function GoToMove(thisPly, thisVar) {
SetAutoPlay(false);
if (typeof (thisVar) == "undefined") {
thisVar = CurrentVar;
} else {
if (thisVar < 0) {
thisVar = 0;
} else if (thisVar >= numberOfVars) {
thisVar = numberOfVars - 1;
}
}
if (thisPly < 0) {
thisPly = 0;
} else if (thisPly >= StartPlyVar[thisVar] + PlyNumberVar[thisVar]) {
thisPly = StartPlyVar[thisVar] + PlyNumberVar[thisVar];
}
if (thisVar === CurrentVar) {
var diff = thisPly - CurrentPly;
if (diff > 0) {
MoveForward(diff);
} else {
MoveBackward(-diff);
}
} else {
var backStart = StartPly;
loopCommonPredecessor:
for (var ii = PredecessorsVars[CurrentVar].length - 1; ii >= 0; ii--) {
for (var jj = PredecessorsVars[thisVar].length - 1; jj >= 0; jj--) {
if (PredecessorsVars[CurrentVar][ii] === PredecessorsVars[thisVar][jj]) {
backStart = Math.min(PredecessorsVars[CurrentVar][ii + 1] ? StartPlyVar[PredecessorsVars[CurrentVar][ii + 1]] : CurrentPly, PredecessorsVars[thisVar][jj + 1] ? StartPlyVar[PredecessorsVars[thisVar][jj + 1]] : thisPly);
break loopCommonPredecessor;
}
}
}
MoveBackward(CurrentPly - backStart, true);
MoveForward(thisPly - backStart, thisVar);
}
}
function SetShortcutKeysEnabled(onOff) {
shortcutKeysEnabled = onOff;
}
function interactivelyToggleShortcutKeys() {
if (confirm("Shortcut keys currently " + (shortcutKeysEnabled ? "enabled" : "disabled") + ".\nToggle shortcut keys to " + (shortcutKeysEnabled ? "DISABLED" : "ENABLED") + "?")) {
SetShortcutKeysEnabled(!shortcutKeysEnabled);
}
}
function SetCommentsIntoMoveText(onOff) {
commentsIntoMoveText = onOff;
}
function SetCommentsOnSeparateLines(onOff) {
commentsOnSeparateLines = onOff;
}
function SetAutostartAutoplay(onOff) {
autostartAutoplay = onOff;
}
function SetAutoplayNextGame(onOff) {
autoplayNextGame = onOff;
}
function SetInitialHalfmove(number_or_string, always) {
alwaysInitialHalfmove = (always === true);
initialHalfmove = typeof (number_or_string) == "undefined" ? 0 : number_or_string;
}
function SetInitialVariation(number) {
initialVariation = isNaN(number = parseInt(number, 10)) ? 0 : number;
}
function SetInitialGame(number_or_string) {
initialGame = typeof (number_or_string) == "undefined" ? 1 : number_or_string;
}
function randomGameRandomPly() {
if (numberOfGames > 1) {
var oldInitialHalfmove = initialHalfmove;
var oldAlwaysInitialHalfmove = alwaysInitialHalfmove;
SetInitialHalfmove("random", true);
Init(Math.floor(Math.random() * numberOfGames));
SetInitialHalfmove(oldInitialHalfmove, oldAlwaysInitialHalfmove);
}
}
// clock detection as [%clk 01:02]
function clockFromComment(plyNum) {
return customPgnCommentTag("clk", null, plyNum);
}
function clockFromHeader(whiteToMove) {
var clockString = customPgnHeaderTag("Clock") + "";
var matches = clockString.match("^" + (whiteToMove ? "W" : "B") + "/(.*)$");
if (matches) {
return matches[1];
} else {
return null;
}
}
function HighlightLastMove() {
var theObj, moveId, text, ii, clockString, clockRegExp, clockMatch;
undoStackStore();
// remove old move highlighting
if (highlightedMoveId) {
if (theObj = document.getElementById(highlightedMoveId)) {
theObj.className = (highlightedMoveId.match(/Var0Mv/) ? 'move' : 'variation') + ' notranslate';
}
}
// halfmove to be highlighted, negative for starting position
var showThisMove = CurrentPly - 1;
if (showThisMove > StartPlyVar[CurrentVar] + PlyNumberVar[CurrentVar]) {
showThisMove = StartPlyVar[CurrentVar] + PlyNumberVar[CurrentVar];
}
if (theObj = document.getElementById("GameLastComment")) {
if (commentsIntoMoveText) {
variationTextDepth = CurrentVar === 0 ? 0 : 1;
text = '' + strippedMoveComment(showThisMove + 1, CurrentVar, true).replace(/\sID="[^"]*"/g, '') + '';
} else {
text = '';
}
theObj.innerHTML = text;
}
// side to move
var whiteToMove = ((showThisMove + 1) % 2 === 0);
text = whiteToMove ? 'white' : 'black';
if (theObj = document.getElementById("GameSideToMove")) {
theObj.innerHTML = text;
}
// clock
var lastMoverClockObj = document.getElementById(whiteToMove ? "GameBlackClock" : "GameWhiteClock");
var initialLastMoverClock = whiteToMove ? gameInitialBlackClock[currentGame] : gameInitialWhiteClock[currentGame];
var beforeLastMoverClockObj = document.getElementById(whiteToMove ? "GameWhiteClock" : "GameBlackClock");
var initialBeforeLastMoverClock = whiteToMove ? gameInitialWhiteClock[currentGame] : gameInitialBlackClock[currentGame];
if (lastMoverClockObj) {
clockString = ((showThisMove + 1 === StartPly + PlyNumber) && ((!LiveBroadcastDemo) || (gameResult[currentGame] !== "*"))) ? clockFromHeader(!whiteToMove) : null;
if (clockString === null) {
clockString = showThisMove + 1 > StartPly ? clockFromComment(showThisMove + 1) : initialLastMoverClock;
if (!clockString && (CurrentPly === StartPly + PlyNumber)) {
// support for time info in the last comment as { White Time: 0h:12min Black Time: 1h:23min }
clockRegExp = new RegExp((whiteToMove ? "Black" : "White") + "\\s+Time:\\s*(\\S+)", "i");
if (clockMatch = strippedMoveComment(StartPly + PlyNumber).match(clockRegExp)) {
clockString = clockMatch[1];
}
}
}
lastMoverClockObj.innerHTML = clockString;
}
if (beforeLastMoverClockObj) {
clockString = ((showThisMove + 1 === StartPly + PlyNumber) && ((!LiveBroadcastDemo) || (gameResult[currentGame] !== "*"))) ? clockFromHeader(whiteToMove) : null;
if (clockString === null) {
clockString = showThisMove > StartPly ? clockFromComment(showThisMove) : initialBeforeLastMoverClock;
if (!clockString && (CurrentPly === StartPly + PlyNumber)) {
// see comment above
clockRegExp = new RegExp((whiteToMove ? "White" : "Black") + "\\s+Time:\\s*(\\S+)", "i");
if (clockMatch = strippedMoveComment(StartPly + PlyNumber).match(clockRegExp)) {
clockString = clockMatch[1];
}
}
}
beforeLastMoverClockObj.innerHTML = clockString;
}
if (lastMoverClockObj && beforeLastMoverClockObj) {
if (lastMoverClockObj.innerHTML && !beforeLastMoverClockObj.innerHTML) {
beforeLastMoverClockObj.innerHTML = "-";
} else if (!lastMoverClockObj.innerHTML && beforeLastMoverClockObj.innerHTML) {
lastMoverClockObj.innerHTML = "-";
}
}
// next move
if (theObj = document.getElementById("GameNextMove")) {
if (CurrentVar === 0 && showThisMove + 1 >= StartPly + PlyNumber) {
text = '' + gameResult[currentGame] + '';
} else if (typeof (Moves[showThisMove + 1]) == "undefined") {
text = "";
} else {
text = printMoveText(showThisMove + 1, CurrentVar, (CurrentVar !== 0), true, false);
}
theObj.innerHTML = text;
}
// next variations
if (theObj = document.getElementById("GameNextVariations")) {
text = '';
if (commentsIntoMoveText) {
var children = childrenVars(showThisMove + 1, CurrentVar);
for (ii = 0; ii < children.length; ii++) {
if (children[ii] !== CurrentVar) {
text += ' ' + printMoveText(showThisMove + 1, children[ii], (children[ii] !== 0), true, false);
}
}
}
theObj.innerHTML = text;
}
// last move
if (theObj = document.getElementById("GameLastMove")) {
if ((showThisMove >= StartPly) && Moves[showThisMove]) {
text = printMoveText(showThisMove, CurrentVar, (CurrentVar !== 0), true, false);
} else if (showThisMove === StartPly - 1) {
text = '' + (Math.floor((showThisMove + 1) / 2) + 1) + (((showThisMove + 1) % 2) ? "..." : ".") + '';
} else {
text = '';
}
theObj.innerHTML = text;
}
// last variations
if (theObj = document.getElementById("GameLastVariations")) {
text = '';
if (commentsIntoMoveText) {
var siblings = childrenVars(showThisMove, HistVar[showThisMove]);
for (ii = 0; ii < siblings.length; ii++) {
if (siblings[ii] !== CurrentVar) {
text += ' ' + printMoveText(showThisMove, siblings[ii], (siblings[ii] !== 0), true, false);
}
}
}
theObj.innerHTML = text;
}
if (showThisMove >= (StartPlyVar[CurrentVar] - 1)) {
moveId = 'Var' + CurrentVar + 'Mv' + (showThisMove + 1);
if (theObj = document.getElementById(moveId)) {
theObj.className = (CurrentVar ? 'variation variationOn' : 'move moveOn') + ' notranslate';
}
highlightedMoveId = moveId;
if (highlightOption) {
var colFrom, rowFrom, colTo, rowTo;
if ((showThisMove < StartPly) || HistNull[showThisMove]) {
colFrom = rowFrom = -1;
colTo = rowTo = -1;
} else {
colFrom = HistCol[0][showThisMove] === undefined ? -1 : HistCol[0][showThisMove];
rowFrom = HistRow[0][showThisMove] === undefined ? -1 : HistRow[0][showThisMove];
colTo = HistCol[2][showThisMove] === undefined ? -1 : HistCol[2][showThisMove];
rowTo = HistRow[2][showThisMove] === undefined ? -1 : HistRow[2][showThisMove];
}
highlightMove(colFrom, rowFrom, colTo, rowTo);
}
}
}
function SetHighlightOption(on) {
highlightOption = on;
}
function SetHighlight(on) {
SetHighlightOption(on);
if (on) {
HighlightLastMove();
} else {
highlightMove(-1, -1, -1, -1);
}
}
var colFromHighlighted = -1;
var rowFromHighlighted = -1;
var colToHighlighted = -1;
var rowToHighlighted = -1;
function highlightMove(colFrom, rowFrom, colTo, rowTo) {
highlightSquare(colFromHighlighted, rowFromHighlighted, false);
highlightSquare(colToHighlighted, rowToHighlighted, false);
if (highlightSquare(colFrom, rowFrom, true)) {
colFromHighlighted = colFrom;
rowFromHighlighted = rowFrom;
} else {
colFromHighlighted = rowFromHighlighted = -1;
}
if (highlightSquare(colTo, rowTo, true)) {
colToHighlighted = colTo;
rowToHighlighted = rowTo;
} else {
colToHighlighted = rowToHighlighted = -1;
}
}
function highlightSquare(col, row, on) {
if ((col === undefined) || (row === undefined)) {
return false;
}
if (!SquareOnBoard(col, row)) {
return false;
}
var trow = IsRotated ? row : 7 - row;
var tcol = IsRotated ? 7 - col : col;
var theObj = document.getElementById('tcol' + tcol + 'trow' + trow);
if (!theObj) {
return false;
}
if (on) {
theObj.className = (trow + tcol) % 2 === 0 ? "highlightWhiteSquare" : "highlightBlackSquare";
} else {
theObj.className = (trow + tcol) % 2 === 0 ? "whiteSquare" : "blackSquare";
}
return true;
}
var undoStackMax = 1000;
var undoStackGame = new Array(undoStackMax);
var undoStackVar = new Array(undoStackMax);
var undoStackPly = new Array(undoStackMax);
var undoStackStart = 0;
var undoStackCurrent = 0;
var undoStackEnd = 0;
var undoRedoInProgress = false;
function undoStackReset() {
undoStackGame = new Array(undoStackMax);
undoStackVar = new Array(undoStackMax);
undoStackPly = new Array(undoStackMax);
undoStackStart = undoStackCurrent = undoStackEnd = 0;
}
function undoStackStore() {
if (undoRedoInProgress) {
return false;
}
if ((undoStackStart === undoStackCurrent) || (currentGame !== undoStackGame[undoStackCurrent]) || (CurrentVar !== undoStackVar[undoStackCurrent]) || (CurrentPly !== undoStackPly[undoStackCurrent])) {
undoStackCurrent = (undoStackCurrent + 1) % undoStackMax;
undoStackGame[undoStackCurrent] = currentGame;
undoStackVar[undoStackCurrent] = CurrentVar;
undoStackPly[undoStackCurrent] = CurrentPly;
undoStackEnd = undoStackCurrent;
if (undoStackStart === undoStackCurrent) {
undoStackStart = (undoStackStart + 1) % undoStackMax;
}
}
return true;
}
function undoStackUndo() {
if ((undoStackCurrent - 1 + undoStackMax) % undoStackMax === undoStackStart) {
return false;
}
undoRedoInProgress = true;
undoStackCurrent = (undoStackCurrent - 1 + undoStackMax) % undoStackMax;
if (undoStackGame[undoStackCurrent] !== currentGame) {
Init(undoStackGame[undoStackCurrent]);
}
GoToMove(undoStackPly[undoStackCurrent], undoStackVar[undoStackCurrent]);
undoRedoInProgress = false;
return true;
}
function undoStackRedo() {
if (undoStackCurrent === undoStackEnd) {
return false;
}
undoRedoInProgress = true;
undoStackCurrent = (undoStackCurrent + 1) % undoStackMax;
if (undoStackGame[undoStackCurrent] !== currentGame) {
Init(undoStackGame[undoStackCurrent]);
}
GoToMove(undoStackPly[undoStackCurrent], undoStackVar[undoStackCurrent]);
undoRedoInProgress = false;
return true;
}
function fixCommonPgnMistakes(text) {
text = text.replace(/[\u00A0\u180E\u2000-\u200A\u202F\u205F\u3000]/g, " "); // some spaces to plain space
text = text.replace(/\u00BD/g, "1/2"); // "half fraction" to "1/2"
text = text.replace(/[\u2010-\u2015]/g, "-"); // "hyphens" to "-"
text = text.replace(/\u2024/g, "."); // "one dot leader" to "."
text = text.replace(/[\u2025-\u2026]/g, "..."); // "two dot leader" and "ellipsis" to "..."
text = text.replace(/\\"/g, "'"); // fix [Opening "Queen\"s gambit"]
return text;
}
function fullPgnGame(gameNum) {
var res = pgnHeader[gameNum] ? pgnHeader[gameNum].replace(/^[^[]*/g, "") : "";
res = res.replace(/\[\s*(\w+)\s*"([^"]*)"\s*\][^[]*/g, '[$1 "$2"]\n');
res += "\n";
res += pgnGame[gameNum] ? pgnGame[gameNum].replace(/(^[\s]*|[\s]*$)/g, "") : "";
return res;
}
function pgnGameFromPgnText(pgnText) {
var newNumGames, headMatch, prevHead, newHead, startNew, afterNew, lastOpen, checkedGame, validHead;
pgnText = simpleHtmlentities(fixCommonPgnMistakes(pgnText));
// PGN standard: ignore lines starting with %
pgnText = pgnText.replace(/(^|\n)%.*(\n|$)/g, "\n");
newNumGames = 0;
checkedGame = "";
while (headMatch = pgnHeaderBlockRegExp.exec(pgnText)) {
newHead = headMatch[0];
startNew = pgnText.indexOf(newHead);
afterNew = startNew + newHead.length;
if (prevHead) {
checkedGame += pgnText.slice(0, startNew);
validHead = ((lastOpen = checkedGame.lastIndexOf("{")) < 0) || (checkedGame.lastIndexOf("}")) > lastOpen;
if (validHead) {
pgnHeader[newNumGames] = prevHead;
pgnGame[newNumGames++] = checkedGame;
checkedGame = "";
} else {
checkedGame += newHead;
}
} else {
validHead = true;
}
if (validHead) {
prevHead = newHead;
}
pgnText = pgnText.slice(afterNew);
}
if (prevHead) {
pgnHeader[newNumGames] = prevHead;
checkedGame += pgnText;
pgnGame[newNumGames++] = checkedGame;
}
if (newNumGames === 0) {
return false;
}
numberOfGames = newNumGames;
return true;
}
function pgnGameFromHttpRequest(httpResponseData) {
// process here any special file types, for instance zipfiles:
// if (pgnUrl && pgnUrl.replace(/[?#].*/, "").match(/\.zip$/i)) { return pgnGameFromPgnText(unzipPgnFiles(httpResponseData)); }
// remember to fix function loadPgnFromPgnUrl() for binary data
return pgnGameFromPgnText(httpResponseData);
}
var http_request_last_processed_id = 0;
function updatePgnFromHttpRequest(this_http_request, this_http_request_id) {
var res = LOAD_PGN_FAIL;
if (this_http_request.readyState != 4) {
return;
}
if (this_http_request_id < http_request_last_processed_id) {
return;
} else {
http_request_last_processed_id = this_http_request_id;
}
// patch: enable loading local PGN files on some browsers by adding: || (this_http_request.status === 0)
if ((this_http_request.status == 200) || (this_http_request.status == 304)) {
if (this_http_request.status == 304) {
if (LiveBroadcastDelay > 0) {
res = LOAD_PGN_UNMODIFIED;
} else {
myAlert('error: unmodified PGN URL when not in live mode');
}
} else if (!this_http_request.responseText) {
myAlert('error: no data received from PGN URL\n' + pgnUrl, true);
} else if (!pgnGameFromHttpRequest(this_http_request.responseText)) {
myAlert('error: no games found at PGN URL\n' + pgnUrl, true);
} else {
if (LiveBroadcastDelay > 0) {
LiveBroadcastLastReceivedLocal = (new Date()).toLocaleString();
if (LiveBroadcastLastModifiedHeader = this_http_request.getResponseHeader("Last-Modified")) {
LiveBroadcastLastModified = new Date(LiveBroadcastLastModifiedHeader);
} else {
LiveBroadcastLastModified_Reset();
}
}
res = LOAD_PGN_OK;
}
} else {
myAlert('error: failed reading PGN URL\n' + pgnUrl, true);
}
if (LiveBroadcastDemo && (res == LOAD_PGN_UNMODIFIED)) {
res = LOAD_PGN_OK;
}
loadPgnCheckingLiveStatus(res);
}
var LOAD_PGN_FAIL = 0;
var LOAD_PGN_OK = 1;
var LOAD_PGN_UNMODIFIED = 2;
function loadPgnCheckingLiveStatus(res) {
switch (res) {
case LOAD_PGN_OK:
if (LiveBroadcastDelay > 0) {
firstStart = true;
var oldParseLastMoveError = ParseLastMoveError;
if (!LiveBroadcastStarted) {
LiveBroadcastStarted = true;
} else {
var oldWhite = gameWhite[currentGame];
var oldBlack = gameBlack[currentGame];
var oldEvent = gameEvent[currentGame];
var oldRound = gameRound[currentGame];
var oldSite = gameSite[currentGame];
var oldDate = gameDate[currentGame];
initialGame = currentGame + 1;
LiveBroadcastOldCurrentVar = CurrentVar;
LiveBroadcastOldCurrentPly = CurrentPly;
LiveBroadcastOldCurrentPlyLast = (CurrentVar === 0 && CurrentPly === StartPlyVar[0] + PlyNumberVar[0]);
var oldAutoplay = isAutoPlayOn;
if (isAutoPlayOn) {
SetAutoPlay(false);
}
LoadGameHeaders();
LiveBroadcastFoundOldGame = false;
for (var ii = 0; ii < numberOfGames; ii++) {
LiveBroadcastFoundOldGame = (gameWhite[ii] == oldWhite) && (gameBlack[ii] == oldBlack) && (gameEvent[ii] == oldEvent) && (gameRound[ii] == oldRound) && (gameSite[ii] == oldSite); // && (gameDate[ii] ==oldDate );
if (LiveBroadcastFoundOldGame) {
break;
}
}
if (LiveBroadcastFoundOldGame) {
initialGame = ii + 1;
}
if (LiveBroadcastFoundOldGame) {
var oldInitialVariation = initialVariation;
var oldInitialHalfmove = initialHalfmove;
initialVariation = CurrentVar;
if (LiveBroadcastSteppingMode) {
initialHalfmove = (LiveBroadcastOldCurrentPlyLast || oldParseLastMoveError) ? LiveBroadcastOldCurrentPly + 1 : LiveBroadcastOldCurrentPly;
} else {
initialHalfmove = (LiveBroadcastOldCurrentPlyLast || oldParseLastMoveError) ? "end" : LiveBroadcastOldCurrentPly;
}
}
}
}
undoStackReset();
Init();
if (LiveBroadcastDelay > 0) {
if (LiveBroadcastFoundOldGame) {
initialHalfmove = oldInitialHalfmove;
initialVariation = oldInitialVariation;
}
checkLiveBroadcastStatus();
}
customFunctionOnPgnTextLoad();
if (LiveBroadcastDelay > 0) {
if (LiveBroadcastFoundOldGame) {
if (LiveBroadcastSteppingMode) {
if (oldAutoplay || LiveBroadcastOldCurrentPlyLast || oldParseLastMoveError) {
SetAutoPlay(true);
}
} else {
if (oldAutoplay) {
SetAutoPlay(true);
}
}
}
}
break;
case LOAD_PGN_UNMODIFIED:
if (LiveBroadcastDelay > 0) {
checkLiveBroadcastStatus();
}
break;
case LOAD_PGN_FAIL:
default:
if (LiveBroadcastDelay === 0) {
pgnGameFromPgnText(alertPgn);
undoStackReset();
Init();
customFunctionOnPgnTextLoad();
} else { // live broadcast: wait for live show start
if (!LiveBroadcastStarted) {
pgnGameFromPgnText(LiveBroadcastPlaceholderPgn);
firstStart = true;
undoStackReset();
Init();
checkLiveBroadcastStatus();
customFunctionOnPgnTextLoad();
} else {
checkLiveBroadcastStatus();
}
}
break;
}
if (LiveBroadcastDelay > 0) {
restartLiveBroadcastTimeout();
}
}
var http_request_last_id = 0;
function loadPgnFromPgnUrl(pgnUrl) {
LiveBroadcastLastRefreshedLocal = (new Date()).toLocaleString();
try {
var http_request = new XMLHttpRequest();
if (http_request.overrideMimeType) {
// 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
http_request.overrideMimeType("text/plain"); // this assumes the PGN file is encoded as unicode UTF-8
// 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
// http_request.overrideMimeType("text/plain; charset=x-user-defined"); // this works with a binary file, such as a zipfile, but would need further processing
}
} catch (e) {
myAlert('error: failed creating XMLHttpRequest for PGN URL\n' + pgnUrl, true);
loadPgnCheckingLiveStatus(LOAD_PGN_FAIL);
return false;
}
var http_request_id = http_request_last_id++;
http_request.onreadystatechange = function () {
updatePgnFromHttpRequest(http_request, http_request_id);
};
try {
var randomizer = "";
// anti-caching #1
if ((LiveBroadcastDelay > 0) && (pgnUrl.indexOf("?") == -1) && (pgnUrl.indexOf("#") == -1)) {
randomizer = "?noCache=" + (0x1000000000 + Math.floor((Math.random() * 0xF000000000))).toString(16).toUpperCase();
}
http_request.open("GET", pgnUrl + randomizer);
// anti-caching #2
if (LiveBroadcastDelay > 0) {
http_request.setRequestHeader("If-Modified-Since", LiveBroadcastLastModifiedHeader);
}
http_request.send(null);
} catch (e) {
myAlert('error: failed sending XMLHttpRequest for PGN URL\n' + pgnUrl, true);
return false;
}
return true;
}
function SetPgnUrl(url) {
pgnUrl = url;
}
function LiveBroadcastLastModified_Reset() {
LiveBroadcastLastModified = new Date(0);
LiveBroadcastLastModifiedHeader = LiveBroadcastLastModified.toUTCString();
}
function LiveBroadcastLastReceivedLocal_Reset() {
LiveBroadcastLastReceivedLocal = 'unavailable';
}
function LiveBroadcastLastModified_ServerTime() {
return LiveBroadcastLastModified.getTime() === 0 ? 'unavailable' : LiveBroadcastLastModifiedHeader;
}
function pauseLiveBroadcast() {
if ((LiveBroadcastDelay === 0) || (LiveBroadcastPaused)) {
return;
}
LiveBroadcastPaused = true;
clearTimeout(LiveBroadcastInterval);
LiveBroadcastInterval = null;
LiveBroadcastTicker--;
checkLiveBroadcastStatus();
LiveBroadcastTicker++;
}
function restartLiveBroadcast() {
if (LiveBroadcastDelay === 0) {
return;
}
LiveBroadcastPaused = false;
refreshPgnSource();
}
function checkLiveBroadcastStatus() {
if (LiveBroadcastDelay === 0) {
return;
}
var theTitle, theHTML, theObj, ii;
// broadcast started yet?
if (LiveBroadcastStarted === false || typeof (pgnHeader) == "undefined" || (numberOfGames == 1 && gameEvent[0] == LiveBroadcastPlaceholderEvent)) {
// no
LiveBroadcastEnded = false;
LiveBroadcastGamesRunning = 0;
theTitle = "live broadcast yet to start";
} else {
// yes
var lbgr = 0, lbga = 0;
for (ii = 0; ii < numberOfGames; ii++) {
if (gameResult[ii].indexOf('*') >= 0) {
lbga++;
if (!pgnGame[ii].match(/^\s*\*?\s*$/)) {
lbgr++;
}
}
}
LiveBroadcastEnded = ((lbga === 0) && (!LiveBroadcastEndlessMode));
LiveBroadcastGamesRunning = lbgr;
theTitle = LiveBroadcastEnded ? "live broadcast ended" : LiveBroadcastPaused ? "live broadcast paused" : lbgr + " live game" + (lbgr == 1 ? "" : "s") + " out of " + numberOfGames;
}
theHTML = LiveBroadcastEnded ? "#" : LiveBroadcastPaused ? "+" : "=";
theHTML = (LiveBroadcastTicker % 4 === 3 ? theHTML : " ") + (LiveBroadcastTicker % 2 === 0 ? theHTML : " ") + (LiveBroadcastTicker % 4 === 1 ? theHTML : " ");
theHTML = LiveBroadcastGamesRunning + "" + theHTML + "" + numberOfGames;
theHTML = "" + theHTML + "";
if (theObj = document.getElementById("GameLiveStatus")) {
theObj.innerHTML = theHTML;
theObj.title = theTitle;
}
if (theObj = document.getElementById("GameLiveLastRefreshed")) {
theObj.innerHTML = LiveBroadcastLastRefreshedLocal;
}
if (theObj = document.getElementById("GameLiveLastReceived")) {
theObj.innerHTML = LiveBroadcastLastReceivedLocal;
}
if (theObj = document.getElementById("GameLiveLastModifiedServer")) {
theObj.innerHTML = LiveBroadcastLastModified_ServerTime();
}
customFunctionOnCheckLiveBroadcastStatus();
}
function restartLiveBroadcastTimeout() {
if (LiveBroadcastDelay === 0) {
return;
}
if (LiveBroadcastInterval) {
clearTimeout(LiveBroadcastInterval);
LiveBroadcastInterval = null;
}
if ((!LiveBroadcastEnded) && (!LiveBroadcastPaused)) {
LiveBroadcastInterval = setTimeout("refreshPgnSource()", LiveBroadcastDelay * 60000);
}
LiveBroadcastTicker++;
}
var LiveBroadcastFoundOldGame = false;
var LiveBroadcastOldCurrentVar;
var LiveBroadcastOldCurrentPly;
var LiveBroadcastOldCurrentPlyLast = false;
function refreshPgnSource() {
if (LiveBroadcastDelay === 0) {
return;
}
if (LiveBroadcastInterval) {
clearTimeout(LiveBroadcastInterval);
LiveBroadcastInterval = null;
}
if (LiveBroadcastDemo) {
var newPly, addedPly = 0;
for (var ii = 0; ii < numberOfGames; ii++) {
// 5% 15% 40%
newPly = [3, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1][Math.floor(20 * Math.random())] || 0;
if (gameDemoMaxPly[ii] <= gameDemoLength[ii]) {
gameDemoMaxPly[ii] += newPly;
addedPly += newPly;
}
}
if (addedPly > 0) {
LiveBroadcastLastReceivedLocal = (new Date()).toLocaleString();
}
}
if (pgnUrl) {
loadPgnFromPgnUrl(pgnUrl);
} else if (document.getElementById("pgnText")) {
loadPgnFromTextarea("pgnText");
} else {
pgnGameFromPgnText(alertPgn);
undoStackReset();
Init();
customFunctionOnPgnTextLoad();
myAlert('error: missing PGN URL location and pgnText object in the HTML file', true);
}
}
function loadPgnFromTextarea(textareaId) {
var res = LOAD_PGN_FAIL, text, theObj;
LiveBroadcastLastRefreshedLocal = (new Date()).toLocaleString();
if (!(theObj = document.getElementById(textareaId))) {
myAlert('error: missing ' + textareaId + ' textarea object in the HTML file', true);
} else {
if (document.getElementById(textareaId).tagName.toLowerCase() == "textarea") {
text = document.getElementById(textareaId).value;
} else { // compatibility with pgn4web up to 1.77: used for pgnText
text = document.getElementById(textareaId).innerHTML;
// fixes browser issue removing \n from innerHTML
if (text.indexOf('\n') < 0) {
text = text.replace(/((\[[^\[\]]*\]\s*)+)/g, "\n$1\n");
}
// fixes browser issue replacing quotes with "
if (text.indexOf('"') < 0) {
text = text.replace(/(")/g, '"');
}
}
// no header: add emptyPgnHeader
if (pgnHeaderTagRegExp.test(text) === false) {
text = emptyPgnHeader + "\n" + text;
}
if (pgnGameFromPgnText(text)) {
res = LOAD_PGN_OK;
LiveBroadcastLastReceivedLocal = (new Date()).toLocaleString();
} else {
myAlert('error: no games found in ' + textareaId + ' object in the HTML file');
}
}
loadPgnCheckingLiveStatus(res);
}
function createBoard() {
if (pgnUrl) {
loadPgnFromPgnUrl(pgnUrl);
} else if (document.getElementById("pgnText")) {
loadPgnFromTextarea("pgnText");
} else {
pgnGameFromPgnText(alertPgn);
undoStackReset();
Init();
customFunctionOnPgnTextLoad();
myAlert('error: missing PGN URL location or pgnText in the HTML file', true);
}
}
function setCurrentGameFromInitialGame() {
switch (initialGame) {
case "first":
currentGame = 0;
break;
case "last":
currentGame = numberOfGames - 1;
break;
case "random":
currentGame = Math.floor(Math.random() * numberOfGames);
break;
default:
if (isNaN(parseInt(initialGame, 10))) {
currentGame = gameNumberSearchPgn(initialGame, false, true);
if (!currentGame) {
currentGame = 0;
}
} else {
initialGame = parseInt(initialGame, 10);
initialGame = initialGame < 0 ? -Math.floor(-initialGame) : Math.floor(initialGame);
if (initialGame < -numberOfGames) {
currentGame = 0;
} else if (initialGame < 0) {
currentGame = numberOfGames + initialGame;
} else if (initialGame === 0) {
currentGame = Math.floor(Math.random() * numberOfGames);
} else if (initialGame <= numberOfGames) {
currentGame = (initialGame - 1);
} else {
currentGame = numberOfGames - 1;
}
}
break;
}
}
function GoToInitialHalfmove() {
var iv, ih;
if (initialVariation < 0) {
iv = Math.max(numberOfVars + initialVariations, 0);
} else {
iv = Math.min(initialVariation, numberOfVars - 1);
}
switch (initialHalfmove) {
case "start":
GoToMove((StartPlyVar[iv] + (iv ? 1 : 0)), iv);
break;
case "end":
GoToMove(StartPlyVar[iv] + PlyNumberVar[iv], iv);
break;
case "random":
GoToMove((StartPlyVar[iv] + (iv ? 1 : 0)) + Math.floor(Math.random() * (StartPlyVar[iv] + PlyNumberVar[iv])), iv);
break;
case "comment":
case "variation":
GoToMove((StartPlyVar[iv] + (iv ? 1 : 0)), iv);
MoveToNextComment(initialHalfmove == "variation");
break;
default:
if (isNaN(initialHalfmove = parseInt(initialHalfmove, 10))) {
initialHalfmove = 0;
}
if (initialHalfmove < 0) {
ih = Math.max(StartPlyVar[iv] + PlyNumberVar[iv] + 1 + initialHalfmove, StartPly);
} else {
ih = Math.min(initialHalfmove, StartPlyVar[iv] + PlyNumberVar[iv]);
}
GoToMove(ih, iv);
break;
}
}
function Init(nextGame) {
if (nextGame !== undefined) {
if ((!isNaN(nextGame)) && (nextGame >= 0) && (nextGame < numberOfGames)) {
currentGame = parseInt(nextGame, 10);
} else {
return;
}
}
if (isAutoPlayOn) {
SetAutoPlay(false);
}
InitImages();
if (firstStart) {
LoadGameHeaders();
setCurrentGameFromInitialGame();
}
if ((gameSetUp[currentGame] !== undefined) && (gameSetUp[currentGame] != "1")) {
InitFEN();
} else {
InitFEN(gameFEN[currentGame]);
}
OpenGame(currentGame);
CurrentPly = StartPly;
if (firstStart || alwaysInitialHalfmove) {
GoToInitialHalfmove();
setTimeout("autoScrollToCurrentMoveIfEnabled();", Math.min(666, 0.9 * Delay));
} else {
synchMoves();
RefreshBoard();
HighlightLastMove();
autoScrollToCurrentMoveIfEnabled();
// customFunctionOnMove here for consistency: null move starting new game
customFunctionOnMove();
if (typeof (engineWinOnMove) == "function") {
engineWinOnMove();
}
}
if ((firstStart) && (autostartAutoplay)) {
SetAutoPlay(true);
}
customFunctionOnPgnGameLoad();
initialVariation = 0;
firstStart = false;
}
function myAlertFEN(FenString, text) {
myAlert("error: invalid FEN in game " + (currentGame + 1) + ": " + text + "\n" + FenString, true);
}
function InitFEN(startingFEN) {
var ii, jj, cc, color, castlingRookCol, fullMoveNumber;
var FenString = typeof (startingFEN) != "string" ? FenStringStart : startingFEN.replace(/\\/g, "/").replace(/[^a-zA-Z0-9\s\/-]/g, " ").replace(/(^\s*|\s*$)/g, "").replace(/\s+/g, " ");
for (ii = 0; ii < 8; ++ii) {
for (jj = 0; jj < 8; ++jj) {
Board[ii][jj] = 0;
}
}
StartPly = 0;
MoveCount = StartPly;
MoveColor = StartPly % 2;
var newEnPassant = false;
var newEnPassantCol;
CastlingLong = [0, 0];
CastlingShort = [7, 7];
InitialHalfMoveClock = 0;
HistVar[StartPly] = 0;
HistNull[StartPly] = 0;
if (FenString == FenStringStart) {
for (color = 0; color < 2; color++) {
// K Q N B R p
PieceType[color] = [1, 2, 5, 5, 4, 4, 3, 3, 6, 6, 6, 6, 6, 6, 6, 6];
PieceCol[color] = [4, 3, 1, 6, 2, 5, 0, 7, 0, 1, 2, 3, 4, 5, 6, 7];
PieceMoveCounter[color] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
PieceRow[color] = color ? [7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6] :
[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1];
for (ii = 0; ii < 16; ii++) {
var col = PieceCol[color][ii];
var row = PieceRow[color][ii];
Board[col][row] = (1 - 2 * color) * PieceType[color][ii];
}
}
} else {
var kk, ll, nn, mm;
for (ii = 0; ii < 2; ii++) {
for (jj = 0; jj < 16; jj++) {
PieceType[ii][jj] = -1;
PieceCol[ii][jj] = 0;
PieceRow[ii][jj] = 0;
PieceMoveCounter[ii][jj] = 0;
}
}
ii = 0;
jj = 7;
ll = 0;
nn = 1;
mm = 1;
cc = FenString.charAt(ll++);
while (cc != " ") {
if (cc == "/") {
if (ii != 8) {
myAlertFEN(FenString, "piece placement: each row needs 8 squares");
InitFEN();
return;
}
ii = 0;
jj--;
}
if (ii == 8) {
myAlertFEN(FenString, "piece placement: each row needs 8 squares");
InitFEN();
return;
}
if (!isNaN(cc)) {
ii += parseInt(cc, 10);
if ((ii < 0) || (ii > 8)) {
myAlertFEN(FenString, "piece placement: each row needs 8 squares");
InitFEN();
return;
}
}
if (cc === PiecesArr[0].toUpperCase()) {
if (PieceType[0][0] != -1) {
myAlertFEN(FenString, "piece placement: more than 1 white king");
InitFEN();
return;
}
PieceType[0][0] = 1;
PieceCol[0][0] = ii;
PieceRow[0][0] = jj;
ii++;
} else if (cc === PiecesArr[0].toLowerCase()) {
if (PieceType[1][0] != -1) {
myAlertFEN(FenString, "piece placement: more than 1 black king");
InitFEN();
return;
}
PieceType[1][0] = 1;
PieceCol[1][0] = ii;
PieceRow[1][0] = jj;
ii++;
}
for (kk = 1; kk < 6; kk++) {
if (cc === PiecesArr[kk].toUpperCase()) {
if (nn == 16) {
myAlertFEN(FenString, "piece placement: more than 16 white pieces");
InitFEN();
return;
}
PieceType[0][nn] = kk + 1;
PieceCol[0][nn] = ii;
PieceRow[0][nn] = jj;
nn++;
ii++;
} else if (cc === PiecesArr[kk].toLowerCase()) {
if (mm == 16) {
myAlertFEN(FenString, "piece placement: more than 16 black pieces");
InitFEN();
return;
}
PieceType[1][mm] = kk + 1;
PieceCol[1][mm] = ii;
PieceRow[1][mm] = jj;
mm++;
ii++;
}
}
cc = ll < FenString.length ? FenString.charAt(ll++) : " ";
}
if ((ii != 8) || (jj !== 0)) {
myAlertFEN(FenString, "piece placement: string termination issue");
InitFEN();
return;
}
if ((PieceType[0][0] == -1) || (PieceType[1][0] == -1)) {
myAlertFEN(FenString, "missing king");
InitFEN();
return;
}
if (ll == FenString.length) {
FenString += " w " + assumedCastleRights() + " - 0 1";
ll++;
}
cc = FenString.charAt(ll++);
if ((cc == "w") || (cc == "b")) {
if (cc == "b") {
StartPly += 1;
MoveColor = 1;
}
} else {
myAlertFEN(FenString, "invalid active color");
}
// set board
for (color = 0; color < 2; ++color) {
for (ii = 0; ii < 16; ii++) {
if (PieceType[color][ii] != -1) {
col = PieceCol[color][ii];
row = PieceRow[color][ii];
Board[col][row] = (1 - 2 * color) * (PieceType[color][ii]);
}
}
}
ll++;
if (ll >= FenString.length) {
myAlertFEN(FenString, "missing castling availability");
FenString += " " + assumedCastleRights() + " - 0 1";
ll++;
}
CastlingLong = [-1, -1];
CastlingShort = [-1, -1];
cc = FenString.charAt(ll++);
while (cc != " ") {
if (cc === PiecesArr[0].toUpperCase()) {
for (CastlingShort[0] = 7; CastlingShort[0] > PieceCol[0][0]; CastlingShort[0]--) {
if (Board[CastlingShort[0]][0] == 3) {
break;
}
}
if (CastlingShort[0] <= PieceCol[0][0]) {
myAlertFEN(FenString, "missing castling rook " + cc);
CastlingShort[0] = -1;
}
} else if (cc === PiecesArr[1].toUpperCase()) {
for (CastlingLong[0] = 0; CastlingLong[0] < PieceCol[0][0]; CastlingLong[0]++) {
if (Board[CastlingLong[0]][0] == 3) {
break;
}
}
if (CastlingLong[0] >= PieceCol[0][0]) {
myAlertFEN(FenString, "missing castling rook " + cc);
CastlingLong[0] = -1;
}
} else if (cc === PiecesArr[0].toLowerCase()) {
for (CastlingShort[1] = 7; CastlingShort[1] > PieceCol[1][0]; CastlingShort[1]--) {
if (Board[CastlingShort[1]][7] == -3) {
break;
}
}
if (CastlingShort[1] <= PieceCol[1][0]) {
myAlertFEN(FenString, "missing castling rook " + cc);
CastlingShort[1] = -1;
}
} else if (cc === PiecesArr[1].toLowerCase()) {
for (CastlingLong[1] = 0; CastlingLong[1] < PieceCol[1][0]; CastlingLong[1]++) {
if (Board[CastlingLong[1]][7] == -3) {
break;
}
}
if (CastlingLong[1] >= PieceCol[1][0]) {
myAlertFEN(FenString, "missing castling rook " + cc);
CastlingLong[1] = -1;
}
}
castlingRookCol = columnsLetters.toUpperCase().indexOf(cc);
if (castlingRookCol >= 0) {
color = 0;
} else {
castlingRookCol = columnsLetters.toLowerCase().indexOf(cc);
if (castlingRookCol >= 0) {
color = 1;
}
}
if (castlingRookCol >= 0) {
if (Board[castlingRookCol][color * 7] == (1 - 2 * color) * 3) {
if (castlingRookCol > PieceCol[color][0]) {
CastlingShort[color] = castlingRookCol;
}
if (castlingRookCol < PieceCol[color][0]) {
CastlingLong[color] = castlingRookCol;
}
} else {
myAlertFEN(FenString, "missing castling rook " + cc);
}
}
cc = ll < FenString.length ? FenString.charAt(ll++) : " ";
}
if (ll >= FenString.length) {
myAlertFEN(FenString, "missing en passant square");
FenString += " - 0 1";
ll++;
}
cc = FenString.charAt(ll++);
while (cc != " ") {
if ((cc.charCodeAt(0) - 97 >= 0) && (cc.charCodeAt(0) - 97 <= 7)) {
newEnPassant = true;
newEnPassantCol = cc.charCodeAt(0) - 97;
}
cc = ll < FenString.length ? FenString.charAt(ll++) : " ";
}
if (ll >= FenString.length) {
myAlertFEN(FenString, "missing halfmove clock");
FenString += " 0 1";
ll++;
}
InitialHalfMoveClock = 0;
cc = FenString.charAt(ll++);
while (cc != " ") {
if (isNaN(cc)) {
myAlertFEN(FenString, "invalid halfmove clock");
break;
}
InitialHalfMoveClock = InitialHalfMoveClock * 10 + parseInt(cc, 10);
cc = ll < FenString.length ? FenString.charAt(ll++) : " ";
}
if (ll >= FenString.length) {
myAlertFEN(FenString, "missing fullmove number");
FenString += " 1";
ll++;
}
fullMoveNumber = 0;
cc = FenString.charAt(ll++);
while (cc != " ") {
if (isNaN(cc)) {
myAlertFEN(FenString, "invalid fullmove number");
fullMoveNumber = 1;
break;
}
fullMoveNumber = fullMoveNumber * 10 + parseInt(cc, 10);
cc = ll < FenString.length ? FenString.charAt(ll++) : " ";
}
if (fullMoveNumber === 0) {
myAlertFEN(FenString, "invalid fullmove 0 set to 1");
fullMoveNumber = 1;
}
StartPly += 2 * (fullMoveNumber - 1);
if (IsCheck(PieceCol[MoveColor ? 0 : 1][0], PieceRow[MoveColor ? 0 : 1][0], MoveColor ? 0 : 1)) {
myAlertFEN(FenString, (MoveColor ? "Black" : "White") + " to move checking " + (MoveColor ? "white" : "black") + " king");
}
HistEnPassant[StartPly] = newEnPassant;
HistEnPassantCol[StartPly] = newEnPassantCol;
HistNull[StartPly] = 0;
HistVar[StartPly] = 0;
}
}
// castling rights assuming kings and rooks starting positions as in normal chess
function assumedCastleRights() {
var ii, rights = "";
if ((PieceRow[0][0] === 0) && (PieceCol[0][0] === 4)) {
for (ii = 0; ii < PieceType[0].length; ii++) {
if ((PieceType[0][ii] === 3) && (PieceRow[0][ii] === 0) && (PieceCol[0][ii] === 7)) {
rights += PiecesArr[0].toUpperCase();
} else if ((PieceType[0][ii] === 3) && (PieceRow[0][ii] === 0) && (PieceCol[0][ii] === 0)) {
rights += PiecesArr[1].toUpperCase();
}
}
}
if ((PieceRow[1][0] === 7) && (PieceCol[1][0] === 4)) {
for (ii = 0; ii < PieceType[1].length; ii++) {
if ((PieceType[1][ii] === 3) && (PieceRow[1][ii] === 7) && (PieceCol[1][ii] === 7)) {
rights += PiecesArr[0].toLowerCase();
} else if ((PieceType[1][ii] === 3) && (PieceRow[1][ii] === 7) && (PieceCol[1][ii] === 0)) {
rights += PiecesArr[1].toLowerCase();
}
}
}
return rights || "-";
}
function SetImageType(extension) {
imageType = extension;
}
function InitImages() {
if (ImagePathOld === ImagePath) {
return;
}
if ((ImagePath.length > 0) && (ImagePath[ImagePath.length - 1] != '/')) {
ImagePath += '/';
}
ClearImg = new Image();
ClearImg.src = ImagePath + 'clear.' + imageType;
var ColorName = new Array("w", "b");
var PiecePrefix = new Array("k", "q", "r", "b", "n", "p");
for (var c = 0; c < 2; ++c) {
for (var p = 1; p < 7; p++) {
PieceImg[c][p] = new Image();
PieceImg[c][p].src = ImagePath + ColorName[c] + PiecePrefix[p - 1] + '.' + imageType;
}
}
ImagePathOld = ImagePath;
}
function IsCheck(col, row, color) {
var ii, jj;
var sign = 2 * color - 1; // white or black
// other king giving check?
if ((Math.abs(PieceCol[1 - color][0] - col) <= 1) && (Math.abs(PieceRow[1 - color][0] - row) <= 1)) {
return true;
}
// knight?
for (ii = -2; ii <= 2; ii += 4) {
for (jj = -1; jj <= 1; jj += 2) {
if (SquareOnBoard(col + ii, row + jj)) {
if (Board[col + ii][row + jj] == sign * 5) {
return true;
}
}
if (SquareOnBoard(col + jj, row + ii)) {
if (Board[col + jj][row + ii] == sign * 5) {
return true;
}
}
}
}
// pawn?
for (ii = -1; ii <= 1; ii += 2) {
if (SquareOnBoard(col + ii, row - sign)) {
if (Board[col + ii][row - sign] == sign * 6) {
return true;
}
}
}
// queens, rooks, bishops?
for (ii = -1; ii <= 1; ++ii) {
for (jj = -1; jj <= 1; ++jj) {
if ((ii !== 0) || (jj !== 0)) {
var checkCol = col + ii;
var checkRow = row + jj;
var thisPiece = 0;
while (SquareOnBoard(checkCol, checkRow) && (thisPiece === 0)) {
thisPiece = Board[checkCol][checkRow];
if (thisPiece === 0) {
checkCol += ii;
checkRow += jj;
} else {
if (thisPiece == sign * 2) {
return true;
}
if ((thisPiece == sign * 3) && ((ii === 0) || (jj === 0))) {
return true;
}
if ((thisPiece == sign * 4) && ((ii !== 0) && (jj !== 0))) {
return true;
}
}
}
}
}
}
return false;
}
function fixRegExp(exp) {
return exp.replace(/([\[\]\(\)\{\}\.\*\+\^\$\|\?\\])/g, "\\$1");
}
function LoadGameHeaders() {
var ii;
var parse;
gameEvent.length = gameSite.length = gameRound.length = gameDate.length = 0;
gameWhite.length = gameBlack.length = gameResult.length = 0;
gameSetUp.length = gameFEN.length = 0;
gameInitialWhiteClock.length = gameInitialBlackClock.length = 0;
gameVariant.length = 0;
pgnHeaderTagRegExpGlobal.lastIndex = 0; // resets global regular expression
for (ii = 0; ii < numberOfGames; ++ii) {
var ss = pgnHeader[ii];
gameEvent[ii] = gameSite[ii] = gameRound[ii] = gameDate[ii] = "";
gameWhite[ii] = gameBlack[ii] = gameResult[ii] = "";
gameInitialWhiteClock[ii] = gameInitialBlackClock[ii] = "";
gameVariant[ii] = "";
while (parse = pgnHeaderTagRegExpGlobal.exec(ss)) {
switch (parse[1]) {
case 'Event':
gameEvent[ii] = parse[2];
break;
case 'Site':
gameSite[ii] = parse[2];
break;
case 'Round':
gameRound[ii] = parse[2];
break;
case 'Date':
gameDate[ii] = parse[2];
break;
case 'White':
gameWhite[ii] = parse[2];
break;
case 'Black':
gameBlack[ii] = parse[2];
break;
case 'Result':
gameResult[ii] = parse[2];
break;
case 'SetUp':
gameSetUp[ii] = parse[2];
break;
case 'FEN':
gameFEN[ii] = parse[2];
break;
case 'WhiteClock':
gameInitialWhiteClock[ii] = parse[2];
break;
case 'BlackClock':
gameInitialBlackClock[ii] = parse[2];
break;
case 'Variant':
gameVariant[ii] = parse[2];
break;
default:
break;
}
}
}
if ((LiveBroadcastDemo) && (numberOfGames > 0)) {
for (ii = 0; ii < numberOfGames; ++ii) {
if ((gameDemoLength[ii] === undefined) || (gameDemoLength[ii] === 0)) {
InitFEN(gameFEN[ii]);
ParsePGNGameString(pgnGame[ii]);
gameDemoLength[ii] = PlyNumber;
}
if (gameDemoMaxPly[ii] === undefined) {
gameDemoMaxPly[ii] = 0;
}
if ((gameDemoMaxPly[ii] <= gameDemoLength[ii]) && (gameDemoLength[ii] > 0)) {
gameResult[ii] = '*';
}
}
}
return;
}
function MoveBackward(diff, scanOnly) {
// CurrentPly counts from 1, starting position 0
var goFromPly = CurrentPly - 1;
var goToPly = goFromPly - diff;
if (goToPly < StartPly) {
goToPly = StartPly - 1;
}
// reconstruct old position
for (var thisPly = goFromPly; thisPly > goToPly; --thisPly) {
CurrentPly--;
MoveColor = 1 - MoveColor;
CurrentVar = HistVar[thisPly];
UndoMove(thisPly);
}
if (scanOnly) {
return;
}
synchMoves();
// old position reconstructed: refresh board
RefreshBoard();
HighlightLastMove();
autoScrollToCurrentMoveIfEnabled();
// autoplay: restart timeout
if (AutoPlayInterval) {
clearTimeout(AutoPlayInterval);
AutoPlayInterval = null;
}
if (isAutoPlayOn) {
if (goToPly >= StartPlyVar[CurrentVar]) {
AutoPlayInterval = setTimeout("MoveBackward(1)", Delay);
} else {
SetAutoPlay(false);
}
}
customFunctionOnMove();
if (typeof (engineWinOnMove) == "function") {
engineWinOnMove();
}
}
function MoveForward(diff, targetVar, scanOnly) {
var nextVar, nextVarStartPly, move, text;
var oldVar = -1;
if (typeof (targetVar) == "undefined") {
targetVar = CurrentVar;
}
// CurrentPly counts from 1, starting position 0
var goToPly = CurrentPly + parseInt(diff, 10);
if (goToPly > StartPlyVar[targetVar] + PlyNumberVar[targetVar]) {
goToPly = StartPlyVar[targetVar] + PlyNumberVar[targetVar];
}
// reach to selected move checking legality
for (var thisPly = CurrentPly; thisPly < goToPly; ++thisPly) {
if (targetVar !== CurrentVar) {
for (var ii = 0; ii < PredecessorsVars[targetVar].length; ii++) {
if (PredecessorsVars[targetVar][ii] === CurrentVar) {
break;
}
}
if (ii === PredecessorsVars[targetVar].length) {
myAlert("error: unknown path to variation " + targetVar + " from " + CurrentVar + " in game " + (currentGame + 1), true);
return;
} else {
nextVarStartPly = StartPlyVar[PredecessorsVars[targetVar][ii + 1]];
for (ii = ii + 1; ii < PredecessorsVars[targetVar].length - 1; ii++) {
if (StartPlyVar[PredecessorsVars[targetVar][ii + 1]] !== StartPlyVar[PredecessorsVars[targetVar][ii]]) {
break;
}
}
nextVar = PredecessorsVars[targetVar][ii];
}
} else {
nextVar = nextVarStartPly = -1;
}
if (thisPly === nextVarStartPly) {
oldVar = CurrentVar;
CurrentVar = nextVar;
}
if (typeof (move = MovesVar[CurrentVar][thisPly]) == "undefined") {
break;
}
if (ParseLastMoveError = !ParseMove(move, thisPly)) {
text = (Math.floor(thisPly / 2) + 1) + ((thisPly % 2) === 0 ? '. ' : '... ');
myAlert('error: invalid ply ' + text + move + ' in game ' + (currentGame + 1) + ' variation ' + CurrentVar, true);
if (thisPly === nextVarStartPly) {
CurrentVar = oldVar;
}
break;
}
MoveColor = 1 - MoveColor;
}
// new position: update ply count, then refresh board
CurrentPly = thisPly;
if (scanOnly) {
return;
}
synchMoves();
RefreshBoard();
HighlightLastMove();
autoScrollToCurrentMoveIfEnabled();
// autoplay: restart timeout
if (AutoPlayInterval) {
clearTimeout(AutoPlayInterval);
AutoPlayInterval = null;
}
if (ParseLastMoveError) {
SetAutoPlay(false);
} else if (thisPly == goToPly) {
if (isAutoPlayOn) {
if (goToPly < StartPlyVar[CurrentVar] + PlyNumberVar[CurrentVar]) {
AutoPlayInterval = setTimeout("MoveForward(1)", Delay);
} else {
if (autoplayNextGame && (CurrentVar === 0)) {
AutoPlayInterval = setTimeout("AutoplayNextGame()", Delay);
} else {
SetAutoPlay(false);
}
}
}
}
customFunctionOnMove();
if (typeof (engineWinOnMove) == "function") {
engineWinOnMove();
}
}
var lastSynchCurrentVar = -1;
function synchMoves() {
var start, end;
if (CurrentVar === lastSynchCurrentVar) {
return;
}
Moves = new Array();
MoveComments = new Array();
for (var ii = 0; ii < PredecessorsVars[CurrentVar].length; ii++) {
start = StartPlyVar[PredecessorsVars[CurrentVar][ii]];
if (ii < PredecessorsVars[CurrentVar].length - 1) {
end = StartPlyVar[PredecessorsVars[CurrentVar][ii + 1]];
} else {
end = StartPlyVar[PredecessorsVars[CurrentVar][ii]] + PlyNumberVar[PredecessorsVars[CurrentVar][ii]];
}
for (var jj = start; jj < end; jj++) {
Moves[jj] = MovesVar[PredecessorsVars[CurrentVar][ii]][jj];
MoveComments[jj] = MoveCommentsVar[PredecessorsVars[CurrentVar][ii]][jj] || "";
}
}
MoveComments[jj] = MoveCommentsVar[PredecessorsVars[CurrentVar][ii - 1]][jj] || "";
PlyNumber = StartPlyVar[CurrentVar] + PlyNumberVar[CurrentVar] - StartPly;
lastSynchCurrentVar = CurrentVar;
}
function AutoplayNextGame() {
if (fatalErrorNumSinceReset === 0) {
if (numberOfGames > 0) {
Init((currentGame + 1) % numberOfGames);
if ((numberOfGames > 1) || (PlyNumber > 0)) {
SetAutoPlay(true);
return;
}
}
}
SetAutoPlay(false);
}
function MoveToNextComment(varOnly) {
for (var ii = CurrentPly + 1; ii <= StartPlyVar[CurrentVar] + PlyNumberVar[CurrentVar]; ii++) {
if (MoveComments[ii].match(pgn4webVariationRegExp) || (!varOnly && strippedMoveComment(ii))) {
GoToMove(ii);
break;
}
}
}
function MoveToPrevComment(varOnly) {
for (var ii = (CurrentPly - 1); ii >= StartPly; ii--) {
if ((ii > 0 || CurrentVar > 0) && ii === StartPlyVar[HistVar[ii + 1]]) {
GoToMove(ii + 1, HistVar[ii]);
break;
}
if (MoveComments[ii].match(pgn4webVariationRegExp) || (!varOnly && strippedMoveComment(ii))) {
GoToMove(ii);
break;
}
}
}
function OpenGame(gameId) {
ParsePGNGameString(pgnGame[gameId]);
currentGame = gameId;
ParseLastMoveError = false;
if (LiveBroadcastDemo) {
if (gameDemoMaxPly[gameId] <= PlyNumber) {
PlyNumber = PlyNumberVar[0] = gameDemoMaxPly[gameId];
}
}
PrintHTML(false);
}
var CurrentVar = -1;
var lastVarWithNoMoves;
var numberOfVars;
var MovesVar;
var MoveCommentsVar;
var GameHasComments;
var GameHasVariations;
var StartPlyVar;
var PlyNumberVar;
var CurrentVarStack;
var PlyNumberStack;
var PredecessorsVars;
function initVar() {
MovesVar = new Array();
MoveCommentsVar = new Array();
GameHasComments = false;
GameHasVariations = false;
StartPlyVar = new Array();
PlyNumberVar = new Array();
CurrentVar = -1;
lastVarWithNoMoves = [false];
numberOfVars = 0;
CurrentVarStack = new Array();
PlyNumber = 1;
PlyNumberStack = new Array();
PredecessorsVars = new Array();
startVar(false);
}
function startVar(isContinuation) {
if (CurrentVar >= 0) {
CurrentVarStack.push(CurrentVar);
PlyNumberStack.push(PlyNumber);
}
CurrentVar = numberOfVars++;
PredecessorsVars[CurrentVar] = CurrentVarStack.slice(0);
PredecessorsVars[CurrentVar].push(CurrentVar);
MovesVar[CurrentVar] = new Array();
MoveCommentsVar[CurrentVar] = new Array();
if (!isContinuation) {
if (lastVarWithNoMoves[lastVarWithNoMoves.length - 1]) {
myAlert("warning: malformed PGN data in game " + (currentGame + 1) + ": variation " + CurrentVar + " starting before parent", true);
} else {
PlyNumber -= 1;
}
}
lastVarWithNoMoves.push(true);
MoveCommentsVar[CurrentVar][StartPly + PlyNumber] = "";
StartPlyVar[CurrentVar] = StartPly + PlyNumber;
}
function closeVar() {
if (StartPly + PlyNumber === StartPlyVar[CurrentVar]) {
myAlert("warning: empty variation " + CurrentVar + " in game " + (currentGame + 1), false);
} else {
GameHasVariations = true;
}
lastVarWithNoMoves.pop();
PlyNumberVar[CurrentVar] = StartPly + PlyNumber - StartPlyVar[CurrentVar];
for (var ii = StartPlyVar[CurrentVar]; ii <= StartPlyVar[CurrentVar] + PlyNumberVar[CurrentVar]; ii++) {
if (MoveCommentsVar[CurrentVar][ii]) {
MoveCommentsVar[CurrentVar][ii] = MoveCommentsVar[CurrentVar][ii].replace(/\s+/g, ' ');
MoveCommentsVar[CurrentVar][ii] = translateNAGs(MoveCommentsVar[CurrentVar][ii]);
MoveCommentsVar[CurrentVar][ii] = MoveCommentsVar[CurrentVar][ii].replace(/\s+$/g, '');
} else {
MoveCommentsVar[CurrentVar][ii] = '';
}
}
if (CurrentVarStack.length) {
CurrentVar = CurrentVarStack.pop();
PlyNumber = PlyNumberStack.pop();
} else {
myAlert("error: closeVar error" + " in game " + (currentGame + 1), true);
}
}
function childrenVars(thisPly, thisVar) {
if (typeof (thisVar) == "undefined") {
thisVar = CurrentVar;
}
if (typeof (thisPly) == "undefined") {
thisPly = CurrentPly;
}
var children = new Array();
for (var ii = thisVar; ii < numberOfVars; ii++) {
if ((ii === thisVar && StartPlyVar[ii] + PlyNumberVar[ii] > thisPly) || (realParentVar(ii) === thisVar && StartPlyVar[ii] === thisPly && PlyNumberVar[ii] > 0)) {
children.push(ii);
}
}
return children;
}
function realParentVar(childVar) {
for (var ii = PredecessorsVars[childVar].length - 1; ii > 0; ii--) {
if (StartPlyVar[PredecessorsVars[childVar][ii]] !== StartPlyVar[PredecessorsVars[childVar][ii - 1]]) {
return PredecessorsVars[childVar][ii - 1];
}
}
return PredecessorsVars[childVar][ii];
}
function goToNextVariationSibling() {
if (CurrentPly === StartPly) {
return false;
}
var siblings = childrenVars(CurrentPly - 1, HistVar[CurrentPly - 1]);
if (siblings.length < 2) {
return false;
}
for (var ii = 0; ii < siblings.length; ii++) {
if (siblings[ii] === CurrentVar) {
break;
}
}
if (siblings[ii] !== CurrentVar) {
return false;
}
GoToMove(CurrentPly, siblings[(ii + 1) % siblings.length]);
return true;
}
function goToFirstChild() {
var children = childrenVars(CurrentPly, CurrentVar);
if (children.length < 1) {
return false;
}
if (children[0] === CurrentVar) {
if (children.length < 2) {
return false;
}
GoToMove(CurrentPly + 1, children[1]);
} else {
GoToMove(CurrentPly + 1, children[0]);
}
return true;
}
function ParsePGNGameString(gameString) {
var ii, start, end, move, moveCount, needle, commentStart, commentEnd, isContinuation;
var ssRep, ss = gameString, ssComm;
ss = ss.replace(pgn4webVariationRegExpGlobal, "[%_pgn4web_variation_ $1]");
// empty variations to comments
while ((ssRep = ss.replace(/\((([\?!+#\s]|\$\d+|{[^}]*})*)\)/g, ' $1 ')) !== ss) {
ss = ssRep;
}
ss = ss.replace(/^\s/, '');
ss = ss.replace(/\s$/, '');
initVar();
PlyNumber = 0;
for (start = 0; start < ss.length; start++) {
switch (ss.charAt(start)) {
case ' ':
case '\b':
case '\f':
case '\n':
case '\r':
case '\t':
break;
case '$':
commentStart = start;
commentEnd = commentStart + 1;
while ('0123456789'.indexOf(ss.charAt(commentEnd)) >= 0) {
commentEnd++;
if (commentEnd >= ss.length) {
break;
}
}
if (MoveCommentsVar[CurrentVar][StartPly + PlyNumber]) {
MoveCommentsVar[CurrentVar][StartPly + PlyNumber] += ' ';
}
MoveCommentsVar[CurrentVar][StartPly + PlyNumber] += translateNAGs(ss.substring(commentStart, commentEnd).replace(/(^\s*|\s*$)/, ''));
start = commentEnd - 1;
break;
case '!':
case '?':
commentStart = start;
commentEnd = commentStart + 1;
while ('!?'.indexOf(ss.charAt(commentEnd)) >= 0) {
commentEnd++;
if (commentEnd >= ss.length) {
break;
}
}
if (MoveCommentsVar[CurrentVar][StartPly + PlyNumber]) {
MoveCommentsVar[CurrentVar][StartPly + PlyNumber] += ' ';
}
MoveCommentsVar[CurrentVar][StartPly + PlyNumber] += ss.substring(commentStart, commentEnd);
start = commentEnd - 1;
break;
case '{':
commentStart = start + 1;
commentEnd = ss.indexOf('}', start + 1);
if (commentEnd < 0) {
myAlert('error: missing end comment } in game ' + (currentGame + 1), true);
commentEnd = ss.length;
}
if (MoveCommentsVar[CurrentVar][StartPly + PlyNumber]) {
MoveCommentsVar[CurrentVar][StartPly + PlyNumber] += ' ';
}
ssComm = translateNAGs(ss.substring(commentStart, commentEnd).replace(/(^\s*|\s*$)/, ''));
MoveCommentsVar[CurrentVar][StartPly + PlyNumber] += ssComm;
GameHasComments = GameHasComments || ssComm.replace(/\[%[^\]]*\]\s*/g, '').replace(basicNAGs, '').replace(/^\s+$/, '') !== '';
start = commentEnd;
break;
case '%':
// % must be first char of the line
if ((start > 0) && (ss.charAt(start - 1) != '\n')) {
break;
}
commentStart = start + 1;
commentEnd = ss.indexOf('\n', start + 1);
if (commentEnd < 0) {
commentEnd = ss.length;
}
start = commentEnd;
break;
case ';':
commentStart = start + 1;
commentEnd = ss.indexOf('\n', start + 1);
if (commentEnd < 0) {
commentEnd = ss.length;
}
if (MoveCommentsVar[CurrentVar][StartPly + PlyNumber]) {
MoveCommentsVar[CurrentVar][StartPly + PlyNumber] += ' ';
}
ssComm = translateNAGs(ss.substring(commentStart, commentEnd).replace(/(^\s*|\s*$)/, ''));
MoveCommentsVar[CurrentVar][StartPly + PlyNumber] += ssComm;
GameHasComments = GameHasComments || ssComm.replace(/\[%[^\]]*\]\s*/g, '').replace(basicNAGs, '').replace(/^\s+$/, '') !== '';
start = commentEnd;
break;
case '(':
if (isContinuation = (ss.charAt(start + 1) == '*')) {
start += 1;
}
MoveCommentsVar[CurrentVar][StartPly + PlyNumber] += ' [%pgn4web_variation ' + numberOfVars + '] ';
startVar(isContinuation);
break;
case ')':
closeVar();
break;
default:
needle = new Array('1-0', '0-1', '1/2-1/2', '*');
for (ii = 0; ii < needle.length; ii++) {
if (ss.indexOf(needle[ii], start) == start) {
if (CurrentVar === 0) {
end = ss.length;
} else {
end = start + needle[ii].length;
if (MoveCommentsVar[CurrentVar][StartPly + PlyNumber]) {
MoveCommentsVar[CurrentVar][StartPly + PlyNumber] += ' ';
}
MoveCommentsVar[CurrentVar][StartPly + PlyNumber] += needle[ii];
}
start = end;
break;
}
}
if (start == ss.length) {
break;
}
moveCount = Math.floor((StartPly + PlyNumber) / 2) + 1;
needle = moveCount.toString();
if (ss.indexOf(needle, start) == start) {
start += needle.length;
while (' .\n\r'.indexOf(ss.charAt(start)) != -1) {
start++;
}
}
if ((end = start + ss.substr(start).search(/[\s${;!?()]/)) < start) {
end = ss.length;
}
move = ss.substring(start, end);
MovesVar[CurrentVar][StartPly + PlyNumber] = CleanMove(move);
lastVarWithNoMoves[lastVarWithNoMoves.length - 1] = false;
if (ss.charAt(end) == ' ') {
start = end;
} else {
start = end - 1;
}
if (!MovesVar[CurrentVar][StartPly + PlyNumber].match(/^[\s+#]*$/)) { // to cope with malsformed PGN data
PlyNumber++;
MoveCommentsVar[CurrentVar][StartPly + PlyNumber] = '';
}
break;
}
}
if (CurrentVar !== 0) {
myAlert("error: ParsePGNGameString ends with CurrentVar " + CurrentVar + " in game " + (currentGame + 1), true);
while (CurrentVar > 0) {
closeVar();
}
}
StartPlyVar[0] = StartPly;
PlyNumberVar[0] = PlyNumber;
GameHasComments = GameHasComments || GameHasVariations;
lastSynchCurrentVar = -1;
}
var NAGstyle = 'default';
var NAG = new Array();
NAG[0] = '';
NAG[1] = '!'; // 'good move';
NAG[2] = '?'; // 'bad move';
NAG[3] = '!!'; // 'very good move';
NAG[4] = '??'; // 'very bad move';
NAG[5] = '!?'; // 'speculative move';
NAG[6] = '?!'; // 'questionable move';
NAG[7] = 'forced move'; // '[]';
NAG[8] = 'singular move'; // '[]';
NAG[9] = 'worst move'; // '??';
NAG[10] = 'drawish position'; // '=';
NAG[11] = 'equal chances, quiet position'; // '=';
NAG[12] = 'equal chances, active position'; // '=';
NAG[13] = 'unclear position'; // '~~';
NAG[14] = 'White has a slight advantage'; // NAG[15] = '+/=';
NAG[16] = 'White has a moderate advantage'; // NAG[17] = '+/-';
NAG[18] = 'White has a decisive advantage'; // NAG[19] = '+-';
NAG[20] = 'White has a crushing advantage'; // NAG[21] = '+-';
NAG[22] = 'White is in zugzwang'; // NAG[23] = '(.)';
NAG[24] = 'White has a slight space advantage'; // NAG[25] = '()';
NAG[26] = 'White has a moderate space advantage'; // NAG[27] = '()';
NAG[28] = 'White has a decisive space advantage'; // NAG[29] = '()';
NAG[30] = 'White has a slight time (development) advantage'; // NAG[31] = '@';
NAG[32] = 'White has a moderate time (development) advantage'; // NAG[33] = '@';
NAG[34] = 'White has a decisive time (development) advantage'; // NAG[35] = '@';
NAG[36] = 'White has the initiative'; // NAG[37] = '|^';
NAG[38] = 'White has a lasting initiative'; // NAG[39] = '|^';
NAG[40] = 'White has the attack'; // NAG[41] = '->';
NAG[42] = 'White has insufficient compensation for material deficit';
NAG[44] = 'White has sufficient compensation for material deficit'; // NAG[45] = '=/~';
NAG[46] = 'White has more than adequate compensation for material deficit'; // NAG[47] = '=/~';
NAG[48] = 'White has a slight center control advantage'; // NAG[49] = '[+]';
NAG[50] = 'White has a moderate center control advantage'; // NAG[51] = '[+]';
NAG[52] = 'White has a decisive center control advantage'; // NAG[53] = '[+]';
NAG[54] = 'White has a slight kingside control advantage'; // NAG[55] = '>>';
NAG[56] = 'White has a moderate kingside control advantage'; // NAG[57] = '>>';
NAG[58] = 'White has a decisive kingside control advantage'; // NAG[59] = '>>';
NAG[60] = 'White has a slight queenside control advantage'; // NAG[61] = '<<';
NAG[62] = 'White has a moderate queenside control advantage'; // NAG[63] = '<<';
NAG[64] = 'White has a decisive queenside control advantage'; // NAG[65] = '<<';
NAG[66] = 'White has a vulnerable first rank';
NAG[68] = 'White has a well protected first rank';
NAG[70] = 'White has a poorly protected king';
NAG[72] = 'White has a well protected king';
NAG[74] = 'White has a poorly placed king';
NAG[76] = 'White has a well placed king';
NAG[78] = 'White has a very weak pawn structure';
NAG[80] = 'White has a moderately weak pawn structure';
NAG[82] = 'White has a moderately strong pawn structure';
NAG[84] = 'White has a very strong pawn structure';
NAG[86] = 'White has poor knight placement';
NAG[88] = 'White has good knight placement';
NAG[90] = 'White has poor bishop placement';
NAG[92] = 'White has good bishop placement';
NAG[94] = 'White has poor rook placement';
NAG[96] = 'White has good rook placement';
NAG[98] = 'White has poor queen placement';
NAG[100] = 'White has good queen placement';
NAG[102] = 'White has poor piece coordination';
NAG[104] = 'White has good piece coordination';
NAG[106] = 'White has played the opening very poorly';
NAG[108] = 'White has played the opening poorly';
NAG[110] = 'White has played the opening well';
NAG[112] = 'White has played the opening very well';
NAG[114] = 'White has played the middlegame very poorly';
NAG[116] = 'White has played the middlegame poorly';
NAG[118] = 'White has played the middlegame well';
NAG[120] = 'White has played the middlegame very well';
NAG[122] = 'White has played the ending very poorly';
NAG[124] = 'White has played the ending poorly';
NAG[126] = 'White has played the ending well';
NAG[128] = 'White has played the ending very well';
NAG[130] = 'White has slight counterplay'; // NAG[131] = '<=>';
NAG[132] = 'White has moderate counterplay'; // NAG[133] = '<=>';
NAG[134] = 'White has decisive counterplay'; // NAG[135] = '<=>';
NAG[136] = 'White has moderate time control pressure'; // NAG[137] = '(+)';
NAG[138] = 'White has severe time control pressure'; // NAG[139] = '(+)';
for (i = 14; i < 139; i += 2) {
NAG[i + 1] = NAG[i].replace("White", "Black");
}
function translateNAGs(comment) {
var matches = comment.match(/\$+[0-9]+/g);
if (matches) {
for (var ii = 0; ii < matches.length; ii++) {
var nag = matches[ii].substr(1);
if (NAG[nag] !== undefined) {
comment = comment.replace(new RegExp("\\$+" + nag + "(?!\\d)"), NAG[nag]);
}
}
}
return comment;
}
function ParseMove(move, plyCount) {
var ii, ll;
var rem;
var toRowMarker = -1;
castleRook = -1;
mvIsCastling = 0;
mvIsPromotion = 0;
mvCapture = 0;
mvFromCol = -1;
mvFromRow = -1;
mvToCol = -1;
mvToRow = -1;
mvPiece = -1;
mvPieceId = -1;
mvPieceOnTo = -1;
mvCaptured = -1;
mvCapturedId = -1;
mvIsNull = 0;
if (typeof (move) == "undefined") {
return false;
}
HistEnPassant[plyCount + 1] = false;
HistEnPassantCol[plyCount + 1] = -1;
if (move.indexOf('--') === 0) {
mvIsNull = 1;
CheckLegality('--', plyCount);
return true;
}
// get destination column/row remembering what's left e.g. Rdxc3 exf8=Q#
for (ii = move.length - 1; ii > 0; ii--) {
if (!isNaN(move.charAt(ii))) {
mvToCol = move.charCodeAt(ii - 1) - 97;
mvToRow = move.charAt(ii) - 1;
rem = move.substring(0, ii - 1);
toRowMarker = ii;
break;
}
}
// final square did not make sense: maybe a castle?
if ((mvToCol < 0) || (mvToCol > 7) || (mvToRow < 0) || (mvToRow > 7)) {
// long castling first: looking for o-o will get o-o-o too
if (move.indexOf('O-O-O') === 0) {
mvIsCastling = 1;
mvPiece = 1;
mvPieceId = 0;
mvPieceOnTo = 1;
mvFromCol = 4;
mvToCol = 2;
mvFromRow = 7 * MoveColor;
mvToRow = 7 * MoveColor;
return CheckLegality('O-O-O', plyCount);
} else if (move.indexOf('O-O') === 0) {
mvIsCastling = 1;
mvPiece = 1;
mvPieceId = 0;
mvPieceOnTo = 1;
mvFromCol = 4;
mvToCol = 6;
mvFromRow = 7 * MoveColor;
mvToRow = 7 * MoveColor;
return CheckLegality('O-O', plyCount);
} else {
return false;
}
}
rem = rem.replace(/-/g, '');
// get piece and origin square: mark captures ('x' is there)
ll = rem.length;
if (ll > 4) {
return false;
}
mvPiece = -1; // make sure mvPiece is properly assigned later
if (ll === 0) {
mvPiece = 6;
} else {
for (ii = 5; ii > 0; ii--) {
if (rem.charAt(0) == PiecesArr[ii - 1]) {
mvPiece = ii;
break;
}
}
if (mvPiece == -1) {
if (columnsLetters.toLowerCase().indexOf(rem.charAt(0)) >= 0) {
mvPiece = 6;
}
}
if (mvPiece == -1) {
return false;
}
if (rem.charAt(ll - 1) == 'x') {
mvCapture = 1;
}
if (isNaN(move.charAt(ll - 1 - mvCapture))) {
mvFromCol = move.charCodeAt(ll - 1 - mvCapture) - 97;
if ((mvFromCol < 0) || (mvFromCol > 7)) {
mvFromCol = -1;
}
} else {
mvFromRow = move.charAt(ll - 1 - mvCapture) - 1;
if ((mvFromRow < 0) || (mvFromRow > 7)) {
mvFromRow = -1;
} else {
mvFromCol = move.charCodeAt(ll - 2 - mvCapture) - 97;
if ((mvFromCol < 0) || (mvFromCol > 7)) {
mvFromCol = -1;
}
}
}
if ((ll > 1) && (!mvCapture) && (mvFromCol == -1) && (mvFromRow == -1)) {
return false;
}
if ((mvPiece == 6) && (!mvCapture) && (mvFromCol == -1) && (mvFromRow == -1)) {
return false;
}
}
// "square to" occupied: capture (note en-passant case)
if ((Board[mvToCol][mvToRow] !== 0) || ((mvPiece == 6) && (HistEnPassant[plyCount]) && (mvToCol == HistEnPassantCol[plyCount]) && (mvToRow == 5 - 3 * MoveColor))) {
mvCapture = 1;
}
mvPieceOnTo = mvPiece;
if (mvPiece == 6) {
// move contains '=' or char after destination row: might be a promotion
ii = move.indexOf('=');
if (ii < 0) {
ii = toRowMarker;
}
if ((ii > 0) && (ii < move.length - 1)) {
var newPiece = move.charAt(ii + 1);
if (newPiece == PiecesArr[1]) {
mvPieceOnTo = 2;
} else if (newPiece == PiecesArr[2]) {
mvPieceOnTo = 3;
} else if (newPiece == PiecesArr[3]) {
mvPieceOnTo = 4;
} else if (newPiece == PiecesArr[4]) {
mvPieceOnTo = 5;
}
if (mvPieceOnTo != mvPiece) {
mvIsPromotion = 1;
}
}
if ((mvToRow == 7 * (1 - MoveColor)) ? !mvIsPromotion : mvIsPromotion) {
return false;
}
}
// which captured piece: if nothing found must be en-passant
if (mvCapture) {
for (mvCapturedId = 15; mvCapturedId >= 0; mvCapturedId--) {
if ((PieceType[1 - MoveColor][mvCapturedId] > 0) && (PieceCol[1 - MoveColor][mvCapturedId] == mvToCol) && (PieceRow[1 - MoveColor][mvCapturedId] == mvToRow)) {
mvCaptured = PieceType[1 - MoveColor][mvCapturedId];
if (mvCaptured == 1) {
return false;
}
break;
}
}
if ((mvPiece == 6) && (mvCapturedId < 1) && (HistEnPassant[plyCount])) {
for (mvCapturedId = 15; mvCapturedId >= 0; mvCapturedId--) {
if ((PieceType[1 - MoveColor][mvCapturedId] == 6) && (PieceCol[1 - MoveColor][mvCapturedId] == mvToCol) && (PieceRow[1 - MoveColor][mvCapturedId] == 4 - MoveColor)) {
mvCaptured = PieceType[1 - MoveColor][mvCapturedId];
break;
}
}
}
}
// check move legality
if (!CheckLegality(PiecesArr[mvPiece - 1], plyCount)) {
return false;
}
// pawn moved: check en-passant possibility
if (mvPiece == 6) {
if (Math.abs(HistRow[0][plyCount] - mvToRow) == 2) {
HistEnPassant[plyCount + 1] = true;
HistEnPassantCol[plyCount + 1] = mvToCol;
}
}
return true;
}
function SetGameSelectorOptions(head, num, chEvent, chSite, chRound, chWhite, chBlack, chResult, chDate) {
if (typeof (head) == "string") {
gameSelectorHead = head;
}
gameSelectorNum = (num === true);
gameSelectorChEvent = Math.max(Math.min(chEvent, 32) || 0, 0) || 0;
gameSelectorChSite = Math.max(Math.min(chSite, 32) || 0, 0) || 0;
gameSelectorChRound = Math.max(Math.min(chRound, 32) || 0, 0) || 0;
gameSelectorChWhite = Math.max(Math.min(chWhite, 32) || 0, 0) || 0;
gameSelectorChBlack = Math.max(Math.min(chBlack, 32) || 0, 0) || 0;
gameSelectorChResult = Math.max(Math.min(chResult, 32) || 0, 0) || 0;
gameSelectorChDate = Math.max(Math.min(chDate, 32) || 0, 0) || 0;
}
var clickedSquareInterval = null;
function clickedSquare(ii, jj) {
if (clickedSquareInterval) {
return;
} // dont trigger twice
var squareId = 'tcol' + jj + 'trow' + ii;
var theObj = document.getElementById(squareId);
if (theObj) {
var oldClass = theObj.className;
theObj.className = (ii + jj) % 2 === 0 ? "blackSquare" : "whiteSquare";
clickedSquareInterval = setTimeout("reset_after_click(" + ii + "," + jj + ",'" + oldClass + "','" + theObj.className + "')", 66);
clearSelectedText();
}
}
function reset_after_click(ii, jj, oldClass, newClass) {
var theObj = document.getElementById('tcol' + jj + 'trow' + ii);
if (theObj) {
// square class changed again by pgn4web already: dont touch it anymore e.g. autoplay
if (theObj.className == newClass) {
theObj.className = oldClass;
}
clickedSquareInterval = null;
}
}
var lastSearchPgnExpression = "";
function gameNumberSearchPgn(searchExpression, backward, includeCurrent) {
lastSearchPgnExpression = searchExpression;
if (searchExpression === "") {
return false;
}
// replace newline with spaces so that we can use regexp "." on whole game
var newlinesRegExp = new RegExp("[\n\r]", "gm");
var searchExpressionRegExp = new RegExp(searchExpression, "im");
// at start currentGame might still be -1
var thisCurrentGame = (currentGame < 0) || (currentGame >= numberOfGames) ? 0 : currentGame;
var needle = fullPgnGame(thisCurrentGame);
if (includeCurrent && needle.replace(newlinesRegExp, " ").match(searchExpressionRegExp)) {
return thisCurrentGame;
}
var delta = backward ? -1 : +1;
for (var thisGame = (thisCurrentGame + delta + numberOfGames) % numberOfGames; thisGame != thisCurrentGame; thisGame = (thisGame + delta + numberOfGames) % numberOfGames) {
needle = fullPgnGame(thisGame);
if (needle.replace(newlinesRegExp, " ").match(searchExpressionRegExp)) {
return thisGame;
}
}
return false;
}
function searchPgnGame(searchExpression, backward) {
if (typeof (searchExpression) == "undefined") {
searchExpression = "";
}
lastSearchPgnExpression = searchExpression;
var theObj = document.getElementById('searchPgnExpression');
if (theObj) {
theObj.value = searchExpression;
}
if ((searchExpression === "") || (numberOfGames < 2)) {
return;
}
var thisGame = gameNumberSearchPgn(searchExpression, backward, false);
if ((thisGame !== false) && (thisGame != currentGame)) {
Init(thisGame);
}
}
function searchPgnGamePrompt() {
if (numberOfGames < 2) {
alert("info: search prompt disabled with less than 2 games");
return;
}
var searchExpression = prompt("Please enter search pattern for PGN games:", lastSearchPgnExpression);
if (searchExpression) {
searchPgnGame(searchExpression);
}
}
function searchPgnGameForm() {
var theObj = document.getElementById('searchPgnExpression');
if (theObj) {
searchPgnGame(document.getElementById('searchPgnExpression').value);
}
}
var 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");
function fixCommentForDisplay(comment) {
return comment.replace(chessMovesRegExp, '$1');
}
var tableSize = 0;
var textSelectOptions = '';
function PrintHTML(forceBoardUpdate) {
var ii, jj, text, theObj, squareId, imageId, squareCoord, squareTitle, numText, textSO;
// chessboard
if (theObj = document.getElementById("GameBoard")) {
if (forceBoardUpdate || !document.getElementById("boardTable")) {
text = '| ';
squareCoord = IsRotated ? String.fromCharCode(72 - jj, 49 + ii) : String.fromCharCode(jj + 65, 56 - ii);
squareTitle = squareCoord;
if (boardTitle[jj][ii] !== '') {
squareTitle += ': ' + boardTitle[jj][ii];
}
text += ' | ';
}
text += '