1<?php 2 3/* 4 * pgn4web javascript chessboard 5 * copyright (C) 2009-2015 Paolo Casaschi 6 * see README file and http://pgn4web.casaschi.net 7 * for credits, license and more details 8 */ 9 10error_reporting(E_ALL | E_STRICT); 11 12/* 13 * URL parameters: 14 * 15 * headlessPage = true | false (default false) 16 * hideForm = true | false (default false) 17 * pgnData (or pgnUrl) = (default null) 18 * pgnText = (default null) 19 * 20 */ 21 22$pgnDebugInfo = ""; 23 24$tmpDir = "php://temp"; 25$fileUploadLimitBytes = 4194304; 26$fileUploadLimitText = round(($fileUploadLimitBytes / 1048576), 0) . "MB"; 27$fileUploadLimitIniText = ini_get("upload_max_filesize"); 28if ($fileUploadLimitIniText === "") { $fileUploadLimitIniText = "unknown"; } 29 30// it would be nice here to evaluate ini_get('allow_fopen_url') and flag the issue (possibly disabling portions of the input forms), but the return values of ini_get() for boolean values are totally unreliable, so we have to leave with the generic server error message when trying to load a remote URL while allow_fopen_url is disabled in php.ini 31 32$zipSupported = function_exists('zip_open'); 33if (!$zipSupported) { $pgnDebugInfo = $pgnDebugInfo . "\\n" . "ZIP support unavailable from server, missing php ZIP library"; } 34 35$http_response_header_status = ""; 36$http_response_header_last_modified = ""; 37 38$debugHelpText = "a flashing chessboard signals errors in the PGN data, click on the top left chessboard square for debug messages"; 39 40$headlessPage = strtolower(get_param("headlessPage", "hp", "")); 41 42$hideForm = strtolower(get_param("hideForm", "hf", "")); 43$hideFormCss = ($hideForm == "true") || ($hideForm == "t") ? "display:none;" : ""; 44 45$forceEncodingFrom = get_param("forceEncodingFrom", "fef", ""); 46 47$startPosition = '[Event ""] [Site ""] [Date ""] [Round ""] [White ""] [Black ""] [Result ""] ' . ((($hideForm == "true") || ($hideForm == "t")) ? '' : '{ please enter chess games in PGN format using the form at the top of the page }'); 48 49 50$presetURLsArray = array(); 51function addPresetURL($label, $javascriptCode) { 52 global $presetURLsArray; 53 array_push($presetURLsArray, array('label' => $label, 'javascriptCode' => $javascriptCode)); 54} 55 56// modify the viewer-preset-URLs.php file to add preset URLs for the viewer's form 57include 'viewer-preset-URLs.php'; 58 59 60$pgnOnly = get_param("pgnOnly", "po", ""); 61$generateParameter = get_param("generateParameter", "gp", ""); 62if (($pgnOnly == "true") || ($pgnOnly == "t")) { 63 64 if (!get_pgn()) { header("HTTP/1.1 204 No Content"); } 65 header("content-type: application/x-chess-pgn"); 66 header("content-disposition: inline; filename=games.pgn"); 67 if ($http_response_header_last_modified) { header($http_response_header_last_modified); } 68 if ($pgnText) { print $pgnText; } 69 70} elseif (($generateParameter == "true") || ($generateParameter == "t")) { 71 72 header("content-type: text/html; charset=utf-8"); 73 $pgnUrl = get_param("pgnData", "pd", ""); 74 if ($pgnUrl == "") { $pgnUrl = get_param("pgnUrl", "pu", ""); } 75 $pgnLink = $_SERVER['SCRIPT_NAME'] . urlencode("?po=t&pd=" . $pgnUrl); 76 print("<div style='font-family:sans-serif; padding:1em;'><a style='text-decoration:none; color:black;' href='" . $pgnLink . "'>" . $pgnLink . "</a></div>"); 77 78} else { 79 80 header("content-type: text/html; charset=utf-8"); 81 if ($goToView = get_pgn()) { 82 $pgnText = str_replace(array("&", "<", ">"), array("&", "<", ">"), $pgnText); 83 } else { 84 $pgnText = preg_match("/^error:/", $pgnStatus) ? '[Event ""] [Site ""] [Date ""] [Round ""] [White ""] [Black ""] [Result ""] { error loading PGN data, click square A8 for more details }' : $startPosition; 85 } 86 print_header(); 87 print_form(); 88 check_tmpDir(); 89 print_menu("board"); 90 print_chessboard_one(); 91 print_menu("moves"); 92 print_chessboard_two(); 93 print_footer(); 94 print_menu("bottom"); 95 print_html_close(); 96 97} 98 99 100function get_param($param, $shortParam, $default) { 101 if (isset($_REQUEST[$param])) { return $_REQUEST[$param]; } 102 if (isset($_REQUEST[$shortParam])) { return $_REQUEST[$shortParam]; } 103 return $default; 104} 105 106 107function http_parse_headers($headerFields) { 108 109 global $http_response_header_status, $http_response_header_last_modified; 110 111 $retVal = array(); 112 foreach ($headerFields as $field) { 113 if (preg_match('/([^:]+): (.+)/m', $field, $match)) { 114 $match[1] = preg_replace('/(?<=^|[\x09\x20\x2D])./e', 'strtoupper("\0")', strtolower(trim($match[1]))); 115 if (isset($retVal[$match[1]])) { 116 $retVal[$match[1]] = array($retVal[$match[1]], $match[2]); 117 } else { 118 $retVal[$match[1]] = trim($match[2]); 119 } 120 } else if (preg_match('/^\S+\s+\d+\s/m', $field)) { 121 $retVal["status"] = $field; 122 } 123 } 124 125 if (isset($retVal["status"])) { $http_response_header_status = $retVal["status"]; } 126 if (isset($retVal["Last-Modified"])) { $http_response_header_last_modified = "Last-Modified: " . $retVal["Last-Modified"]; } 127 128 return $retVal; 129} 130 131 132function http_response_header_isInvalid() { 133 global $http_response_header_status; 134 return $http_response_header_status ? preg_match("/^\S+\s+[45]\d\d\s/", $http_response_header_status) : FALSE; 135} 136 137 138function get_pgn() { 139 140 global $pgnText, $pgnTextbox, $pgnUrl, $pgnFileName, $pgnFileSize, $pgnStatus, $forceEncodingFrom, $tmpDir, $debugHelpText, $pgnDebugInfo; 141 global $fileUploadLimitIniText, $fileUploadLimitText, $fileUploadLimitBytes, $startPosition, $goToView, $zipSupported; 142 global $http_response_header_status, $http_response_header_last_modified; 143 144 $pgnDebugInfo = $pgnDebugInfo . get_param("debug", "d", ""); 145 146 $pgnText = get_param("pgnText", "pt", ""); 147 148 $pgnUrl = get_param("pgnData", "pd", ""); 149 if ($pgnUrl == "") { $pgnUrl = get_param("pgnUrl", "pu", ""); } 150 151 if ($pgnText) { 152 $pgnStatus = "info: games from textbox input"; 153 $pgnTextbox = $pgnText = str_replace("\\\"", "\"", $pgnText); 154 155 $pgnText = preg_replace("/\[/", "\n\n[", $pgnText); 156 $pgnText = preg_replace("/\]/", "]\n\n", $pgnText); 157 $pgnText = preg_replace("/([012\*])(\s*)(\[)/", "$1\n\n$3", $pgnText); 158 $pgnText = preg_replace("/\]\s*\[/", "]\n[", $pgnText); 159 $pgnText = preg_replace("/^\s*\[/", "[", $pgnText); 160 $pgnText = preg_replace("/\n[\s*\n]+/", "\n\n", $pgnText); 161 162 $pgnTextbox = $pgnText; 163 164 return TRUE; 165 } else if ($pgnUrl) { 166 $pgnStatus = "info: games from $pgnUrl"; 167 $isPgn = preg_match("/\.(pgn|txt)$/i", preg_replace("/[?#].*$/", "", $pgnUrl)); 168 $isZip = preg_match("/\.zip$/i", preg_replace("/[?#].*$/", "", $pgnUrl)); 169 if ($isZip) { 170 if (!$zipSupported) { 171 $pgnStatus = "error: zipfile support unavailable, unable to open $pgnUrl"; 172 return FALSE; 173 } else { 174 $tempZipName = tempnam($tmpDir, "pgn4webViewer_"); 175 // $pgnUrlOpts tries forcing following location redirects 176 // depending on server configuration, the script might still fail if the ZIP URL is redirected 177 $pgnUrlOpts = array("http" => array("follow_location" => TRUE, "max_redirects" => 20)); 178 $pgnUrlHandle = @fopen($pgnUrl, "rb", false, stream_context_create($pgnUrlOpts)); 179 if (!$pgnUrlHandle) { 180 $pgnStatus = "error: failed to get $pgnUrl: file not found or server error"; 181 if ((isset($tempZipName)) && ($tempZipName) && (file_exists($tempZipName))) { unlink($tempZipName); } 182 return FALSE; 183 } else { 184 $tempZipHandle = fopen($tempZipName, "wb"); 185 $copiedBytes = stream_copy_to_stream($pgnUrlHandle, $tempZipHandle, $fileUploadLimitBytes + 1, 0); 186 fclose($pgnUrlHandle); 187 fclose($tempZipHandle); 188 if (isset($http_response_header)) { http_parse_headers($http_response_header); } 189 if ((($copiedBytes > 0) && ($copiedBytes <= $fileUploadLimitBytes)) && (!http_response_header_isInvalid())) { 190 $pgnSource = $tempZipName; 191 } else { 192 $pgnStatus = "error: failed to get $pgnUrl: " . (http_response_header_isInvalid() ? "server error: $http_response_header_status" : "file not found, file size exceeds $fileUploadLimitText form limit, $fileUploadLimitIniText server limit or server error"); 193 if ((isset($tempZipName)) && ($tempZipName) && (file_exists($tempZipName))) { unlink($tempZipName); } 194 return FALSE; 195 } 196 } 197 } 198 } else { 199 $pgnSource = $pgnUrl; 200 } 201 } elseif (count($_FILES) == 0) { 202 $pgnStatus = "info: no games supplied"; 203 return FALSE; 204 } elseif ($_FILES['pgnFile']['error'] === UPLOAD_ERR_OK) { 205 $pgnFileName = $_FILES['pgnFile']['name']; 206 $pgnStatus = "info: games from file $pgnFileName"; 207 $pgnFileSize = $_FILES['pgnFile']['size']; 208 if ($pgnFileSize == 0) { 209 $pgnStatus = "info: failed uploading games: file not found, file empty or upload error"; 210 return FALSE; 211 } elseif ($pgnFileSize > $fileUploadLimitBytes) { 212 $pgnStatus = "error: failed uploading games: file size exceeds $fileUploadLimitText limit"; 213 return FALSE; 214 } else { 215 $isPgn = preg_match("/\.(pgn|txt)$/i",$pgnFileName); 216 $isZip = preg_match("/\.zip$/i",$pgnFileName); 217 $pgnSource = $_FILES['pgnFile']['tmp_name']; 218 } 219 } else { 220 $pgnStatus = "error: failed uploading games: "; 221 switch ($_FILES['pgnFile']['error']) { 222 case UPLOAD_ERR_INI_SIZE: 223 case UPLOAD_ERR_FORM_SIZE: 224 $pgnStatus = $pgnStatus . "file size exceeds $fileUploadLimitText form limit or $fileUploadLimitIniText server limit"; 225 break; 226 case UPLOAD_ERR_PARTIAL: 227 case UPLOAD_ERR_NO_FILE: 228 $pgnStatus = $pgnStatus . "file missing or truncated"; 229 break; 230 case UPLOAD_ERR_NO_TMP_DIR: 231 case UPLOAD_ERR_CANT_WRITE: 232 case UPLOAD_ERR_EXTENSION: 233 $pgnStatus = $pgnStatus . "server error"; 234 break; 235 default: 236 $pgnStatus = $pgnStatus . "unknown upload error"; 237 break; 238 } 239 return FALSE; 240 } 241 242 if ($isZip) { 243 if ($zipSupported) { 244 if ($pgnUrl) { $zipFileString = $pgnUrl; } 245 else { $zipFileString = "zip file"; } 246 $pgnZip = zip_open($pgnSource); 247 if (is_resource($pgnZip)) { 248 while (is_resource($zipEntry = zip_read($pgnZip))) { 249 if (zip_entry_open($pgnZip, $zipEntry)) { 250 if (preg_match("/\.pgn$/i",zip_entry_name($zipEntry))) { 251 $pgnText = $pgnText . zip_entry_read($zipEntry, zip_entry_filesize($zipEntry)) . "\n\n\n"; 252 } 253 zip_entry_close($zipEntry); 254 } else { 255 $pgnStatus = "error: failed reading $zipFileString content"; 256 zip_close($pgnZip); 257 if ((isset($tempZipName)) && ($tempZipName) && (file_exists($tempZipName))) { unlink($tempZipName); } 258 return FALSE; 259 } 260 } 261 zip_close($pgnZip); 262 if ((isset($tempZipName)) && ($tempZipName) && (file_exists($tempZipName))) { unlink($tempZipName); } 263 if (!$pgnText) { 264 $pgnStatus = "error: games not found in $zipFileString"; 265 return FALSE; 266 } 267 } else { 268 if ((isset($tempZipName)) && ($tempZipName) && (file_exists($tempZipName))) { unlink($tempZipName); } 269 $pgnStatus = "error: failed opening $zipFileString"; 270 return FALSE; 271 } 272 } else { 273 $pgnStatus = "error: ZIP support unavailable from this server, only PGN files are supported"; 274 return FALSE; 275 } 276 } elseif ($isPgn) { 277 if ($pgnUrl) { $pgnFileString = $pgnUrl; } 278 else { $pgnFileString = "pgn file"; } 279 $pgnText = @file_get_contents($pgnSource, NULL, NULL, 0, $fileUploadLimitBytes + 1); 280 if (isset($http_response_header)) { http_parse_headers($http_response_header); } 281 if ((!$pgnText) || (($pgnUrl) && (http_response_header_isInvalid()))) { 282 $pgnStatus = "error: failed reading $pgnFileString: " . (http_response_header_isInvalid() ? "server error: $http_response_header_status" : "file not found or server error"); 283 return FALSE; 284 } 285 if ((strlen($pgnText) == 0) || (strlen($pgnText) > $fileUploadLimitBytes)) { 286 $pgnStatus = "error: failed reading $pgnFileString: file size exceeds $fileUploadLimitText form limit, $fileUploadLimitIniText server limit or server error"; 287 return FALSE; 288 } 289 } elseif ($pgnSource) { 290 if ($zipSupported) { 291 $pgnStatus = "error: only PGN and ZIP (zipped pgn) files are supported"; 292 } else { 293 $pgnStatus = "error: only PGN files are supported, ZIP support unavailable from this server"; 294 } 295 return FALSE; 296 } 297 298 $assumedEncoding = $forceEncodingFrom; 299 if ($assumedEncoding == "") { 300 301 302// DeploymentCheck: conversion for given URLs 303 304// end DeploymentCheck 305 306 307 } 308 if (($assumedEncoding != "") && (strtoupper($assumedEncoding) != "NONE")) { 309 // convert text encoding to UNICODE, for example from windows WINDOWS-1252 files 310 $pgnText = html_entity_decode(htmlentities($pgnText, ENT_QUOTES, $assumedEncoding), ENT_QUOTES , "UNICODE"); 311 } 312 313 return TRUE; 314} 315 316function check_tmpDir() { 317 318 global $pgnText, $pgnTextbox, $pgnUrl, $pgnFileName, $pgnFileSize, $pgnStatus, $forceEncodingFrom, $tmpDir, $debugHelpText, $pgnDebugInfo; 319 global $fileUploadLimitIniText, $fileUploadLimitText, $fileUploadLimitBytes, $startPosition, $goToView, $zipSupported; 320 321 if (preg_match("/^[a-zA-Z]+:\/\/.+/", $tmpDir)) { return; } 322 323 $unexpectedFiles = ""; 324 if ($tmpDirHandle = opendir($tmpDir)) { 325 while($entryName = readdir($tmpDirHandle)) { 326 if (($entryName !== ".") && ($entryName !== "..") && ($entryName !== "index.html")) { 327 if ((time() - filemtime($tmpDir . "/" . $entryName)) > 3600) { 328 $unexpectedFiles = $unexpectedFiles . " " . $entryName; 329 } 330 } 331 } 332 closedir($tmpDirHandle); 333 if ($unexpectedFiles) { 334 $pgnDebugInfo = $pgnDebugInfo . "\\n" . "clean temporary directory " . $tmpDir . ":" . $unexpectedFiles; 335 } 336 } else { 337 $pgnDebugInfo = $pgnDebugInfo . "\\n" . "failed opening temporary directory " . $tmpDir; 338 } 339 340} 341 342function print_menu($item) { 343 344 print <<<END 345 346<div style="height:0.2em; overflow:hidden;"><a name="$item"> </a></div> 347<div style="width:100%; text-align:right; font-size:66%; padding-bottom:0.5em;"> 348 <a href="#bottom" style="color: #B0B0B0;" onclick="this.blur();">bottom</a> 349 <a href="#moves" style="color: #B0B0B0;" onclick="this.blur();">moves</a> 350 <a href="#board" style="color: #B0B0B0;" onclick="this.blur();">board</a> 351 <a href="#top" style="color: #B0B0B0;" onclick="this.blur();">top</a> 352</div> 353 354END; 355} 356 357function print_header() { 358 359 global $headlessPage; 360 361 if (($headlessPage == "true") || ($headlessPage == "t")) { 362 $headClass = " display:none;"; 363 } else { 364 $headClass = ""; 365 } 366 367 print <<<END 368<!DOCTYPE HTML> 369<html> 370 371<head> 372 373<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1"> 374 375<meta name="viewport" content="width=800"> 376<link rel="icon" sizes="16x16" href="pawn.ico" /> 377<title>pgn4web games viewer</title> 378 379<style type="text/css"> 380 381html, 382body { 383 margin: 0px; 384 padding: 0px; 385} 386 387body { 388 color: black; 389 background: white; 390 font-family: 'pgn4web Liberation Sans', sans-serif; 391 font-size: 16px; 392 padding: 1.75em; 393 overflow-x: hidden; 394 overflow-y: scroll; 395} 396 397div, span, table, tr, td { 398 font-family: 'pgn4web Liberation Sans', sans-serif; /* fixes IE9 body css issue */ 399 font-size: 16px; /* fixes Opera table css issue */ 400 line-height: 1.4em; 401} 402 403a { 404 color: black; 405 text-decoration: none; 406} 407 408.formControl { 409 font-size: smaller; 410 margin: 0px; 411} 412 413.verticalMiddle { 414 display: block; 415 vertical-align: middle; 416} 417 418.borderBox { 419 box-sizing: border-box; 420 -moz-box-sizing: border-box; 421 -webkit-box-sizing: border-box; 422} 423 424.textboxAppearance { 425 appearance: field; 426 -moz-appearance: textfield; 427 -webkit-appearance: textfield; 428} 429 430.headClass { 431$headClass 432} 433 434</style> 435 436</head> 437 438<body onResize="if (typeof(updateAnnotationGraph) != 'undefined') { updateAnnotationGraph(); }"> 439 440<h1 class="headClass" style="margin-top:0px; padding-top:0px; text-align:right;"> 441<a style="float:left; color:red;"> 442pgn4web games viewer 443</a> 444<a href="." onfocus="this.blur();" style="width:49px; height:29px; background:url(pawns.png) -47px -15px; vertical-align:baseline; display:inline-block;"></a> 445</h1> 446 447<div style="height:1em;" class="headClass"> </div> 448 449END; 450} 451 452 453function print_form() { 454 455 global $pgnText, $pgnTextbox, $pgnUrl, $pgnFileName, $pgnFileSize, $pgnStatus, $forceEncodingFrom, $tmpDir, $debugHelpText, $pgnDebugInfo; 456 global $fileUploadLimitIniText, $fileUploadLimitText, $fileUploadLimitBytes, $startPosition, $goToView, $zipSupported; 457 global $headlessPage, $hideFormCss, $presetURLsArray; 458 459 $thisScript = $_SERVER['SCRIPT_NAME']; 460 if (($headlessPage == "true") || ($headlessPage == "t")) { $thisScript .= "?hp=t"; } 461 462 print <<<END 463 464<script type="text/javascript"> 465 "use strict"; 466 467 function setPgnUrl(newPgnUrl) { 468 if (!newPgnUrl) { newPgnUrl = ""; } 469 document.getElementById("urlFormText").value = newPgnUrl; 470 return false; 471 } 472 473 function checkPgnUrl() { 474 var theObj = document.getElementById("urlFormText"); 475 if (!theObj) { return false; } 476 if (!checkPgnExtension(theObj.value)) { return false; } 477 else { return (theObj.value !== ""); } 478 } 479 480 function checkPgnFile() { 481 var theObj = document.getElementById("uploadFormFile"); 482 if (!theObj) { return false; } 483 if (!checkPgnExtension(theObj.value)) { return false; } 484 else { return (theObj.value !== ""); } 485 } 486 487END; 488 489 if ($zipSupported) { print <<<END 490 491 function checkPgnExtension(uri) { 492 if (uri.replace(/[?#].*$/, "").match(/\\.(zip|pgn|txt)\$/i)) { 493 return true; 494 } else if (uri !== "") { 495 alert("only PGN and ZIP (zipped pgn) files are supported"); 496 } 497 return false; 498 } 499 500END; 501 502 } else { print <<<END 503 504 function checkPgnExtension(uri) { 505 if (uri.match(/\\.(pgn|txt)\$/i)) { 506 return true; 507 } else if (uri.match(/\\.zip\$/i)) { 508 alert("ZIP support unavailable from this server, only PGN files are supported\\n\\nplease submit locally extracted PGN"); 509 } else if (uri !== "") { 510 alert("only PGN files are supported (ZIP support unavailable from this server)"); 511 } 512 return false; 513 } 514 515END; 516 517 } 518 519 print <<<END 520 521 function checkPgnFormTextSize() { 522 document.getElementById("pgnFormButton").title = "view games from textbox: PGN textbox size is " + document.getElementById("pgnFormText").value.length; 523 if (document.getElementById("pgnFormText").value.length == 1) { 524 document.getElementById("pgnFormButton").title += " char;"; 525 } else { 526 document.getElementById("pgnFormButton").title += " chars;"; 527 } 528 document.getElementById("pgnFormButton").title += " $debugHelpText"; 529 document.getElementById("pgnFormText").title = document.getElementById("pgnFormButton").title; 530 } 531 532 533 function loadPgnFromForm() { 534 535 var theObjPgnFormText = document.getElementById('pgnFormText'); 536 if (!theObjPgnFormText) { return; } 537 if (theObjPgnFormText.value === "") { return; } 538 539 var theObjPgnText = document.getElementById('pgnText'); 540 if (!theObjPgnText) { return; } 541 542 theObjPgnText.value = theObjPgnFormText.value; 543 544 theObjPgnText.value = theObjPgnText.value.replace(/\\[/g,'\\n\\n['); 545 theObjPgnText.value = theObjPgnText.value.replace(/\\]/g,']\\n\\n'); 546 theObjPgnText.value = theObjPgnText.value.replace(/([012\\*])(\\s*)(\\[)/g,'\$1\\n\\n\$3'); 547 theObjPgnText.value = theObjPgnText.value.replace(/\\]\\s*\\[/g,']\\n['); 548 theObjPgnText.value = theObjPgnText.value.replace(/^\\s*\\[/g,'['); 549 theObjPgnText.value = theObjPgnText.value.replace(/\\n[\\s*\\n]+/g,'\\n\\n'); 550 551 document.getElementById('uploadFormFile').value = ""; 552 document.getElementById('urlFormText').value = ""; 553 554 if (analysisStarted) { stopAnalysis(); } 555 firstStart = true; 556 start_pgn4web(); 557 resetAlert(); 558 myAlert("info: games from textbox input", false, true); 559 560 goToHash("board"); 561 return; 562 } 563 564 function urlFormSelectChange() { 565 var theObj = document.getElementById("urlFormSelect"); 566 if (!theObj) { return; } 567 568 var targetPgnUrl = ""; 569 switch (theObj.value) { 570 571END; 572 573 foreach($presetURLsArray as $value) { 574 print("\n" . ' case "' . $value['label'] . '":' . "\n" . ' targetPgnUrl = (function(){ ' . $value['javascriptCode'] . '})();' . "\n" . ' break;' . "\n"); 575 } 576 577 $formVariableColspan = $presetURLsArray ? 2: 1; 578 print <<<END 579 580 default: 581 break; 582 } 583 setPgnUrl(targetPgnUrl); 584 theObj.value = "header"; 585 } 586 587var textFormMinHeight = ""; 588function getTextFormMinHeight() { 589 var theObj; 590 if ((theObj = document.getElementById("pgnFormText")) && (theObj.offsetHeight)) { 591 return (theObj.offsetHeight + "px"); 592 } else { 593 return "5em"; 594 } 595} 596 597function reset_viewer() { 598 599 document.getElementById("uploadFormFile").value = ""; 600 document.getElementById("urlFormText").value = ""; 601 document.getElementById("pgnFormText").value = ""; 602 document.getElementById("pgnFormText").style.height = textFormMinHeight; 603 checkPgnFormTextSize(); 604 document.getElementById("pgnText").value = '$startPosition'; 605 606 if (typeof(start_pgn4web) == "function") { 607 if (analysisStarted) { stopAnalysis(); } 608 firstStart = true; 609 SetAutoplayNextGame(false); 610 if (IsRotated) { FlipBoard(); } 611 start_pgn4web(); 612 resetAlert(); 613 resetLastCommentArea(); 614 } 615 616 goToHash("top"); 617} 618 619// fake functions to avoid warnings before pgn4web.js is loaded 620function disableShortcutKeysAndStoreStatus() {} 621function restoreShortcutKeysStatus() {} 622 623</script> 624 625<table style="margin-bottom:1.5em; $hideFormCss" width="100%" cellspacing="0" cellpadding="3" border="0"><tbody> 626 627 <form id="uploadForm" action="$thisScript" enctype="multipart/form-data" method="POST" style="display:inline;"> 628 <tr> 629 <td align="left" valign="middle"> 630 <input id="uploadFormSubmitButton" type="submit" class="formControl" value=" view games from local file " style="width:100%;" title="view games from local file: PGN and ZIP files must be smaller than $fileUploadLimitText (form limit) and $fileUploadLimitIniText (server limit); $debugHelpText" onClick="this.blur(); return checkPgnFile();"> 631 </td> 632 <td colspan="$formVariableColspan" width="100%" align="left" valign="middle"> 633 <input type="hidden" name="MAX_FILE_SIZE" value="$fileUploadLimitBytes"> 634 <input id="uploadFormFile" name="pgnFile" type="file" class="formControl borderBox" style="width:100%;" title="view games from local file: PGN and ZIP files must be smaller than $fileUploadLimitText (form limit) and $fileUploadLimitIniText (server limit); $debugHelpText" onClick="this.blur();"> 635 <input type="hidden" name="forceEncodingFrom" value="$forceEncodingFrom"> 636 </td> 637 </tr> 638 </form> 639 640 <form id="urlForm" action="$thisScript" method="POST" style="display:inline;"> 641 <tr> 642 <td align="left" valign="middle"> 643 <input id="urlFormSubmitButton" type="submit" class="formControl" value=" view games from remote URL " title="view games from remote URL: PGN and ZIP files must be smaller than $fileUploadLimitText (form limit) and $fileUploadLimitIniText (server limit); $debugHelpText" onClick="this.blur(); return checkPgnUrl();"> 644 </td> 645 <td width="100%" align="left" valign="middle"> 646 <input id="urlFormText" name="pgnUrl" type="text" class="formControl verticalMiddle borderBox" value="" style="width:100%;" onFocus="disableShortcutKeysAndStoreStatus();" onBlur="restoreShortcutKeysStatus();" title="view games from remote URL: PGN and ZIP files must be smaller than $fileUploadLimitText (form limit) and $fileUploadLimitIniText (server limit); $debugHelpText"> 647 <input type="hidden" name="forceEncodingFrom" value="$forceEncodingFrom"> 648 </td> 649END; 650 651 if ($presetURLsArray) { 652 print(' <td align="right" valign="middle">' . "\n" . ' <select id="urlFormSelect" class="formControl verticalMiddle" style="font-family:monospace; display:block; vertical-align:middle; max-width:20ex;" title="view games from remote URL: select the download URL from the preset options; please support the sites providing the PGN games downloads" onChange="this.blur(); urlFormSelectChange();">' . "\n" . ' <option value="header"> </option>' . "\n"); 653 foreach($presetURLsArray as $value) { 654 print(' <option value="' . $value['label'] . '">' . $value['label'] . '</option>' . "\n"); 655 } 656 print(' <option value="clear">clear URL</option>' . "\n" . ' </select>' . "\n" . ' </td>' . "\n"); 657 } 658 659 print <<<END 660 </tr> 661 </form> 662 663 <form id="textForm" style="display:inline;"> 664 <tr> 665 <td align="left" valign="top"> 666 <input id="pgnFormButton" type="button" class="formControl" value=" view games from textbox " style="width:100%;" onClick="this.blur(); loadPgnFromForm();"> 667 </td> 668 <td colspan="$formVariableColspan" rowspan="2" width="100%" align="right" valign="middle"> 669 <textarea id="pgnFormText" class="formControl verticalMiddle borderBox textboxAppearance" name="pgnTextbox" rows=4 style="width:100%; resize:vertical;" onFocus="disableShortcutKeysAndStoreStatus();" onBlur="restoreShortcutKeysStatus();" onChange="checkPgnFormTextSize();">$pgnTextbox</textarea> 670 </td> 671 </tr> 672 </form> 673 674 <tr> 675 <td align="left" valign="bottom"> 676 <input id="clearButton" type="button" class="formControl" value=" reset viewer " onClick="this.blur(); if (confirm('reset viewer: current PGN games and inputs will be lost')) { reset_viewer(); }" title="reset viewer: current PGN games and inputs will be lost"> 677 </td> 678 </tr> 679 680</tbody></table> 681 682<script type="text/javascript"> 683"use strict"; 684 685var textFormMinHeight = getTextFormMinHeight(); 686var theObj = document.getElementById("pgnFormText"); 687if (theObj) { 688 theObj.style.height = textFormMinHeight; 689 theObj.style.minHeight = textFormMinHeight; 690} 691 692</script> 693 694END; 695} 696 697function print_chessboard_one() { 698 699 global $pgnText, $pgnTextbox, $pgnUrl, $pgnFileName, $pgnFileSize, $pgnStatus, $forceEncodingFrom, $tmpDir, $debugHelpText, $pgnDebugInfo; 700 global $fileUploadLimitIniText, $fileUploadLimitText, $fileUploadLimitBytes, $startPosition, $goToView, $zipSupported; 701 global $hideFormCss; 702 703 print <<<END 704 705<style type="text/css"> 706 707@import url("fonts/pgn4web-font-LiberationSans.css"); 708@import url("fonts/pgn4web-font-ChessSansUsual.css"); 709 710.gameBoard, .boardTable { 711 width: 392px !important; 712 height: 392px !important; 713} 714 715.boardTable { 716 border-style: solid; 717 border-color: #663300; 718 border-width: 4px; 719 box-shadow: 0px 0px 20px #663300; 720} 721 722.pieceImage { 723 width: 36px; 724 height: 36px; 725} 726 727.whiteSquare, 728.blackSquare, 729.highlightWhiteSquare, 730.highlightBlackSquare { 731 width: 44px; 732 height: 44px; 733 border-style: solid; 734 border-width: 2px; 735} 736 737.whiteSquare, 738.highlightWhiteSquare { 739 border-color: #FFCC99; 740 background: #FFCC99; 741} 742 743.blackSquare, 744.highlightBlackSquare { 745 border-color: #CC9966; 746 background: #CC9966; 747} 748 749.highlightWhiteSquare, 750.highlightBlackSquare { 751 border-color: #663300; 752} 753 754.selectControl { 755/* a "width" attribute here must use the !important flag to override default settings */ 756 width: 100% !important; 757 margin-top: 1em; 758} 759 760.optionSelectControl { 761} 762 763.gameButtons { 764 width: 392px; 765} 766 767.buttonControlPlay, 768.buttonControlStop, 769.buttonControl { 770/* a "width" attribute here must use the !important flag to override default settings */ 771 width: 75.2px !important; 772 font-family: 'pgn4web ChessSansUsual', 'pgn4web Liberation Sans', sans-serif; 773 font-size: 1em; 774 color: #B0B0B0; 775 -moz-appearance: none; 776 -webkit-appearance: none; 777 border: none; 778 background: transparent; 779 margin-top: 25px; 780 margin-bottom: 10px; 781} 782 783.buttonControlSpace { 784/* a "width" attribute here must use the !important flag to override default settings */ 785 width: 4px !important; 786} 787 788.searchPgnButton { 789/* a "width" attribute here must use the !important flag to override default settings */ 790 width: 10% !important; 791} 792 793.searchPgnExpression { 794/* a "width" attribute here must use the !important flag to override default settings */ 795 width: 90% !important; 796} 797 798.move, 799.variation, 800.comment { 801 line-height: 1.4em; 802 font-weight: normal; 803} 804 805.move, 806.variation, 807.commentMove { 808 font-family: 'pgn4web ChessSansUsual', 'pgn4web Liberation Sans', sans-serif; 809} 810 811a.move, 812a.variation, 813.commentMove { 814 white-space: nowrap; 815} 816 817.move, 818.variation { 819 text-decoration: none; 820} 821 822.move { 823 color: black; 824} 825 826.moveText { 827 clear: both; 828 text-align: justify; 829} 830 831.comment, 832.variation { 833 color: #808080; 834} 835 836a.variation { 837 color: #808080; 838} 839 840.moveOn, 841.variationOn { 842 background-color: #FFCC99; 843} 844 845.selectSearchContainer { 846 text-align: center; 847} 848 849.emMeasure { 850 height: 1em; /* required */ 851 padding-top: 1em; 852} 853 854.mainContainer { 855 padding-top: 0.5em; 856 padding-bottom: 1em; 857} 858 859.columnsContainer { 860 float: left; 861 width: 100%; 862} 863 864.boardColumn { 865 float: left; 866 width: 60%; 867} 868 869.headerColumn { 870 margin-left: 60%; 871} 872 873.headerItem { 874 width: 100%; 875 height: 1.4em; 876 white-space: nowrap; 877 overflow: hidden; 878} 879 880.innerHeaderItem, 881.innerHeaderItemNoMargin { 882 color: black; 883 text-decoration: none; 884} 885 886.innerHeaderItem { 887 margin-right: 1.25em; 888} 889 890.innerHeaderItemNoMargin { 891 margin-right: 0px; 892} 893 894.headerSpacer { 895 height: 0.66em; 896} 897 898.gameAnnotationContainer { 899 height: 6em; 900 width: 100%; 901} 902 903.toggleComments, .toggleAnalysis { 904 white-space: nowrap; 905 text-align: right; 906} 907 908.toggleCommentsLink, .toggleAnalysisLink, .backButton { 909 display: inline-block; 910 width: 1em; 911 padding-left: 1em; 912 text-decoration: none; 913 text-align: right; 914 color: #B0B0B0; 915} 916 917.gameAnnotationMessage { 918 display: inline-block; 919 white-space: nowrap; 920 color: #B0B0B0; 921 margin-top: 25px; 922 margin-bottom: 10px; 923} 924 925.lastMoveAndVariations { 926 float: left; 927} 928 929.lastMove { 930} 931 932.lastVariations { 933 padding-left: 1em; 934} 935 936.nextMoveAndVariations { 937 float: right; 938} 939 940.nextMove { 941} 942 943.nextVariations { 944 padding-right: 1em; 945} 946 947.backButton { 948} 949 950.lastMoveAndComment { 951 clear: both; 952 line-height: 1.4em; 953 display: none; 954} 955 956.lastComment { 957 clear: both; 958 resize: vertical; 959 overflow-y: auto; 960 height: 4.2em; 961 min-height: 1.4em; 962 max-height: 21em; 963 padding-right: 1em; 964 margin-bottom: 1em; 965 text-align: justify; 966} 967 968.analysisEval { 969 display: inline-block; 970 min-width: 3em; 971} 972 973.analysisMove { 974} 975 976.tablebase { 977 display: none; 978} 979 980.analysisPv { 981 margin-left: 0.5em; 982} 983 984</style> 985 986<script src="pgn4web.js" type="text/javascript"></script> 987<script src="engine.js" type="text/javascript"></script> 988<script src="fonts/chess-informant-NAG-symbols.js" type="text/javascript"></script> 989<script src="fide-lookup.js" type="text/javascript"></script> 990 991<style type="text/css"> 992 993.NAGs { 994 font-size: 19px; 995 line-height: 0.9em; 996} 997 998</style> 999 1000<!-- paste your PGN below and make sure you dont specify an external source with SetPgnUrl() --> 1001<form style="display: none;"><textarea style="display: none;" id="pgnText"> 1002 1003$pgnText 1004 1005</textarea></form> 1006<!-- paste your PGN above and make sure you dont specify an external source with SetPgnUrl() --> 1007 1008<script type="text/javascript"> 1009 "use strict"; 1010 1011 var pgn4web_engineWindowUrlParameters = "pf=m"; 1012 1013 var highlightOption_default = true; 1014 var commentsOnSeparateLines_default = false; 1015 var commentsIntoMoveText_default = true; 1016 var initialHalfmove_default = "start"; 1017 1018 SetImagePath("images/merida/36"); 1019 SetImageType("png"); 1020 SetHighlightOption(getHighlightOptionFromLocalStorage()); 1021 SetCommentsIntoMoveText(getCommentsIntoMoveTextFromLocalStorage()); 1022 SetCommentsOnSeparateLines(getCommentsOnSeparateLinesFromLocalStorage()); 1023 SetInitialGame(1); 1024 SetInitialVariation(0); 1025 SetInitialHalfmove(initialHalfmove_default, true); 1026 SetGameSelectorOptions(null, true, 12, 12, 2, 15, 15, 3, 10); 1027 SetAutostartAutoplay(false); 1028 SetAutoplayNextGame(false); 1029 SetAutoplayDelay(getDelayFromLocalStorage()); 1030 SetShortcutKeysEnabled(true); 1031 1032 function getHighlightOptionFromLocalStorage() { 1033 var ho; 1034 try { ho = (localStorage.getItem("pgn4web_chess_viewer_highlightOption") != "false"); } 1035 catch(e) { return highlightOption_default; } 1036 return ho === null ? highlightOption_default : ho; 1037 } 1038 function setHighlightOptionToLocalStorage(ho) { 1039 try { localStorage.setItem("pgn4web_chess_viewer_highlightOption", ho ? "true" : "false"); } 1040 catch(e) { return false; } 1041 return true; 1042 } 1043 1044 function getCommentsIntoMoveTextFromLocalStorage() { 1045 var cimt; 1046 try { cimt = !(localStorage.getItem("pgn4web_chess_viewer_commentsIntoMoveText") == "false"); } 1047 catch(e) { return commentsIntoMoveText_default; } 1048 return cimt === null ? commentsIntoMoveText_default : cimt; 1049 } 1050 function setCommentsIntoMoveTextToLocalStorage(cimt) { 1051 try { localStorage.setItem("pgn4web_chess_viewer_commentsIntoMoveText", cimt ? "true" : "false"); } 1052 catch(e) { return false; } 1053 return true; 1054 } 1055 1056 function getCommentsOnSeparateLinesFromLocalStorage() { 1057 var cosl; 1058 try { cosl = (localStorage.getItem("pgn4web_chess_viewer_commentsOnSeparateLines") == "true"); } 1059 catch(e) { return commentsOnSeparateLines_default; } 1060 return cosl === null ? commentsOnSeparateLines_default : cosl; 1061 } 1062 function setCommentsOnSeparateLinesToLocalStorage(cosl) { 1063 try { localStorage.setItem("pgn4web_chess_viewer_commentsOnSeparateLines", cosl ? "true" : "false"); } 1064 catch(e) { return false; } 1065 return true; 1066 } 1067 var Delay_default = 2000; 1068 function getDelayFromLocalStorage() { 1069 var d; 1070 try { d = parseInt(localStorage.getItem("pgn4web_chess_viewer_Delay"), 10); } 1071 catch(e) { return Delay_default; } 1072 return ((d === null) || (isNaN(d))) ? Delay_default : d; 1073 } 1074 function setDelayToLocalStorage(d) { 1075 try { localStorage.setItem("pgn4web_chess_viewer_Delay", d); } 1076 catch(e) { return false; } 1077 return true; 1078 } 1079 1080 function searchTag(tag, key, event) { 1081 searchPgnGame('\\\\[\\\\s*' + tag + '\\\\s*"' + fixRegExp(key) + '"\\\\s*\\\\]', event.shiftKey); 1082 } 1083 function searchTagDifferent(tag, key, event) { 1084 searchPgnGame('\\\\[\\\\s*' + tag + '\\\\s*"(?!' + fixRegExp(key) + '"\\\\s*\\\\])', event.shiftKey); 1085 } 1086 1087 function fixHeaderTag(elementId) { 1088 var headerId = ["GameEvent", "GameSite", "GameDate", "GameRound", "GameWhite", "GameBlack", "GameResult", "GameMode", "GameSection", "GameStage", "GameBoardNum", "Timecontrol", "GameWhiteTeam", "GameBlackTeam", "GameWhiteTitle", "GameBlackTitle", "GameWhiteElo", "GameBlackElo", "GameECO", "GameOpening", "GameVariation", "GameSubVariation", "GameTermination", "GameAnnotator", "GameWhiteClock", "GameBlackClock", "GameTimeControl"]; 1089 var headerLabel = ["event", "site", "date", "round", "white player", "black player", "result", "mode", "section", "stage", "board", "time control", "white team", "black team", "white title", "black title", "white elo", "black elo", "eco", "opening", "variation", "subvariation", "termination", "annotator", "white clock", "black clock", "time control"]; 1090 var theObj = document.getElementById(elementId); 1091 if (theObj) { 1092 theObj.className = (theObj.innerHTML === "") ? "innerHeaderItemNoMargin" : "innerHeaderItem"; 1093 for (var ii = 0; ii < headerId.length; ii++) { 1094 if (headerId[ii] === elementId) { break; } 1095 } 1096 theObj.title = simpleHtmlentitiesDecode((ii < headerId.length ? headerLabel[ii] : elementId) + ": " + theObj.innerHTML); 1097 } 1098 } 1099 1100 function customPgnHeaderTagWithFix(tag, elementId, fixForDisplay) { 1101 var theObj; 1102 customPgnHeaderTag(tag, elementId); 1103 fixHeaderTag(elementId); 1104 if (fixForDisplay && (theObj = document.getElementById(elementId)) && theObj.innerHTML) { 1105 theObj.innerHTML = fixCommentForDisplay(theObj.innerHTML); 1106 } 1107 } 1108 1109 var previousCurrentVar = -1; 1110 function customFunctionOnMove() { 1111 1112 if (analysisStarted) { 1113 if (engineUnderstandsGame(currentGame)) { 1114 if (previousCurrentVar !== CurrentVar) { scanGameForFen(); } 1115 restartAnalysis(); 1116 } 1117 else { stopAnalysis(); } 1118 } else { 1119 clearAnalysisHeader(); 1120 clearAnnotationGraph(); 1121 } 1122 previousCurrentVar = CurrentVar; 1123 1124 fixHeaderTag('GameWhiteClock'); 1125 fixHeaderTag('GameBlackClock'); 1126 1127 if ((annotateInProgress) && (!analysisStarted)) { stopAnnotateGame(false); } 1128 else if (theObj = document.getElementById("GameAnnotationMessage")) { 1129 if ((!annotateInProgress) && (theObj.innerHTML.indexOf("completed") > -1)) { 1130 theObj.style.display = "none"; 1131 theObj.innerHTML = ""; 1132 theObj.title = ""; 1133 if (theObj = document.getElementById("GameButtons")) { 1134 theObj.style.display = ""; 1135 } 1136 } 1137 } 1138 } 1139 1140 var PlyNumberMax; 1141 function customFunctionOnPgnGameLoad() { 1142 var theObj; 1143 fixHeaderTag('GameDate'); 1144 customPgnHeaderTagWithFix('Mode', 'GameMode'); 1145 fixHeaderTag('GameSite'); 1146 fixHeaderTag('GameEvent'); 1147 customPgnHeaderTagWithFix('Section', 'GameSection'); 1148 customPgnHeaderTagWithFix('Stage', 'GameStage'); 1149 fixHeaderTag('GameRound'); 1150 if (theObj = document.getElementById("GameRound")) { 1151 if (theObj.innerHTML) { 1152 theObj.innerHTML = "round " + theObj.innerHTML; 1153 } 1154 } 1155 customPgnHeaderTagWithFix('Board', 'GameBoardNum'); 1156 if (theObj = document.getElementById("GameBoardNum")) { 1157 if (theObj.innerHTML) { 1158 theObj.innerHTML = "board " + theObj.innerHTML; 1159 } 1160 } 1161 customPgnHeaderTagWithFix('TimeControl', 'GameTimeControl'); 1162 fixHeaderTag('GameWhite'); 1163 fixHeaderTag('GameBlack'); 1164 customPgnHeaderTagWithFix('WhiteTeam', 'GameWhiteTeam'); 1165 customPgnHeaderTagWithFix('BlackTeam', 'GameBlackTeam'); 1166 customPgnHeaderTagWithFix('WhiteTitle', 'GameWhiteTitle'); 1167 customPgnHeaderTagWithFix('BlackTitle', 'GameBlackTitle'); 1168 customPgnHeaderTagWithFix('WhiteElo', 'GameWhiteElo'); 1169 customPgnHeaderTagWithFix('BlackElo', 'GameBlackElo'); 1170 customPgnHeaderTagWithFix('ECO', 'GameECO'); 1171 customPgnHeaderTagWithFix('Opening', 'GameOpening', true); 1172 customPgnHeaderTagWithFix('Variation', 'GameVariation', true); 1173 customPgnHeaderTagWithFix('SubVariation', 'GameSubVariation', true); 1174 fixHeaderTag('GameResult'); 1175 customPgnHeaderTagWithFix('Termination', 'GameTermination'); 1176 customPgnHeaderTagWithFix('Annotator', 'GameAnnotator'); 1177 if (PlyNumber > 0) { customPgnHeaderTag('Result', 'ResultAtGametextEnd'); } 1178 else { if (theObj = document.getElementById('ResultAtGametextEnd')) { theObj.innerHTML = ""; } } 1179 1180 if (theObj = document.getElementById("GameNumCurrent")) { 1181 theObj.innerHTML = currentGame + 1; 1182 theObj.title = "current game: " + (currentGame + 1); 1183 } 1184 1185 if (theObj = document.getElementById('lastMoveAndComment')) { 1186 var lastDisplayStyle; 1187 if ((PlyNumber === 0) && (gameFEN[currentGame])) { 1188 lastDisplayStyle = "block"; 1189 } else if (commentsIntoMoveText && ((PlyNumber > 0) || (gameFEN[currentGame]))) { 1190 lastDisplayStyle = GameHasComments ? "block" : "none"; 1191 } else { 1192 lastDisplayStyle = "none"; 1193 } 1194 theObj.style.display = lastDisplayStyle; 1195 } 1196 if (theObj = document.getElementById("toggleCommentsLink")) { 1197 if (GameHasComments) { 1198 theObj.innerHTML = commentsIntoMoveText ? "×" : "+"; 1199 } else { 1200 theObj.innerHTML = ""; 1201 } 1202 } 1203 1204 PlyNumberMax = 0; 1205 for (ii = 0; ii < numberOfVars; ii++) { 1206 PlyNumberMax = Math.max(PlyNumberMax, StartPlyVar[ii] + PlyNumberVar[ii] - StartPly); 1207 } 1208 1209 if (analysisStarted) { 1210 if (engineUnderstandsGame(currentGame)) { scanGameForFen(); } 1211 else { stopAnalysis(); } 1212 } 1213 if (theObj = document.getElementById("toggleAnalysisLink")) { 1214 theObj.style.visibility = (annotationSupported && engineUnderstandsGame(currentGame)) ? "visible" : "hidden"; 1215 } 1216 if (theObj = document.getElementById("GameAnalysisEval")) { 1217 theObj.style.visibility = (annotationSupported && engineUnderstandsGame(currentGame)) ? "visible" : "hidden"; 1218 } 1219 1220 stopAnnotateGame(false); 1221 } 1222 1223 function customFunctionOnPgnTextLoad() { 1224 var theObj; 1225 var gameLoadStatus = "$pgnStatus"; 1226 if (gameLoadStatus) { myAlert(gameLoadStatus, gameLoadStatus.match(/^error:/), !gameLoadStatus.match(/^error:/)); } 1227 if (theObj = document.getElementById("GameNumInfo")) { 1228 theObj.style.display = numberOfGames > 1 ? "block" : "none"; 1229 } 1230 if (theObj = document.getElementById("GameNumTotal")) { 1231 theObj.innerHTML = numberOfGames; 1232 theObj.title = "number of games: " + numberOfGames; 1233 } 1234 } 1235 1236 function searchPlayer(name, FideId, event) { 1237 if (name) { 1238 if (event.shiftKey) { 1239 if (typeof(openFidePlayerUrl) == "function") { openFidePlayerUrl(name, FideId); } 1240 } else { 1241 searchPgnGame('\\\\[\\\\s*(White|Black)\\\\s*"' + fixRegExp(name) + '"\\\\s*\\\\]', false); 1242 } 1243 } 1244 } 1245 1246 function searchTeam(name) { 1247 searchPgnGame('\\\\[\\\\s*(White|Black)Team\\\\s*"' + fixRegExp(name) + '"\\\\s*\\\\]', false); 1248 } 1249 1250 function cycleHash() { 1251 switch (location.hash) { 1252 case "#top": goToHash("board"); break; 1253 case "#board": goToHash("moves"); break; 1254 case "#zoom": goToHash("moves"); break; 1255 case "#moves": goToHash("bottom"); break; 1256 case "#bottom": goToHash("top"); break; 1257 default: goToHash("board"); break; 1258 } 1259 } 1260 1261 function goToHash(hash) { 1262 if (hash) { location.hash = ""; } 1263 else { location.hash = "#board"; } 1264 location.hash = "#" + hash; 1265 } 1266 1267 var shortcutKeyTimeout = null; 1268 1269 // customShortcutKey_Shift_1 defined by fide-lookup.js 1270 // customShortcutKey_Shift_2 defined by fide-lookup.js 1271 1272 function customShortcutKey_Shift_3() { if (shortcutKeyTimeout) { SetInitialHalfmove(initialHalfmove_default, true); } else { shortcutKeyTimeout = setTimeout("shortcutKeyTimeout = null;", 333); SetInitialHalfmove(initialHalfmove == "end" ? "start" : "end", true); } } 1273 1274 function customShortcutKey_Shift_4() { if (shortcutKeyTimeout) { goToHash("zoom"); } else { shortcutKeyTimeout = setTimeout("shortcutKeyTimeout = null;", 333); cycleHash(); } } 1275 1276 function customShortcutKey_Shift_5() { cycleLastCommentArea(); } 1277 1278 function customShortcutKey_Shift_6() { if (annotationSupported) { userToggleAnalysis(); } } 1279 function customShortcutKey_Shift_7() { if (annotationSupported) { goToMissingAnalysis(true); } } 1280 1281 // customShortcutKey_Shift_8 defined by engine.js 1282 // customShortcutKey_Shift_9 defined by engine.js 1283 // customShortcutKey_Shift_0 defined by engine.js 1284 1285 1286 function gameIsNormalChess(gameNum) { 1287 return ((typeof(gameVariant[gameNum]) == "undefined") || (gameVariant[gameNum].match(/^(chess|normal|standard|)$/i) !== null)); 1288 } 1289 1290 1291 function emPixels(em) { return em * document.getElementById("emMeasure").offsetHeight; } 1292 1293 var cycleLCA = 0; 1294 function cycleLastCommentArea() { 1295 var theObj = document.getElementById("GameLastComment"); 1296 if (theObj) { 1297 switch (cycleLCA++ % 3) { 1298 case 0: 1299 if (theObj.scrollHeight === theObj.clientHeight) { cycleLastCommentArea(); } 1300 else { fitLastCommentArea(); } 1301 break; 1302 case 1: 1303 if (theObj.offsetHeight == emPixels(21)) { cycleLastCommentArea(); } 1304 else { maximizeLastCommentArea(); } 1305 break; 1306 case 2: 1307 if (theObj.offsetHeight == emPixels(4.2)) { cycleLastCommentArea(); } 1308 else { resetLastCommentArea(); } 1309 break; 1310 default: 1311 break; 1312 } 1313 } 1314 } 1315 1316 function resetLastCommentArea() { 1317 var theObj = document.getElementById("GameLastComment"); 1318 if (theObj) { theObj.style.height = ""; } 1319 } 1320 1321 function fitLastCommentArea() { 1322 var theObj = document.getElementById("GameLastComment"); 1323 if (theObj) { 1324 theObj.style.height = ""; 1325 theObj.style.height = theObj.scrollHeight + "px"; 1326 } 1327 } 1328 1329 function maximizeLastCommentArea() { 1330 var theObj = document.getElementById("GameLastComment"); 1331 if (theObj) { theObj.style.height = "21em"; } 1332 } 1333 1334 function clickedGameAnalysisEval() { 1335 displayHelp('informant_symbols'); 1336 } 1337 1338</script> 1339 1340<div class="selectSearchContainer"> 1341<table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr> 1342<td colspan="2" align="left" valign="bottom"> 1343<div id="GameSelector" class="gameSelector"></div> 1344</td> 1345</tr><tr> 1346<td width="100%" align="left" valign="top"> 1347<div id="GameSearch" style="white-space:nowrap;"></div> 1348</td><td align="right" valign="bottom"> 1349<div id="GameNumInfo" style="width:15ex; margin-right:0.5ex; display:none; color: #808080; font-size: 66%;"><span id="GameNumCurrent" style="font-size: 100%;" title="current game"></span> / <span id="GameNumTotal" style="font-size: 100%;" title="number of games"></span></div> 1350</td> 1351</tr></tbody></table> 1352<div id="emMeasure" class="emMeasure"><a href="#zoom" onclick="this.blur();" id="zoom" class="NAGs" style="width:392px; font-size:14px; display:inline-block;"> </a></div> 1353<div><a name="zoom"> </a></div> 1354</div> 1355 1356<div class="mainContainer"> 1357 1358<div class="columnsContainer"> 1359 1360<div class="boardColumn"> 1361<center> 1362<div id="GameBoard" class="gameBoard"></div> 1363<div id="GameButtons" class="gameButtons"></div> 1364<a href="javascript:void(0);" onclick="stopAnnotateGame(false); this.blur();" class="gameAnnotationMessage" style="display:none;" id="GameAnnotationMessage"></a> 1365</center> 1366</div> 1367 1368<div class="headerColumn"> 1369<div class="headerItem"><a class="innerHeaderItem" id="GameDate" href="javascript:void(0);" onclick="searchTagDifferent('Date', this.innerHTML, event); this.blur();"></a><span class="innerHeaderItem" id="GameMode"></span><b> </b></div> 1370<div class="headerItem"><a class="innerHeaderItem" id="GameSite" href="javascript:void(0);" onclick="searchTagDifferent('Site', this.innerHTML, event); this.blur();"></a><b> </b></div> 1371<div class="headerItem headerSpacer"><b> </b></div> 1372<div class="headerItem"><a class="innerHeaderItem" id="GameEvent" href="javascript:void(0);" onclick="searchTagDifferent('Event', this.innerHTML, event); this.blur();"></a><a class="innerHeaderItem" id="GameSection" href="javascript:void(0);" onclick="searchTagDifferent('Section', this.innerHTML, event); this.blur();"></a><a class="innerHeaderItem" id="GameStage" href="javascript:void(0);" onclick="searchTagDifferent('Stage', this.innerHTML, event); this.blur();"></a><b> </b></div> 1373<div class="headerItem"><a class="innerHeaderItem" id="GameRound" href="javascript:void(0);" onclick="searchTagDifferent('Round', this.innerHTML.replace('round ', ''), event); this.blur();"></a><a class="innerHeaderItem" id="GameBoardNum" href="javascript:void(0);" onclick="searchTagDifferent('Board', this.innerHTML, event); this.blur();"></a><a class="innerHeaderItem" id="GameTimeControl" href="javascript:void(0);" onclick="searchTagDifferent('TimeControl', this.innerHTML, event); this.blur();"></a><b> </b></div> 1374<div class="headerItem headerSpacer"><b> </b></div> 1375<div class="headerItem"><a class="innerHeaderItem" id="GameECO" href="javascript:void(0);" onclick="searchTag('ECO', this.innerHTML, event); this.blur();"></a><a class="innerHeaderItem" id="GameOpening" href="javascript:void(0);" onclick="searchTag('Opening', customPgnHeaderTag('Opening'), event); this.blur();"></a><a class="innerHeaderItem" id="GameVariation" href="javascript:void(0);" onclick="searchTag('Variation', customPgnHeaderTag('Variation'), event); this.blur();"></a><a class="innerHeaderItem" id="GameSubVariation" href="javascript:void(0);" onclick="searchTag('SubVariation', customPgnHeaderTag('SubVariation'), event); this.blur();"></a><b> </b></div> 1376<div class="headerItem headerSpacer"><b> </b></div> 1377<div class="headerItem"><span class="innerHeaderItem" id="GameWhiteClock"></span><b> </b></div> 1378<div class="headerItem"><b><a href="javascript:void(0);" onclick="searchPlayer(this.innerHTML, customPgnHeaderTag('WhiteFideId'), event); this.blur();" class="innerHeaderItem" id="GameWhite"></a></b><span class="innerHeaderItem" id="GameWhiteTitle"></span><span class="innerHeaderItem" id="GameWhiteElo"></span><a class="innerHeaderItem" id="GameWhiteTeam" href="javascript:void(0);" onclick="searchTeam(this.innerHTML); this.blur();"></a><b> </b></div> 1379<div class="headerItem"><b><a href="javascript:void(0);" onclick="searchPlayer(this.innerHTML, customPgnHeaderTag('BlackFideId'), event); this.blur();" class="innerHeaderItem" id="GameBlack"></a></b><span class="innerHeaderItem" id="GameBlackTitle"></span><span class="innerHeaderItem" id="GameBlackElo"></span><a class="innerHeaderItem" id="GameBlackTeam" href="javascript:void(0);" onclick="searchTeam(this.innerHTML); this.blur();"></a><b> </b></div> 1380<div class="headerItem"><span class="innerHeaderItem" id="GameBlackClock"></span><b> </b></div> 1381<div class="headerItem headerSpacer"><b> </b></div> 1382<div class="headerItem"><b><a href="javascript:void(0);" onclick="SetInitialHalfmove(event.shiftKey ? initialHalfmove_default : (initialHalfmove == 'end' ? 'start' : 'end'), true); GoToMove(initialHalfmove == 'end' ? StartPlyVar[0] + PlyNumberVar[0] : StartPlyVar[0], 0); this.blur();" class="innerHeaderItem" id="GameResult"></a></b><span class="innerHeaderItem" id="GameTermination"></span><span class="innerHeaderItem" id="GameAnnotator"></span><b> </b></div> 1383<div class="headerItem headerSpacer"><b> </b></div> 1384<div class="headerItem headerSpacer"><b> </b></div> 1385<div class="headerItem headerSpacer"><b> </b></div> 1386<div class="headerItem"><a href="javascript:void(0);" onclick="if (event.shiftKey) { clickedGameAnalysisEval(); } else { userToggleAnalysis(); } this.blur(); return false;" class="innerHeaderItem analysisEval" id="GameAnalysisEval" title="start annotation">· </a><a href="javascript:void(0);" onclick="if (event.shiftKey) { MoveBackward(1); } else { goToMissingAnalysis(false); } this.blur();" class="innerHeaderItem move analysisMove notranslate" id="GameAnalysisMove" title="annotated move"></a><a href="javascript:void(0);" onclick="clickedGameTablebase();" class="innerHeaderItem tablebase" id="GameTablebase" title="probe endgame tablebase"> </a><a href="javascript:void(0);" onclick="if (event.shiftKey) { MoveForward(1); } else { goToMissingAnalysis(true); } this.blur();" class="innerHeaderItemNoMargin move analysisPv notranslate" id="GameAnalysisPv"></a><b> </b></div> 1387<div class="headerItem headerSpacer"><b> </b></div> 1388<div class="gameAnnotationContainer" id="GameAnnotationContainer"> 1389<canvas class="gameAnnotationGraph" id="GameAnnotationGraph" height="1" width="1" onclick="annotationGraphClick(event); this.blur();" onmousemove="annotationGraphMousemove(event);" onmouseover="annotationGraphMouseover(event);" onmouseout="annotationGraphMouseout(event);"></canvas> 1390</div> 1391<div class="headerItem headerSpacer"><b> </b></div> 1392<div class="toggleAnalysis" id="toggleAnalysis"> <a class="toggleAnalysisLink" style="visibility:hidden;" id="toggleAnalysisLink" href="javascript:void(0);" onclick="if (event.shiftKey) { annotateGame(false); } else { userToggleAnalysis(); } this.blur();" title="toggle annotation">+</a></div> 1393<div class="toggleComments" id="toggleComments"> <a class="toggleCommentsLink" id="toggleCommentsLink" href="javascript:void(0);" onClick="if (event.shiftKey && commentsIntoMoveText) { cycleLastCommentArea(); } else { SetCommentsIntoMoveText(!commentsIntoMoveText); var oldPly = CurrentPly; var oldVar = CurrentVar; Init(); GoToMove(oldPly, oldVar); } this.blur();" title="toggle show comments in game text"></a></div> 1394</div> 1395 1396</div> 1397 1398<div class="lastMoveAndComment" id="lastMoveAndComment"> 1399<div class="lastMoveAndVariations"> 1400<span class="lastMove" id="GameLastMove" title="last move"></span> 1401<span class="lastVariations" id="GameLastVariations" title="last move alternatives"></span> 1402</div> 1403<div class="nextMoveAndVariations"> 1404<span class="nextVariations" id="GameNextVariations" title="next move alternatives"></span> 1405<span class="nextMove" id="GameNextMove" title="next move"></span><a class="backButton" href="javascript:void(0);" onclick="backButton(event); this.blur();" title="move backward"><</a> 1406</div> 1407<div> </div> 1408<div class="lastComment" title="current position comment" id="GameLastComment"></div> 1409</div> 1410</div> 1411 1412END; 1413} 1414 1415function print_chessboard_two() { 1416 1417 global $pgnText, $pgnTextbox, $pgnUrl, $pgnFileName, $pgnFileSize, $pgnStatus, $forceEncodingFrom, $tmpDir, $debugHelpText, $pgnDebugInfo; 1418 global $fileUploadLimitIniText, $fileUploadLimitText, $fileUploadLimitBytes, $startPosition, $goToView, $zipSupported; 1419 1420 print <<<END 1421 1422<div class="mainContainer"> 1423<div id="moveText" class="moveText"><span id="GameText"></span> <span class="move" style="white-space:nowrap;" id="ResultAtGametextEnd"></span></div> 1424</div> 1425 1426 1427<script type="text/javascript"> 1428 "use strict"; 1429 1430 var theObj; 1431 1432 var maxMenInTablebase = 0; 1433 var minMenInTablebase = 3; 1434 function probeTablebase() {} 1435 1436 1437// DeploymentCheck: tablebase glue code 1438 1439// end DeploymentCheck 1440 1441 1442 function clickedGameTablebase() { 1443 var menPosition = CurrentFEN().replace(/\s.*$/, "").replace(/[0-9\/]/g, "").length; 1444 if ((menPosition >= minMenInTablebase) && (menPosition <= maxMenInTablebase)) { 1445 probeTablebase(); 1446 } else { 1447 myAlert("warning: endgame tablebase only supports positions with " + minMenInTablebase + " to " + maxMenInTablebase + " men"); 1448 } 1449 } 1450 theObj = document.getElementById("GameTablebase"); 1451 if (theObj) { theObj.innerHTML = translateNAGs("$148"); } 1452 1453 function updateTablebaseFlag(thisFen) { 1454 if (typeof(thisFen) == "undefined") { thisFen = CurrentFEN(); } 1455 var menPosition = thisFen.replace(/\s.*$/, "").replace(/[0-9\/]/g, "").length; 1456 var theObj = document.getElementById("GameTablebase"); 1457 if (theObj) { 1458 theObj.style.display = (menPosition >= minMenInTablebase) && (menPosition <= maxMenInTablebase) && (!g_initErrors) && (analysisStarted) ? "inline" : "none"; 1459 } 1460 } 1461 1462 var annotationSupported = !!window.Worker; 1463 try { 1464 document.getElementById("GameAnnotationGraph").getContext("2d"); 1465 } catch(e) { annotationSupported = false; } 1466 1467 var analysisStarted = false; 1468 function toggleAnalysis() { 1469 if (analysisStarted) { stopAnalysis(); } 1470 else { restartAnalysis(); } 1471 } 1472 1473 function restartAnalysis() { 1474 analysisStarted = StartEngineAnalysis(); 1475 if (theObj = document.getElementById("toggleAnalysisLink")) { theObj.innerHTML = "×"; } 1476 updateAnnotationGraph(); 1477 updateAnalysisHeader(); 1478 } 1479 1480 function stopAnalysis() { 1481 stopAnnotateGame(false); 1482 StopBackgroundEngine(); 1483 analysisStarted = false; 1484 var theObj = document.getElementById("toggleAnalysisLink"); 1485 if (theObj) { theObj.innerHTML = "+"; } 1486 clearAnnotationGraph(); 1487 clearAnalysisHeader(); 1488 save_cache_to_localStorage(); 1489 } 1490 1491 var fenPositions; 1492 var fenPositionsEval; 1493 var fenPositionsPv; 1494 var fenPositionsDepth; 1495 resetFenPositions(); 1496 1497 function resetFenPositions() { 1498 fenPositions = new Array(); 1499 fenPositionsEval = new Array(); 1500 fenPositionsPv = new Array(); 1501 fenPositionsDepth = new Array(); 1502 } 1503 1504 var annotationBarWidth; 1505 function updateAnnotationGraph() { 1506 if (!annotationSupported) { return; } 1507 var index, theObj; 1508 if (!analysisStarted) { clearAnnotationGraph(); } 1509 else if (theObj = document.getElementById("GameAnnotationGraph")) { 1510 1511 var canvasWidth = graphCanvasWidth(); 1512 theObj.width = canvasWidth; 1513 var canvasHeight = graphCanvasHeight(); 1514 theObj.height = canvasHeight; 1515 1516 var annotationPlyBlock = 40; 1517 annotationBarWidth = canvasWidth / (Math.max(Math.ceil(PlyNumberMax / annotationPlyBlock) * annotationPlyBlock, 2 * annotationPlyBlock) + 2); 1518 var barOverlap = Math.ceil(annotationBarWidth / 20); 1519 var lineHeight = Math.ceil(canvasHeight / 100); 1520 var lineTop = Math.floor((canvasHeight - lineHeight) / 2); 1521 var lineBottom = lineTop + lineHeight; 1522 var maxBarHeight = lineTop + barOverlap; 1523 1524 var context = theObj.getContext("2d"); 1525 context.beginPath(); 1526 var thisBarTopLeftX = 0; 1527 var thisBarHeight = lineHeight; 1528 var thisBarTopLeftY = lineTop; 1529 context.rect(thisBarTopLeftX, thisBarTopLeftY, (PlyNumber + 1) * annotationBarWidth + barOverlap, thisBarHeight); 1530 context.fillStyle = "#D9D9D9"; 1531 context.fill(); 1532 context.fillStyle = "#666666"; 1533 var highlightTopLeftX = null; 1534 var highlightTopLeftY = null; 1535 var highlightBarHeight = null; 1536 for (var annPly = StartPly; annPly <= StartPly + PlyNumber; annPly++) { 1537 var annGraphEval = typeof(fenPositionsEval[annPly]) != "undefined" ? fenPositionsEval[annPly] : (annPly === CurrentPly ? 0 : null); 1538 if (annGraphEval !== null) { 1539 thisBarTopLeftX = (annPly - StartPly) * annotationBarWidth; 1540 if (annGraphEval >= 0) { 1541 thisBarHeight = Math.max((1 - Math.pow(2, -annGraphEval)) * maxBarHeight, lineHeight); 1542 thisBarTopLeftY = lineBottom - thisBarHeight; 1543 } else { 1544 thisBarHeight = Math.max((1 - Math.pow(2, annGraphEval)) * maxBarHeight, lineHeight); 1545 thisBarTopLeftY = lineTop; 1546 } 1547 if (annPly !== CurrentPly) { 1548 context.beginPath(); 1549 context.rect(thisBarTopLeftX, thisBarTopLeftY, annotationBarWidth + barOverlap, thisBarHeight); 1550 context.fill(); 1551 } else { 1552 highlightTopLeftX = thisBarTopLeftX; 1553 highlightTopLeftY = thisBarTopLeftY; 1554 highlightBarHeight = thisBarHeight; 1555 } 1556 } 1557 } 1558 if (highlightBarHeight !== null) { 1559 context.beginPath(); 1560 context.rect(highlightTopLeftX, highlightTopLeftY, annotationBarWidth + barOverlap, highlightBarHeight); 1561 context.fillStyle = "#FF6633"; 1562 context.fill(); 1563 } 1564 } 1565 } 1566 1567 function clearAnnotationGraph() { 1568 if (!annotationSupported) { return; } 1569 var theObj = document.getElementById("GameAnnotationGraph"); 1570 if (theObj) { 1571 var context = theObj.getContext("2d"); 1572 theObj.width = graphCanvasWidth(); 1573 theObj.height = graphCanvasHeight(); 1574 context.clearRect(0, 0, theObj.width, theObj.height); 1575 } 1576 } 1577 1578 function graphCanvasWidth() { 1579 var theObj = document.getElementById("GameAnnotationContainer"); 1580 if (theObj) { return theObj.offsetWidth; } 1581 else { return 320; } 1582 } 1583 function graphCanvasHeight() { 1584 var theObj = document.getElementById("GameAnnotationContainer"); 1585 if (theObj) { return theObj.offsetHeight; } 1586 else { return 96; } 1587 } 1588 1589 function updateAnalysisHeader() { 1590 if (!analysisStarted) { clearAnalysisHeader(); return; } 1591 1592 var theObj; 1593 var annPly = (lastMousemoveAnnPly == -1) ? CurrentPly : lastMousemoveAnnPly; 1594 var annMove = "· "; 1595 if (theObj = document.getElementById("GameAnalysisMove")) { 1596 if ((annPly > StartPly) && (annPly <= StartPly + PlyNumber)) { 1597 annMove = (Math.floor(annPly / 2) + (annPly % 2)) + (annPly % 2 ? ". " : "... ") + Moves[annPly - 1]; 1598 if (isBlunder(annPly, blunderThreshold)) { annMove += translateNAGs("$4"); } 1599 else if (isBlunder(annPly, mistakeThreshold)) { annMove += translateNAGs("$2"); } 1600 annMove += " "; 1601 } 1602 theObj.innerHTML = annMove; 1603 } 1604 1605 var annEval = fenPositionsEval[annPly]; 1606 var annPv = fenPositionsPv[annPly]; 1607 var annDepth = fenPositionsDepth[annPly]; 1608 1609 if (theObj = document.getElementById("GameAnalysisEval")) { 1610 theObj.innerHTML = (annEval || annEval === 0) ? ev2NAG(annEval) : ""; 1611 theObj.title = (annEval || annEval === 0) ? "engine evaluation: " + (annEval > 0 ? "+" : "") + annEval + (annEval == Math.floor(annEval) ? ".0" : "") + (annDepth ? " depth: " + annDepth : "") : ""; 1612 } 1613 if (theObj = document.getElementById("GameAnalysisPv")) { 1614 theObj.innerHTML = annPv ? annPv : ""; 1615 theObj.title = annPv ? "engine principal variation: " + annPv : ""; 1616 } 1617 1618 updateTablebaseFlag(fenPositions[annPly]); 1619 } 1620 1621 1622 var moderateDefiniteThreshold = 1.85; 1623 var slightModerateThreshold = 0.85; 1624 var equalSlightThreshold = 0.25; 1625 1626 var useNAGeval = (NAGstyle != 'default'); 1627 function ev2NAG(ev) { 1628 if ((ev === null) || (ev === "") || (isNaN(ev = parseFloat(ev)))) { return ""; } 1629 if (!useNAGeval) { return (ev > 0 ? "+" : "") + ev + (ev == Math.floor(ev) ? ".0" : ""); } 1630 if (ev < -moderateDefiniteThreshold) { return NAG[19]; } // -+ 1631 if (ev > moderateDefiniteThreshold) { return NAG[18]; } // +- 1632 if (ev < -slightModerateThreshold) { return NAG[17]; } // -/+ 1633 if (ev > slightModerateThreshold) { return NAG[16]; } // +/- 1634 if (ev < -equalSlightThreshold) { return NAG[15]; } // =/+ 1635 if (ev > equalSlightThreshold) { return NAG[14]; } // +/= 1636 return NAG[11]; // = 1637 } 1638 1639 function clearAnalysisHeader() { 1640 var theObj; 1641 if (theObj = document.getElementById("GameAnalysisMove")) { theObj.innerHTML = ""; } 1642 if (theObj = document.getElementById("GameAnalysisEval")) { theObj.innerHTML = "· "; theObj.title = "start annotation"; } 1643 if (theObj = document.getElementById("GameTablebase")) { theObj.style.display = "none"; } 1644 if (theObj = document.getElementById("GameAnalysisPv")) { theObj.innerHTML = ""; } 1645 } 1646 1647 1648 var lastMousemoveAnnPly = -1; 1649 var lastMousemoveAnnGame = -1; 1650 1651 function annotationGraphMouseover(e) { 1652 } 1653 1654 function annotationGraphMouseout(e) { 1655 lastMousemoveAnnPly = -1; 1656 lastMousemoveAnnGame = -1; 1657 if (analysisStarted) { updateAnalysisHeader(); } 1658 } 1659 1660 function annotationGraphMousemove(e) { 1661 var newMousemoveAnnPly = StartPly + Math.floor((e.pageX - document.getElementById("GameAnnotationGraph").offsetLeft) / annotationBarWidth); 1662 if ((newMousemoveAnnPly !== lastMousemoveAnnPly) || (currentGame !== lastMousemoveAnnGame)) { 1663 lastMousemoveAnnPly = newMousemoveAnnPly <= StartPly + PlyNumber ? newMousemoveAnnPly : -1; 1664 lastMousemoveAnnGame = currentGame; 1665 if (analysisStarted) { updateAnalysisHeader(); } 1666 } 1667 } 1668 1669 function annotationGraphClick(e) { 1670 if ((analysisStarted) && (typeof(annotationBarWidth) != "undefined")) { 1671 var annPly = StartPly + Math.floor((e.pageX - document.getElementById("GameAnnotationGraph").offsetLeft) / annotationBarWidth); 1672 if ((annPly >= StartPly) && (annPly <= StartPly + PlyNumber)) { 1673 if (e.shiftKey) { save_cache_to_localStorage(); } 1674 else { GoToMove(annPly); } 1675 } 1676 } 1677 } 1678 1679 function num2string(num) { 1680 var unit = ""; 1681 if (num >= Math.pow(10, 12)) { num = Math.round(num / Math.pow(10, 11)) / 10; unit = "T"; } 1682 else if (num >= Math.pow(10, 9)) { num = Math.round(num / Math.pow(10, 8)) / 10; unit = "G"; } 1683 else if (num >= Math.pow(10, 6)) { num = Math.round(num / Math.pow(10, 5)) / 10; unit = "M"; } 1684 else if (num >= Math.pow(10, 3)) { num = Math.round(num / Math.pow(10, 2)) / 10; unit = "K"; } 1685 if ((unit !== "") && (num === Math.floor(num))) { num += ".0"; } 1686 return num + unit; 1687 } 1688 1689 1690 var annotateInProgress = null; 1691 var minAnnotationSeconds = Math.max(1, Math.floor(minAutoplayDelay/1000)); 1692 var maxAnnotationSeconds = Math.max(100, Math.floor(maxAutoplayDelay/1000)); 1693 var annotationSeconds_default = 15; 1694 var annotationSeconds = annotationSeconds_default; 1695 1696 function getAnnotationSecondsFromLocalStorage() { 1697 var as; 1698 try { as = parseFloat(localStorage.getItem("pgn4web_chess_viewer_annotationSeconds")); } 1699 catch(e) { return annotationSeconds_default; } 1700 return ((as === null) || (isNaN(as))) ? annotationSeconds_default : as; 1701 } 1702 function setAnnotationSecondsToLocalStorage(as) { 1703 try { localStorage.setItem("pgn4web_chess_viewer_annotationSeconds", as); } 1704 catch(e) { return false; } 1705 return true; 1706 } 1707 1708 1709 function annotateGame(promptUser) { 1710 if ((checkEngineUnderstandsGameAndWarn()) && (annotationSeconds = promptUser ? prompt("Automated game" + (annotateGameMulti ? "s" : "") + " annotation from the current position; please do not interact with the chessboard until the annotation has completed.\\n\\nEnter annotation time per move, in seconds, between " + minAnnotationSeconds + " and " + maxAnnotationSeconds + ":", getAnnotationSecondsFromLocalStorage()) : getAnnotationSecondsFromLocalStorage())) { 1711 if (isNaN(annotationSeconds = parseFloat(annotationSeconds))) { annotationSeconds = getAnnotationSecondsFromLocalStorage(); } 1712 annotationSeconds = Math.min(maxAnnotationSeconds, Math.max(minAnnotationSeconds, annotationSeconds)); 1713 setAnnotationSecondsToLocalStorage(annotationSeconds); 1714 SetAutoPlay(false); 1715 if (!analysisStarted) { 1716 scanGameForFen(); 1717 toggleAnalysis(); 1718 } 1719 if (annotateInProgress) { 1720 clearTimeout(annotateInProgress); 1721 annotateInProgress = null; 1722 } 1723 var theObj = document.getElementById("GameAnnotationMessage"); 1724 if (theObj) { 1725 theObj.innerHTML = "automated game" + (annotateGameMulti ? "s" : "") + " annotation in progress"; 1726 theObj.title = theObj.innerHTML + " at " + annotationSeconds + " second" + (annotationSeconds == 1 ? "" : "s") + " per move; please do not interact with the chessboard until the annotation has completed; click here to stop the automated annotation"; 1727 theObj.style.display = ""; 1728 if (theObj = document.getElementById("GameButtons")) { 1729 theObj.style.display = "none"; 1730 } 1731 } 1732 annotateGameStep(CurrentPly, CurrentVar, annotationSeconds * 1000); 1733 } 1734 } 1735 1736 var annotateGameMulti = false; 1737 function annotateGameStep(thisPly, thisVar, thisDelay) { 1738 if (analysisStarted) { 1739 GoToMove(thisPly, thisVar); 1740 var thisCmd = "stopAnnotateGame(true);"; 1741 if (thisPly < StartPlyVar[thisVar] + PlyNumberVar[thisVar]) { 1742 thisCmd = "annotateGameStep(" + (thisPly + 1) + ", " + thisVar + ", " + thisDelay + ");"; 1743 } else if (thisVar + 1 < numberOfVars) { 1744 thisCmd = "annotateGameStep(" + (StartPlyVar[thisVar + 1] + 1) + ", " + (thisVar + 1) + ", " + thisDelay + ");"; 1745 } else if ((annotateGameMulti) && (currentGame + 1 < numberOfGames)) { 1746 thisCmd = "Init(" + (currentGame + 1) + "); GoToMove(StartPly, 0); annotateGame(false);"; 1747 } 1748 annotateInProgress = setTimeout(thisCmd, thisDelay); 1749 } else { 1750 stopAnnotateGame(false); 1751 return; 1752 } 1753 } 1754 1755 function stopAnnotateGame(annotationCompleted) { 1756 var theObj = document.getElementById("GameAnnotationMessage"); 1757 if (theObj) { 1758 theObj.style.display = ((annotateInProgress) && (annotationCompleted)) ? "" : "none"; 1759 theObj.innerHTML = ((annotateInProgress) && (annotationCompleted)) ? "automated game" + (annotateGameMulti ? "s" : "") + " annotation completed" : ""; 1760 theObj.title = ""; 1761 if (theObj = document.getElementById("GameButtons")) { 1762 theObj.style.display = ((annotateInProgress) && (annotationCompleted)) ? "none" : ""; 1763 } 1764 } 1765 if (annotateInProgress) { 1766 clearTimeout(annotateInProgress); 1767 annotateInProgress = null; 1768 } 1769 } 1770 1771 function engineUnderstandsGame(gameNum) { 1772 return gameIsNormalChess(gameNum); 1773 } 1774 1775 function checkEngineUnderstandsGameAndWarn() { 1776 var retVal = engineUnderstandsGame(currentGame); 1777 if (!retVal) { myAlert("warning: engine annotation unavailable for the " + gameVariant[currentGame] + " variant", true); } 1778 return retVal; 1779 } 1780 1781 function userToggleAnalysis() { 1782 if (checkEngineUnderstandsGameAndWarn()) { 1783 if (!analysisStarted) { scanGameForFen(); } 1784 toggleAnalysis(); 1785 } 1786 } 1787 1788 function scanGameForFen() { 1789 var index; 1790 var savedCurrentPly = CurrentPly; 1791 var savedCurrentVar = CurrentVar; 1792 var wasAutoPlayOn = isAutoPlayOn; 1793 if (wasAutoPlayOn) { SetAutoPlay(false); } 1794 MoveForward(StartPly + PlyNumber - savedCurrentPly, CurrentVar, true); 1795 resetFenPositions(); 1796 while (true) { 1797 fenPositions[CurrentPly] = CurrentFEN(); 1798 if ((index = cache_fen_lastIndexOf(fenPositions[CurrentPly])) != -1) { 1799 fenPositionsEval[CurrentPly] = cache_ev[index]; 1800 fenPositionsPv[CurrentPly] = cache_pv[index]; 1801 fenPositionsDepth[CurrentPly] = cache_depth[index]; 1802 } 1803 if (CurrentPly === StartPly) { break; } 1804 MoveBackward(1, true); 1805 } 1806 MoveForward(savedCurrentPly - StartPly, savedCurrentVar, true); 1807 updateAnnotationGraph(); 1808 updateAnalysisHeader(); 1809 if (wasAutoPlayOn) { SetAutoPlay(true); } 1810 } 1811 1812 function goToMissingAnalysis(forward) { 1813 if (!analysisStarted) { return; } 1814 if (typeof(fenPositions[CurrentPly]) == "undefined") { return; } 1815 if (typeof(fenPositionsEval[CurrentPly]) == "undefined") { return; } 1816 1817 if (typeof(forward) == "undefined") { 1818 forward = ((typeof(event) != "undefined") && (typeof(event.shiftKey) != "undefined")) ? !event.shiftKey : true; 1819 } 1820 var wasAutoPlayOn = isAutoPlayOn; 1821 if (wasAutoPlayOn) { SetAutoPlay(false); } 1822 for (var thisPly = CurrentPly + (forward ? 1 : -1); ; thisPly = thisPly + (forward ? 1 : -1)) { 1823 if (forward) { if (thisPly > StartPly + PlyNumber) { thisPly = StartPly; } } 1824 else { if (thisPly < StartPly) { thisPly = StartPly + PlyNumber; } } 1825 if (thisPly === CurrentPly) { break; } 1826 if (typeof(fenPositions[thisPly]) == "undefined") { break; } 1827 if (typeof(fenPositionsEval[thisPly]) == "undefined") { GoToMove(thisPly); break; } 1828 } 1829 if (wasAutoPlayOn) { SetAutoPlay(true); } 1830 } 1831 1832 1833 var mistakeThreshold = 0.55; 1834 var blunderThreshold = 1.15; 1835 var ignoreThreshold = 4.95; 1836 1837 function isBlunder(thisPly, threshold) { 1838 if (typeof(fenPositionsEval[thisPly]) == "undefined") { return false; } 1839 if (typeof(fenPositionsEval[thisPly - 1]) == "undefined") { return false; } 1840 if ((fenPositionsEval[thisPly] > ignoreThreshold) && (fenPositionsEval[thisPly - 1] > ignoreThreshold)) { return false; } 1841 if ((fenPositionsEval[thisPly] < -ignoreThreshold) && (fenPositionsEval[thisPly - 1] < -ignoreThreshold)) { return false; } 1842 return (((thisPly % 2 ? -1 : 1) * (fenPositionsEval[thisPly] - fenPositionsEval[thisPly - 1])) > threshold); 1843 } 1844 1845 function blunderCheck(threshold, backwards) { 1846 var thisPly = StartPly + ((CurrentPly - StartPly + (backwards ? -1 : 1) + (PlyNumber + 1)) % (PlyNumber + 1)); 1847 while (thisPly !== CurrentPly) { 1848 if (isBlunder(thisPly, threshold)) { 1849 GoToMove(thisPly); 1850 break; 1851 } 1852 thisPly = StartPly + ((thisPly - StartPly + (backwards ? -1 : 1) + (PlyNumber + 1)) % (PlyNumber + 1)); 1853 } 1854 } 1855 1856 function annotationSupportedCheckAndWarnUser() { 1857 if (!annotationSupported) { myAlert("warning: engine annotation unavailable", true); } 1858 return annotationSupported; 1859 } 1860 1861 1862 // F5 1863 boardShortcut("F5", "adjust last move and current comment text area, if present", function(t,e){ if (e.shiftKey) { resetLastCommentArea(); } else { cycleLastCommentArea(); } }); 1864 1865 // A6 1866 boardShortcut("A6", "go to previous annotated blunder", function(t,e){ if (annotationSupportedCheckAndWarnUser()) { if (e.shiftKey) { GoToMove(CurrentPly - 1); } else { if (!analysisStarted) { userToggleAnalysis(); } blunderCheck(blunderThreshold, true); } } }); 1867 // B6 1868 boardShortcut("B6", "go to previous annotated mistake", function(t,e){ if (annotationSupportedCheckAndWarnUser()) { if (e.shiftKey) { GoToMove(CurrentPly - 1); } else { if (!analysisStarted) { userToggleAnalysis(); } blunderCheck(mistakeThreshold, true); } } }); 1869 // G6 1870 boardShortcut("G6", "go to next annotated mistake", function(t,e){ if (annotationSupportedCheckAndWarnUser()) { if (e.shiftKey) { GoToMove(CurrentPly - 1); } else { if (!analysisStarted) { userToggleAnalysis(); } blunderCheck(mistakeThreshold, false); } } }); 1871 // H6 1872 boardShortcut("H6", "go to next annotated blunder", function(t,e){ if (annotationSupportedCheckAndWarnUser()) { if (e.shiftKey) { GoToMove(CurrentPly - 1); } else { if (!analysisStarted) { userToggleAnalysis(); } blunderCheck(blunderThreshold, false); } } }); 1873 1874 // G5 1875 boardShortcut("G5", "start/stop automated game annotation", function(t,e){ if (annotationSupportedCheckAndWarnUser()) { annotateGameMulti = e.shiftKey; if (annotateInProgress) { stopAnnotateGame(false); } else { annotateGame(true); } } }); 1876 // H5 1877 boardShortcut("H5", "start/stop annotation", function(t,e){ if (annotationSupportedCheckAndWarnUser()) { if (e.shiftKey) { if (confirm("clear annotation cache, all current and stored annotation data will be lost")) { clear_cache_from_localStorage(); cache_clear(); if (analysisStarted) { updateAnnotationGraph(); updateAnalysisHeader(); } } } else { userToggleAnalysis(); } } }); 1878 1879 1880 var pgn4web_chess_engine_id = "garbochess-pgn4web-" + pgn4web_version; 1881 1882 var engineWorker = "libs/garbochess/garbochess.js"; 1883 1884 var g_backgroundEngine; 1885 var g_topNodesPerSecond = 0; 1886 var g_ev = ""; 1887 var g_maxEv = 99.9; 1888 var g_pv = ""; 1889 var g_depth = ""; 1890 var g_nodes = ""; 1891 var g_initErrors = 0; 1892 var g_lastFenError = ""; 1893 1894 function InitializeBackgroundEngine() { 1895 1896 if (!g_backgroundEngine) { 1897 try { 1898 g_backgroundEngine = new Worker(engineWorker); 1899 g_backgroundEngine.addEventListener("message", function (e) { 1900 if ((e.data.match("^pv")) && (fenString == CurrentFEN())) { 1901 var matches = e.data.substr(3, e.data.length - 3).match(/Ply:(\d+) Score:(-*\d+) Nodes:(\d+) NPS:(\d+) (.*)/); 1902 if (matches) { 1903 g_depth = parseInt(matches[1], 10); 1904 if (isNaN(g_ev = parseInt(matches[2], 10))) { 1905 g_ev = ""; 1906 } else { 1907 g_ev = Math.round(g_ev / 100) / 10; 1908 if (g_ev < -g_maxEv) { g_ev = -g_maxEv; } else if (g_ev > g_maxEv) { g_ev = g_maxEv; } 1909 if (fenString.indexOf(" b ") !== -1) { g_ev = -g_ev; } 1910 } 1911 g_nodes = parseInt(matches[3], 10); 1912 var nodesPerSecond = parseInt(matches[4], 10); 1913 g_topNodesPerSecond = Math.max(nodesPerSecond, g_topNodesPerSecond); 1914 g_pv = matches[5].replace(/(^\s+|\s*[x+=]|\s+$)/g, "").replace(/\s*stalemate/, "=").replace(/\s*checkmate/, "#"); // patch: pgn notation: remove/add '+' 'x' '=' chars for full chess informant style or pgn style for the game text 1915 if (searchMeaningful()) { 1916 validateSearchWithCache(); 1917 if ((typeof(fenPositionsDepth[CurrentPly]) == "undefined") || (g_depth > fenPositionsDepth[CurrentPly])) { 1918 fenPositionsEval[CurrentPly] = g_ev; 1919 fenPositionsPv[CurrentPly] = g_pv; 1920 fenPositionsDepth[CurrentPly] = g_depth; 1921 updateAnnotationGraph(); 1922 updateAnalysisHeader(); 1923 } 1924 } 1925 if (detectGameEnd(g_pv, "")) { StopBackgroundEngine(); } 1926 } 1927 } else if (e.data.match("^message Invalid FEN")) { 1928 stopAnalysis(); 1929 if (fenString != g_lastFenError) { 1930 g_lastFenError = fenString; 1931 myAlert("error: engine: " + e.data.replace(/^message /, "") + "\\n" + fenString); 1932 } 1933 } 1934 }, false); 1935 g_initErrors = 0; 1936 return true; 1937 } catch(e) { 1938 stopAnalysis(); 1939 if (!g_initErrors++) { myAlert("error: engine exception " + e); } 1940 return false; 1941 } 1942 } 1943 } 1944 1945 var cache_local_storage_prefix = "pgn4web_chess_viewer_engine_cache_"; // default "pgn4web_chess_engine_cache_" 1946 1947 var localStorage_supported; 1948 try { localStorage_supported = (("localStorage" in window) && (window["localStorage"] !== null)); } 1949 catch(e) { localStorage_supported = false; } 1950 1951 function load_cache_from_localStorage() { 1952 if (!localStorage_supported) { return; } 1953 if (pgn4web_chess_engine_id != localStorage[cache_local_storage_prefix + "id"]) { 1954 clear_cache_from_localStorage(); 1955 localStorage[cache_local_storage_prefix + "id"] = pgn4web_chess_engine_id; 1956 return; 1957 } 1958 if (cache_pointer = localStorage[cache_local_storage_prefix + "pointer"]) { 1959 cache_pointer = parseInt(cache_pointer, 10) % cache_max; 1960 } else { cache_pointer = -1; } 1961 if (cache_fen = localStorage[cache_local_storage_prefix + "fen"]) { 1962 cache_fen = cache_fen.split(","); 1963 } else { cache_fen = new Array(); } 1964 if (cache_ev = localStorage[cache_local_storage_prefix + "ev"]) { 1965 cache_ev = cache_ev.split(","); 1966 if (typeof(cache_ev.map == "function")) { cache_ev = cache_ev.map(parseFloat); } 1967 } else { cache_ev = new Array(); } 1968 if (cache_pv = localStorage[cache_local_storage_prefix + "pv"]) { 1969 cache_pv = cache_pv.split(","); 1970 } else { cache_pv = new Array(); } 1971 if (cache_depth = localStorage[cache_local_storage_prefix + "depth"]) { 1972 cache_depth = cache_depth.split(","); 1973 if (typeof(cache_depth.map == "function")) { cache_depth = cache_depth.map(parseFloat); } 1974 } else { cache_depth = new Array(); } 1975 cache_needs_sync = 0; 1976 if ((cache_fen.length !== cache_ev.length) || (cache_fen.length !== cache_pv.length) || (cache_fen.length !== cache_depth.length)) { 1977 clear_cache_from_localStorage(); 1978 cache_clear(); 1979 } 1980 } 1981 1982 function save_cache_to_localStorage() { 1983 if (!localStorage_supported) { return; } 1984 if (!cache_needs_sync) { return; } 1985 localStorage[cache_local_storage_prefix + "pointer"] = cache_pointer; 1986 localStorage[cache_local_storage_prefix + "fen"] = cache_fen.toString(); 1987 localStorage[cache_local_storage_prefix + "ev"] = cache_ev.toString(); 1988 localStorage[cache_local_storage_prefix + "pv"] = cache_pv.toString(); 1989 localStorage[cache_local_storage_prefix + "depth"] = cache_depth.toString(); 1990 cache_needs_sync = 0; 1991 } 1992 1993 function clear_cache_from_localStorage() { 1994 if (!localStorage_supported) { return; } 1995 localStorage.removeItem(cache_local_storage_prefix + "pointer"); 1996 localStorage.removeItem(cache_local_storage_prefix + "fen"); 1997 localStorage.removeItem(cache_local_storage_prefix + "ev"); 1998 localStorage.removeItem(cache_local_storage_prefix + "pv"); 1999 localStorage.removeItem(cache_local_storage_prefix + "depth"); 2000 localStorage.removeItem(cache_local_storage_prefix + "nodes"); // backward compatibility 2001 cache_needs_sync++; 2002 } 2003 2004 function cacheDebugInfo() { 2005 var dbg = ""; 2006 if (localStorage_supported) { 2007 dbg += " cache="; 2008 try { 2009 dbg += num2string(localStorage[cache_local_storage_prefix + "pointer"].length + localStorage[cache_local_storage_prefix + "fen"].length + localStorage[cache_local_storage_prefix + "ev"].length + localStorage[cache_local_storage_prefix + "pv"].length + localStorage[cache_local_storage_prefix + "depth"].length); 2010 } catch(e) { 2011 dbg += "0"; 2012 } 2013 } 2014 return dbg; 2015 } 2016 2017 var cache_pointer = -1; 2018 var cache_max = 8000; // ~ 64 games of 60 moves ~ 1MB of local storage 2019 var cache_fen = new Array(); 2020 var cache_ev = new Array(); 2021 var cache_pv = new Array(); 2022 var cache_depth = new Array(); 2023 2024 var cache_needs_sync = 0; 2025 2026 load_cache_from_localStorage(); 2027 2028 function searchMeaningful() { 2029 var minNodesForAnnotation = 12345; 2030 return ((g_nodes > minNodesForAnnotation) || (g_ev === g_maxEv) || (g_ev === -g_maxEv) || (g_ev === 0)); 2031 } 2032 2033 function validateSearchWithCache() { 2034 var id = cache_fen_lastIndexOf(fenString); 2035 if (id == -1) { 2036 cache_last = cache_pointer = (cache_pointer + 1) % cache_max; 2037 cache_fen[cache_pointer] = fenString.replace(/\s+\d+\s+\d+\s*$/, ""); 2038 cache_ev[cache_pointer] = g_ev; 2039 cache_pv[cache_pointer] = g_pv; 2040 cache_depth[cache_pointer] = g_depth; 2041 cache_needs_sync++; 2042 } else { 2043 if (g_depth > cache_depth[id]) { 2044 cache_ev[id] = g_ev; 2045 cache_pv[id] = g_pv; 2046 cache_depth[id] = g_depth; 2047 cache_needs_sync++; 2048 } else { 2049 g_ev = parseFloat(cache_ev[id]); 2050 g_pv = cache_pv[id]; 2051 g_depth = parseInt(cache_depth[id], 10); 2052 } 2053 } 2054 if (cache_needs_sync > 3) { save_cache_to_localStorage(); } 2055 } 2056 2057 var cache_last = 0; 2058 function cache_fen_lastIndexOf(fenString) { 2059 fenString = fenString.replace(/\s+\d+\s+\d+\s*$/, ""); 2060 if (fenString === cache_fen[cache_last]) { return cache_last; } 2061 if (typeof(cache_fen.lastIndexOf) == "function") { return (cache_last = cache_fen.lastIndexOf(fenString)); } 2062 for (var n = cache_fen.length - 1; n >= 0; n--) { 2063 if (fenString === cache_fen[n]) { return (cache_last = n); } 2064 } 2065 return -1; 2066 } 2067 2068 function cache_clear() { 2069 cache_pointer = -1; 2070 cache_fen = new Array(); 2071 cache_ev = new Array(); 2072 cache_pv = new Array(); 2073 cache_depth = new Array(); 2074 } 2075 2076 2077 function StopBackgroundEngine() { 2078 if (analysisTimeout) { clearTimeout(analysisTimeout); } 2079 if (g_backgroundEngine) { 2080 g_backgroundEngine.terminate(); 2081 g_backgroundEngine = null; 2082 } 2083 } 2084 2085 var analysisTimeout; 2086 function setAnalysisTimeout(seconds) { 2087 if (analysisTimeout) { clearTimeout(analysisTimeout); } 2088 analysisTimeout = setTimeout("analysisTimeout = null; save_cache_to_localStorage(); StopBackgroundEngine();", seconds * 1000); 2089 } 2090 2091 var fenString; 2092 function StartEngineAnalysis() { 2093 StopBackgroundEngine(); 2094 if (InitializeBackgroundEngine()) { 2095 fenString = CurrentFEN(); 2096 g_backgroundEngine.postMessage("position " + fenString); 2097 g_backgroundEngine.postMessage("analyze"); 2098 setAnalysisTimeout(analysisSeconds); 2099 return true; 2100 } else { 2101 stopAnnotateGame(false); 2102 return false; 2103 } 2104 } 2105 2106 var analysisSeconds = 300; 2107 2108 function detectGameEnd(pv, FEN) { 2109 if ((pv !== "") && (pv.match(/^[#=]/))) { return true; } 2110 var matches = FEN.match(/\s*\S+\s+\S+\s+\S+\s+\S+\s+(\d+)\s+\S+\s*/); 2111 if ((matches) && (parseInt(matches[1], 10) > 100)) { return true; } 2112 return false; 2113 } 2114 2115 function customDebugInfo() { 2116 var dbg = "initialHalfmove=" + initialHalfmove; 2117 dbg += " annotation="; 2118 if (!annotationSupported) { dbg += "unavailable"; } 2119 else if (!analysisStarted) { dbg += "disabled"; } 2120 else { dbg += (g_backgroundEngine ? ( annotateInProgress ? ("automatedGame" + (annotateGameMulti ? "s" : "") + " annotationSeconds=" + getAnnotationSecondsFromLocalStorage()) : "pondering") : "idle") + " analysisSeconds=" + analysisSeconds + " topNodesPerSecond=" + num2string(g_topNodesPerSecond) + cacheDebugInfo(); } 2121 if ("$forceEncodingFrom") { dbg += " forceEncodingFrom=$forceEncodingFrom"; } 2122 return dbg; 2123 } 2124 2125 window.onunload = function() { 2126 setDelayToLocalStorage(Delay); 2127 setHighlightOptionToLocalStorage(highlightOption); 2128 setCommentsIntoMoveTextToLocalStorage(commentsIntoMoveText); 2129 setCommentsOnSeparateLinesToLocalStorage(commentsOnSeparateLines); 2130 if (analysisStarted) { stopAnalysis(); } 2131 }; 2132 2133</script> 2134 2135 2136END; 2137} 2138 2139function print_footer() { 2140 2141 global $pgnText, $pgnTextbox, $pgnUrl, $pgnFileName, $pgnFileSize, $pgnStatus, $forceEncodingFrom, $tmpDir, $debugHelpText, $pgnDebugInfo; 2142 global $fileUploadLimitIniText, $fileUploadLimitText, $fileUploadLimitBytes, $startPosition, $goToView, $zipSupported; 2143 2144 if ($goToView) { $hashStatement = " goToHash('board');"; } 2145 else { $hashStatement = ""; } 2146 2147 if (($pgnDebugInfo) != "") { $pgnDebugMessage = "warning: system: " . $pgnDebugInfo; } 2148 else {$pgnDebugMessage = ""; } 2149 2150 print <<<END 2151 2152<script type="text/javascript"> 2153"use strict"; 2154 2155function pgn4web_onload(e) { 2156 setPgnUrl("$pgnUrl"); 2157 checkPgnFormTextSize(); 2158 start_pgn4web(); 2159 if ("$pgnDebugMessage".length > 0) { myAlert("$pgnDebugMessage", false, true); } 2160$hashStatement 2161} 2162 2163</script> 2164 2165END; 2166} 2167 2168function print_html_close() { 2169 2170 print <<<END 2171 2172</body> 2173 2174</html> 2175END; 2176} 2177 2178?> 2179