Advertisement
Guest User

Untitled

a guest
Dec 10th, 2020 (edited)
1,997
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 41.15 KB | None | 0 0
  1. // ==UserScript==
  2. // @name ChessBotPy
  3. // @namespace ChessBotPy
  4. // @match *://lichess.org/*
  5. // @match *://www.chess.com/*
  6. // @match *://chess24.com/*
  7. // @match *://gameknot.com/*
  8. // @match *://arena.myfide.net/*
  9. // @grant none
  10. // @require https://cdn.jsdelivr.net/npm/[email protected]
  11. // @version 10.0
  12. // @author FallDownTheSystem
  13. // @description ChessBotPy Client
  14. // ==/UserScript==
  15.  
  16. // Global state
  17. let numOfMoves = -1;
  18. let fen = '.fen';
  19. let uid = uuidv4();
  20. let ws = null;
  21. let app = null;
  22. let popup = null;
  23. let host = window.location.host;
  24. let origin = window.location.origin;
  25. const WHITE = 1;
  26. const BLACK = 0;
  27.  
  28. if (host === 'www.chess.com') {
  29. window.document.domain = 'chess.com';
  30.  
  31. }
  32.  
  33. // A new window is opened to allow a websocket connection, since the main page has a restricted CSP
  34. // this is just an alias to the document object of the main window
  35. let doc = window.document;
  36. if (window.opener != null) {
  37. doc = window.opener.document;
  38. }
  39.  
  40. let siteMap = {
  41. 'lichess.org': {
  42. movesSelector: '.round__app__table, .tview2-column',
  43. sanSelector: 'u8t, move',
  44. overlaySelector: '.cg-wrap, .puzzle__board.main-board',
  45. analysisSelector: '.analyse__tools',
  46. sideFinder: () => (doc.querySelector('.orientation-white') != null ? WHITE : BLACK),
  47. },
  48. 'arena.myfide.net': {
  49. movesSelector: '.notifications__table',
  50. sanSelector: '.notifications__move',
  51. overlaySelector: '.cg-board',
  52. analysisSelector: '.analyse__tools',
  53. sideFinder: () => (doc.querySelector('.orientation-white') != null ? WHITE : BLACK)
  54. },
  55. 'www.chess.com': {
  56. movesSelector: '.vertical-move-list, .horizontal-move-list-component, .computer-move-list, .move-list-controls-component',
  57. sanSelector: '.move-text, .gotomove, .move-list-controls-move, .node',
  58. overlaySelector: '#chessboard_boardarea, .board-layout-chessboard, .board-board',
  59. analysisSelector: '.with-analysis, .with-analysis-collapsed',
  60. sideFinder: () => (doc.querySelector('.clock-component.clock-black.clock-bottom.clock-live.player-clock') != null ? BLACK : WHITE),
  61. },
  62. 'chess24.com': {
  63. movesSelector: '.Moves',
  64. sanSelector: '.move',
  65. overlaySelector: '.chess-board > .svg',
  66. analysisSelector: '.with-analysis',
  67. sideFinder: () => (doc.querySelector('.bottom .playerInfo.black') != null ? BLACK : WHITE),
  68. },
  69. 'gameknot.com': {
  70. movesSelector: '#chess-board-moves, #game-board-moves',
  71. sanSelector: '.fig-all',
  72. overlaySelector: '#chess-board-acboard, #game-board-acboard',
  73. analysisSelector: '.with-analysis',
  74. sideFinder: () =>
  75. doc.querySelector('#chess-board-my-side-color .player_white, #game-board-my-side-color .player_white') != null ? WHITE : BLACK,
  76. },
  77. };
  78.  
  79.  
  80. function uuidv4() {
  81. return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) =>
  82. (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16)
  83. );
  84. }
  85.  
  86. function hotkey(e) {
  87. switch (e.code) {
  88. case 'ControlLeft':
  89. if (app.pieceOnly !== true) {
  90. app.pieceOnly = true;
  91. ws.send(JSON.stringify({ type: 'setting', data: { key: 'piece_only', value: true } }));
  92. }
  93. break;
  94. case 'Space':
  95. if (app.pieceOnly !== false) {
  96. app.pieceOnly = false;
  97. ws.send(JSON.stringify({ type: 'setting', data: { key: 'piece_only', value: false } }));
  98. }
  99. break;
  100. default:
  101. break;
  102. }
  103. }
  104.  
  105. function updateSide() {
  106. let side = siteMap[host].sideFinder();
  107. if (app.playingAs !== side) {
  108. app.playingAs = side;
  109. console.log('Starting as', side == 0 ? 'black' : 'white');
  110. ws.send(JSON.stringify({ type: 'setting', data: { key: 'side', value: side } }));
  111. }
  112. }
  113.  
  114. function parseLAN(LAN, turn) {
  115. let moves = LAN.split('-');
  116. if (moves.length == 1) {
  117. moves = LAN.split('x');
  118. }
  119.  
  120. [from, to] = moves;
  121.  
  122. // Long castles (O-O-O)
  123. if (moves.length == 3) {
  124. if (turn == WHITE) {
  125. return { from: 'e1', to: 'c1' };
  126. }
  127. return { from: 'e8', to: 'c8' };
  128. }
  129.  
  130. // Short castles
  131. if (from.toLowerCase() == 'o') {
  132. if (turn == WHITE) {
  133. return { from: 'e1', to: 'g1' };
  134. }
  135. return { from: 'e8', to: 'g8' };
  136. }
  137.  
  138. return { from: from.slice(-2), to: to.slice(0, 2) };
  139. }
  140.  
  141. function range(start, end) {
  142. return Array(end - start + 1)
  143. .fill()
  144. .map((_, idx) => start + idx);
  145. }
  146.  
  147. function squareToPos(square, size) {
  148. const files = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
  149. const ranks = range(1, 8);
  150.  
  151. app.playingAs == WHITE ? ranks.reverse() : files.reverse();
  152.  
  153. let [file, rank] = square.split('');
  154. rank = parseInt(rank);
  155.  
  156. const x = files.indexOf(file);
  157. const y = ranks.indexOf(rank);
  158.  
  159. const squareSize = size / 8;
  160.  
  161. return [squareSize * x + squareSize / 2, squareSize * y + squareSize / 2];
  162. }
  163.  
  164. function drawPieceOnlyOverlay({ width, height, top, left }) {
  165. let span = doc.createElement('span');
  166. span.innerText = app.bestPiece;
  167. span.id = 'py-overlay';
  168. span.style.position = 'absolute';
  169. span.style.zIndex = '50000';
  170. span.style.pointerEvents = 'none';
  171. span.style.top = 900 + 'px';
  172. span.style.left = 100 + 'px';
  173. span.style.width = 250 + 'px';
  174. span.style.height = 1 + 'px';
  175. span.style.color = 'DimGrey';
  176. span.style.fontSize = '16px';
  177. span.style.textShadow = '0px 0px 5px #000';
  178. span.style.marginTop = '0px';
  179. doc.body.appendChild(span);
  180. }
  181.  
  182. function drawOnScreen() {
  183. let existing = doc.getElementById('py-overlay');
  184. if (existing) {
  185. existing.remove();
  186. }
  187.  
  188. if (!app.drawOverlay) {
  189. return;
  190. }
  191.  
  192. let boardElement = doc.querySelector(siteMap[host].overlaySelector);
  193. let rect = boardElement.getBoundingClientRect();
  194. let { width, height, top, left } = rect;
  195.  
  196. if (app.pieceOnly) {
  197. drawPieceOnlyOverlay(rect);
  198. return;
  199. }
  200.  
  201. let svg = doc.createElementNS('http://www.w3.org/2000/svg', 'svg');
  202. svg.id = 'py-overlay';
  203. svg.style.position = 'absolute';
  204. svg.style.zIndex = '99999';
  205. svg.style.pointerEvents = 'none';
  206. svg.style.width = width + 'px';
  207. svg.style.height = height + 'px';
  208. svg.style.top = top + 'px';
  209. svg.style.left = left + 'px';
  210. svg.setAttribute('width', width);
  211. svg.setAttribute('height', width);
  212.  
  213. let turn = +app.turn;
  214. let lanMoves = app.pvs.map((x) => x.lan);
  215. let lanPV = lanMoves[app.selectedPV - 1];
  216.  
  217. if (lanPV && lanPV.length > 0) {
  218. drawArrow(svg, lanPV[0], turn, width);
  219. }
  220.  
  221. doc.body.appendChild(svg);
  222. }
  223.  
  224. function drawArrow(svg, move, turn, size) {
  225. colors = {
  226. 0: 'hsla(180, 100%, 50%, 0.2)', // BLACK
  227. 1: 'hsla(180, 100%, 50%, 0.2)', // WHITE
  228. };
  229.  
  230.  
  231. const squareSize = size / 8;
  232.  
  233. let marker = doc.createElementNS('http://www.w3.org/2000/svg', 'marker');
  234.  
  235. marker.id = 'triangle' + turn;
  236. marker.setAttribute('viewBox', '0 0 20 20');
  237. marker.setAttribute('refX', '0');
  238. marker.setAttribute('refY', '5');
  239. marker.setAttribute('markerUnits', 'strokeWidth');
  240. marker.setAttribute('markerWidth', squareSize / 12);
  241. marker.setAttribute('markerHeight', squareSize / 12);
  242. marker.setAttribute('orient', 'auto');
  243. marker.setAttribute('fill', colors[turn]);
  244.  
  245. let path = doc.createElementNS('http://www.w3.org/2000/svg', 'path');
  246. path.setAttribute('d', 'M 0 0 L 7.5 5 L 0 10 z');
  247. marker.appendChild(path);
  248.  
  249. svg.appendChild(marker);
  250.  
  251. let { from, to } = parseLAN(move, turn);
  252. [x1, y1] = squareToPos(from, size);
  253. [x2, y2] = squareToPos(to, size);
  254.  
  255. const xDist = x2 - x1;
  256. const yDist = y2 - y1;
  257. const dist = Math.sqrt(xDist * xDist + yDist * yDist);
  258. const newDist = dist - squareSize * (2 / 5);
  259. const scale = newDist / dist;
  260.  
  261. x2 = x1 + xDist * scale;
  262. y2 = y1 + yDist * scale;
  263.  
  264. let line = doc.createElementNS('http://www.w3.org/2000/svg', 'line');
  265. line.setAttribute('x1', x1);
  266. line.setAttribute('y1', y1);
  267. line.setAttribute('x2', x2);
  268. line.setAttribute('y2', y2);
  269. line.setAttribute('marker-end', `url(#triangle${turn})`);
  270. line.setAttribute('stroke', colors[turn]);
  271. line.setAttribute('stroke-width', squareSize / 6);
  272. // line.setAttribute('stroke-linecap', 'round');
  273. svg.appendChild(line);
  274. }
  275.  
  276. const findGame = async () => {
  277. await waitForElement(siteMap[host].sanSelector, 1);
  278. // Get the side you're plaing as
  279. updateSide();
  280. let boardSize = 0;
  281. console.log('Starting loop');
  282. while (true) {
  283. await sleep(50);
  284.  
  285. // Force resizing because sometimes elements are dynamic and event listeners get broken
  286. let boardElement = doc.querySelector(siteMap[host].overlaySelector);
  287. if (boardElement) {
  288. let newBoardSize = boardElement.getBoundingClientRect().width;
  289. if (newBoardSize != boardSize) {
  290. boardSize = newBoardSize;
  291. drawOnScreen();
  292. }
  293. }
  294. // Handle getting FEN directly
  295. if (host === 'lichess.org') {
  296. let fenput = doc.querySelector('fen');
  297. if (fenput != null) {
  298. if (fenput.value != fen) {
  299. fen = fenput.value;
  300. console.log('Sending updated FEN.');
  301. ws.send(JSON.stringify({ type: 'fen', data: fen }));
  302. }
  303. continue;
  304. }
  305. }
  306. // Get moves through SAN
  307. let moves = [...doc.querySelectorAll(siteMap[host].sanSelector)].map((x) => cleanse(x.innerText)).filter((x) => x != '');
  308.  
  309. if (host == 'arena.myfide.net') {
  310. moves = [...doc.querySelectorAll(siteMap[host].sanSelector)].map((x) => cleanse(parseFideSAN(x))).filter((x) => x != '');
  311. }
  312.  
  313. // Number of moves changed, update all the things!
  314. if (moves.length != numOfMoves) {
  315. if (moves.length < 2 || (host == 'lichess.org' && doc.location.pathname.startsWith('/training'))) {
  316. updateSide();
  317. }
  318. numOfMoves = moves.length;
  319. console.log('Sending updated moves.');
  320. ws.send(JSON.stringify({ type: 'moves', data: moves }));
  321. }
  322. }
  323. };
  324.  
  325. const cleanse = (x) => {
  326. chars = ['↵', '✓', '1-0', '0-1', '1/2-1/2', '\n', /\[+-][0-9.]+/];
  327. for (let c of chars) {
  328. x = x.replace(c, '');
  329. }
  330. return x.trim();
  331. };
  332. const parseFideSAN = (x) => {
  333. let innerText = x.innerText;
  334. if (innerText.includes('0-0')) {
  335. innerText = innerText.replace(/0/g, 'O');
  336. }
  337. const img = x.querySelector('img');
  338. if (img == null) {
  339. return innerText;
  340. }
  341. const src = img.src.split('/');
  342. const piece = src[src.length - 1].replace('.svg', '').split('')[1];
  343. return piece == 'P' ? innerText : piece + innerText;
  344. };
  345.  
  346. const connect = (url) => {
  347. return new Promise((resolve, reject) => {
  348. const ws = new WebSocket(url);
  349. ws.onopen = () => resolve(ws);
  350. ws.onerror = (err) => reject(err);
  351. });
  352. };
  353.  
  354. const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
  355.  
  356. const waitForElement = async (selector, timeout) => {
  357. let now = Date.now();
  358. const end = now + timeout * 1000;
  359. while (end > now) {
  360. now = Date.now();
  361. let element = document.querySelector(selector);
  362. if (element != null) {
  363. console.log('Found element.');
  364. return element;
  365. }
  366. await sleep(100);
  367. }
  368. console.warn('Waiting for element timed out');
  369. return null;
  370. };
  371.  
  372. const loadCSS = (url) => {
  373. return new Promise((resolve, reject) => {
  374. const linkElement = document.createElement('link');
  375. linkElement.rel = 'stylesheet';
  376. linkElement.type = 'text/css';
  377. linkElement.href = url;
  378. linkElement.media = 'all';
  379. linkElement.onload = (event) => resolve(event);
  380. linkElement.onerror = (err) => reject(err);
  381. const head = document.querySelector('head');
  382. head.appendChild(linkElement);
  383. });
  384. };
  385.  
  386. const main = async () => {
  387. if (window.location.href != `${origin}/.css`) {
  388. let element = await waitForElement(siteMap[host].movesSelector, 60);
  389. if (element == null) {
  390. return;
  391. }
  392.  
  393. let hasAnalysis = (await waitForElement(siteMap[host].analysisSelector, 1)) != null;
  394. if (hasAnalysis) {
  395. return;
  396. }
  397.  
  398. popup = window.open(`${origin}/.css`, '_blank');
  399. window.addEventListener('beforeunload', function () {
  400. popup.close();
  401. });
  402. window.focus(); // Return back to main window (on FF at least)
  403. return;
  404. }
  405. window.document.title = `Client: ${window.opener.location.pathname}`;
  406.  
  407. // await loadCSS('http://127.0.0.1:8080/dist/tailwind.css');
  408. window.document.head.innerHTML = '';
  409. await loadCSS('https://fonts.googleapis.com/css?family=Nunito:400,700|Open+Sans:400,700&display=swap');
  410.  
  411. const styleString = /*css*/ `
  412. #layout {
  413. display: grid;
  414. grid-gap: 2px;
  415. height: 100vh;
  416. padding: 2px;
  417. grid-template-columns: 1fr;
  418. grid-template-areas:
  419. "board"
  420. "pvs"
  421. "settings"
  422. }
  423.  
  424. @media (min-width: 1280px) {
  425. #layout {
  426. grid-template-columns: 42fr 58fr;
  427. grid-template-rows: 67fr 33fr;
  428. grid-template-areas:
  429. "settings board"
  430. "settings pvs";
  431. }
  432. }
  433.  
  434. #settings {
  435. grid-area: settings;
  436. }
  437.  
  438. #board {
  439. grid-area: board;
  440. }
  441.  
  442. #board svg {
  443. height: 100%;
  444. width: 100%;
  445. }
  446.  
  447. #board svg line, #board svg polygon {
  448. opacity: 100%;
  449. }
  450.  
  451. #board-container {
  452. height: 63vh;
  453. width: 63vh;
  454. }
  455.  
  456. #pvs {
  457. grid-area: pvs;
  458. }
  459.  
  460. .font-sans {
  461. font-family: 'Open Sans', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !important;
  462. }
  463.  
  464. .font-serif {
  465. font-family: Georgia, Cambria, "Times New Roman", Times, serif !important;
  466. }
  467.  
  468. .font-mono {
  469. font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !important;
  470. }
  471.  
  472. .font-display {
  473. font-family: 'Nunito', 'Open Sans', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !important;
  474. }
  475.  
  476. .checkbox > input:checked {
  477. background-color: #667eea;
  478. }
  479.  
  480. .checkbox > input:checked + span::before {
  481. content: '✓';
  482. color: white;
  483. font-weight: bolder;
  484. position: absolute;
  485. left: 0.4rem;
  486. }
  487.  
  488. .radio > input:checked {
  489. background-color: #667eea;
  490. }
  491.  
  492. .radio > input:checked + span::before {
  493. content: '';
  494. display: block;
  495. background: white;
  496. width: 0.6rem;
  497. height: 0.6rem;
  498. border-radius: 9999px;
  499. position: absolute;
  500. left: 0.45rem;
  501. top: 0.45rem;
  502. }
  503.  
  504. .slider::-webkit-slider-thumb {
  505. -webkit-appearance: none;
  506. appearance: none;
  507. width: 1.5rem;
  508. height: 1.5rem;
  509. background-color: #667eea;
  510. border-radius: 9999px;
  511. cursor: pointer;
  512. }
  513.  
  514. .slider::-moz-range-thumb {
  515. -webkit-appearance: none;
  516. appearance: none;
  517. width: 1.5rem;
  518. height: 1.5rem;
  519. background-color: #667eea;
  520. border-radius: 9999px;
  521. cursor: pointer;
  522. }
  523. // Generated tailwindcss goes here
  524. /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}b{font-weight:bolder}code{font-family:monospace,monospace;font-size:1em}button,input,select{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}[hidden]{display:none}h1,pre{margin:0}button{background-color:transparent;background-image:none;padding:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}html{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5}*,:after,:before{box-sizing:border-box;border:0 solid #e2e8f0}input::-moz-placeholder{color:#a0aec0}input:-ms-input-placeholder{color:#a0aec0}input::-ms-input-placeholder{color:#a0aec0}input::placeholder{color:#a0aec0}[role=button],button{cursor:pointer}h1{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}button,input,select{padding:0;line-height:inherit;color:inherit}code,pre{font-family:Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}object,svg{display:block;vertical-align:middle}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.bg-gray-100{--bg-opacity:1;background-color:#f7fafc;background-color:rgba(247,250,252,var(--bg-opacity))}.bg-gray-200{--bg-opacity:1;background-color:#edf2f7;background-color:rgba(237,242,247,var(--bg-opacity))}.bg-gray-700{--bg-opacity:1;background-color:#4a5568;background-color:rgba(74,85,104,var(--bg-opacity))}.bg-gray-800{--bg-opacity:1;background-color:#2d3748;background-color:rgba(45,55,72,var(--bg-opacity))}.bg-gray-900{--bg-opacity:1;background-color:#1a202c;background-color:rgba(26,32,44,var(--bg-opacity))}.bg-indigo-500{--bg-opacity:1;background-color:#667eea;background-color:rgba(102,126,234,var(--bg-opacity))}.hover\\:bg-indigo-600:hover{--bg-opacity:1;background-color:#5a67d8;background-color:rgba(90,103,216,var(--bg-opacity))}.border-gray-500{--border-opacity:1;border-color:#a0aec0;border-color:rgba(160,174,192,var(--border-opacity))}.border-gray-700{--border-opacity:1;border-color:#4a5568;border-color:rgba(74,85,104,var(--border-opacity))}.border-gray-900{--border-opacity:1;border-color:#1a202c;border-color:rgba(26,32,44,var(--border-opacity))}.border-indigo-500{--border-opacity:1;border-color:#667eea;border-color:rgba(102,126,234,var(--border-opacity))}.hover\\:border-indigo-400:hover{--border-opacity:1;border-color:#7f9cf5;border-color:rgba(127,156,245,var(--border-opacity))}.focus\\:border-indigo-500:focus{--border-opacity:1;border-color:#667eea;border-color:rgba(102,126,234,var(--border-opacity))}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.border-2{border-width:2px}.border{border-width:1px}.cursor-pointer{cursor:pointer}.focus-within\\:cursor-move:focus-within{cursor:move}.first\\:cursor-move:first-child{cursor:move}.last\\:cursor-move:last-child{cursor:move}.odd\\:cursor-move:nth-child(odd){cursor:move}.even\\:cursor-move:nth-child(2n){cursor:move}.hover\\:cursor-move:hover{cursor:move}.focus\\:cursor-move:focus{cursor:move}.active\\:cursor-move:active{cursor:move}.visited\\:cursor-move:visited{cursor:move}.disabled\\:cursor-move:disabled{cursor:move}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.hidden{display:none}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-center{justify-content:center}.font-sans{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.font-serif{font-family:Georgia,Cambria,Times New Roman,Times,serif}.font-mono{font-family:Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.font-bold{font-weight:700}.h-3{height:.75rem}.h-4{height:1rem}.h-6{height:1.5rem}.h-8{height:2rem}.h-10{height:2.5rem}.h-full{height:100%}.text-xs{font-size:.75rem}.text-sm{font-size:.875rem}.text-4xl{font-size:2.25rem}.mt-2{margin-top:.5rem}.mr-2{margin-right:.5rem}.mb-2{margin-bottom:.5rem}.ml-2{margin-left:.5rem}.mr-3{margin-right:.75rem}.mb-3{margin-bottom:.75rem}.mr-4{margin-right:1rem}.mb-4{margin-bottom:1rem}.mb-5{margin-bottom:1.25rem}.mr-10{margin-right:2.5rem}.outline-none{outline:0}.focus\\:outline-none:focus{outline:0}.p-2{padding:.5rem}.p-3{padding:.75rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-4{padding-left:1rem;padding-right:1rem}.pr-8{padding-right:2rem}.pointer-events-none{pointer-events:none}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-y-0{top:0;bottom:0}.right-0{right:0}.fill-current{fill:currentColor}.text-white{--text-opacity:1;color:#fff;color:rgba(255,255,255,var(--text-opacity))}.text-gray-100{--text-opacity:1;color:#f7fafc;color:rgba(247,250,252,var(--text-opacity))}.text-gray-200{--text-opacity:1;color:#edf2f7;color:rgba(237,242,247,var(--text-opacity))}.text-gray-400{--text-opacity:1;color:#cbd5e0;color:rgba(203,213,224,var(--text-opacity))}.text-gray-500{--text-opacity:1;color:#a0aec0;color:rgba(160,174,192,var(--text-opacity))}.text-gray-600{--text-opacity:1;color:#718096;color:rgba(113,128,150,var(--text-opacity))}.text-gray-700{--text-opacity:1;color:#4a5568;color:rgba(74,85,104,var(--text-opacity))}.uppercase{text-transform:uppercase}.tracking-wide{letter-spacing:.025em}.align-bottom{vertical-align:bottom}.visible{visibility:visible}.whitespace-pre-wrap{white-space:pre-wrap}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.w-1{width:.25rem}.w-4{width:1rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-10{width:2.5rem}.w-32{width:8rem}.w-40{width:10rem}.w-48{width:12rem}.w-64{width:16rem}.w-1\\/2{width:50%}.w-full{width:100%}@media (min-width:640px){.sm\\:cursor-move{cursor:move}.sm\\:focus-within\\:cursor-move:focus-within{cursor:move}.sm\\:first\\:cursor-move:first-child{cursor:move}.sm\\:last\\:cursor-move:last-child{cursor:move}.sm\\:odd\\:cursor-move:nth-child(odd){cursor:move}.sm\\:even\\:cursor-move:nth-child(2n){cursor:move}.sm\\:hover\\:cursor-move:hover{cursor:move}.sm\\:focus\\:cursor-move:focus{cursor:move}.sm\\:active\\:cursor-move:active{cursor:move}.sm\\:visited\\:cursor-move:visited{cursor:move}.sm\\:disabled\\:cursor-move:disabled{cursor:move}}@media (min-width:768px){.md\\:cursor-move{cursor:move}.md\\:focus-within\\:cursor-move:focus-within{cursor:move}.md\\:first\\:cursor-move:first-child{cursor:move}.md\\:last\\:cursor-move:last-child{cursor:move}.md\\:odd\\:cursor-move:nth-child(odd){cursor:move}.md\\:even\\:cursor-move:nth-child(2n){cursor:move}.md\\:hover\\:cursor-move:hover{cursor:move}.md\\:focus\\:cursor-move:focus{cursor:move}.md\\:active\\:cursor-move:active{cursor:move}.md\\:visited\\:cursor-move:visited{cursor:move}.md\\:disabled\\:cursor-move:disabled{cursor:move}}@media (min-width:1024px){.lg\\:cursor-move{cursor:move}.lg\\:focus-within\\:cursor-move:focus-within{cursor:move}.lg\\:first\\:cursor-move:first-child{cursor:move}.lg\\:last\\:cursor-move:last-child{cursor:move}.lg\\:odd\\:cursor-move:nth-child(odd){cursor:move}.lg\\:even\\:cursor-move:nth-child(2n){cursor:move}.lg\\:hover\\:cursor-move:hover{cursor:move}.lg\\:focus\\:cursor-move:focus{cursor:move}.lg\\:active\\:cursor-move:active{cursor:move}.lg\\:visited\\:cursor-move:visited{cursor:move}.lg\\:disabled\\:cursor-move:disabled{cursor:move}.lg\\:overflow-y-auto{overflow-y:auto}}@media (min-width:1280px){.xl\\:cursor-move{cursor:move}.xl\\:focus-within\\:cursor-move:focus-within{cursor:move}.xl\\:first\\:cursor-move:first-child{cursor:move}.xl\\:last\\:cursor-move:last-child{cursor:move}.xl\\:odd\\:cursor-move:nth-child(odd){cursor:move}.xl\\:even\\:cursor-move:nth-child(2n){cursor:move}.xl\\:hover\\:cursor-move:hover{cursor:move}.xl\\:focus\\:cursor-move:focus{cursor:move}.xl\\:active\\:cursor-move:active{cursor:move}.xl\\:visited\\:cursor-move:visited{cursor:move}.xl\\:disabled\\:cursor-move:disabled{cursor:move}}
  525. `;
  526.  
  527. let head = document.getElementsByTagName('head')[0];
  528. style = document.createElement('style');
  529. style.type = 'text/css';
  530. style.innerHTML = styleString;
  531. head.appendChild(style);
  532.  
  533. let body = document.getElementsByTagName('body')[0];
  534. body.classList.add('bg-gray-900');
  535. // To make sure PurgeCSS generates html classes
  536. // <html></html>
  537. /*html*/
  538. body.innerHTML = `
  539. <div id="app" class="font-sans text-gray-100">
  540. <div id="layout">
  541. <div id="settings" class="bg-gray-800 p-3 lg:overflow-y-auto">
  542. <div>
  543. <div class='inline-flex flex-col'>
  544. <div class="inline-flex flex-col mr-10 mb-4">
  545. <span class="text-gray-500 font-display font-bold mb-2 text-xs uppercase tracking-wide">Status</span>
  546. <label class="checkbox inline-flex cursor-pointer relative mb-2">
  547. <input
  548. type="checkbox" name="running" v-model="running" @change="handleSettingChange($event, 'running', 'checkbox')"
  549. class="w-6 h-6 bg-gray-900 rounded cursor-pointer outline-none appearance-none"
  550. >
  551. <span class="ml-2" :title=" running ? 'ALT + S' : 'ALT + A'">{{ running ? 'Running' : 'Paused' }}</span>
  552. </label>
  553. </div>
  554.  
  555. <div class="inline-flex flex-col mr-10 mb-4">
  556. <span class="text-gray-500 font-display font-bold mb-2 text-xs uppercase tracking-wide">Playing as</span>
  557. <label class="radio inline-flex cursor-pointer relative mb-2">
  558. <input
  559. type="radio" name="side" value="1" v-model.number="playingAs" @change="handleSettingChange($event, 'side', 'int')"
  560. class="w-6 h-6 bg-gray-900 rounded-full cursor-pointer outline-none appearance-none"
  561. >
  562. <span class="ml-2" title="ALT + W">White</span>
  563. </label>
  564. <label class="radio inline-flex cursor-pointer relative mb-2">
  565. <input
  566. type="radio" name="side" value="0" v-model.number="playingAs" @change="handleSettingChange($event, 'side', 'int')"
  567. class="w-6 h-6 bg-gray-900 rounded-full cursor-pointer outline-none appearance-none"
  568. >
  569. <span class="ml-2" title="ALT + Q">Black</span>
  570. </label>
  571. </div>
  572.  
  573. <div class="inline-flex flex-col mr-10 mb-4">
  574. <span class="text-gray-500 font-display font-bold mb-2 text-xs uppercase tracking-wide">Run engine for</span>
  575. <label class="radio inline-flex cursor-pointer relative mb-2">
  576. <input
  577. type="radio" name="run-engine" value="0" v-model.number="runEngineFor" @change="handleSettingChange($event, 'run', 'int')"
  578. class="w-6 h-6 bg-gray-900 rounded-full cursor-pointer outline-none appearance-none"
  579. >
  580. <span class="ml-2">Me</span>
  581. </label>
  582. <label class="radio inline-flex cursor-pointer relative mb-2">
  583. <input
  584. type="radio" name="run-engine" value="1" v-model.number="runEngineFor" @change="handleSettingChange($event, 'run', 'int')"
  585. class="w-6 h-6 bg-gray-900 rounded-full cursor-pointer outline-none appearance-none"
  586. >
  587. <span class="ml-2">Opponent</span>
  588. </label>
  589. <label class="radio inline-flex cursor-pointer relative mb-2">
  590. <input
  591. type="radio" name="run-engine" value="2" v-model.number="runEngineFor" @change="handleSettingChange($event, 'run', 'int')"
  592. class="w-6 h-6 bg-gray-900 rounded-full cursor-pointer outline-none appearance-none"
  593. >
  594. <span class="ml-2">Both</span>
  595. </label>
  596. </div>
  597. </div>
  598.  
  599. <div class='inline-flex flex-col'>
  600.  
  601. <div class="inline-flex flex-col mb-4">
  602. <span class="text-gray-500 font-display font-bold mb-2 text-xs uppercase tracking-wide">Engine path</span>
  603. <input
  604. type="text" v-model="enginePath" @change="handleSettingChange($event, 'engine_path', 'path')" :title="enginePath"
  605. class="bg-gray-900 w-64 h-10 appearance-none border-2 border-gray-900 rounded py-2 px-4 text-gray-400 focus:outline-none focus:border-indigo-500"
  606. >
  607. </div>
  608.  
  609. <div class="inline-flex flex-col mr-10 mb-4">
  610. <span class="text-gray-500 font-display font-bold mb-2 text-xs uppercase tracking-wide">Limit</span>
  611. <label class="checkbox inline-flex cursor-pointer relative mb-2">
  612. <input
  613. type="checkbox" v-model="useDepth" @change="handleSettingChange($event, 'use_depth', 'checkbox')"
  614. class="w-6 h-6 bg-gray-900 rounded cursor-pointer outline-none appearance-none"
  615. >
  616. <span class="ml-2">Depth {{ depth }}</span>
  617. </label>
  618. <input
  619. type="range" min="1" max="30" v-model.number="depth" @change="handleSettingChange($event, 'depth', 'int')"
  620. class="slider w-64 appearance-none bg-gray-900 outline-none h-3 rounded-full mt-2 mb-4"
  621. >
  622. <label class="checkbox inline-flex cursor-pointer relative mb-2">
  623. <input
  624. type="checkbox" v-model="useTime" @change="handleSettingChange($event, 'use_time', 'checkbox')"
  625. class="w-6 h-6 bg-gray-900 rounded cursor-pointer outline-none appearance-none"
  626. >
  627. <span class="ml-2">Time {{ time }}</span>
  628. </label>
  629. <input
  630. type="range" min="0.01" max="30" step="0.01" v-model.number="time" @change="handleSettingChange($event, 'time', 'float')"
  631. class="slider appearance-none bg-gray-900 outline-none h-3 rounded-full mt-2 mb-4"
  632. >
  633. </div>
  634.  
  635. <div class="flex flex-col mr-10 mb-4">
  636. <span class="w-48 text-gray-500 font-display font-bold mb-2 text-xs uppercase tracking-wide">Principal variations</span>
  637. <label class="mb-2">
  638. PV {{ multipv }}
  639. </label>
  640. <input
  641. type="range" min="1" max="16" v-model.number="multipv" @change="handleSettingChange($event, 'multipv', 'int')"
  642. class="slider appearance-none bg-gray-900 outline-none h-3 rounded-full mt-2 mb-4"
  643. >
  644. </div>
  645. </div>
  646.  
  647. <div class='inline-flex flex-col'>
  648. <span class="text-gray-500 font-display font-bold mb-2 text-xs uppercase tracking-wide">Misc settings</span>
  649.  
  650. <div class="inline-flex flex-col mb-4">
  651. <label class="checkbox inline-flex cursor-pointer relative mb-2">
  652. <input
  653. type="checkbox" v-model="drawBoard" @change="handleSettingChange($event, 'draw_board', 'checkbox')"
  654. class="w-6 h-6 bg-gray-900 rounded cursor-pointer outline-none appearance-none"
  655. >
  656. <span class="ml-2" title="Draw the SVG board with suggested moves">Draw board</span>
  657. </label>
  658. </div>
  659.  
  660. <div class="inline-flex flex-col mb-4">
  661. <label class="checkbox inline-flex cursor-pointer relative mb-2">
  662. <input
  663. type="checkbox" v-model="drawOverlay" @change="handleSettingChange($event, 'draw_overlay', 'checkbox')"
  664. class="w-6 h-6 bg-gray-900 rounded cursor-pointer outline-none appearance-none"
  665. >
  666. <span class="ml-2" title="Draw best move and response over the actual board">Draw overlay</span>
  667. </label>
  668. </div>
  669.  
  670. <div class="inline-flex flex-col mb-4">
  671. <label class="checkbox inline-flex cursor-pointer relative mb-2">
  672. <input
  673. type="checkbox" v-model="drawEvalbar" @change="handleSettingChange($event, 'draw_evalbar', 'checkbox')"
  674. class="w-6 h-6 bg-gray-900 rounded cursor-pointer outline-none appearance-none"
  675. >
  676. <span class="ml-2" title="Show evaluation bar next to the board">Draw eval bar</span>
  677. </label>
  678. </div>
  679.  
  680. <div class="inline-flex flex-col mb-4">
  681. <label class="checkbox inline-flex cursor-pointer relative mb-2">
  682. <input
  683. type="checkbox" v-model="drawPVs" @change="handleSettingChange($event, 'draw_pvs', 'checkbox')"
  684. class="w-6 h-6 bg-gray-900 rounded cursor-pointer outline-none appearance-none"
  685. >
  686. <span class="ml-2" title="Show principal variations">Draw principal variations</span>
  687. </label>
  688. </div>
  689.  
  690. <div class="inline-flex flex-col mb-4">
  691. <label class="checkbox inline-flex cursor-pointer relative mb-2">
  692. <input
  693. type="checkbox" v-model="useVoice" @change="handleSettingChange($event, 'use_voice', 'checkbox')"
  694. class="w-6 h-6 bg-gray-900 rounded cursor-pointer outline-none appearance-none"
  695. >
  696. <span class="ml-2" title="Read the best move out loud">Enable voice</span>
  697. </label>
  698. </div>
  699.  
  700. <div class="inline-flex flex-col mb-4">
  701. <label class="checkbox inline-flex cursor-pointer relative mb-2">
  702. <input
  703. type="checkbox" v-model="logEngine" @change="handleSettingChange($event, 'clear_log', 'checkbox')"
  704. class="w-6 h-6 bg-gray-900 rounded cursor-pointer outline-none appearance-none"
  705. >
  706. <span class="ml-2" title="Clear engine debug log each time before engine is ran">Clear log</span>
  707. </label>
  708. </div>
  709.  
  710. <div class="inline-flex flex-col mb-4">
  711. <label class="checkbox inline-flex cursor-pointer relative mb-2">
  712. <input
  713. type="checkbox" v-model="useBook" @change="handleSettingChange($event, 'use_book', 'checkbox')"
  714. class="w-6 h-6 bg-gray-900 rounded cursor-pointer outline-none appearance-none"
  715. >
  716. <span class="ml-2" title="Load opening book files from 'books' folder">Use opening books</span>
  717. </label>
  718. </div>
  719.  
  720. <div class="inline-flex flex-col mb-4">
  721. <label class="checkbox inline-flex cursor-pointer relative mb-2">
  722. <input
  723. type="checkbox" v-model="pieceOnly" @change="handleSettingChange($event, 'piece_only', 'checkbox')"
  724. class="w-6 h-6 bg-gray-900 rounded cursor-pointer outline-none appearance-none"
  725. >
  726. <span class="ml-2" title="Best move only tells you what piece to move">Piece only</span>
  727. </label>
  728. </div>
  729.  
  730. </div>
  731. </div>
  732. <div>
  733. <h1 class="text-4xl font-display text-gray-200">Engine settings</h1>
  734. <div v-for="setting in engineSettings" class="mb-4">
  735.  
  736. <div class="text-gray-500 font-display font-bold text-xs uppercase tracking-wide mb-2">{{ setting.name }}</div>
  737.  
  738. <span v-if="setting.type === 'spin'">
  739. <input
  740. type="range" :min="setting.min" :max="setting.max" v-model.number="setting.value"
  741. class="slider appearance-none w-64 bg-gray-900 outline-none h-3 rounded-full mb-4"
  742. @change="handleEngineSettingChange(setting.name, setting.value)"
  743. >
  744. <input
  745. type="number" :min="setting.min" :max="setting.max" v-model="setting.value" :title="setting.value"
  746. @change="handleEngineSettingChange(setting.name, setting.value)"
  747. class="bg-gray-900 ml-2 h-8 appearance-none border-2 border-gray-900 rounded py-2 px-4 text-gray-400 mb-4 focus:outline-none focus:border-indigo-500"
  748. >
  749. <span class="text-sm text-gray-600"> ({{ setting.default }})</span>
  750. </span>
  751.  
  752. <span v-else-if="setting.type === 'combo'">
  753. <div class="inline-block relative w-64">
  754. <select
  755. v-model="setting.value"
  756. @change="handleEngineSettingChange(setting.name, setting.value)"
  757. class="block appearance-none w-full bg-gray-700 px-4 py-2 pr-8 rounded border-2 border-gray-700 focus:outline-none focus:border-indigo-500"
  758. >
  759. <option v-for="opt in setting.var">{{opt}}</option>
  760. </select>
  761. <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700">
  762. <svg class="fill-current text-white h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"/></svg>
  763. </div>
  764. </div>
  765. </span>
  766.  
  767. <span v-else-if="setting.type === 'string'">
  768. <input
  769. type="text" v-model="setting.value" :title="setting.value"
  770. @change="handleEngineSettingChange(setting.name, setting.value)"
  771. class="bg-gray-900 w-64 h-10 appearance-none border-2 border-gray-900 rounded py-2 px-4 text-gray-400 mb-4 focus:outline-none focus:border-indigo-500"
  772. >
  773. <span class="text-sm text-gray-600 ml-2"> ({{ setting.default }})</span>
  774. </span>
  775.  
  776. <span v-else-if="setting.type === 'check'">
  777. <label class="checkbox inline-flex cursor-pointer relative mb-2">
  778. <input
  779. type="checkbox" v-model="setting.value" true-value="True" false-value="False"
  780. @change="handleEngineSettingChange(setting.name, setting.value)"
  781. class="w-6 h-6 bg-gray-900 rounded cursor-pointer outline-none appearance-none"
  782. >
  783. <span class="ml-2">{{ setting.value.toString().toLowerCase() == 'true' ? 'Enabled' : 'Disabled' }}</span>
  784. </label>
  785. </span>
  786.  
  787. <span v-else-if="setting.type === 'button'">
  788. <button
  789. class="bg-indigo-500 w-64 hover:bg-indigo-600 text-white py-2 px-4 rounded"
  790. @click='handleButton(setting.name)'
  791. >
  792. {{ setting.name }}
  793. </button>
  794. </span>
  795.  
  796. <span v-else class="mb-5">
  797. {{ setting.type }} ???
  798. </span>
  799.  
  800. </div>
  801. </div>
  802. </div>
  803.  
  804. <div id="board" class="flex bg-gray-800 p-3">
  805. <div class="flex items-end mr-3 bg-gray-100 w-10" v-if="drawEvalbar" style="min-height: 30vh">
  806. <div class="flex items-end justify-center text-sm font-display text-white bg-gray-900 w-10"
  807. :style="{ height: evalBarHeight + '%' }">
  808. <span class="text-gray-600"> {{ currentScore }} </span>
  809. </div>
  810. </div>
  811. <div class="flex flex-col">
  812. <span class='font-display text-green-200'>{{ currentEco }}</span>
  813. <span class='font-display text-green-100' v-if="pieceOnly">{{ bestPiece }}</span>
  814. <div id="board-container" v-html="board" v-else-if="drawBoard"></div>
  815. </div>
  816. </div>
  817.  
  818. <div id="pvs" class="bg-green-400 p-3 lg:overflow-y-auto">
  819. <div class="text-green font-display text-xs" v-if="pieceOnly">
  820. {{ bestPiece }}
  821. </div>
  822. <div class="flex flex-row flex-wrap" v-if="drawPVs">
  823. <div
  824. v-for="(line, pv_index) in pvs"
  825. @click="onSelectPV(pv_index + 1)"
  826. class="w-48 mr-4 mb-3 p-2 border-2 border-gray-500 rounded hover:border-indigo-400"
  827. :class="{ 'border-indigo-500': selectedPV == pv_index + 1 }"
  828. >
  829. <div class="text-white font-display text-xs" :title="line.eco">
  830. {{ line.eco }}
  831. </div>
  832. <div class="text-gray-500 font-display font-bold text-xs uppercase tracking-wide mb-2">
  833. PV {{ line.multipv }}
  834. <span class="font-sans text-white text-sm ml-2">
  835. {{ line.score }}
  836. </span>
  837. </div>
  838. <div class="flex flex-row flex-wrap">
  839. <div v-for="(mov, i) in line.pv" class="w-1/2">
  840. <span :class="{ 'text-gray-400': i % 2 != 0 }">{{ mov }}</span>
  841. </div>
  842. </div>
  843. </div>
  844. </div>
  845. </div>
  846. </div>
  847. </div>`;
  848.  
  849. app = new Vue({
  850. el: '#app',
  851. data: {
  852. board: '',
  853. messages: [],
  854. enginePath: '',
  855. playingAs: WHITE,
  856. runEngineFor: 0,
  857. useDepth: true,
  858. depth: 8,
  859. useTime: true,
  860. time: 1.0,
  861. engineSettings: [],
  862. drawBoard: true,
  863. useVoice: true,
  864. multipv: 1,
  865. logEngine: '',
  866. useBook: true,
  867. drawOverlay: true,
  868. drawEvalbar: true,
  869. drawPVs: true,
  870. pvs: [],
  871. selectedPV: 1,
  872. running: true,
  873. turn: WHITE,
  874. currentEco: '',
  875. analysis: false,
  876. book: false,
  877. pieceOnly: false,
  878. bestPiece: '',
  879. },
  880. computed: {
  881. currentScore() {
  882. if (this.pvs.length && !this.book) {
  883. let score = this.pvs[0].score;
  884. if (typeof score === 'string' && score.includes('#')) {
  885. score = score.includes('-') ? -9999 : 9999;
  886. }
  887. if (this.turn == BLACK) {
  888. score *= -1;
  889. }
  890. return score;
  891. }
  892. return 0;
  893. },
  894. evalBarHeight() {
  895. return 100 - (2 / (1 + Math.exp(-0.004 * this.currentScore)) - 1 + 1) * 50;
  896. },
  897. },
  898. methods: {
  899. handleSettingChange(event, key, type) {
  900. let value = event.target.value;
  901. if (type == 'checkbox') {
  902. value = event.target.checked;
  903. } else if (type == 'int') {
  904. value = parseInt(value);
  905. } else if (type == 'float') {
  906. value = parseFloat(value);
  907. } else if (type == 'path') {
  908. value = value.replace(/\\/g, '/');
  909. }
  910. console.log(`Changed ${key} to:`, value);
  911. ws.send(JSON.stringify({ type: 'setting', data: { key, value } }));
  912. },
  913. handleEngineSettingChange(key, value) {
  914. console.log(`Changed engine setting ${key} to:`, value);
  915. ws.send(JSON.stringify({ type: 'engine_setting', data: { key, value } }));
  916. },
  917. handleButton(key) {
  918. if (key === 'Clear Hash') {
  919. console.log('Clearing hash...');
  920. ws.send(JSON.stringify({ type: 'clear_hash', data: true }));
  921. }
  922. },
  923. onSelectPV(pv) {
  924. console.log('Changing PV to', pv);
  925. this.selectedPV = pv;
  926. drawOnScreen();
  927. ws.send(JSON.stringify({ type: 'draw_svg', data: pv }));
  928. },
  929. },
  930. });
  931.  
  932. try {
  933. ws = await connect(`ws://127.0.0.1:5678/${uid}`);
  934. } catch {
  935. console.error('Failed to connect. Make sure the server is running and refresh the page.');
  936. return;
  937. }
  938.  
  939. console.log('Connection estabished.');
  940. ws.onmessage = function (event) {
  941. data = JSON.parse(event.data);
  942. switch (data.target) {
  943. case 'board':
  944. app.board = data.message;
  945. break;
  946. case 'error':
  947. console.log(data.message);
  948. break;
  949. case 'setting':
  950. const { key, value } = data.message;
  951. if (key in app.$data) {
  952. app.$data[key] = value;
  953. } else {
  954. console.error(`No key: ${key} found in app data!`);
  955. }
  956. break;
  957. case 'engine_settings':
  958. app.engineSettings = data.message;
  959. console.log('Received engine settings');
  960. break;
  961. case 'multipv':
  962. app.pvs = data.message.multipv;
  963. app.turn = data.message.turn;
  964. app.currentEco = data.message.current_eco;
  965. app.book = data.message.book;
  966. app.bestPiece = data.message.best_piece;
  967. app.selectedPV = 1;
  968. drawOnScreen();
  969. break;
  970. default:
  971. console.warn('Received unknown message type:');
  972. console.log(data);
  973. }
  974. };
  975.  
  976. doc.addEventListener('keydown', hotkey);
  977. await findGame();
  978. };
  979.  
  980. main();
  981.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement