diff --git a/assets/css/png4web.css b/assets/css/png4web.css new file mode 100644 index 0000000..282214d --- /dev/null +++ b/assets/css/png4web.css @@ -0,0 +1,134 @@ +/* * pgn4web javascript chessboard + * copyright (C) 2009-2013 Paolo Casaschi + * see README file and http://pgn4web.casaschi.net + * for credits, license and more details */ + @import url("../fonts/pgn4web-font-LiberationSans.css"); + @import url("../fonts/pgn4web-font-ChessSansUsual.css"); + + div, + span, + table, + tr, + td { + font-family: 'pgn4web Liberation Sans', sans-serif; + + /* fixes IE9 body css issue */ + } + + .boardTable { + border-style: double; + border-color: black; + border-width: 3px; + width: 600px; + height: 600px; + } + + .pieceImage { + width: 60px; + height: 60px; + } + + .whiteSquare, + .blackSquare, + .highlightWhiteSquare, + .highlightBlackSquare { + width: 55px; + height: 55px; + border-style: solid; + border-width: 1px; + } + + .whiteSquare, + .highlightWhiteSquare { + border-color: #EFF4EC; + background: #EFF4EC; + } + + .blackSquare, + .highlightBlackSquare { + border-color: #C6CEC3; + background: #C6CEC3; + } + + .highlightWhiteSquare, + .highlightBlackSquare { + border-style: inset; + border-color: gray; + } + + .selectControl { + /* a "width" attribute here must use the !important flag to override default settings */ + } + + .optionSelectControl { + } + + .buttonControlPlay, + .buttonControlStop, + .buttonControl { + /* a "width" attribute here must use the !important flag to override default settings */ + } + + .buttonControlSpace { + /* a "width" attribute here must use the !important flag to override default settings */ + } + + .searchPgnButton { + /* a "width" attribute here must use the !important flag to override default settings */ + } + + .searchPgnExpression { + /* a "width" attribute here must use the !important flag to override default settings */ + } + + .move, + .variation, + .comment { + line-height: 1.4em; + font-weight: normal; + font-size: 1.5rem; + font-family: 'pgn4web ChessSansUsual', 'pgn4web Liberation Sans', sans-serif; + } + + .move, + .variation, + .commentMove { + font-family: 'pgn4web ChessSansUsual', 'pgn4web Liberation Sans', sans-serif; + } + + a.move, + a.variation, + .commentMove { + white-space: nowrap; + } + + .move, + .variation { + text-decoration: none; + } + + a.move:hover, + a.variation:hover { + text-decoration: underline; + } + + .move { + color: #c6c6c6; + } + + .comment, + .variation, + .label { + color: gray; + } + + a.variation { + color: gray; + } + + .moveOn, + .variationOn { + background: #DAF4D7; + } + + \ No newline at end of file diff --git a/assets/css/styles.css b/assets/css/styles.css index 46fdce1..b523fc4 100644 --- a/assets/css/styles.css +++ b/assets/css/styles.css @@ -267,6 +267,9 @@ .hidden { display: none; } + .table { + display: table; + } .h-4 { height: calc(var(--spacing) * 4); } @@ -288,6 +291,9 @@ .h-full { height: 100%; } + .w-3 { + width: calc(var(--spacing) * 3); + } .w-3\/4 { width: calc(3/4 * 100%); } @@ -315,12 +321,18 @@ .max-w-7xl { max-width: var(--container-7xl); } + .border-collapse { + border-collapse: collapse; + } .rotate-180 { rotate: 180deg; } .transform { transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,); } + .resize { + resize: both; + } .flex-col { flex-direction: column; } @@ -372,6 +384,10 @@ .rounded-md { border-radius: var(--radius-md); } + .border { + border-style: var(--tw-border-style); + border-width: 1px; + } .border-t { border-top-style: var(--tw-border-style); border-top-width: 1px; @@ -379,6 +395,9 @@ .border-gray-700 { border-color: var(--color-gray-700); } + .bg-blue-500 { + background-color: var(--color-blue-500); + } .bg-blue-500\/40 { background-color: color-mix(in srgb, oklch(62.3% 0.214 259.815) 40%, transparent); @supports (color: color-mix(in lab, red, red)) { @@ -496,6 +515,9 @@ .text-white { color: var(--color-white); } + .underline { + text-decoration-line: underline; + } .antialiased { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; @@ -514,6 +536,10 @@ --tw-shadow: 0 20px 25px -5px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 8px 10px -6px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); } + .outline { + outline-style: var(--tw-outline-style); + outline-width: 1px; + } .transition-all { transition-property: all; transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); @@ -801,6 +827,11 @@ inherits: false; initial-value: 0 0 #0000; } +@property --tw-outline-style { + syntax: "*"; + inherits: false; + initial-value: solid; +} @property --tw-duration { syntax: "*"; inherits: false; @@ -831,6 +862,7 @@ --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; + --tw-outline-style: solid; --tw-duration: initial; } } diff --git a/assets/js/png4web.js b/assets/js/png4web.js new file mode 100644 index 0000000..6e87e23 --- /dev/null +++ b/assets/js/png4web.js @@ -0,0 +1,4010 @@ +/* + * 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 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 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 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; iipgn4web debug info\n
\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 = "pgn4web PGN source\n
\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 = "pgn4web FEN string\n
\n\n" + thisFEN + "\n\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 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= 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 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) { + 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) { + 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) { + 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 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= 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 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 = ' 0) ? ' STYLE="width: ' + tableSize + 'px; height: ' + tableSize + 'px;">' : '>'; + for (ii = 0; ii < 8; ++ii) { + text += ''; + for (jj = 0; jj < 8; ++jj) { + squareId = 'tcol' + jj + 'trow' + ii; + imageId = 'img_' + squareId; + text += (ii+jj)%2 === 0 ? ''; + } + text += ''; + } + 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 += '
'; + + theObj.innerHTML = text; + } + } + + if (theObj = document.getElementById("boardTable")) { + tableSize = theObj.offsetWidth; + if (tableSize > 0) { // coping with browser always returning 0 to offsetWidth + theObj.style.height = tableSize + "px"; + } + } + + // control buttons + + if (theObj = document.getElementById("GameButtons")) { + var numButtons = 5; + var spaceSize = 3; + var buttonSize = (tableSize - spaceSize*(numButtons - 1)) / numButtons; + text = '
'; + text += '
'; + + theObj.innerHTML = text; + } + + // game selector + + if (theObj = document.getElementById("GameSelector")) { + if (firstStart) { textSelectOptions=''; } + if (numberOfGames < 2) { + while (theObj.firstChild) { theObj.removeChild(theObj.firstChild); } + textSelectOptions = ''; + } else { + if (textSelectOptions === '') { + if (gameSelectorNum) { gameSelectorNumLenght = Math.floor(Math.log(numberOfGames)/Math.log(10)) + 1; } + text = '
'; // see function simpleHtmlentities() + theObj.innerHTML = text; + } + } + } + + // game event + + if (theObj = document.getElementById("GameEvent")) { theObj.innerHTML = gameEvent[currentGame]; } + + // game round + + if (theObj = document.getElementById("GameRound")) { theObj.innerHTML = gameRound[currentGame]; } + + // game site + + if (theObj = document.getElementById("GameSite")) { theObj.innerHTML = gameSite[currentGame]; } + + // game date + + if (theObj = document.getElementById("GameDate")) { + theObj.innerHTML = gameDate[currentGame]; + theObj.style.whiteSpace = "nowrap"; + } + + // game white + + if (theObj = document.getElementById("GameWhite")) { theObj.innerHTML = gameWhite[currentGame]; } + + // game black + + if (theObj = document.getElementById("GameBlack")) { theObj.innerHTML = gameBlack[currentGame]; } + + // game result + + if (theObj = document.getElementById("GameResult")) { + theObj.innerHTML = gameResult[currentGame]; + theObj.style.whiteSpace = "nowrap"; + } + + // game text + + if (theObj = document.getElementById("GameText")) { + variationTextDepth = -1; + text = '' + variationTextFromId(0); + ''; + theObj.innerHTML = text; + } + + setB1C1F1G1boardShortcuts(); // depend on presence of comments + + // game searchbox + + if ((theObj = document.getElementById("GameSearch")) && firstStart) { + if (numberOfGames < 2) { + while (theObj.firstChild) { theObj.removeChild(theObj.firstChild); } + } else { + text = '
'; + theObj.innerHTML = text; + theObj = document.getElementById('searchPgnExpression'); + if (theObj) { theObj.value = lastSearchPgnExpression; } + } + } +} + +function startButton(e) { + if (e.shiftKey) { + GoToMove(StartPlyVar[CurrentVar] + (CurrentPly <= StartPlyVar[CurrentVar] + 1 ? 0 : 1)); + } else { GoToMove(StartPlyVar[0], 0); } +} + +function backButton(e) { + if (e.shiftKey) { GoToMove(StartPlyVar[CurrentVar]); } + else { GoToMove(CurrentPly - 1); } +} + +function forwardButton(e) { + if (e.shiftKey) { if (!goToNextVariationSibling()) { GoToMove(CurrentPly + 1); } } + else { GoToMove(CurrentPly + 1); } +} + +function endButton(e) { + if (e.shiftKey) { + if (CurrentPly === StartPlyVar[CurrentVar] + PlyNumberVar[CurrentVar]) { goToFirstChild(); } + else { GoToMove(StartPlyVar[CurrentVar] + PlyNumberVar[CurrentVar]); } + } else { GoToMove(StartPlyVar[0] + PlyNumberVar[0], 0); } +} + +function clickedBbtn(t,e) { + switch (t.id) { + case "startButton": + startButton(e); + break; + case "backButton": + backButton(e); + break; + case "autoplayButton": + if (e.shiftKey) { goToNextVariationSibling(); } + else { SwitchAutoPlay(); } + break; + case "forwardButton": + forwardButton(e); + break; + case "endButton": + endButton(e); + break; + default: + break; + } +} + +var basicNAGs = /^[\?!+#\s]+(\s|$)/; +function strippedMoveComment(plyNum, varId, addHtmlTags) { + if (typeof(addHtmlTags) == "undefined") { + addHtmlTags = false; + if (typeof(varId) == "undefined") { varId = CurrentVar; } + } + if (!MoveCommentsVar[varId][plyNum]) { return ""; } + return fixCommentForDisplay(MoveCommentsVar[varId][plyNum]).replace(pgn4webVariationRegExpGlobal, function (m) { return variationTextFromTag(m, addHtmlTags); }).replace(/\[%[^\]]*\]\s*/g,'').replace(plyNum === StartPlyVar[varId] ? '' : basicNAGs, '').replace(/^\s+$/,''); +} + +function basicNAGsMoveComment(plyNum, varId) { + if (typeof(varId) == "undefined") { varId = CurrentVar; } + if (!MoveCommentsVar[varId][plyNum]) { return ""; } + var thisBasicNAGs = MoveCommentsVar[varId][plyNum].replace(/\[%[^\]]*\]\s*/g,'').match(basicNAGs, ''); + return thisBasicNAGs ? thisBasicNAGs[0].replace(/\s+(?!class=)/gi,'') : ''; +} + +function variationTextFromTag(variationTag, addHtmlTags) { + if (typeof(addHtmlTags) == "undefined") { addHtmlTags = false; } + var varId = variationTag.replace(pgn4webVariationRegExp, "$1"); + if (isNaN(varId)) { + myAlert("error: issue parsing variation tag " + variationTag + " in game " + (currentGame+1), true); + return ""; + } + var text = variationTextFromId(varId); + if (text) { + if (addHtmlTags) { text = '
' + text + ''; } + } else { text = ''; } + return text; +} + +var variationTextDepth, printedComment, printedVariation; +function variationTextFromId(varId) { + var punctChars = ",.;:!?", thisComment; + + if (isNaN(varId) || varId < 0 || varId >= numberOfVars || typeof(StartPlyVar[varId]) == "undefined" || typeof(PlyNumberVar[varId]) == "undefined") { + myAlert("error: issue parsing variation id " + varId + " in game " + (currentGame+1), true); + return ""; + } + var text = ++variationTextDepth ? ('' + (printedVariation ? ' ' : '') + (variationTextDepth > 1 ? '(' : '[')) + '' : ''; + printedVariation = false; + for (var ii = StartPlyVar[varId]; ii < StartPlyVar[varId] + PlyNumberVar[varId]; ii++) { + printedComment = false; + if (commentsIntoMoveText && (thisComment = strippedMoveComment(ii, varId, true))) { + if (commentsOnSeparateLines && variationTextDepth === 0 && ii > StartPlyVar[varId]) { + text += '
 
'; + } + if (printedVariation) { if (punctChars.indexOf(thisComment.charAt(0)) == -1) { text += ' '; } } + else { printedVariation = variationTextDepth > 0; } + text += '' + thisComment + ''; + if (commentsOnSeparateLines && variationTextDepth === 0) { + text += '
 
'; + } + printedComment = true; + } + if (printedComment || printedVariation) { text += ' '; } + printedVariation = true; + + text += printMoveText(ii, varId, (variationTextDepth > 0), ((printedComment) || (ii == StartPlyVar[varId])), true); + } + if (commentsIntoMoveText && (thisComment = strippedMoveComment(StartPlyVar[varId] + PlyNumberVar[varId], varId, true))) { + if (commentsOnSeparateLines && variationTextDepth === 0) { + text += '
 
'; + } + if (printedVariation && (punctChars.indexOf(thisComment.charAt(0)) == -1)) { text += ' '; } + text += '' + thisComment + ''; + printedComment = true; + } + text += variationTextDepth-- ? ('' + (variationTextDepth ? ')' : ']') + '') : ''; + printedVariation = true; + return text; +} + +function printMoveText(thisPly, thisVar, isVar, hasLeadingNum, hasId) { + if (typeof(thisVar) == "undefined") { thisVar = CurrentVar; } + if (typeof(thisPly) == "undefined") { thisPly = CurrentPly; } + var text = ''; + + if (thisVar >= numberOfVars || thisPly < StartPlyVar[thisVar] || thisPly > StartPlyVar[thisVar] + PlyNumberVar[thisVar]) { + return text; + } + + var moveCount = Math.floor(thisPly/2)+1; + if (thisPly%2 === 0) { + text += '' + moveCount + '. '; + } else { + if (hasLeadingNum) { + text += '' + moveCount + '... '; + } + } + var jj = thisPly+1; + text += ' theContainerObjOffsetVeryTop + theContainerObj.scrollTop + theContainerObj.clientHeight) || (theMoveObjOffsetVeryTop < theContainerObjOffsetVeryTop + theContainerObj.scrollTop)) { + theContainerObj.scrollTop = theMoveObjOffsetVeryTop - theContainerObjOffsetVeryTop; + } + } + } + } +} + + +function FlipBoard() { + var oldHighlightOption = highlightOption; + if (oldHighlightOption) { SetHighlight(false); } + IsRotated = !IsRotated; + PrintHTML(true); + RefreshBoard(); + if (oldHighlightOption) { SetHighlight(true); } +} + +function RefreshBoard() { + for (var jj = 0; jj < 8; ++jj) { + for (var ii = 0; ii < 8; ++ii) { + if (Board[jj][ii] === 0) { SetImage(jj, ii, ClearImg.src); } + } + } + for (jj = 0; jj < 2; ++jj) { + for (ii = 0; ii < 16; ++ii) { + if (PieceType[jj][ii] > 0) { + SetImage(PieceCol[jj][ii], PieceRow[jj][ii], PieceImg[jj][PieceType[jj][ii]].src); + } + } + } +} + +function SetAutoPlay(vv) { + isAutoPlayOn = vv; + + if (AutoPlayInterval) { clearTimeout(AutoPlayInterval); AutoPlayInterval = null; } + + if (isAutoPlayOn) { + if (document.GameButtonsForm) { + if (document.GameButtonsForm.AutoPlay) { + document.GameButtonsForm.AutoPlay.value = "="; + document.GameButtonsForm.AutoPlay.title = "toggle autoplay (stop)"; + document.GameButtonsForm.AutoPlay.className = "buttonControlStop"; + } + } + if (CurrentPly < StartPlyVar[CurrentVar] + PlyNumberVar[CurrentVar]) { AutoPlayInterval=setTimeout("MoveForward(1)", Delay); } + else { + if (autoplayNextGame && (CurrentVar === 0)) { AutoPlayInterval=setTimeout("AutoplayNextGame()", Delay); } + else { SetAutoPlay(false); } + } + } else { + if (document.GameButtonsForm) { + if (document.GameButtonsForm.AutoPlay) { + document.GameButtonsForm.AutoPlay.value = "+"; + document.GameButtonsForm.AutoPlay.title = "toggle autoplay (start)"; + document.GameButtonsForm.AutoPlay.className = "buttonControlPlay"; + } + } + } +} + + +var minAutoplayDelay = 500; +var maxAutoplayDelay = 300000; +function setCustomAutoplayDelay() { + var newDelaySec = prompt("Enter custom autoplay delay, in seconds, between " + (minAutoplayDelay/1000) + " and " + (maxAutoplayDelay/1000) + ":", Math.floor(Delay / 100) / 10); + if (!isNaN(newDelaySec = parseInt(newDelaySec, 10))) { + SetAutoplayDelayAndStart(newDelaySec * 1000); + } +} + +function SetAutoplayDelay(vv) { + if (isNaN(vv = parseInt(vv, 10))) { return; } + Delay = Math.min(Math.max(vv, minAutoplayDelay), maxAutoplayDelay); +} + +function SetAutoplayDelayAndStart(vv) { + MoveForward(1); + SetAutoplayDelay(vv); + SetAutoPlay(true); +} + +function SetLiveBroadcast(delay, alertFlag, demoFlag, stepFlag, endlessFlag) { + LiveBroadcastDelay = delay; // zero delay: no live broadcast + LiveBroadcastAlert = (alertFlag === true); + LiveBroadcastDemo = (demoFlag === true); + LiveBroadcastSteppingMode = (stepFlag === true); + LiveBroadcastEndlessMode = (endlessFlag === true); + setG7A6B6H7boardShortcuts(); +} + +function SetImage(col, row, image) { + var trow = IsRotated ? row : 7 - row; + var tcol = IsRotated ? 7 - col : col; + var theObj = document.getElementById('img_' + 'tcol' + tcol + 'trow' + trow); + if ((theObj) && (theObj.src != image)) { theObj.src = image; } +} + +function SetImagePath(path) { + ImagePath = path; +} + +function SwitchAutoPlay() { + if (!isAutoPlayOn) { MoveForward(1); } + SetAutoPlay(!isAutoPlayOn); +} + +function StoreMove(thisPly) { + + HistVar[thisPly+1] = CurrentVar; + + if (HistNull[thisPly] = mvIsNull) { return; } + + // "square from" history + HistPieceId[0][thisPly] = mvPieceId; + HistCol[0][thisPly] = PieceCol[MoveColor][mvPieceId]; + HistRow[0][thisPly] = PieceRow[MoveColor][mvPieceId]; + HistType[0][thisPly] = PieceType[MoveColor][mvPieceId]; + + // "square to" history + HistCol[2][thisPly] = mvToCol; + HistRow[2][thisPly] = mvToRow; + + if (mvIsCastling) { + HistPieceId[1][thisPly] = castleRook; + HistCol[1][thisPly] = PieceCol[MoveColor][castleRook]; + HistRow[1][thisPly] = PieceRow[MoveColor][castleRook]; + HistType[1][thisPly] = PieceType[MoveColor][castleRook]; + } else if (mvCapturedId >= 0) { + HistPieceId[1][thisPly] = mvCapturedId+16; + HistCol[1][thisPly] = PieceCol[1-MoveColor][mvCapturedId]; + HistRow[1][thisPly] = PieceRow[1-MoveColor][mvCapturedId]; + HistType[1][thisPly] = PieceType[1-MoveColor][mvCapturedId]; + } else { + HistPieceId[1][thisPly] = -1; + } + + // update "square from" and captured square (not "square to" for en-passant) + Board[PieceCol[MoveColor][mvPieceId]][PieceRow[MoveColor][mvPieceId]] = 0; + + // mark captured piece + if (mvCapturedId >= 0) { + PieceType[1-MoveColor][mvCapturedId] = -1; + PieceMoveCounter[1-MoveColor][mvCapturedId]++; + Board[PieceCol[1-MoveColor][mvCapturedId]][PieceRow[1-MoveColor][mvCapturedId]] = 0; + } + + // update piece arrays: promotion changes piece type + PieceType[MoveColor][mvPieceId] = mvPieceOnTo; + PieceMoveCounter[MoveColor][mvPieceId]++; + PieceCol[MoveColor][mvPieceId] = mvToCol; + PieceRow[MoveColor][mvPieceId] = mvToRow; + if (mvIsCastling) { + PieceMoveCounter[MoveColor][castleRook]++; + PieceCol[MoveColor][castleRook] = mvToCol == 2 ? 3 : 5; + PieceRow[MoveColor][castleRook] = mvToRow; + } + + // update board + Board[mvToCol][mvToRow] = PieceType[MoveColor][mvPieceId]*(1-2*MoveColor); + if (mvIsCastling) { + Board[PieceCol[MoveColor][castleRook]][PieceRow[MoveColor][castleRook]] = PieceType[MoveColor][castleRook]*(1-2*MoveColor); + } + return; +} + +function UndoMove(thisPly) { + + if (HistNull[thisPly]) { return; } + + // moved piece back to original square + var chgPiece = HistPieceId[0][thisPly]; + Board[PieceCol[MoveColor][chgPiece]][PieceRow[MoveColor][chgPiece]] = 0; + + Board[HistCol[0][thisPly]][HistRow[0][thisPly]] = HistType[0][thisPly] * (1-2*MoveColor); + PieceType[MoveColor][chgPiece] = HistType[0][thisPly]; + PieceCol[MoveColor][chgPiece] = HistCol[0][thisPly]; + PieceRow[MoveColor][chgPiece] = HistRow[0][thisPly]; + PieceMoveCounter[MoveColor][chgPiece]--; + + // castling: rook back to original square + chgPiece = HistPieceId[1][thisPly]; + if ((chgPiece >= 0) && (chgPiece < 16)) { + Board[PieceCol[MoveColor][chgPiece]][PieceRow[MoveColor][chgPiece]] = 0; + Board[HistCol[1][thisPly]][HistRow[1][thisPly]] = HistType[1][thisPly] * (1-2*MoveColor); + PieceType[MoveColor][chgPiece] = HistType[1][thisPly]; + PieceCol[MoveColor][chgPiece] = HistCol[1][thisPly]; + PieceRow[MoveColor][chgPiece] = HistRow[1][thisPly]; + PieceMoveCounter[MoveColor][chgPiece]--; + } + + // capture: captured piece back to original square + chgPiece -= 16; + if ((chgPiece >= 0) && (chgPiece < 16)) { + Board[PieceCol[1-MoveColor][chgPiece]][PieceRow[1-MoveColor][chgPiece]] = 0; + Board[HistCol[1][thisPly]][HistRow[1][thisPly]] = HistType[1][thisPly] * (2*MoveColor-1); + PieceType[1-MoveColor][chgPiece] = HistType[1][thisPly]; + PieceCol[1-MoveColor][chgPiece] = HistCol[1][thisPly]; + PieceRow[1-MoveColor][chgPiece] = HistRow[1][thisPly]; + PieceMoveCounter[1-MoveColor][chgPiece]--; + } +} + +function Color(nn) { + if (nn < 0) { return 1; } + if (nn > 0) { return 0; } + return 2; +} + +function sign(nn) { + if (nn > 0) { return 1; } + if (nn < 0) { return -1; } + return 0; +} + +function SquareOnBoard(col, row) { + return col >= 0 && col <= 7 && row >= 0 && row <= 7; +} + +var pgn4webMaxTouches = 0; +var pgn4webOngoingTouches = new Array(); +function pgn4webOngoingTouchIndexById(needle) { + var id; + for (var ii = 0; ii < pgn4webOngoingTouches.length; ii++) { + id = pgn4webOngoingTouches[ii].identifier; + if (pgn4webOngoingTouches[ii].identifier === needle) { return ii; } + } + return -1; +} + +function pgn4web_handleTouchStart(e) { + e.stopPropagation(); + for (var ii = 0; ii < e.changedTouches.length; ii++) { + pgn4webMaxTouches++; + pgn4webOngoingTouches.push({ identifier: e.changedTouches[ii].identifier, clientX: e.changedTouches[ii].clientX, clientY: e.changedTouches[ii].clientY }); + } +} + +function pgn4web_handleTouchMove(e) { + e.stopPropagation(); + e.preventDefault(); +} + +function pgn4web_handleTouchEnd(e) { + e.stopPropagation(); + var jj; + for (var ii = 0; ii < e.changedTouches.length; ii++) { + if ((jj = pgn4webOngoingTouchIndexById(e.changedTouches[ii].identifier)) != -1) { + if (pgn4webOngoingTouches.length == 1) { + customFunctionOnTouch(e.changedTouches[ii].clientX - pgn4webOngoingTouches[jj].clientX, e.changedTouches[ii].clientY - pgn4webOngoingTouches[jj].clientY); + pgn4webMaxTouches = 0; + } + pgn4webOngoingTouches.splice(jj, 1); + } + } + clearSelectedText(); +} + +function pgn4web_handleTouchCancel(e) { + e.stopPropagation(); + var jj; + for (var ii = 0; ii < e.changedTouches.length; ii++) { + if ((jj = pgn4webOngoingTouchIndexById(e.changedTouches[ii].identifier)) != -1) { + pgn4webOngoingTouches.splice(jj, 1); + if (pgn4webOngoingTouches.length === 0) { pgn4webMaxTouches = 0; } + } + } + clearSelectedText(); +} + +function pgn4web_initTouchEvents() { + var theObj = document.getElementById("GameBoard"); + if (theObj && touchEventEnabled) { + simpleAddEvent(theObj, "touchstart", pgn4web_handleTouchStart); + simpleAddEvent(theObj, "touchmove", pgn4web_handleTouchMove); + simpleAddEvent(theObj, "touchend", pgn4web_handleTouchEnd); + simpleAddEvent(theObj, "touchleave", pgn4web_handleTouchEnd); + simpleAddEvent(theObj, "touchcancel", pgn4web_handleTouchCancel); + } +} + +var waitForDoubleLeftTouchTimer = null; +function customFunctionOnTouch(deltaX, deltaY) { + if (Math.max(Math.abs(deltaX), Math.abs(deltaY)) < 13) { return; } + if (Math.abs(deltaY) > 1.5 * Math.abs(deltaX)) { // vertical up or down + if (numberOfGames > 1) { + if ((currentGame === 0) && (deltaY < 0)) { Init(numberOfGames - 1); } + else if ((currentGame === numberOfGames - 1) && (deltaY > 0)) { Init(0); } + else { Init(currentGame + sign(deltaY)); } + } + } else if (Math.abs(deltaX) > 1.5 * Math.abs(deltaY)) { + if (deltaX > 0) { // horizontal right + if (isAutoPlayOn) { GoToMove(StartPlyVar[CurrentVar] + PlyNumberVar[CurrentVar]); } + else { SwitchAutoPlay(); } + } else { // horizontal left + if (isAutoPlayOn && !waitForDoubleLeftTouchTimer) { SwitchAutoPlay(); } + else { + if (waitForDoubleLeftTouchTimer) { + clearTimeout(waitForDoubleLeftTouchTimer); + waitForDoubleLeftTouchTimer = null; + } + if ((LiveBroadcastDelay > 0) && (CurrentVar === 0) && (CurrentPly === StartPly + PlyNumber)) { + waitForDoubleLeftTouchTimer = setTimeout("waitForDoubleLeftTouchTimer = null;", 900); + replayPreviousMoves(6); + } else { GoToMove(StartPlyVar[CurrentVar] + (((CurrentPly <= StartPlyVar[CurrentVar] + 1) || (CurrentVar === 0)) ? 0 : 1)); } + } + } + } +} + +var touchGestures_helpActions = [ "chessboard top-down swipe", "chessboard bottom-up swipe", "chessboard left-right swipe", "chessboard right-left swipe" ]; +var touchGestures_helpText = [ "load next game, cycling through", "load previous game, cyclig through", "start autoplay; if autoplay already active: go to variation end or to game end", "stop autoplay; if autoplay not active: go to variation start, then to parent variation, then to game start; if at last move of live broadcast: replay up to 6 previous half-moves, then autoplay forward" ]; + +var touchEventEnabled = true; +function SetTouchEventEnabled(onOff) { + touchEventEnabled = onOff; +} + +function clearSelectedText() { + if (window.getSelection) { + if (window.getSelection().empty) { window.getSelection().empty(); } + else if (window.getSelection().removeAllRanges) { window.getSelection().removeAllRanges(); } + } else if (document.selection && document.selection.empty) { document.selection.empty(); } +} + +function simpleHtmlentities(text) { + return text.replace(/&/g, "&").replace(//g, ">"); +} + +function simpleHtmlentitiesDecode(text) { + return text.replace(/>/g, ">").replace(/</g, "<").replace(/&/g, "&"); +} + diff --git a/site/snippets/header.php b/site/snippets/header.php index 467204a..35b7a3c 100644 --- a/site/snippets/header.php +++ b/site/snippets/header.php @@ -40,6 +40,7 @@