From 584937a093b902d7490aabe8fb9a376b25f27428 Mon Sep 17 00:00:00 2001 From: fohristiwhirl Date: Fri, 7 Jun 2019 10:43:06 +0100 Subject: [PATCH 01/29] gut the renderer --- 8_renderer.js | 396 +----------------- 8_renderer_old.js | 961 +++++++++++++++++++++++++++++++++++++++++++ engine_controller.js | 60 +++ 3 files changed, 1038 insertions(+), 379 deletions(-) create mode 100644 8_renderer_old.js create mode 100644 engine_controller.js diff --git a/8_renderer.js b/8_renderer.js index a13d5dc1..cd0c9d5e 100644 --- a/8_renderer.js +++ b/8_renderer.js @@ -170,272 +170,16 @@ function make_renderer() { renderer.ever_received_info = false; // When false, we write stderr log instead of move info. renderer.stderr_log = ""; // All output received from the engine's stderr. renderer.infobox_string = ""; // Just to help not redraw the infobox when not needed. - renderer.pgn_choices = null; // Made into a temporary array when displaying the PGN choice. - renderer.pgn_line_end = null; // The terminal position of the loaded PGN, if any. + renderer.pgn_choices = null; // All games found when opening a PGN file. renderer.clickable_pv_lines = []; // List of PV objects we use to tell what the user clicked on. - // The following are never actually null (i.e. they're set immediately): + renderer.start_pos = LoadFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); + renderer.user_line = []; // Entire history of the user variation, as a list of moves. + renderer.pos = renderer.start_pos // Currently shown position. - renderer.user_line_end = null; - renderer.pos = null; + // -------------------------------------------------------------------------------------------- - // --------------------------------------------- - - renderer.square_size = () => { - return config.board_size / 8; - }; - - renderer.pos_changed = (new_game_flag) => { - - renderer.info_table.clear(); - - fenbox.value = renderer.pos.fen(); - renderer.draw_main_line(); - - if (renderer.running) { - renderer.go(new_game_flag); - } else if (new_game_flag) { - send("ucinewgame"); - } - - renderer.escape(); // Among other things, this draws. - }; - - renderer.game_changed = () => { - renderer.pos_changed(true); - }; - - renderer.load_fen = (s) => { - - try { - renderer.pos = LoadFEN(s); - } catch (err) { - alert(err); - return; - } - - renderer.pgn_line_end = null; - renderer.user_line_end = renderer.pos; - renderer.game_changed(); - }; - - renderer.new = () => { - renderer.load_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); - }; - - renderer.load_pgn_object = (o) => { - - // Returns true or false - whether this actually succeeded. - - let final_pos; - - try { - final_pos = LoadPGN(o.movetext); - } catch (err) { - alert(err); - return false; - } - - // Icky way of storing the fact that a position is on the PGN... - - for (let p of final_pos.position_list()) { - p.pgn_flag = true; - } - - renderer.pgn_line_end = final_pos; - renderer.user_line_end = final_pos; - renderer.pos = final_pos.root(); - renderer.game_changed(); - return true; - }; - - renderer.choose_pgn = (n) => { - renderer.hide_pgn_chooser(); - if (renderer.pgn_choices && n >= 0 && n < renderer.pgn_choices.length) { - renderer.load_pgn_object(renderer.pgn_choices[n]); - } - }; - - renderer.open = (filename) => { - - let buf = fs.readFileSync(filename); // i.e. binary buffer object - let new_pgn_choices = pre_parse_pgn(buf); - - if (new_pgn_choices.length === 1) { - let success = renderer.load_pgn_object(new_pgn_choices[0]); - if (success) { - renderer.pgn_choices = new_pgn_choices; // We only want to set this to a 1 value array if it actually worked. - } - } else { - renderer.pgn_choices = new_pgn_choices; // Setting it to a multi-value array is "always" OK. - renderer.show_pgn_chooser(); // Now we need to have the user choose a game. - } - }; - - renderer.show_pgn_chooser = () => { - - if (!renderer.pgn_choices) { - alert("No PGN loaded"); - return; - } - - renderer.halt(); // It's lame to run the GPU when we're clearly switching games. - - let lines = []; - - lines.push(" "); - - let max_ordinal_length = renderer.pgn_choices.length.toString().length; - let padding = ""; - for (let n = 0; n < max_ordinal_length - 1; n++) { - padding += " "; - } - - for (let n = 0; n < renderer.pgn_choices.length; n++) { - - if (n === 9 || n === 99 || n === 999 || n === 9999 || n === 99999 || n === 999999) { - padding = padding.slice(0, padding.length - 6); - } - - let p = renderer.pgn_choices[n]; - - let s; - - if (p.tags.Result === "1-0") { - s = `${padding}${n + 1}. ${p.tags.White} - ${p.tags.Black}`; - } else if (p.tags.Result === "0-1") { - s = `${padding}${n + 1}. ${p.tags.White} - ${p.tags.Black}`; - } else { - s = `${padding}${n + 1}. ${p.tags.White} - ${p.tags.Black}`; - } - lines.push(`  ${s}`); - } - - lines.push(" "); - - pgnchooser.innerHTML = lines.join("
"); - pgnchooser.style.display = "block"; - }; - - renderer.hide_pgn_chooser = () => { - pgnchooser.style.display = "none"; - }; - - renderer.escape = () => { // Set things into a clean state. - renderer.hide_pgn_chooser(); - renderer.active_square = null; - renderer.draw(); - }; - - renderer.validate_pgn = (filename) => { - - let buf = fs.readFileSync(filename); // i.e. binary buffer object - let pgn_list = pre_parse_pgn(buf); - - for (let n = 0; n < pgn_list.length; n++) { - - let o = pgn_list[n]; - - try { - LoadPGN(o.movetext); - } catch (err) { - alert(`Game ${n + 1} - ${err.toString()}`); - return; - } - } - - alert(`This file seems OK. ${pgn_list.length} games checked.`); - return true; - }; - - renderer.prev = () => { - if (renderer.pos.parent) { - renderer.pos = renderer.pos.parent; - renderer.pos_changed(); - } - }; - - renderer.next = () => { - - if (renderer.pos === renderer.user_line_end) { - return; - } - - for (let p of renderer.user_line_end.position_list()) { - if (p.parent === renderer.pos) { - renderer.pos = p; - renderer.pos_changed(); - return; - } - } - }; - - renderer.goto_root = () => { - renderer.pos = renderer.pos.root(); - renderer.pos_changed(); - }; - - renderer.goto_end = () => { - renderer.pos = renderer.user_line_end; - renderer.pos_changed(); - }; - - renderer.return_to_pgn = () => { - - if (!renderer.pgn_line_end) { - alert("No PGN loaded"); - return; - } - - let node = renderer.pos; - - while (!node.pgn_flag) { - if (node.parent === null) { - break; - } - node = node.parent; - } - - if (node.pgn_flag) { - renderer.user_line_end = renderer.pgn_line_end; - renderer.pos = node; - renderer.pos_changed(); - return; - } - - alert("Couldn't rejoin the PGN. This is a bug, tell the author how you achieved it."); - }; - - renderer.move_stays_on_user_line = (s) => { - - for (let p of renderer.user_line_end.position_list()) { - if (p.parent === renderer.pos) { - if (p.lastmove === s) { - return true; - } else { - return false; - } - } - } - - return false; - }; - - renderer.move = (s, skip_pos_changed) => { - - // Add promotion if needed and not present... - - if (s.length === 4) { - let source = Point(s.slice(0, 2)); - if (renderer.pos.piece(source) === "P" && source.y === 1) { - console.log(`Move ${s} was promotion but had no promotion piece set; adjusting to ${s + "q"}`); - s += "q"; - } - if (renderer.pos.piece(source) === "p" && source.y === 6) { - console.log(`Move ${s} was promotion but had no promotion piece set; adjusting to ${s + "q"}`); - s += "q"; - } - } + renderer.move = (s) => { let illegal_reason = renderer.pos.illegal(s) if (illegal_reason !== "") { @@ -443,70 +187,14 @@ function make_renderer() { return; } - if (renderer.move_stays_on_user_line(s)) { - for (let p of renderer.user_line_end.position_list()) { - if (p.parent === renderer.pos) { - renderer.pos = p; - if (!skip_pos_changed) { - renderer.pos_changed(); - } - return; - } - } - console.log("Shouldn't get here."); - } - renderer.pos = renderer.pos.move(s); - renderer.user_line_end = renderer.pos; - - if (!skip_pos_changed) { - renderer.pos_changed(); - } - }; - - renderer.play_best = () => { - let info_list = renderer.info_table.sorted(); - if (info_list.length > 0) { - renderer.move(info_list[0].move); - } + renderer.draw(); }; - renderer.go = (new_game_flag) => { - renderer.escape(); - renderer.running = true; - let setup; - - let initial_fen = renderer.pos.initial_fen(); - if (initial_fen !== "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") { - setup = `fen ${initial_fen}`; - } else { - setup = "startpos"; - } - - send("stop"); - if (new_game_flag) { - send("ucinewgame"); - } - - send(`position ${setup} moves ${renderer.pos.history().join(" ")}`); - sync(); // See comment on how sync() works - send("go infinite"); - }; - - renderer.halt = () => { - send("stop"); - renderer.running = false; - }; - - renderer.reset_leela_cache = () => { - if (renderer.running) { - renderer.go(true); - } else { - send("ucinewgame"); - } - }; + // -------------------------------------------------------------------------------------------- + // Things below this point are not related to the difficult task of keeping track of positions. renderer.receive = (s) => { @@ -528,6 +216,10 @@ function make_renderer() { } }; + renderer.square_size = () => { + return config.board_size / 8; + }; + renderer.canvas_click = (event) => { let point = null; @@ -572,6 +264,10 @@ function make_renderer() { renderer.draw(); }; + renderer.draw_main_line = () => { + // TODO + }; + renderer.pv_click = (i, n) => { if (i < 0 || i >= renderer.clickable_pv_lines.length) { @@ -598,63 +294,6 @@ function make_renderer() { renderer.pos_changed(); }; - renderer.draw_main_line = () => { - - let elements1 = []; - let elements2 = []; - - // First, have the moves actually made on the visible board. - - let poslist = renderer.pos.position_list(); - - for (let p of poslist.slice(1)) { // Start on the first position that has a lastmove - - if (!p.pgn_flag && p.parent.pgn_flag) { - elements1.push(`(deviated)`); - } - - if (p.parent.active === "w") { - elements1.push(`${p.parent.fullmove}.`); - } - - elements1.push(p.nice_lastmove()); - } - - // Next, have the moves to the end of the user line. - - let start_flag = false; - for (let p of renderer.user_line_end.position_list()) { - - if (p === renderer.pos) { - start_flag = true; - continue; - } - - if (start_flag === false) { - continue; - } - - if (!p.pgn_flag && p.parent.pgn_flag) { - elements2.push(`(deviated)`); - } - - if (p.parent.active === "w") { - elements2.push(`${p.parent.fullmove}.`); - } - - elements2.push(p.nice_lastmove()); - } - - let s1 = elements1.join(" "); // Possibly empty string - let s2 = elements2.join(" "); // Possibly empty string - - if (s2.length > 0) { - s2 = `` + s2 + ""; - } - - mainline.innerHTML = [s1, s2].filter(s => s !== "").join(" "); - }; - renderer.draw_infobox = () => { renderer.clickable_pv_lines = []; @@ -904,7 +543,6 @@ function make_renderer() { setTimeout(renderer.draw_loop, 500); }; - renderer.load_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); return renderer; } diff --git a/8_renderer_old.js b/8_renderer_old.js new file mode 100644 index 00000000..a13d5dc1 --- /dev/null +++ b/8_renderer_old.js @@ -0,0 +1,961 @@ +"use strict"; + +function send(msg) { + try { + msg = msg.trim(); + exe.stdin.write(msg); + exe.stdin.write("\n"); + Log("--> " + msg); + } catch (err) { + // pass + } +} + +function setoption(name, value) { + send(`setoption name ${name} value ${value}`); +} + +// The sync function exists so that we can disregard all output until a certain point. +// Basically we use it after sending a position, so that we can ignore all analysis +// that comes until LZ sends "readyok" in response to our "isready". All output before +// that moment would refer to the obsolete position. +// +// While this seems to work correctly with Lc0, tests with Stockfish show that it +// definitely violates our assumptions and sends things out of order, hence the need +// for validity checking on incoming messages anyway. + +function sync() { + send("isready"); + readyok_required++; +} + +// ------------------------------------------------------------------------------------------------ + +try { + if (fs.existsSync("config.json")) { + config = JSON.parse(debork_json(fs.readFileSync("config.json", "utf8"))); + } else if (fs.existsSync("config.json.example")) { + config = JSON.parse(debork_json(fs.readFileSync("config.json.example", "utf8"))); + config.warn_filename = true; + } else { + alert("config.json not present"); + } +} catch (err) { + alert("Failed to parse config file - make sure it is valid JSON, and in particular, if on Windows, use \\\\ instead of \\ as a path separator."); +} + +// Some tolerable default values for config... + +assign_without_overwrite(config, { + "options": {}, + + "width": 1280, + "height": 840, + "board_size": 640, + "mainline_height": 108, + + "show_n": true, + "show_p": true, + "show_pv": true, + "show_winrate": true, + + "rank_font": "24px Arial", + + "light_square": "#dadada", + "dark_square": "#b4b4b4", + "active_square": "#cc9966", + + "best_colour": "#66aaaa", + "good_colour": "#66aa66", + "bad_colour": "#cccc66", + "terrible_colour": "#cc6666", + + "bad_move_threshold": 0.02, + "terrible_move_threshold": 0.04, + + "max_info_lines": 10, + "node_display_threshold": 0.02, + + "logfile": null +}); + +infobox.style.height = config.board_size.toString() + "px"; +mainline.style.height = config.mainline_height.toString() + "px"; // Is there a way to avoid needing this, to get the scroll bar? +canvas.width = config.board_size; +canvas.height = config.board_size; + +Log(""); +Log("======================================================================================================================================"); +Log(`Nibbler startup at ${new Date().toUTCString()}`); +Log(""); + +if (config.path) { + exe = child_process.spawn(config.path); + exe.on("error", (err) => { + alert("Couldn't spawn process - check the path in the config file"); // Note that this alert will come some time in the future, not instantly. + }); + + scanner = readline.createInterface({ + input: exe.stdout, + output: undefined, + terminal: false + }); + + err_scanner = readline.createInterface({ + input: exe.stderr, + output: undefined, + terminal: false + }); + + err_scanner.on("line", (line) => { + Log("! " + line); + renderer.err_receive(line); + }); + + scanner.on("line", (line) => { + + // We want to ignore all output when waiting for readyok + + if (line.includes("readyok") && readyok_required > 0) { + readyok_required--; + } + + if (readyok_required > 0) { + Log("(ignored) < " + line); + return; + } + + Log("< " + line); + renderer.receive(line); + }); +} + +send("uci"); + +for (let key of Object.keys(config.options)) { + setoption(key, config.options[key]); +} + +setoption("VerboseMoveStats", true); // Required for LogLiveStats to work. +setoption("LogLiveStats", true); // "Secret" Lc0 command. +setoption("MultiPV", 500); + +// ------------------------------------------------------------------------------------------------ + +let images = Object.create(null); +let loads = 0; + +for (let c of Array.from("KkQqRrBbNnPp")) { + images[c] = new Image(); + if (c === c.toUpperCase()) { + images[c].src = `./pieces/${c}.png`; + } else { + images[c].src = `./pieces/_${c.toUpperCase()}.png`; + } + images[c].onload = () => { + loads++; + }; +} + +// ------------------------------------------------------------------------------------------------ + +function make_renderer() { + + let renderer = Object.create(null); + renderer.info_table = NewInfoTable(); + + renderer.squares = []; // Info about clickable squares. + renderer.active_square = null; // Square clicked by user. + renderer.running = false; // Whether to resend "go" to the engine after move, undo, etc. + renderer.ever_received_info = false; // When false, we write stderr log instead of move info. + renderer.stderr_log = ""; // All output received from the engine's stderr. + renderer.infobox_string = ""; // Just to help not redraw the infobox when not needed. + renderer.pgn_choices = null; // Made into a temporary array when displaying the PGN choice. + renderer.pgn_line_end = null; // The terminal position of the loaded PGN, if any. + renderer.clickable_pv_lines = []; // List of PV objects we use to tell what the user clicked on. + + // The following are never actually null (i.e. they're set immediately): + + renderer.user_line_end = null; + renderer.pos = null; + + // --------------------------------------------- + + renderer.square_size = () => { + return config.board_size / 8; + }; + + renderer.pos_changed = (new_game_flag) => { + + renderer.info_table.clear(); + + fenbox.value = renderer.pos.fen(); + renderer.draw_main_line(); + + if (renderer.running) { + renderer.go(new_game_flag); + } else if (new_game_flag) { + send("ucinewgame"); + } + + renderer.escape(); // Among other things, this draws. + }; + + renderer.game_changed = () => { + renderer.pos_changed(true); + }; + + renderer.load_fen = (s) => { + + try { + renderer.pos = LoadFEN(s); + } catch (err) { + alert(err); + return; + } + + renderer.pgn_line_end = null; + renderer.user_line_end = renderer.pos; + renderer.game_changed(); + }; + + renderer.new = () => { + renderer.load_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); + }; + + renderer.load_pgn_object = (o) => { + + // Returns true or false - whether this actually succeeded. + + let final_pos; + + try { + final_pos = LoadPGN(o.movetext); + } catch (err) { + alert(err); + return false; + } + + // Icky way of storing the fact that a position is on the PGN... + + for (let p of final_pos.position_list()) { + p.pgn_flag = true; + } + + renderer.pgn_line_end = final_pos; + renderer.user_line_end = final_pos; + renderer.pos = final_pos.root(); + renderer.game_changed(); + return true; + }; + + renderer.choose_pgn = (n) => { + renderer.hide_pgn_chooser(); + if (renderer.pgn_choices && n >= 0 && n < renderer.pgn_choices.length) { + renderer.load_pgn_object(renderer.pgn_choices[n]); + } + }; + + renderer.open = (filename) => { + + let buf = fs.readFileSync(filename); // i.e. binary buffer object + let new_pgn_choices = pre_parse_pgn(buf); + + if (new_pgn_choices.length === 1) { + let success = renderer.load_pgn_object(new_pgn_choices[0]); + if (success) { + renderer.pgn_choices = new_pgn_choices; // We only want to set this to a 1 value array if it actually worked. + } + } else { + renderer.pgn_choices = new_pgn_choices; // Setting it to a multi-value array is "always" OK. + renderer.show_pgn_chooser(); // Now we need to have the user choose a game. + } + }; + + renderer.show_pgn_chooser = () => { + + if (!renderer.pgn_choices) { + alert("No PGN loaded"); + return; + } + + renderer.halt(); // It's lame to run the GPU when we're clearly switching games. + + let lines = []; + + lines.push(" "); + + let max_ordinal_length = renderer.pgn_choices.length.toString().length; + let padding = ""; + for (let n = 0; n < max_ordinal_length - 1; n++) { + padding += " "; + } + + for (let n = 0; n < renderer.pgn_choices.length; n++) { + + if (n === 9 || n === 99 || n === 999 || n === 9999 || n === 99999 || n === 999999) { + padding = padding.slice(0, padding.length - 6); + } + + let p = renderer.pgn_choices[n]; + + let s; + + if (p.tags.Result === "1-0") { + s = `${padding}${n + 1}. ${p.tags.White} - ${p.tags.Black}`; + } else if (p.tags.Result === "0-1") { + s = `${padding}${n + 1}. ${p.tags.White} - ${p.tags.Black}`; + } else { + s = `${padding}${n + 1}. ${p.tags.White} - ${p.tags.Black}`; + } + lines.push(`  ${s}`); + } + + lines.push(" "); + + pgnchooser.innerHTML = lines.join("
"); + pgnchooser.style.display = "block"; + }; + + renderer.hide_pgn_chooser = () => { + pgnchooser.style.display = "none"; + }; + + renderer.escape = () => { // Set things into a clean state. + renderer.hide_pgn_chooser(); + renderer.active_square = null; + renderer.draw(); + }; + + renderer.validate_pgn = (filename) => { + + let buf = fs.readFileSync(filename); // i.e. binary buffer object + let pgn_list = pre_parse_pgn(buf); + + for (let n = 0; n < pgn_list.length; n++) { + + let o = pgn_list[n]; + + try { + LoadPGN(o.movetext); + } catch (err) { + alert(`Game ${n + 1} - ${err.toString()}`); + return; + } + } + + alert(`This file seems OK. ${pgn_list.length} games checked.`); + return true; + }; + + renderer.prev = () => { + if (renderer.pos.parent) { + renderer.pos = renderer.pos.parent; + renderer.pos_changed(); + } + }; + + renderer.next = () => { + + if (renderer.pos === renderer.user_line_end) { + return; + } + + for (let p of renderer.user_line_end.position_list()) { + if (p.parent === renderer.pos) { + renderer.pos = p; + renderer.pos_changed(); + return; + } + } + }; + + renderer.goto_root = () => { + renderer.pos = renderer.pos.root(); + renderer.pos_changed(); + }; + + renderer.goto_end = () => { + renderer.pos = renderer.user_line_end; + renderer.pos_changed(); + }; + + renderer.return_to_pgn = () => { + + if (!renderer.pgn_line_end) { + alert("No PGN loaded"); + return; + } + + let node = renderer.pos; + + while (!node.pgn_flag) { + if (node.parent === null) { + break; + } + node = node.parent; + } + + if (node.pgn_flag) { + renderer.user_line_end = renderer.pgn_line_end; + renderer.pos = node; + renderer.pos_changed(); + return; + } + + alert("Couldn't rejoin the PGN. This is a bug, tell the author how you achieved it."); + }; + + renderer.move_stays_on_user_line = (s) => { + + for (let p of renderer.user_line_end.position_list()) { + if (p.parent === renderer.pos) { + if (p.lastmove === s) { + return true; + } else { + return false; + } + } + } + + return false; + }; + + renderer.move = (s, skip_pos_changed) => { + + // Add promotion if needed and not present... + + if (s.length === 4) { + let source = Point(s.slice(0, 2)); + if (renderer.pos.piece(source) === "P" && source.y === 1) { + console.log(`Move ${s} was promotion but had no promotion piece set; adjusting to ${s + "q"}`); + s += "q"; + } + if (renderer.pos.piece(source) === "p" && source.y === 6) { + console.log(`Move ${s} was promotion but had no promotion piece set; adjusting to ${s + "q"}`); + s += "q"; + } + } + + let illegal_reason = renderer.pos.illegal(s) + if (illegal_reason !== "") { + alert(`Illegal move requested (${s}, ${illegal_reason}). This should be impossible, please tell the author how you managed it.`); + return; + } + + if (renderer.move_stays_on_user_line(s)) { + for (let p of renderer.user_line_end.position_list()) { + if (p.parent === renderer.pos) { + renderer.pos = p; + if (!skip_pos_changed) { + renderer.pos_changed(); + } + return; + } + } + console.log("Shouldn't get here."); + } + + renderer.pos = renderer.pos.move(s); + renderer.user_line_end = renderer.pos; + + if (!skip_pos_changed) { + renderer.pos_changed(); + } + }; + + renderer.play_best = () => { + let info_list = renderer.info_table.sorted(); + if (info_list.length > 0) { + renderer.move(info_list[0].move); + } + }; + + renderer.go = (new_game_flag) => { + + renderer.escape(); + renderer.running = true; + + let setup; + + let initial_fen = renderer.pos.initial_fen(); + if (initial_fen !== "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") { + setup = `fen ${initial_fen}`; + } else { + setup = "startpos"; + } + + send("stop"); + if (new_game_flag) { + send("ucinewgame"); + } + + send(`position ${setup} moves ${renderer.pos.history().join(" ")}`); + sync(); // See comment on how sync() works + send("go infinite"); + }; + + renderer.halt = () => { + send("stop"); + renderer.running = false; + }; + + renderer.reset_leela_cache = () => { + if (renderer.running) { + renderer.go(true); + } else { + send("ucinewgame"); + } + }; + + renderer.receive = (s) => { + + if (s.startsWith("info")) { + renderer.ever_received_info = true; + renderer.info_table.receive(s, renderer.pos); + } + + if (s.startsWith("error")) { + renderer.err_receive(s); + } + }; + + renderer.err_receive = (s) => { + if (s.indexOf("WARNING") !== -1) { + renderer.stderr_log += `${s}
`; + } else { + renderer.stderr_log += `${s}
`; + } + }; + + renderer.canvas_click = (event) => { + + let point = null; + + for (let n = 0; n < renderer.squares.length; n++) { + let foo = renderer.squares[n]; + if (foo.x1 < event.offsetX && foo.y1 < event.offsetY && foo.x2 > event.offsetX && foo.y2 > event.offsetY) { + point = foo.point; + break; + } + } + + if (point === null) { + return; + } + + if (renderer.active_square) { + + let move_string = renderer.active_square.s + point.s; // e.g. "e2e4" + + let illegal_reason = renderer.pos.illegal(move_string); + + renderer.active_square = null; + + if (illegal_reason === "") { + renderer.move(move_string); + return; // Skip the draw, below, since move() will do that. + } else { + console.log(illegal_reason); + } + + } else { + + if (renderer.pos.active === "w" && renderer.pos.is_white(point)) { + renderer.active_square = point; + } + if (renderer.pos.active === "b" && renderer.pos.is_black(point)) { + renderer.active_square = point; + } + } + + renderer.draw(); + }; + + renderer.pv_click = (i, n) => { + + if (i < 0 || i >= renderer.clickable_pv_lines.length) { + return; + } + + let o = renderer.clickable_pv_lines[i]; + + if (o.board !== renderer.pos) { + return; + } + + let moves = o.pv.slice(0, n + 1); + + // It's best that pos_changed() not be called until we've finished moving. + // That way, all analysis coming from Lc0 will still refer to the original + // position until we send a single message with the new position, and clear + // our info_table a single time. + + for (let move of moves) { + renderer.move(move, true); + } + + renderer.pos_changed(); + }; + + renderer.draw_main_line = () => { + + let elements1 = []; + let elements2 = []; + + // First, have the moves actually made on the visible board. + + let poslist = renderer.pos.position_list(); + + for (let p of poslist.slice(1)) { // Start on the first position that has a lastmove + + if (!p.pgn_flag && p.parent.pgn_flag) { + elements1.push(`(deviated)`); + } + + if (p.parent.active === "w") { + elements1.push(`${p.parent.fullmove}.`); + } + + elements1.push(p.nice_lastmove()); + } + + // Next, have the moves to the end of the user line. + + let start_flag = false; + for (let p of renderer.user_line_end.position_list()) { + + if (p === renderer.pos) { + start_flag = true; + continue; + } + + if (start_flag === false) { + continue; + } + + if (!p.pgn_flag && p.parent.pgn_flag) { + elements2.push(`(deviated)`); + } + + if (p.parent.active === "w") { + elements2.push(`${p.parent.fullmove}.`); + } + + elements2.push(p.nice_lastmove()); + } + + let s1 = elements1.join(" "); // Possibly empty string + let s2 = elements2.join(" "); // Possibly empty string + + if (s2.length > 0) { + s2 = `` + s2 + ""; + } + + mainline.innerHTML = [s1, s2].filter(s => s !== "").join(" "); + }; + + renderer.draw_infobox = () => { + + renderer.clickable_pv_lines = []; + + if (!renderer.ever_received_info) { + if (infobox.innerHTML !== renderer.stderr_log) { // Only update when needed, so user can select and copy. + infobox.innerHTML = renderer.stderr_log; + } + return; + } + + let info_list = renderer.info_table.sorted(); + + let s = ""; + + if (!renderer.running) { + s += "

<halted>

"; + } + + for (let i = 0; i < info_list.length && i < config.max_info_lines; i++) { + + s += `

${info_list[i].nice_pv_string(renderer.pos, config, i)}

`; + + renderer.clickable_pv_lines.push({ + board: renderer.pos, + pv: info_list[i].pv + }) + } + + // Only update when needed, so user can select and copy. A direct comparison + // of s with innerHTML seems to fail (something must get changed). + + if (renderer.infobox_string !== s) { + renderer.infobox_string = s; + infobox.innerHTML = s; + } + }; + + renderer.canvas_coords = (x, y) => { + + // Given the x, y coordinates on the board (a8 is 0, 0) + // return an object with the canvas coordinates for + // the square, and also the centre. Also has rss. + // + // x1,y1-------- + // | | + // | cx,cy | + // | | + // --------x2,y2 + + let rss = renderer.square_size(); + let x1 = x * rss; + let y1 = y * rss; + let x2 = x1 + rss; + let y2 = y1 + rss; + + if (config.flip) { + [x1, x2] = [(rss * 8) - x2, (rss * 8) - x1]; + [y1, y2] = [(rss * 8) - y2, (rss * 8) - y1]; + } + + let cx = x1 + rss / 2; + let cy = y1 + rss / 2; + + return {x1, y1, x2, y2, cx, cy, rss}; + }; + + renderer.draw_board = (light, dark) => { + + renderer.squares = []; + + for (let x = 0; x < 8; x++) { + for (let y = 0; y < 8; y++) { + if (x % 2 === y % 2) { + context.fillStyle = light; + } else { + context.fillStyle = dark; + } + + let cc = renderer.canvas_coords(x, y); + + if (renderer.active_square === Point(x, y)) { + context.fillStyle = config.active_square; + } + + context.fillRect(cc.x1, cc.y1, cc.rss, cc.rss); + + // Update renderer.squares each draw - our list of clickable coordinates. + + renderer.squares.push({x1: cc.x1, y1: cc.y1, x2: cc.x2, y2: cc.y2, point: Point(x, y)}); + } + } + }; + + renderer.draw_piece = (o) => { + let cc = renderer.canvas_coords(o.x, o.y); + context.drawImage(images[o.piece], cc.x1, cc.y1, cc.rss, cc.rss); + }; + + renderer.draw_arrow_line = (o) => { // Doesn't draw the arrowhead + let cc1 = renderer.canvas_coords(o.x1, o.y1); + let cc2 = renderer.canvas_coords(o.x2, o.y2); + context.strokeStyle = o.colour; + context.fillStyle = o.colour; + context.beginPath(); + context.moveTo(cc1.cx, cc1.cy); + context.lineTo(cc2.cx, cc2.cy); + context.stroke(); + }; + + renderer.draw_ranking = (o) => { // Does draw the arrowhead + let cc = renderer.canvas_coords(o.x, o.y); + context.fillStyle = o.colour; + context.beginPath(); + context.arc(cc.cx, cc.cy, 12, 0, 2 * Math.PI); + context.fill(); + context.fillStyle = "black"; + context.fillText(`${o.rank}`, cc.cx, cc.cy + 1); + }; + + renderer.draw_normal = () => { + + context.lineWidth = 8; + context.textAlign = "center"; + context.textBaseline = "middle"; + context.font = config.rank_font; + + renderer.draw_board(config.light_square, config.dark_square); + + let pieces = []; + + for (let x = 0; x < 8; x++) { + for (let y = 0; y < 8; y++) { + if (renderer.pos.state[x][y] === "") { + continue; + } + pieces.push({ + fn: renderer.draw_piece, + piece: renderer.pos.state[x][y], + colour: renderer.pos.state[x][y].toUpperCase() === renderer.pos.state[x][y] ? "w" : "b", + x: x, + y: y + }); + } + } + + let info_list = renderer.info_table.sorted(); + + let arrows = []; + let rankings = Object.create(null); + + if (info_list.length > 0) { + + let best_nodes = info_list[0].n; + + for (let i = 0; i < info_list.length; i++) { + + let [x1, y1] = XY(info_list[i].move.slice(0, 2)); + let [x2, y2] = XY(info_list[i].move.slice(2, 4)); + + if (info_list[i].n >= best_nodes * config.node_display_threshold) { + + let loss = 0; + + if (typeof info_list[0].winrate === "number" && typeof info_list[i].winrate === "number") { + loss = info_list[0].winrate - info_list[i].winrate; + } + + let colour; + + if (i === 0) { + colour = config.best_colour; + } else if (loss > config.terrible_move_threshold) { + colour = config.terrible_colour; + } else if (loss > config.bad_move_threshold) { + colour = config.bad_colour; + } else { + colour = config.good_colour; + } + + arrows.push({ + fn: renderer.draw_arrow_line, + colour: colour, + x1: x1, + y1: y1, + x2: x2, + y2: y2 + }); + + // We only draw the best ranking for each particular target square... + + if (rankings[info_list[i].move.slice(2, 4)] === undefined) { + rankings[info_list[i].move.slice(2, 4)] = { + fn: renderer.draw_ranking, + colour: colour, + rank: i + 1, + x: x2, + y: y2 + }; + } + } + } + } + + // It looks best if the longest arrows are drawn underneath. Manhattan distance is good enough. + + arrows.sort((a, b) => { + if (Math.abs(a.x2 - a.x1) + Math.abs(a.y2 - a.y1) < Math.abs(b.x2 - b.x1) + Math.abs(b.y2 - b.y1)) { + return 1; + } + if (Math.abs(a.x2 - a.x1) + Math.abs(a.y2 - a.y1) > Math.abs(b.x2 - b.x1) + Math.abs(b.y2 - b.y1)) { + return -1; + } + return 0; + }); + + let drawables = []; + + for (let o of pieces) { + if (o.colour !== renderer.pos.active) { + drawables.push(o); + } + } + + drawables = drawables.concat(arrows); + + for (let o of pieces) { + if (o.colour === renderer.pos.active) { + drawables.push(o); + } + } + + drawables = drawables.concat(Object.values(rankings)); + + for (let o of drawables) { + o.fn(o); + } + }; + + renderer.draw = () => { + renderer.draw_infobox(); + renderer.draw_normal(); + }; + + renderer.draw_loop = () => { + renderer.draw(); + setTimeout(renderer.draw_loop, 500); + }; + + renderer.load_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); + return renderer; +} + +// ------------------------------------------------------------------------------------------------ + +let renderer = make_renderer(); + +if (config && config.warn_filename) { + renderer.err_receive(`Nibbler says: You should rename config.json.example to config.json`); + renderer.err_receive(""); +} + +ipcRenderer.on("call", (event, msg) => { + if (typeof msg === "string") { + renderer[msg](); + } else if (typeof msg === "object" && msg.fn && msg.args) { + renderer[msg.fn](...msg.args); + } else { + console.log("Bad call, msg was..."); + console.log(msg); + } +}); + +ipcRenderer.on("toggle", (event, cfgvar) => { + config[cfgvar] = !config[cfgvar]; + renderer.draw(); +}); + +ipcRenderer.on("set", (event, msg) => { + config[msg.key] = msg.value; + renderer.draw(); +}); + +canvas.addEventListener("mousedown", (event) => { + renderer.canvas_click(event); +}); + +// Setup return key on FEN box... +fenbox.onkeydown = (event) => { + console.log(event); + if (event.key === "Enter") { + renderer.load_fen(fenbox.value); + } +}; + +function draw_after_images_load() { + if (loads === 12) { + renderer.draw_loop(); + } else { + setTimeout(draw_after_images_load, 25); + } +} + +draw_after_images_load(); diff --git a/engine_controller.js b/engine_controller.js new file mode 100644 index 00000000..7d9bf53a --- /dev/null +++ b/engine_controller.js @@ -0,0 +1,60 @@ +"use strict"; + +function NewEngineController(path) { + + // Full of closures. + + let controller = Object.create(null); + + let info_table = NewInfoTable(); + let failed = false; + let running = false; + let pos = null; + let readyok_required = 0; + let output_queue = []; + + let exe = child_process.spawn(config.path); + exe.on("error", (err) => { + failed = true; + }); + + let scanner = readline.createInterface({ + input: exe.stdout, + output: undefined, + terminal: false + }); + + scanner.on("line", (line) => { + if (line.includes("readyok") && readyok_required > 0) { + readyok_required--; + } + if (readyok_required > 0) { + Log("(ignored) < " + line); + return; + } + Log("< " + line); + receive(line); + }); + + let send = (msg) => { + try { + msg = msg.trim(); + exe.stdin.write(msg); + exe.stdin.write("\n"); + Log("--> " + msg); + if (msg === "isready") { + readyok_required++; + } + } catch (err) { + // pass + } + }; + + let controller.set_pos = (p) => { + + pos = p; + + output_queue.push("stop"); + output_queue.push("isready"); + + From 16cb6d7a5b1aa51c710d4545a89247bc957239ba Mon Sep 17 00:00:00 2001 From: fohristiwhirl Date: Fri, 7 Jun 2019 11:41:43 +0100 Subject: [PATCH 02/29] renderer.getboard, renderer.move --- 2_utils.js | 4 ++ 8_renderer.js | 130 ++++++++++++++++++++++---------------------------- 2 files changed, 60 insertions(+), 74 deletions(-) diff --git a/2_utils.js b/2_utils.js index 946a1162..e8fe4d4f 100644 --- a/2_utils.js +++ b/2_utils.js @@ -59,6 +59,10 @@ function InfoPV(s) { function CompareArrays(a, b) { + if (!a || !b) { // i.e. undefined or null + return false; + } + if (a.length !== b.length) { return false; } diff --git a/8_renderer.js b/8_renderer.js index cd0c9d5e..e7d9339c 100644 --- a/8_renderer.js +++ b/8_renderer.js @@ -175,24 +175,60 @@ function make_renderer() { renderer.start_pos = LoadFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); renderer.user_line = []; // Entire history of the user variation, as a list of moves. - renderer.pos = renderer.start_pos // Currently shown position. + renderer.moves = []; // History of the currently shown position. + + renderer.board_cache = null; // -------------------------------------------------------------------------------------------- + renderer.getboard = () => { + + if (renderer.board_cache && CompareArrays(renderer.board_cache.moves, renderer.moves)) { + return renderer.board_cache.board; + } + + let board = renderer.start_pos; + for (let move of renderer.moves) { + console.log(move); + board = board.move(move); + } + + renderer.board_cache = { + moves: Array.from(renderer.moves), // Copy, not reference! + board: board + }; + + return renderer.board_cache.board; + } + renderer.move = (s) => { - let illegal_reason = renderer.pos.illegal(s) + let board = renderer.getboard(); + + // Add promotion if needed and not present... + + if (s.length === 4) { + let source = Point(s.slice(0, 2)); + if (board.piece(source) === "P" && source.y === 1) { + console.log(`Move ${s} was promotion but had no promotion piece set; adjusting to ${s + "q"}`); + s += "q"; + } + if (board.piece(source) === "p" && source.y === 6) { + console.log(`Move ${s} was promotion but had no promotion piece set; adjusting to ${s + "q"}`); + s += "q"; + } + } + + let illegal_reason = board.illegal(s) if (illegal_reason !== "") { alert(`Illegal move requested (${s}, ${illegal_reason}). This should be impossible, please tell the author how you managed it.`); return; } - renderer.pos = renderer.pos.move(s); + renderer.moves.push(s); renderer.draw(); }; - - // -------------------------------------------------------------------------------------------- // Things below this point are not related to the difficult task of keeping track of positions. @@ -200,7 +236,7 @@ function make_renderer() { if (s.startsWith("info")) { renderer.ever_received_info = true; - renderer.info_table.receive(s, renderer.pos); + renderer.info_table.receive(s, renderer.getboard()); } if (s.startsWith("error")) { @@ -236,14 +272,15 @@ function make_renderer() { return; } + let board = renderer.getboard(); + if (renderer.active_square) { let move_string = renderer.active_square.s + point.s; // e.g. "e2e4" - - let illegal_reason = renderer.pos.illegal(move_string); - renderer.active_square = null; + let illegal_reason = board.illegal(move_string); + if (illegal_reason === "") { renderer.move(move_string); return; // Skip the draw, below, since move() will do that. @@ -253,10 +290,10 @@ function make_renderer() { } else { - if (renderer.pos.active === "w" && renderer.pos.is_white(point)) { + if (board.active === "w" && board.is_white(point)) { renderer.active_square = point; } - if (renderer.pos.active === "b" && renderer.pos.is_black(point)) { + if (board.active === "b" && board.is_black(point)) { renderer.active_square = point; } } @@ -269,67 +306,11 @@ function make_renderer() { }; renderer.pv_click = (i, n) => { - - if (i < 0 || i >= renderer.clickable_pv_lines.length) { - return; - } - - let o = renderer.clickable_pv_lines[i]; - - if (o.board !== renderer.pos) { - return; - } - - let moves = o.pv.slice(0, n + 1); - - // It's best that pos_changed() not be called until we've finished moving. - // That way, all analysis coming from Lc0 will still refer to the original - // position until we send a single message with the new position, and clear - // our info_table a single time. - - for (let move of moves) { - renderer.move(move, true); - } - - renderer.pos_changed(); + // TODO }; renderer.draw_infobox = () => { - - renderer.clickable_pv_lines = []; - - if (!renderer.ever_received_info) { - if (infobox.innerHTML !== renderer.stderr_log) { // Only update when needed, so user can select and copy. - infobox.innerHTML = renderer.stderr_log; - } - return; - } - - let info_list = renderer.info_table.sorted(); - - let s = ""; - - if (!renderer.running) { - s += "

<halted>

"; - } - - for (let i = 0; i < info_list.length && i < config.max_info_lines; i++) { - - s += `

${info_list[i].nice_pv_string(renderer.pos, config, i)}

`; - - renderer.clickable_pv_lines.push({ - board: renderer.pos, - pv: info_list[i].pv - }) - } - - // Only update when needed, so user can select and copy. A direct comparison - // of s with innerHTML seems to fail (something must get changed). - - if (renderer.infobox_string !== s) { - renderer.infobox_string = s; - infobox.innerHTML = s; - } + // TODO }; renderer.canvas_coords = (x, y) => { @@ -424,16 +405,17 @@ function make_renderer() { renderer.draw_board(config.light_square, config.dark_square); let pieces = []; + let board = renderer.getboard(); for (let x = 0; x < 8; x++) { for (let y = 0; y < 8; y++) { - if (renderer.pos.state[x][y] === "") { + if (board.state[x][y] === "") { continue; } pieces.push({ fn: renderer.draw_piece, - piece: renderer.pos.state[x][y], - colour: renderer.pos.state[x][y].toUpperCase() === renderer.pos.state[x][y] ? "w" : "b", + piece: board.state[x][y], + colour: board.state[x][y].toUpperCase() === board.state[x][y] ? "w" : "b", x: x, y: y }); @@ -513,7 +495,7 @@ function make_renderer() { let drawables = []; for (let o of pieces) { - if (o.colour !== renderer.pos.active) { + if (o.colour !== board.active) { drawables.push(o); } } @@ -521,7 +503,7 @@ function make_renderer() { drawables = drawables.concat(arrows); for (let o of pieces) { - if (o.colour === renderer.pos.active) { + if (o.colour === board.active) { drawables.push(o); } } From dd1287d54e5f0705d014980c4f1e5e7ea31432ce Mon Sep 17 00:00:00 2001 From: fohristiwhirl Date: Fri, 7 Jun 2019 11:56:30 +0100 Subject: [PATCH 03/29] draw_main_line, prev, position_changed --- 2_utils.js | 17 ++++++++++++++--- 8_renderer.js | 40 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/2_utils.js b/2_utils.js index e8fe4d4f..66317edd 100644 --- a/2_utils.js +++ b/2_utils.js @@ -59,15 +59,26 @@ function InfoPV(s) { function CompareArrays(a, b) { - if (!a || !b) { // i.e. undefined or null + if (a.length !== b.length) { return false; } - if (a.length !== b.length) { + for (let n = 0; n < a.length; n++) { + if (a[n] !== b[n]) { + return false; + } + } + + return true; +} + +function ArrayStartsWith(a, b) { + + if (b.length > a.length) { return false; } - for (let n = 0; n < a.length; n++) { + for (let n = 0; n < b.length; n++) { if (a[n] !== b[n]) { return false; } diff --git a/8_renderer.js b/8_renderer.js index e7d9339c..cd3e3e38 100644 --- a/8_renderer.js +++ b/8_renderer.js @@ -189,12 +189,11 @@ function make_renderer() { let board = renderer.start_pos; for (let move of renderer.moves) { - console.log(move); board = board.move(move); } renderer.board_cache = { - moves: Array.from(renderer.moves), // Copy, not reference! + moves: Array.from(renderer.moves), // Copy, not reference! board: board }; @@ -226,7 +225,20 @@ function make_renderer() { } renderer.moves.push(s); + renderer.position_changed(); + }; + + renderer.position_changed = () => { + renderer.user_line = Array.from(renderer.moves); // FIXME renderer.draw(); + renderer.draw_main_line(); + }; + + renderer.prev = () => { + if (renderer.moves.length > 0) { + renderer.moves = renderer.moves.slice(0, renderer.moves.length - 1); + renderer.position_changed(); + } }; // -------------------------------------------------------------------------------------------- @@ -302,7 +314,29 @@ function make_renderer() { }; renderer.draw_main_line = () => { - // TODO + let elements1 = []; + let elements2 = []; + + // First, have the moves actually made on the visible board. + + for (let m of renderer.moves) { + elements1.push(m); + } + + // Next, have the moves to the end of the user line. + + for (let m of renderer.user_line.slice(renderer.moves.length)) { + elements2.push(m); + } + + let s1 = elements1.join(" "); // Possibly empty string + let s2 = elements2.join(" "); // Possibly empty string + + if (s2.length > 0) { + s2 = `` + s2 + ""; + } + + mainline.innerHTML = [s1, s2].filter(s => s !== "").join(" "); }; renderer.pv_click = (i, n) => { From d07fc7b9e2a346e5c68833d22786e387fc17a255 Mon Sep 17 00:00:00 2001 From: fohristiwhirl Date: Fri, 7 Jun 2019 12:32:24 +0100 Subject: [PATCH 04/29] next, load_fen, new_game, load_pgn_object --- 4_position.js | 4 ++- 8_renderer.js | 79 +++++++++++++++++++++++++++++++++++++++++++++++++-- main.js | 2 +- 3 files changed, 80 insertions(+), 5 deletions(-) diff --git a/4_position.js b/4_position.js index 8806cda7..cd386306 100644 --- a/4_position.js +++ b/4_position.js @@ -714,7 +714,7 @@ const position_prototype = { return "??"; } - if (this.nice_lastmove_cache === undefined) { + if (!this.nice_lastmove_cache) { this.nice_lastmove_cache = this.parent.nice_string(this.lastmove); } @@ -896,6 +896,7 @@ const position_prototype = { }, history: function() { + // Note, if this ever returns a cached list, it should return Array.from(cache) instead. let list = []; let node = this; while (node.parent) { // no parent implies no lastmove @@ -907,6 +908,7 @@ const position_prototype = { }, position_list: function() { + // Note, if this ever returns a cached list, it should return Array.from(cache) instead. let list = []; let node = this; while (node) { diff --git a/8_renderer.js b/8_renderer.js index cd3e3e38..4859d255 100644 --- a/8_renderer.js +++ b/8_renderer.js @@ -80,7 +80,7 @@ assign_without_overwrite(config, { }); infobox.style.height = config.board_size.toString() + "px"; -mainline.style.height = config.mainline_height.toString() + "px"; // Is there a way to avoid needing this, to get the scroll bar? +mainline.style.height = config.mainline_height.toString() + "px"; // Is there a way to avoid needing this, to get the scroll bar? canvas.width = config.board_size; canvas.height = config.board_size; @@ -92,7 +92,7 @@ Log(""); if (config.path) { exe = child_process.spawn(config.path); exe.on("error", (err) => { - alert("Couldn't spawn process - check the path in the config file"); // Note that this alert will come some time in the future, not instantly. + alert("Couldn't spawn process - check the path in the config file"); // Note that this alert will come some time in the future, not instantly. }); scanner = readline.createInterface({ @@ -179,6 +179,8 @@ function make_renderer() { renderer.board_cache = null; + fenbox.value = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; + // -------------------------------------------------------------------------------------------- renderer.getboard = () => { @@ -228,10 +230,60 @@ function make_renderer() { renderer.position_changed(); }; + // There are 3 ways the position can change... + // + // Moving inside a game. + // New game. + // Loaded game. + renderer.position_changed = () => { - renderer.user_line = Array.from(renderer.moves); // FIXME + + if (ArrayStartsWith(renderer.user_line, renderer.moves) === false) { + // The new position (from moves) is not inside the current user_line + renderer.user_line = Array.from(renderer.moves); + } + + renderer.draw(); + renderer.draw_main_line(); + fenbox.value = renderer.getboard().fen(); + }; + + renderer.new_game = (start_pos) => { + + if (!start_pos) { + start_pos = LoadFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); + } + + renderer.start_pos = start_pos; + renderer.user_line = []; + renderer.moves = []; + + renderer.draw(); + renderer.draw_main_line(); + fenbox.value = renderer.start_pos.fen(); + }; + + renderer.load_pgn_object = (o) => { // Returns true or false - whether this actually succeeded. + + let final_pos; + + try { + final_pos = LoadPGN(o.movetext); + } catch (err) { + alert(err); + return false; + } + + // FIXME: I think a PGN can actually specify a different starting position? + renderer.start_pos = LoadFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); + renderer.user_line = Array.from(final_pos.history()); + renderer.moves = []; + renderer.draw(); renderer.draw_main_line(); + fenbox.value = renderer.start_pos.fen(); + + return true; }; renderer.prev = () => { @@ -241,6 +293,27 @@ function make_renderer() { } }; + renderer.next = () => { + if (renderer.user_line.length > renderer.moves.length) { + renderer.moves = renderer.user_line.slice(0, renderer.moves.length + 1); + renderer.position_changed(); + } + }; + + renderer.load_fen = (s) => { + + let newpos; + + try { + newpos = LoadFEN(s); + } catch (err) { + alert(err); + return; + } + + renderer.new_game(newpos); + }; + // -------------------------------------------------------------------------------------------- // Things below this point are not related to the difficult task of keeping track of positions. diff --git a/main.js b/main.js index 4f73b3b5..0823aeea 100644 --- a/main.js +++ b/main.js @@ -57,7 +57,7 @@ function menu_build() { label: "New Game", accelerator: "CommandOrControl+N", click: () => { - windows.send("main-window", "call", "new"); + windows.send("main-window", "call", "new_game"); } }, { From d53e5693683d3e1b58dd8868e0fcbc90816c2796 Mon Sep 17 00:00:00 2001 From: fohristiwhirl Date: Fri, 7 Jun 2019 12:42:00 +0100 Subject: [PATCH 05/29] Update 8_renderer.js --- 8_renderer.js | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/8_renderer.js b/8_renderer.js index 4859d255..2f58892c 100644 --- a/8_renderer.js +++ b/8_renderer.js @@ -174,11 +174,12 @@ function make_renderer() { renderer.clickable_pv_lines = []; // List of PV objects we use to tell what the user clicked on. renderer.start_pos = LoadFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); + renderer.board_cache = null; + + // IMPORTANT! These next two arrays must NEVER be the same object. Use Array.from() a lot to avoid this... renderer.user_line = []; // Entire history of the user variation, as a list of moves. renderer.moves = []; // History of the currently shown position. - renderer.board_cache = null; - fenbox.value = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; // -------------------------------------------------------------------------------------------- @@ -300,6 +301,20 @@ function make_renderer() { } }; + renderer.goto_root = () => { + if (renderer.moves.length > 0) { + renderer.moves = []; + renderer.position_changed(); + } + }; + + renderer.goto_end = () => { + if (renderer.moves.length !== renderer.user_line.length) { + renderer.moves = Array.from(renderer.user_line); + renderer.position_changed(); + } + }; + renderer.load_fen = (s) => { let newpos; @@ -314,6 +329,26 @@ function make_renderer() { renderer.new_game(newpos); }; + renderer.open = (filename) => { + + let buf = fs.readFileSync(filename); // i.e. binary buffer object + let new_pgn_choices = pre_parse_pgn(buf); + + if (new_pgn_choices.length === 1) { + let success = renderer.load_pgn_object(new_pgn_choices[0]); + if (success) { + renderer.pgn_choices = new_pgn_choices; // We only want to set this to a 1 value array if it actually worked. + } + } else { + renderer.pgn_choices = new_pgn_choices; // Setting it to a multi-value array is "always" OK. + renderer.show_pgn_chooser(); // Now we need to have the user choose a game. + } + }; + + renderer.show_pgn_chooser = () => { + // TODO + }; + // -------------------------------------------------------------------------------------------- // Things below this point are not related to the difficult task of keeping track of positions. From 420afedcf85fa3e1ac8aaa6c228592f324112cab Mon Sep 17 00:00:00 2001 From: fohristiwhirl Date: Fri, 7 Jun 2019 12:55:49 +0100 Subject: [PATCH 06/29] Update 8_renderer.js --- 8_renderer.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/8_renderer.js b/8_renderer.js index 2f58892c..6d0036eb 100644 --- a/8_renderer.js +++ b/8_renderer.js @@ -176,7 +176,10 @@ function make_renderer() { renderer.start_pos = LoadFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); renderer.board_cache = null; - // IMPORTANT! These next two arrays must NEVER be the same object. Use Array.from() a lot to avoid this... + // IMPORTANT! These next two arrays must NEVER be the same object. Use Array.from() a lot to avoid this. + // Note also that user_line is always supposed to contain moves. While in some ways it would be simpler + // to simply store an index of where we are in the user_line, this way has some advantages too... + renderer.user_line = []; // Entire history of the user variation, as a list of moves. renderer.moves = []; // History of the currently shown position. @@ -662,7 +665,17 @@ function make_renderer() { renderer.draw_normal(); }; + renderer.programmer_mistake_check = () => { + if (renderer.moves === renderer.user_line) { + alert("renderer.moves is the same object as renderer.user_line"); + } + if (ArrayStartsWith(renderer.user_line, renderer.moves) === false) { + alert("renderer.user_line does not start with renderer.moves"); + } + }; + renderer.draw_loop = () => { + renderer.programmer_mistake_check(); // Regularly check that we haven't violated some assumptions... renderer.draw(); setTimeout(renderer.draw_loop, 500); }; From 4effb258af8e99d789b27982f5be9d73e012e95b Mon Sep 17 00:00:00 2001 From: fohristiwhirl Date: Fri, 7 Jun 2019 13:10:33 +0100 Subject: [PATCH 07/29] Update 4_position.js --- 4_position.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/4_position.js b/4_position.js index cd386306..f3b7b754 100644 --- a/4_position.js +++ b/4_position.js @@ -40,7 +40,7 @@ const position_prototype = { let ret = this.copy(); ret.parent = this; - let promotion = s.length > 4 ? s[4] : "q"; + let promotion_char = s.length > 4 ? s[4].toLowerCase() : "q"; let white_flag = this.is_white(Point(x1, y1)); let pawn_flag = "Pp".includes(ret.state[x1][y1]); @@ -143,12 +143,12 @@ const position_prototype = { let promotion_flag; if (y2 === 0 && pawn_flag) { - ret.state[x2][y2] = promotion.toUpperCase(); + ret.state[x2][y2] = promotion_char.toUpperCase(); promotion_flag = true; } if (y2 === 7 && pawn_flag) { - ret.state[x2][y2] = promotion.toLowerCase(); + ret.state[x2][y2] = promotion_char; // Always lowercase. promotion_flag = true; } @@ -158,7 +158,7 @@ const position_prototype = { ret.lastmove = s; if (ret.lastmove.length === 4 && promotion_flag) { - ret.lastmove += promotion.toLowerCase(); + ret.lastmove += promotion_char; } return ret; From 91afde80cd16456928afc747840c3c40826daea5 Mon Sep 17 00:00:00 2001 From: fohristiwhirl Date: Fri, 7 Jun 2019 13:10:35 +0100 Subject: [PATCH 08/29] Update 8_renderer.js --- 8_renderer.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/8_renderer.js b/8_renderer.js index 6d0036eb..13d0e848 100644 --- a/8_renderer.js +++ b/8_renderer.js @@ -187,6 +187,15 @@ function make_renderer() { // -------------------------------------------------------------------------------------------- + renderer.programmer_mistake_check = () => { + if (renderer.moves === renderer.user_line) { + alert("renderer.moves is the same object as renderer.user_line"); + } + if (ArrayStartsWith(renderer.user_line, renderer.moves) === false) { + alert("renderer.user_line does not start with renderer.moves"); + } + }; + renderer.getboard = () => { if (renderer.board_cache && CompareArrays(renderer.board_cache.moves, renderer.moves)) { @@ -665,15 +674,6 @@ function make_renderer() { renderer.draw_normal(); }; - renderer.programmer_mistake_check = () => { - if (renderer.moves === renderer.user_line) { - alert("renderer.moves is the same object as renderer.user_line"); - } - if (ArrayStartsWith(renderer.user_line, renderer.moves) === false) { - alert("renderer.user_line does not start with renderer.moves"); - } - }; - renderer.draw_loop = () => { renderer.programmer_mistake_check(); // Regularly check that we haven't violated some assumptions... renderer.draw(); From 87897c942d1d6be856bf76e3aafaf441e6d57aee Mon Sep 17 00:00:00 2001 From: fohristiwhirl Date: Fri, 7 Jun 2019 13:20:10 +0100 Subject: [PATCH 09/29] show_pgn_chooser etc --- 6_pgn.js | 2 +- 8_renderer.js | 74 +++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 70 insertions(+), 6 deletions(-) diff --git a/6_pgn.js b/6_pgn.js index 83eeed5d..41bbf8f6 100644 --- a/6_pgn.js +++ b/6_pgn.js @@ -115,7 +115,7 @@ function new_byte_pusher() { }; } -function pre_parse_pgn(buf) { +function PreParsePGN(buf) { // Returns an array of the pgn_record objects, of at least length 1. diff --git a/8_renderer.js b/8_renderer.js index 13d0e848..90e1dea6 100644 --- a/8_renderer.js +++ b/8_renderer.js @@ -342,9 +342,8 @@ function make_renderer() { }; renderer.open = (filename) => { - let buf = fs.readFileSync(filename); // i.e. binary buffer object - let new_pgn_choices = pre_parse_pgn(buf); + let new_pgn_choices = PreParsePGN(buf); if (new_pgn_choices.length === 1) { let success = renderer.load_pgn_object(new_pgn_choices[0]); @@ -357,12 +356,15 @@ function make_renderer() { } }; - renderer.show_pgn_chooser = () => { - // TODO + renderer.choose_pgn = (n) => { + renderer.hide_pgn_chooser(); + if (renderer.pgn_choices && n >= 0 && n < renderer.pgn_choices.length) { + renderer.load_pgn_object(renderer.pgn_choices[n]); + } }; // -------------------------------------------------------------------------------------------- - // Things below this point are not related to the difficult task of keeping track of positions. + // Engine stuff... renderer.receive = (s) => { @@ -384,6 +386,68 @@ function make_renderer() { } }; + renderer.halt = () => { + // TODO + }; + + // -------------------------------------------------------------------------------------------- + // Visual stuff... + + renderer.escape = () => { // Set things into a clean state. + renderer.hide_pgn_chooser(); + renderer.active_square = null; + renderer.draw(); + }; + + renderer.show_pgn_chooser = () => { + + if (!renderer.pgn_choices) { + alert("No PGN loaded"); + return; + } + + renderer.halt(); // It's lame to run the GPU when we're clearly switching games. + + let lines = []; + + lines.push(" "); + + let max_ordinal_length = renderer.pgn_choices.length.toString().length; + let padding = ""; + for (let n = 0; n < max_ordinal_length - 1; n++) { + padding += " "; + } + + for (let n = 0; n < renderer.pgn_choices.length; n++) { + + if (n === 9 || n === 99 || n === 999 || n === 9999 || n === 99999 || n === 999999) { + padding = padding.slice(0, padding.length - 6); + } + + let p = renderer.pgn_choices[n]; + + let s; + + if (p.tags.Result === "1-0") { + s = `${padding}${n + 1}. ${p.tags.White} - ${p.tags.Black}`; + } else if (p.tags.Result === "0-1") { + s = `${padding}${n + 1}. ${p.tags.White} - ${p.tags.Black}`; + } else { + s = `${padding}${n + 1}. ${p.tags.White} - ${p.tags.Black}`; + } + lines.push(`  ${s}`); + } + + lines.push(" "); + + pgnchooser.innerHTML = lines.join("
"); + pgnchooser.style.display = "block"; + }; + + renderer.hide_pgn_chooser = () => { + pgnchooser.style.display = "none"; + }; + renderer.square_size = () => { return config.board_size / 8; }; From b17d40fc86df3105a991813228ed9e1906b80c45 Mon Sep 17 00:00:00 2001 From: fohristiwhirl Date: Fri, 7 Jun 2019 13:38:23 +0100 Subject: [PATCH 10/29] info table stuff --- 7_infotable.js | 41 +++++++++++++++++++++-------------------- 8_renderer.js | 9 +++++++-- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/7_infotable.js b/7_infotable.js index d8b411b0..31e878c0 100644 --- a/7_infotable.js +++ b/7_infotable.js @@ -1,10 +1,11 @@ "use strict"; -function new_info() { +function new_info(board, move) { return { + board: board, cp: -999999, - move: "??", + move: move, multipv: 999, n: 0, // The draw logic will only ever draw things with non-negative n, so make this 0 p: "?", @@ -13,10 +14,9 @@ function new_info() { nice_pv_string_cache: null, winrate: null, - nice_pv: function(board) { + nice_pv: function() { - // Given the board for which this info is valid, generate a list of - // human readable moves. Since there's no real guarantee that our + // Human readable moves. Since there's no real guarantee that our // moves list is legal, we legality check them. We at least know // the initial move is legal, since it's checked on receipt. @@ -24,25 +24,27 @@ function new_info() { return this.nice_pv_cache; } + let tmp_board = this.board; + if (!this.pv || this.pv.length === 0) { - return [board.nice_string(this.move)]; + return [tmp_board.nice_string(this.move)]; } let ret = []; for (let move of this.pv) { - if (board.illegal(move) !== "") { + if (tmp_board.illegal(move) !== "") { break; } - ret.push(board.nice_string(move)); - board = board.move(move); + ret.push(tmp_board.nice_string(move)); + tmp_board = tmp_board.move(move); } this.nice_pv_cache = ret; return this.nice_pv_cache; }, - nice_pv_string: function(board, options, i) { + nice_pv_string: function(options, i) { // The caller should ensure that i is unique for each move in the moves list, // then we can use i to ensure that each move has a unique way of calling @@ -52,7 +54,7 @@ function new_info() { return this.nice_pv_string_cache; } - let nice_pv_list = this.nice_pv(board); + let nice_pv_list = this.nice_pv(); let blobs = []; @@ -78,7 +80,7 @@ function new_info() { // ------------------------------------------------- - let colour = board.active; + let colour = this.board.active; let n = 0; for (let move of nice_pv_list) { @@ -115,21 +117,20 @@ function new_info() { }; } -function NewInfoTable() { // There's only ever going to be one of these made. +function NewInfoTable(board) { // There's only ever going to be one of these made I guess. return { - clears: 0, + board: board, table: Object.create(null), - clear: function() { + change: function(board) { + this.board = board; this.table = Object.create(null); - Log(`------------------------- info cleared (${++this.clears}) -------------------------`); }, - receive: function(s, board) { + receive: function(s) { - // The current board is sent just so we can check the move is valid. // Although the renderer tries to avoid sending invalid moves by // syncing with "isready" "readyok" an engine like Stockfish doesn't // behave properly, IMO. @@ -145,7 +146,7 @@ function NewInfoTable() { // There's only ever going to be one of these made. if (this.table[move]) { // We already have move info for this move. move_info = this.table[move]; } else { // We don't. - if (board.illegal(move) !== "") { + if (this.board.illegal(move) !== "") { Log(`... Nibbler: invalid move received!: ${move}`); return; } @@ -183,7 +184,7 @@ function NewInfoTable() { // There's only ever going to be one of these made. let move = InfoVal(s, "string"); - if (board.illegal(move) !== "") { + if (this.board.illegal(move) !== "") { Log(`... Nibbler: invalid move received!: ${move}`); return; } diff --git a/8_renderer.js b/8_renderer.js index 90e1dea6..d66f18ea 100644 --- a/8_renderer.js +++ b/8_renderer.js @@ -162,7 +162,6 @@ for (let c of Array.from("KkQqRrBbNnPp")) { function make_renderer() { let renderer = Object.create(null); - renderer.info_table = NewInfoTable(); renderer.squares = []; // Info about clickable squares. renderer.active_square = null; // Square clicked by user. @@ -174,6 +173,7 @@ function make_renderer() { renderer.clickable_pv_lines = []; // List of PV objects we use to tell what the user clicked on. renderer.start_pos = LoadFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); + renderer.info_table = NewInfoTable(renderer.start_pos); renderer.board_cache = null; // IMPORTANT! These next two arrays must NEVER be the same object. Use Array.from() a lot to avoid this. @@ -256,9 +256,12 @@ function make_renderer() { renderer.user_line = Array.from(renderer.moves); } + let board = renderer.getboard(); + renderer.info_table.change(board); + renderer.draw(); renderer.draw_main_line(); - fenbox.value = renderer.getboard().fen(); + fenbox.value = board.fen(); }; renderer.new_game = (start_pos) => { @@ -270,6 +273,7 @@ function make_renderer() { renderer.start_pos = start_pos; renderer.user_line = []; renderer.moves = []; + renderer.info_table.change(renderer.start_pos); renderer.draw(); renderer.draw_main_line(); @@ -291,6 +295,7 @@ function make_renderer() { renderer.start_pos = LoadFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); renderer.user_line = Array.from(final_pos.history()); renderer.moves = []; + renderer.info_table.change(renderer.start_pos); renderer.draw(); renderer.draw_main_line(); From 5accf3e36e8f0502cf020cc69f18273e9adf51c1 Mon Sep 17 00:00:00 2001 From: fohristiwhirl Date: Fri, 7 Jun 2019 13:51:56 +0100 Subject: [PATCH 11/29] go --- 8_renderer.js | 46 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/8_renderer.js b/8_renderer.js index d66f18ea..5c1a2b90 100644 --- a/8_renderer.js +++ b/8_renderer.js @@ -259,9 +259,13 @@ function make_renderer() { let board = renderer.getboard(); renderer.info_table.change(board); - renderer.draw(); + renderer.escape(); renderer.draw_main_line(); fenbox.value = board.fen(); + + if (renderer.running) { + renderer.go(); + } }; renderer.new_game = (start_pos) => { @@ -275,9 +279,13 @@ function make_renderer() { renderer.moves = []; renderer.info_table.change(renderer.start_pos); - renderer.draw(); + renderer.escape(); renderer.draw_main_line(); fenbox.value = renderer.start_pos.fen(); + + if (renderer.running) { + renderer.go(true); + } }; renderer.load_pgn_object = (o) => { // Returns true or false - whether this actually succeeded. @@ -297,10 +305,14 @@ function make_renderer() { renderer.moves = []; renderer.info_table.change(renderer.start_pos); - renderer.draw(); + renderer.escape(); renderer.draw_main_line(); fenbox.value = renderer.start_pos.fen(); + if (renderer.running) { + renderer.go(true); + } + return true; }; @@ -362,7 +374,6 @@ function make_renderer() { }; renderer.choose_pgn = (n) => { - renderer.hide_pgn_chooser(); if (renderer.pgn_choices && n >= 0 && n < renderer.pgn_choices.length) { renderer.load_pgn_object(renderer.pgn_choices[n]); } @@ -392,7 +403,32 @@ function make_renderer() { }; renderer.halt = () => { - // TODO + send("stop"); + renderer.running = false; + }; + + renderer.go = (new_game_flag) => { + + renderer.escape(); + renderer.running = true; + + let setup; + let start_fen = renderer.start_pos.fen(); + + if (start_fen !== "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") { + setup = `fen ${start_fen}`; + } else { + setup = "startpos"; + } + + send("stop"); + if (new_game_flag) { + send("ucinewgame"); + } + + send(`position ${setup} moves ${renderer.moves.join(" ")}`); + sync(); // See comment on how sync() works + send("go infinite"); }; // -------------------------------------------------------------------------------------------- From 422335beaf3fc189909424062b427adb27fa8305 Mon Sep 17 00:00:00 2001 From: fohristiwhirl Date: Fri, 7 Jun 2019 14:08:19 +0100 Subject: [PATCH 12/29] draw_infobox --- 7_infotable.js | 2 +- 8_renderer.js | 112 ++++++++++++++++++++++++++++++++++++------------- 2 files changed, 83 insertions(+), 31 deletions(-) diff --git a/7_infotable.js b/7_infotable.js index 31e878c0..93ecef76 100644 --- a/7_infotable.js +++ b/7_infotable.js @@ -150,7 +150,7 @@ function NewInfoTable(board) { // There's only ever going to be one of these m Log(`... Nibbler: invalid move received!: ${move}`); return; } - move_info = new_info(); + move_info = new_info(this.board, move); this.table[move] = move_info; } diff --git a/8_renderer.js b/8_renderer.js index 5c1a2b90..8c2cf530 100644 --- a/8_renderer.js +++ b/8_renderer.js @@ -139,6 +139,7 @@ for (let key of Object.keys(config.options)) { setoption("VerboseMoveStats", true); // Required for LogLiveStats to work. setoption("LogLiveStats", true); // "Secret" Lc0 command. setoption("MultiPV", 500); +send("ucinewgame"); // ------------------------------------------------------------------------------------------------ @@ -194,6 +195,9 @@ function make_renderer() { if (ArrayStartsWith(renderer.user_line, renderer.moves) === false) { alert("renderer.user_line does not start with renderer.moves"); } + if (renderer.info_table.board !== renderer.getboard()) { + alert("renderer.info_table.board !== renderer.getboard()"); + } }; renderer.getboard = () => { @@ -215,34 +219,6 @@ function make_renderer() { return renderer.board_cache.board; } - renderer.move = (s) => { - - let board = renderer.getboard(); - - // Add promotion if needed and not present... - - if (s.length === 4) { - let source = Point(s.slice(0, 2)); - if (board.piece(source) === "P" && source.y === 1) { - console.log(`Move ${s} was promotion but had no promotion piece set; adjusting to ${s + "q"}`); - s += "q"; - } - if (board.piece(source) === "p" && source.y === 6) { - console.log(`Move ${s} was promotion but had no promotion piece set; adjusting to ${s + "q"}`); - s += "q"; - } - } - - let illegal_reason = board.illegal(s) - if (illegal_reason !== "") { - alert(`Illegal move requested (${s}, ${illegal_reason}). This should be impossible, please tell the author how you managed it.`); - return; - } - - renderer.moves.push(s); - renderer.position_changed(); - }; - // There are 3 ways the position can change... // // Moving inside a game. @@ -316,6 +292,44 @@ function make_renderer() { return true; }; + renderer.move = (s) => { + + let board = renderer.getboard(); + + // Add promotion if needed and not present... + + if (s.length === 4) { + let source = Point(s.slice(0, 2)); + if (board.piece(source) === "P" && source.y === 1) { + console.log(`Move ${s} was promotion but had no promotion piece set; adjusting to ${s + "q"}`); + s += "q"; + } + if (board.piece(source) === "p" && source.y === 6) { + console.log(`Move ${s} was promotion but had no promotion piece set; adjusting to ${s + "q"}`); + s += "q"; + } + } + + let illegal_reason = board.illegal(s) + if (illegal_reason !== "") { + alert(`Illegal move requested (${s}, ${illegal_reason}). This should be impossible, please tell the author how you managed it.`); + return; + } + + renderer.moves.push(s); + renderer.position_changed(); + }; + + renderer.play_best = () => { + if (renderer.info_table.board !== renderer.getboard()) { + return; + } + let info_list = renderer.info_table.sorted(); + if (info_list.length > 0) { + renderer.move(info_list[0].move); + } + }; + renderer.prev = () => { if (renderer.moves.length > 0) { renderer.moves = renderer.moves.slice(0, renderer.moves.length - 1); @@ -565,11 +579,49 @@ function make_renderer() { }; renderer.pv_click = (i, n) => { - // TODO + alert([i, n]); }; renderer.draw_infobox = () => { - // TODO + + renderer.clickable_pv_lines = []; + + if (!renderer.ever_received_info) { + if (infobox.innerHTML !== renderer.stderr_log) { // Only update when needed, so user can select and copy. + infobox.innerHTML = renderer.stderr_log; + } + return; + } + + if (renderer.info_table.board !== renderer.getboard()) { + return; + } + + let info_list = renderer.info_table.sorted(); + + let s = ""; + + if (!renderer.running) { + s += "

<halted>

"; + } + + for (let i = 0; i < info_list.length && i < config.max_info_lines; i++) { + + s += `

${info_list[i].nice_pv_string(config, i)}

`; + + renderer.clickable_pv_lines.push({ + board: renderer.pos, + pv: info_list[i].pv + }) + } + + // Only update when needed, so user can select and copy. A direct comparison + // of s with innerHTML seems to fail (something must get changed). + + if (renderer.infobox_string !== s) { + renderer.infobox_string = s; + infobox.innerHTML = s; + } }; renderer.canvas_coords = (x, y) => { From 47a08d090249ccdc935f9ce6633704583ba88b53 Mon Sep 17 00:00:00 2001 From: fohristiwhirl Date: Fri, 7 Jun 2019 14:34:10 +0100 Subject: [PATCH 13/29] compare --- 4_position.js | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/4_position.js b/4_position.js index f3b7b754..de62d8e1 100644 --- a/4_position.js +++ b/4_position.js @@ -936,18 +936,22 @@ const position_prototype = { return false; }, - initial_fen: function() { - - // When sending the engine the position, the UCI specs involve sending the initial FEN - // and then a list of moves. This method finds the initial FEN. - - let node = this; - - while (node.parent) { - node = node.parent; + compare: function(other) { + if (this.active !== other.active) return false; + if (this.enpassant !== other.enpassant) return false; + if (this.castling !== other.castling) return false; + if (this.halfmove !== other.halfmove) return false; + if (this.fullmove !== other.fullmove) return false; + if (this.lastmove !== other.lastmove) return false; + for (let x = 0; x < 8; x++) { + for (let y = 0; y < 8; y++) { + if (this.state[x][y] !== other.state[x][y]) { + return false; + } + } } - return node.fen(); + return true; } }; From 5768d362a4955d7cc9369a2ab68eb25c53e388bd Mon Sep 17 00:00:00 2001 From: fohristiwhirl Date: Fri, 7 Jun 2019 14:49:11 +0100 Subject: [PATCH 14/29] revert having board in info table --- 7_infotable.js | 14 ++++++------- 8_renderer.js | 56 ++++++++++++++++++++++++++------------------------ 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/7_infotable.js b/7_infotable.js index 93ecef76..ff1a3c54 100644 --- a/7_infotable.js +++ b/7_infotable.js @@ -121,19 +121,17 @@ function NewInfoTable(board) { // There's only ever going to be one of these m return { - board: board, table: Object.create(null), - change: function(board) { - this.board = board; + clear: function() { this.table = Object.create(null); }, - receive: function(s) { + receive: function(s, board) { // Although the renderer tries to avoid sending invalid moves by // syncing with "isready" "readyok" an engine like Stockfish doesn't - // behave properly, IMO. + // behave properly, IMO. So we use the board to check legality. if (s.startsWith("info") && s.indexOf(" pv ") !== -1) { @@ -146,11 +144,11 @@ function NewInfoTable(board) { // There's only ever going to be one of these m if (this.table[move]) { // We already have move info for this move. move_info = this.table[move]; } else { // We don't. - if (this.board.illegal(move) !== "") { + if (board.illegal(move) !== "") { Log(`... Nibbler: invalid move received!: ${move}`); return; } - move_info = new_info(this.board, move); + move_info = new_info(board, move); this.table[move] = move_info; } @@ -184,7 +182,7 @@ function NewInfoTable(board) { // There's only ever going to be one of these m let move = InfoVal(s, "string"); - if (this.board.illegal(move) !== "") { + if (board.illegal(move) !== "") { Log(`... Nibbler: invalid move received!: ${move}`); return; } diff --git a/8_renderer.js b/8_renderer.js index 8c2cf530..9ab028e5 100644 --- a/8_renderer.js +++ b/8_renderer.js @@ -177,10 +177,11 @@ function make_renderer() { renderer.info_table = NewInfoTable(renderer.start_pos); renderer.board_cache = null; - // IMPORTANT! These next two arrays must NEVER be the same object. Use Array.from() a lot to avoid this. + // IMPORTANT! The following arrays must NEVER be the same object. Use Array.from() a lot to avoid this. // Note also that user_line is always supposed to contain moves. While in some ways it would be simpler // to simply store an index of where we are in the user_line, this way has some advantages too... + renderer.pgn_line = []; // The loaded PGN object, as a list of moves. renderer.user_line = []; // Entire history of the user variation, as a list of moves. renderer.moves = []; // History of the currently shown position. @@ -189,34 +190,29 @@ function make_renderer() { // -------------------------------------------------------------------------------------------- renderer.programmer_mistake_check = () => { + if (renderer.programmer_mistake_check.warned) { + return; + } if (renderer.moves === renderer.user_line) { + renderer.programmer_mistake_check.warned = true; alert("renderer.moves is the same object as renderer.user_line"); } if (ArrayStartsWith(renderer.user_line, renderer.moves) === false) { + renderer.programmer_mistake_check.warned = true; alert("renderer.user_line does not start with renderer.moves"); } - if (renderer.info_table.board !== renderer.getboard()) { - alert("renderer.info_table.board !== renderer.getboard()"); - } }; renderer.getboard = () => { - - if (renderer.board_cache && CompareArrays(renderer.board_cache.moves, renderer.moves)) { - return renderer.board_cache.board; + if (renderer.board_cache) { + return renderer.board_cache; } - let board = renderer.start_pos; for (let move of renderer.moves) { board = board.move(move); } - - renderer.board_cache = { - moves: Array.from(renderer.moves), // Copy, not reference! - board: board - }; - - return renderer.board_cache.board; + renderer.board_cache = board; + return renderer.board_cache; } // There are 3 ways the position can change... @@ -224,6 +220,9 @@ function make_renderer() { // Moving inside a game. // New game. // Loaded game. + // + // Although it seems like we do a lot of book-keeping, + // we only need to do it in these 3 functions... renderer.position_changed = () => { @@ -232,8 +231,8 @@ function make_renderer() { renderer.user_line = Array.from(renderer.moves); } - let board = renderer.getboard(); - renderer.info_table.change(board); + renderer.board_cache = null; + renderer.info_table.clear(); renderer.escape(); renderer.draw_main_line(); @@ -253,7 +252,9 @@ function make_renderer() { renderer.start_pos = start_pos; renderer.user_line = []; renderer.moves = []; - renderer.info_table.change(renderer.start_pos); + + renderer.board_cache = null; + renderer.info_table.clear(); renderer.escape(); renderer.draw_main_line(); @@ -279,7 +280,9 @@ function make_renderer() { renderer.start_pos = LoadFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); renderer.user_line = Array.from(final_pos.history()); renderer.moves = []; - renderer.info_table.change(renderer.start_pos); + + renderer.board_cache = null; + renderer.info_table.clear(); renderer.escape(); renderer.draw_main_line(); @@ -321,9 +324,6 @@ function make_renderer() { }; renderer.play_best = () => { - if (renderer.info_table.board !== renderer.getboard()) { - return; - } let info_list = renderer.info_table.sorted(); if (info_list.length > 0) { renderer.move(info_list[0].move); @@ -358,6 +358,13 @@ function make_renderer() { } }; + renderer.return_to_pgn = () => { + if (renderer.pgn_line.length === 0) { + alert("No PGN loaded."); + return; + } + }; + renderer.load_fen = (s) => { let newpos; @@ -593,10 +600,6 @@ function make_renderer() { return; } - if (renderer.info_table.board !== renderer.getboard()) { - return; - } - let info_list = renderer.info_table.sorted(); let s = ""; @@ -876,7 +879,6 @@ canvas.addEventListener("mousedown", (event) => { // Setup return key on FEN box... fenbox.onkeydown = (event) => { - console.log(event); if (event.key === "Enter") { renderer.load_fen(fenbox.value); } From e244028b0f1ccb15c053e63c57e0b1785bdf2b75 Mon Sep 17 00:00:00 2001 From: fohristiwhirl Date: Fri, 7 Jun 2019 14:50:03 +0100 Subject: [PATCH 15/29] rm arg --- 7_infotable.js | 2 +- 8_renderer.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/7_infotable.js b/7_infotable.js index ff1a3c54..1727ee3f 100644 --- a/7_infotable.js +++ b/7_infotable.js @@ -117,7 +117,7 @@ function new_info(board, move) { }; } -function NewInfoTable(board) { // There's only ever going to be one of these made I guess. +function NewInfoTable() { // There's only ever going to be one of these made I guess. return { diff --git a/8_renderer.js b/8_renderer.js index 9ab028e5..a024f97a 100644 --- a/8_renderer.js +++ b/8_renderer.js @@ -174,7 +174,7 @@ function make_renderer() { renderer.clickable_pv_lines = []; // List of PV objects we use to tell what the user clicked on. renderer.start_pos = LoadFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); - renderer.info_table = NewInfoTable(renderer.start_pos); + renderer.info_table = NewInfoTable(); renderer.board_cache = null; // IMPORTANT! The following arrays must NEVER be the same object. Use Array.from() a lot to avoid this. @@ -280,7 +280,7 @@ function make_renderer() { renderer.start_pos = LoadFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); renderer.user_line = Array.from(final_pos.history()); renderer.moves = []; - + renderer.board_cache = null; renderer.info_table.clear(); From f61850484d894d9392e20b6a1e135d37611b3ecd Mon Sep 17 00:00:00 2001 From: fohristiwhirl Date: Fri, 7 Jun 2019 14:57:31 +0100 Subject: [PATCH 16/29] Update 8_renderer.js --- 8_renderer.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/8_renderer.js b/8_renderer.js index a024f97a..d571b603 100644 --- a/8_renderer.js +++ b/8_renderer.js @@ -217,9 +217,9 @@ function make_renderer() { // There are 3 ways the position can change... // - // Moving inside a game. - // New game. - // Loaded game. + // Moving inside a game. + // New game. + // Loaded game. // // Although it seems like we do a lot of book-keeping, // we only need to do it in these 3 functions... @@ -227,7 +227,7 @@ function make_renderer() { renderer.position_changed = () => { if (ArrayStartsWith(renderer.user_line, renderer.moves) === false) { - // The new position (from moves) is not inside the current user_line + // The new position is not inside the current user_line renderer.user_line = Array.from(renderer.moves); } From d807e2c83be1783c43fcaaf0cbdc87633a42a3c9 Mon Sep 17 00:00:00 2001 From: fohristiwhirl Date: Fri, 7 Jun 2019 15:12:48 +0100 Subject: [PATCH 17/29] return_to_pgn --- 8_renderer.js | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/8_renderer.js b/8_renderer.js index d571b603..4ae991b9 100644 --- a/8_renderer.js +++ b/8_renderer.js @@ -222,7 +222,12 @@ function make_renderer() { // Loaded game. // // Although it seems like we do a lot of book-keeping, - // we only need to do it in these 3 functions... + // we only need to do it in these 3 functions. + // + // In general, changing position is as simple as setting + // renderer.moves and calling renderer.position_changed(). + // + // Thankfully position_changed() is the simplest function. renderer.position_changed = () => { @@ -236,7 +241,7 @@ function make_renderer() { renderer.escape(); renderer.draw_main_line(); - fenbox.value = board.fen(); + fenbox.value = renderer.getboard().fen(); // Must be after the cache is cleared! if (renderer.running) { renderer.go(); @@ -250,6 +255,7 @@ function make_renderer() { } renderer.start_pos = start_pos; + renderer.pgn_line = []; renderer.user_line = []; renderer.moves = []; @@ -278,6 +284,7 @@ function make_renderer() { // FIXME: I think a PGN can actually specify a different starting position? renderer.start_pos = LoadFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); + renderer.pgn_line = Array.from(final_pos.history()); renderer.user_line = Array.from(final_pos.history()); renderer.moves = []; @@ -359,10 +366,23 @@ function make_renderer() { }; renderer.return_to_pgn = () => { - if (renderer.pgn_line.length === 0) { + + if (!renderer.pgn_line || renderer.pgn_line.length === 0) { alert("No PGN loaded."); return; } + + let new_moves_list = []; + for (let i = 0; i < renderer.pgn_line.length; i++) { + if (renderer.pgn_line[i] !== renderer.moves[i]) { // renderer.moves[i] may be undefined, that's OK + break; + } + new_moves_list.push(renderer.pgn_line[i]); + } + + renderer.moves = new_moves_list; + renderer.user_line = Array.from(renderer.pgn_line); + renderer.position_changed(); }; renderer.load_fen = (s) => { From 97f31099b7e0e89856cfc11015c77adb61824823 Mon Sep 17 00:00:00 2001 From: fohristiwhirl Date: Fri, 7 Jun 2019 15:26:24 +0100 Subject: [PATCH 18/29] mostly fix draw_main_line --- 8_renderer.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/8_renderer.js b/8_renderer.js index 4ae991b9..d343479f 100644 --- a/8_renderer.js +++ b/8_renderer.js @@ -215,6 +215,7 @@ function make_renderer() { return renderer.board_cache; } + // -------------------------------------------------------------------------------------------- // There are 3 ways the position can change... // // Moving inside a game. @@ -302,6 +303,8 @@ function make_renderer() { return true; }; + // -------------------------------------------------------------------------------------------- + renderer.move = (s) => { let board = renderer.getboard(); @@ -583,16 +586,20 @@ function make_renderer() { let elements1 = []; let elements2 = []; + let board = renderer.start_pos; + // First, have the moves actually made on the visible board. for (let m of renderer.moves) { - elements1.push(m); + elements1.push(board.nice_string(m)); + board = board.move(m); } // Next, have the moves to the end of the user line. for (let m of renderer.user_line.slice(renderer.moves.length)) { - elements2.push(m); + elements2.push(board.nice_string(m)); + board = board.move(m); } let s1 = elements1.join(" "); // Possibly empty string From 5b106e2fe257006df868206e9b41d9d7474ee1a4 Mon Sep 17 00:00:00 2001 From: fohristiwhirl Date: Fri, 7 Jun 2019 15:28:48 +0100 Subject: [PATCH 19/29] move numbers --- 8_renderer.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/8_renderer.js b/8_renderer.js index d343479f..16e2ea10 100644 --- a/8_renderer.js +++ b/8_renderer.js @@ -591,6 +591,11 @@ function make_renderer() { // First, have the moves actually made on the visible board. for (let m of renderer.moves) { + + if (board.active === "w") { + elements1.push(`${board.fullmove}.`); + } + elements1.push(board.nice_string(m)); board = board.move(m); } @@ -598,6 +603,11 @@ function make_renderer() { // Next, have the moves to the end of the user line. for (let m of renderer.user_line.slice(renderer.moves.length)) { + + if (board.active === "w") { + elements2.push(`${board.fullmove}.`); + } + elements2.push(board.nice_string(m)); board = board.move(m); } From 3a842b0205a5263915c7d2c974d78a1c14aaeb25 Mon Sep 17 00:00:00 2001 From: fohristiwhirl Date: Fri, 7 Jun 2019 15:47:20 +0100 Subject: [PATCH 20/29] var rename --- 1_globals.js | 2 +- 6_pgn.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/1_globals.js b/1_globals.js index 8abd9046..caaab3cf 100644 --- a/1_globals.js +++ b/1_globals.js @@ -28,7 +28,7 @@ let scanner = null; let err_scanner = null; let readyok_required = 0; -let __decoder = new TextDecoder("utf8"); +let decoder = new TextDecoder("utf8"); let total_moves_made = 0; // For debugging / info let total_positions_made = 0; // For debugging / info diff --git a/6_pgn.js b/6_pgn.js index 41bbf8f6..86f86c60 100644 --- a/6_pgn.js +++ b/6_pgn.js @@ -110,7 +110,7 @@ function new_byte_pusher() { }, string: function() { - return __decoder.decode(this.bytes()); + return decoder.decode(this.bytes()); } }; } @@ -153,7 +153,7 @@ function PreParsePGN(buf) { // Parse the tag line... - let line = __decoder.decode(rawline).trim(); + let line = decoder.decode(rawline).trim(); if (line.endsWith("]")) { From e98e1c30470d3a106ffd8927bcb81bc9944a413e Mon Sep 17 00:00:00 2001 From: fohristiwhirl Date: Fri, 7 Jun 2019 15:48:02 +0100 Subject: [PATCH 21/29] pv_click --- 7_infotable.js | 41 +++++++++++++++-------------------------- 8_renderer.js | 26 ++++++++++++++++++++++---- 2 files changed, 37 insertions(+), 30 deletions(-) diff --git a/7_infotable.js b/7_infotable.js index 1727ee3f..0da9d6a5 100644 --- a/7_infotable.js +++ b/7_infotable.js @@ -11,7 +11,7 @@ function new_info(board, move) { p: "?", pv: [], nice_pv_cache: null, - nice_pv_string_cache: null, + // nice_pv_string_cache: null, // Can't have this because the pv_string changes as the sort order does. winrate: null, nice_pv: function() { @@ -48,11 +48,7 @@ function new_info(board, move) { // The caller should ensure that i is unique for each move in the moves list, // then we can use i to ensure that each move has a unique way of calling - // renderer.pv_click() - - if (this.nice_pv_string_cache) { - return this.nice_pv_string_cache; - } + // renderer.pv_click(). let nice_pv_list = this.nice_pv(); @@ -111,8 +107,7 @@ function new_info(board, move) { blobs.push(`(${tech_elements.join(" ")})`); } - this.nice_pv_string_cache = blobs.join(" "); - return this.nice_pv_string_cache; + return blobs.join(" "); } }; } @@ -141,9 +136,9 @@ function NewInfoTable() { // There's only ever going to be one of these made I let move = InfoVal(s, "pv"); let move_info; - if (this.table[move]) { // We already have move info for this move. + if (this.table[move]) { // We already have move info for this move. move_info = this.table[move]; - } else { // We don't. + } else { // We don't. if (board.illegal(move) !== "") { Log(`... Nibbler: invalid move received!: ${move}`); return; @@ -152,16 +147,14 @@ function NewInfoTable() { // There's only ever going to be one of these made I this.table[move] = move_info; } - move_info.move = move; - let tmp; - tmp = parseInt(InfoVal(s, "cp"), 10); // Score in centipawns + tmp = parseInt(InfoVal(s, "cp"), 10); // Score in centipawns if (Number.isNaN(tmp) === false) { move_info.cp = tmp; } - tmp = parseInt(InfoVal(s, "multipv"), 10); // Leela's ranking of the move, starting at 1 + tmp = parseInt(InfoVal(s, "multipv"), 10); // Leela's ranking of the move, starting at 1 if (Number.isNaN(tmp) === false) { move_info.multipv = tmp; } @@ -170,7 +163,6 @@ function NewInfoTable() { // There's only ever going to be one of these made I if (new_pv.length > 0) { if (CompareArrays(new_pv, move_info.pv) === false) { - move_info.nice_pv_string_cache = null; move_info.nice_pv_cache = null; move_info.pv = new_pv; } @@ -182,28 +174,25 @@ function NewInfoTable() { // There's only ever going to be one of these made I let move = InfoVal(s, "string"); - if (board.illegal(move) !== "") { - Log(`... Nibbler: invalid move received!: ${move}`); - return; - } - let move_info; - if (this.table[move]) { + if (this.table[move]) { // We already have move info for this move. move_info = this.table[move]; - } else { - move_info = new_info(); + } else { // We don't. + if (board.illegal(move) !== "") { + Log(`... Nibbler: invalid move received!: ${move}`); + return; + } + move_info = new_info(board, move); this.table[move] = move_info; } - move_info.move = move; - let tmp = parseInt(InfoVal(s, "N:"), 10); if (Number.isNaN(tmp) === false) { move_info.n = tmp; } - move_info.p = InfoVal(s, "(P:"); // Worse case here is just empty string, which is OK. + move_info.p = InfoVal(s, "(P:"); // Worst case here is just empty string, which is OK. tmp = InfoVal(s, "(Q:"); tmp = parseFloat(tmp); diff --git a/8_renderer.js b/8_renderer.js index 16e2ea10..e9f92d5f 100644 --- a/8_renderer.js +++ b/8_renderer.js @@ -607,7 +607,7 @@ function make_renderer() { if (board.active === "w") { elements2.push(`${board.fullmove}.`); } - + elements2.push(board.nice_string(m)); board = board.move(m); } @@ -623,7 +623,25 @@ function make_renderer() { }; renderer.pv_click = (i, n) => { - alert([i, n]); + + if (i < 0 || i >= renderer.clickable_pv_lines.length) { + return; + } + + let o = renderer.clickable_pv_lines[i]; + + if (o.board.compare(renderer.getboard()) === false) { + alert("pv_click() failed due to board mismatch. This should be impossible, please tell the author how you managed it."); + return; + } + + let moves = o.pv.slice(0, n + 1); + + for (let move of moves) { + renderer.moves.push(move); + } + + renderer.position_changed(); }; renderer.draw_infobox = () => { @@ -637,8 +655,8 @@ function make_renderer() { return; } + let board = renderer.getboard(); let info_list = renderer.info_table.sorted(); - let s = ""; if (!renderer.running) { @@ -650,7 +668,7 @@ function make_renderer() { s += `

${info_list[i].nice_pv_string(config, i)}

`; renderer.clickable_pv_lines.push({ - board: renderer.pos, + board: board, pv: info_list[i].pv }) } From 5eee139ab47656bfefa8952c71fc7f51032b656b Mon Sep 17 00:00:00 2001 From: fohristiwhirl Date: Fri, 7 Jun 2019 15:50:54 +0100 Subject: [PATCH 22/29] Update 8_renderer.js --- 8_renderer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/8_renderer.js b/8_renderer.js index e9f92d5f..77e390ce 100644 --- a/8_renderer.js +++ b/8_renderer.js @@ -195,11 +195,11 @@ function make_renderer() { } if (renderer.moves === renderer.user_line) { renderer.programmer_mistake_check.warned = true; - alert("renderer.moves is the same object as renderer.user_line"); + alert("renderer.moves is the same object as renderer.user_line. This should be impossible, please tell the author how you managed it."); } if (ArrayStartsWith(renderer.user_line, renderer.moves) === false) { renderer.programmer_mistake_check.warned = true; - alert("renderer.user_line does not start with renderer.moves"); + alert("renderer.user_line does not start with renderer.moves. This should be impossible, please tell the author how you managed it."); } }; From 9fe4d6d03803644bc824d2438edac63f08f984fc Mon Sep 17 00:00:00 2001 From: fohristiwhirl Date: Fri, 7 Jun 2019 15:52:27 +0100 Subject: [PATCH 23/29] validate_pgn --- 8_renderer.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/8_renderer.js b/8_renderer.js index 77e390ce..33dba6f1 100644 --- a/8_renderer.js +++ b/8_renderer.js @@ -423,6 +423,26 @@ function make_renderer() { } }; + renderer.validate_pgn = (filename) => { + let buf = fs.readFileSync(filename); // i.e. binary buffer object + let pgn_list = PreParsePGN(buf); + + for (let n = 0; n < pgn_list.length; n++) { + + let o = pgn_list[n]; + + try { + LoadPGN(o.movetext); + } catch (err) { + alert(`Game ${n + 1} - ${err.toString()}`); + return false; + } + } + + alert(`This file seems OK. ${pgn_list.length} games checked.`); + return true; + }; + // -------------------------------------------------------------------------------------------- // Engine stuff... From a580d58759596c0b53d2a55a81d17a9b7b4d5e80 Mon Sep 17 00:00:00 2001 From: fohristiwhirl Date: Fri, 7 Jun 2019 15:54:45 +0100 Subject: [PATCH 24/29] Update 8_renderer.js --- 8_renderer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/8_renderer.js b/8_renderer.js index 33dba6f1..97199d2f 100644 --- a/8_renderer.js +++ b/8_renderer.js @@ -439,7 +439,7 @@ function make_renderer() { } } - alert(`This file seems OK. ${pgn_list.length} games checked.`); + alert(`This file seems OK. ${pgn_list.length} ${pgn_list.length === 1 ? "game" : "games"} checked.`); return true; }; From 7aec2a4769602d7bb4fda23f9ab81e5c31dc1fa8 Mon Sep 17 00:00:00 2001 From: fohristiwhirl Date: Fri, 7 Jun 2019 16:07:41 +0100 Subject: [PATCH 25/29] Delete 8_renderer_old.js --- 8_renderer_old.js | 961 ---------------------------------------------- 1 file changed, 961 deletions(-) delete mode 100644 8_renderer_old.js diff --git a/8_renderer_old.js b/8_renderer_old.js deleted file mode 100644 index a13d5dc1..00000000 --- a/8_renderer_old.js +++ /dev/null @@ -1,961 +0,0 @@ -"use strict"; - -function send(msg) { - try { - msg = msg.trim(); - exe.stdin.write(msg); - exe.stdin.write("\n"); - Log("--> " + msg); - } catch (err) { - // pass - } -} - -function setoption(name, value) { - send(`setoption name ${name} value ${value}`); -} - -// The sync function exists so that we can disregard all output until a certain point. -// Basically we use it after sending a position, so that we can ignore all analysis -// that comes until LZ sends "readyok" in response to our "isready". All output before -// that moment would refer to the obsolete position. -// -// While this seems to work correctly with Lc0, tests with Stockfish show that it -// definitely violates our assumptions and sends things out of order, hence the need -// for validity checking on incoming messages anyway. - -function sync() { - send("isready"); - readyok_required++; -} - -// ------------------------------------------------------------------------------------------------ - -try { - if (fs.existsSync("config.json")) { - config = JSON.parse(debork_json(fs.readFileSync("config.json", "utf8"))); - } else if (fs.existsSync("config.json.example")) { - config = JSON.parse(debork_json(fs.readFileSync("config.json.example", "utf8"))); - config.warn_filename = true; - } else { - alert("config.json not present"); - } -} catch (err) { - alert("Failed to parse config file - make sure it is valid JSON, and in particular, if on Windows, use \\\\ instead of \\ as a path separator."); -} - -// Some tolerable default values for config... - -assign_without_overwrite(config, { - "options": {}, - - "width": 1280, - "height": 840, - "board_size": 640, - "mainline_height": 108, - - "show_n": true, - "show_p": true, - "show_pv": true, - "show_winrate": true, - - "rank_font": "24px Arial", - - "light_square": "#dadada", - "dark_square": "#b4b4b4", - "active_square": "#cc9966", - - "best_colour": "#66aaaa", - "good_colour": "#66aa66", - "bad_colour": "#cccc66", - "terrible_colour": "#cc6666", - - "bad_move_threshold": 0.02, - "terrible_move_threshold": 0.04, - - "max_info_lines": 10, - "node_display_threshold": 0.02, - - "logfile": null -}); - -infobox.style.height = config.board_size.toString() + "px"; -mainline.style.height = config.mainline_height.toString() + "px"; // Is there a way to avoid needing this, to get the scroll bar? -canvas.width = config.board_size; -canvas.height = config.board_size; - -Log(""); -Log("======================================================================================================================================"); -Log(`Nibbler startup at ${new Date().toUTCString()}`); -Log(""); - -if (config.path) { - exe = child_process.spawn(config.path); - exe.on("error", (err) => { - alert("Couldn't spawn process - check the path in the config file"); // Note that this alert will come some time in the future, not instantly. - }); - - scanner = readline.createInterface({ - input: exe.stdout, - output: undefined, - terminal: false - }); - - err_scanner = readline.createInterface({ - input: exe.stderr, - output: undefined, - terminal: false - }); - - err_scanner.on("line", (line) => { - Log("! " + line); - renderer.err_receive(line); - }); - - scanner.on("line", (line) => { - - // We want to ignore all output when waiting for readyok - - if (line.includes("readyok") && readyok_required > 0) { - readyok_required--; - } - - if (readyok_required > 0) { - Log("(ignored) < " + line); - return; - } - - Log("< " + line); - renderer.receive(line); - }); -} - -send("uci"); - -for (let key of Object.keys(config.options)) { - setoption(key, config.options[key]); -} - -setoption("VerboseMoveStats", true); // Required for LogLiveStats to work. -setoption("LogLiveStats", true); // "Secret" Lc0 command. -setoption("MultiPV", 500); - -// ------------------------------------------------------------------------------------------------ - -let images = Object.create(null); -let loads = 0; - -for (let c of Array.from("KkQqRrBbNnPp")) { - images[c] = new Image(); - if (c === c.toUpperCase()) { - images[c].src = `./pieces/${c}.png`; - } else { - images[c].src = `./pieces/_${c.toUpperCase()}.png`; - } - images[c].onload = () => { - loads++; - }; -} - -// ------------------------------------------------------------------------------------------------ - -function make_renderer() { - - let renderer = Object.create(null); - renderer.info_table = NewInfoTable(); - - renderer.squares = []; // Info about clickable squares. - renderer.active_square = null; // Square clicked by user. - renderer.running = false; // Whether to resend "go" to the engine after move, undo, etc. - renderer.ever_received_info = false; // When false, we write stderr log instead of move info. - renderer.stderr_log = ""; // All output received from the engine's stderr. - renderer.infobox_string = ""; // Just to help not redraw the infobox when not needed. - renderer.pgn_choices = null; // Made into a temporary array when displaying the PGN choice. - renderer.pgn_line_end = null; // The terminal position of the loaded PGN, if any. - renderer.clickable_pv_lines = []; // List of PV objects we use to tell what the user clicked on. - - // The following are never actually null (i.e. they're set immediately): - - renderer.user_line_end = null; - renderer.pos = null; - - // --------------------------------------------- - - renderer.square_size = () => { - return config.board_size / 8; - }; - - renderer.pos_changed = (new_game_flag) => { - - renderer.info_table.clear(); - - fenbox.value = renderer.pos.fen(); - renderer.draw_main_line(); - - if (renderer.running) { - renderer.go(new_game_flag); - } else if (new_game_flag) { - send("ucinewgame"); - } - - renderer.escape(); // Among other things, this draws. - }; - - renderer.game_changed = () => { - renderer.pos_changed(true); - }; - - renderer.load_fen = (s) => { - - try { - renderer.pos = LoadFEN(s); - } catch (err) { - alert(err); - return; - } - - renderer.pgn_line_end = null; - renderer.user_line_end = renderer.pos; - renderer.game_changed(); - }; - - renderer.new = () => { - renderer.load_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); - }; - - renderer.load_pgn_object = (o) => { - - // Returns true or false - whether this actually succeeded. - - let final_pos; - - try { - final_pos = LoadPGN(o.movetext); - } catch (err) { - alert(err); - return false; - } - - // Icky way of storing the fact that a position is on the PGN... - - for (let p of final_pos.position_list()) { - p.pgn_flag = true; - } - - renderer.pgn_line_end = final_pos; - renderer.user_line_end = final_pos; - renderer.pos = final_pos.root(); - renderer.game_changed(); - return true; - }; - - renderer.choose_pgn = (n) => { - renderer.hide_pgn_chooser(); - if (renderer.pgn_choices && n >= 0 && n < renderer.pgn_choices.length) { - renderer.load_pgn_object(renderer.pgn_choices[n]); - } - }; - - renderer.open = (filename) => { - - let buf = fs.readFileSync(filename); // i.e. binary buffer object - let new_pgn_choices = pre_parse_pgn(buf); - - if (new_pgn_choices.length === 1) { - let success = renderer.load_pgn_object(new_pgn_choices[0]); - if (success) { - renderer.pgn_choices = new_pgn_choices; // We only want to set this to a 1 value array if it actually worked. - } - } else { - renderer.pgn_choices = new_pgn_choices; // Setting it to a multi-value array is "always" OK. - renderer.show_pgn_chooser(); // Now we need to have the user choose a game. - } - }; - - renderer.show_pgn_chooser = () => { - - if (!renderer.pgn_choices) { - alert("No PGN loaded"); - return; - } - - renderer.halt(); // It's lame to run the GPU when we're clearly switching games. - - let lines = []; - - lines.push(" "); - - let max_ordinal_length = renderer.pgn_choices.length.toString().length; - let padding = ""; - for (let n = 0; n < max_ordinal_length - 1; n++) { - padding += " "; - } - - for (let n = 0; n < renderer.pgn_choices.length; n++) { - - if (n === 9 || n === 99 || n === 999 || n === 9999 || n === 99999 || n === 999999) { - padding = padding.slice(0, padding.length - 6); - } - - let p = renderer.pgn_choices[n]; - - let s; - - if (p.tags.Result === "1-0") { - s = `${padding}${n + 1}. ${p.tags.White} - ${p.tags.Black}`; - } else if (p.tags.Result === "0-1") { - s = `${padding}${n + 1}. ${p.tags.White} - ${p.tags.Black}`; - } else { - s = `${padding}${n + 1}. ${p.tags.White} - ${p.tags.Black}`; - } - lines.push(`  ${s}`); - } - - lines.push(" "); - - pgnchooser.innerHTML = lines.join("
"); - pgnchooser.style.display = "block"; - }; - - renderer.hide_pgn_chooser = () => { - pgnchooser.style.display = "none"; - }; - - renderer.escape = () => { // Set things into a clean state. - renderer.hide_pgn_chooser(); - renderer.active_square = null; - renderer.draw(); - }; - - renderer.validate_pgn = (filename) => { - - let buf = fs.readFileSync(filename); // i.e. binary buffer object - let pgn_list = pre_parse_pgn(buf); - - for (let n = 0; n < pgn_list.length; n++) { - - let o = pgn_list[n]; - - try { - LoadPGN(o.movetext); - } catch (err) { - alert(`Game ${n + 1} - ${err.toString()}`); - return; - } - } - - alert(`This file seems OK. ${pgn_list.length} games checked.`); - return true; - }; - - renderer.prev = () => { - if (renderer.pos.parent) { - renderer.pos = renderer.pos.parent; - renderer.pos_changed(); - } - }; - - renderer.next = () => { - - if (renderer.pos === renderer.user_line_end) { - return; - } - - for (let p of renderer.user_line_end.position_list()) { - if (p.parent === renderer.pos) { - renderer.pos = p; - renderer.pos_changed(); - return; - } - } - }; - - renderer.goto_root = () => { - renderer.pos = renderer.pos.root(); - renderer.pos_changed(); - }; - - renderer.goto_end = () => { - renderer.pos = renderer.user_line_end; - renderer.pos_changed(); - }; - - renderer.return_to_pgn = () => { - - if (!renderer.pgn_line_end) { - alert("No PGN loaded"); - return; - } - - let node = renderer.pos; - - while (!node.pgn_flag) { - if (node.parent === null) { - break; - } - node = node.parent; - } - - if (node.pgn_flag) { - renderer.user_line_end = renderer.pgn_line_end; - renderer.pos = node; - renderer.pos_changed(); - return; - } - - alert("Couldn't rejoin the PGN. This is a bug, tell the author how you achieved it."); - }; - - renderer.move_stays_on_user_line = (s) => { - - for (let p of renderer.user_line_end.position_list()) { - if (p.parent === renderer.pos) { - if (p.lastmove === s) { - return true; - } else { - return false; - } - } - } - - return false; - }; - - renderer.move = (s, skip_pos_changed) => { - - // Add promotion if needed and not present... - - if (s.length === 4) { - let source = Point(s.slice(0, 2)); - if (renderer.pos.piece(source) === "P" && source.y === 1) { - console.log(`Move ${s} was promotion but had no promotion piece set; adjusting to ${s + "q"}`); - s += "q"; - } - if (renderer.pos.piece(source) === "p" && source.y === 6) { - console.log(`Move ${s} was promotion but had no promotion piece set; adjusting to ${s + "q"}`); - s += "q"; - } - } - - let illegal_reason = renderer.pos.illegal(s) - if (illegal_reason !== "") { - alert(`Illegal move requested (${s}, ${illegal_reason}). This should be impossible, please tell the author how you managed it.`); - return; - } - - if (renderer.move_stays_on_user_line(s)) { - for (let p of renderer.user_line_end.position_list()) { - if (p.parent === renderer.pos) { - renderer.pos = p; - if (!skip_pos_changed) { - renderer.pos_changed(); - } - return; - } - } - console.log("Shouldn't get here."); - } - - renderer.pos = renderer.pos.move(s); - renderer.user_line_end = renderer.pos; - - if (!skip_pos_changed) { - renderer.pos_changed(); - } - }; - - renderer.play_best = () => { - let info_list = renderer.info_table.sorted(); - if (info_list.length > 0) { - renderer.move(info_list[0].move); - } - }; - - renderer.go = (new_game_flag) => { - - renderer.escape(); - renderer.running = true; - - let setup; - - let initial_fen = renderer.pos.initial_fen(); - if (initial_fen !== "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") { - setup = `fen ${initial_fen}`; - } else { - setup = "startpos"; - } - - send("stop"); - if (new_game_flag) { - send("ucinewgame"); - } - - send(`position ${setup} moves ${renderer.pos.history().join(" ")}`); - sync(); // See comment on how sync() works - send("go infinite"); - }; - - renderer.halt = () => { - send("stop"); - renderer.running = false; - }; - - renderer.reset_leela_cache = () => { - if (renderer.running) { - renderer.go(true); - } else { - send("ucinewgame"); - } - }; - - renderer.receive = (s) => { - - if (s.startsWith("info")) { - renderer.ever_received_info = true; - renderer.info_table.receive(s, renderer.pos); - } - - if (s.startsWith("error")) { - renderer.err_receive(s); - } - }; - - renderer.err_receive = (s) => { - if (s.indexOf("WARNING") !== -1) { - renderer.stderr_log += `${s}
`; - } else { - renderer.stderr_log += `${s}
`; - } - }; - - renderer.canvas_click = (event) => { - - let point = null; - - for (let n = 0; n < renderer.squares.length; n++) { - let foo = renderer.squares[n]; - if (foo.x1 < event.offsetX && foo.y1 < event.offsetY && foo.x2 > event.offsetX && foo.y2 > event.offsetY) { - point = foo.point; - break; - } - } - - if (point === null) { - return; - } - - if (renderer.active_square) { - - let move_string = renderer.active_square.s + point.s; // e.g. "e2e4" - - let illegal_reason = renderer.pos.illegal(move_string); - - renderer.active_square = null; - - if (illegal_reason === "") { - renderer.move(move_string); - return; // Skip the draw, below, since move() will do that. - } else { - console.log(illegal_reason); - } - - } else { - - if (renderer.pos.active === "w" && renderer.pos.is_white(point)) { - renderer.active_square = point; - } - if (renderer.pos.active === "b" && renderer.pos.is_black(point)) { - renderer.active_square = point; - } - } - - renderer.draw(); - }; - - renderer.pv_click = (i, n) => { - - if (i < 0 || i >= renderer.clickable_pv_lines.length) { - return; - } - - let o = renderer.clickable_pv_lines[i]; - - if (o.board !== renderer.pos) { - return; - } - - let moves = o.pv.slice(0, n + 1); - - // It's best that pos_changed() not be called until we've finished moving. - // That way, all analysis coming from Lc0 will still refer to the original - // position until we send a single message with the new position, and clear - // our info_table a single time. - - for (let move of moves) { - renderer.move(move, true); - } - - renderer.pos_changed(); - }; - - renderer.draw_main_line = () => { - - let elements1 = []; - let elements2 = []; - - // First, have the moves actually made on the visible board. - - let poslist = renderer.pos.position_list(); - - for (let p of poslist.slice(1)) { // Start on the first position that has a lastmove - - if (!p.pgn_flag && p.parent.pgn_flag) { - elements1.push(`(deviated)`); - } - - if (p.parent.active === "w") { - elements1.push(`${p.parent.fullmove}.`); - } - - elements1.push(p.nice_lastmove()); - } - - // Next, have the moves to the end of the user line. - - let start_flag = false; - for (let p of renderer.user_line_end.position_list()) { - - if (p === renderer.pos) { - start_flag = true; - continue; - } - - if (start_flag === false) { - continue; - } - - if (!p.pgn_flag && p.parent.pgn_flag) { - elements2.push(`(deviated)`); - } - - if (p.parent.active === "w") { - elements2.push(`${p.parent.fullmove}.`); - } - - elements2.push(p.nice_lastmove()); - } - - let s1 = elements1.join(" "); // Possibly empty string - let s2 = elements2.join(" "); // Possibly empty string - - if (s2.length > 0) { - s2 = `` + s2 + ""; - } - - mainline.innerHTML = [s1, s2].filter(s => s !== "").join(" "); - }; - - renderer.draw_infobox = () => { - - renderer.clickable_pv_lines = []; - - if (!renderer.ever_received_info) { - if (infobox.innerHTML !== renderer.stderr_log) { // Only update when needed, so user can select and copy. - infobox.innerHTML = renderer.stderr_log; - } - return; - } - - let info_list = renderer.info_table.sorted(); - - let s = ""; - - if (!renderer.running) { - s += "

<halted>

"; - } - - for (let i = 0; i < info_list.length && i < config.max_info_lines; i++) { - - s += `

${info_list[i].nice_pv_string(renderer.pos, config, i)}

`; - - renderer.clickable_pv_lines.push({ - board: renderer.pos, - pv: info_list[i].pv - }) - } - - // Only update when needed, so user can select and copy. A direct comparison - // of s with innerHTML seems to fail (something must get changed). - - if (renderer.infobox_string !== s) { - renderer.infobox_string = s; - infobox.innerHTML = s; - } - }; - - renderer.canvas_coords = (x, y) => { - - // Given the x, y coordinates on the board (a8 is 0, 0) - // return an object with the canvas coordinates for - // the square, and also the centre. Also has rss. - // - // x1,y1-------- - // | | - // | cx,cy | - // | | - // --------x2,y2 - - let rss = renderer.square_size(); - let x1 = x * rss; - let y1 = y * rss; - let x2 = x1 + rss; - let y2 = y1 + rss; - - if (config.flip) { - [x1, x2] = [(rss * 8) - x2, (rss * 8) - x1]; - [y1, y2] = [(rss * 8) - y2, (rss * 8) - y1]; - } - - let cx = x1 + rss / 2; - let cy = y1 + rss / 2; - - return {x1, y1, x2, y2, cx, cy, rss}; - }; - - renderer.draw_board = (light, dark) => { - - renderer.squares = []; - - for (let x = 0; x < 8; x++) { - for (let y = 0; y < 8; y++) { - if (x % 2 === y % 2) { - context.fillStyle = light; - } else { - context.fillStyle = dark; - } - - let cc = renderer.canvas_coords(x, y); - - if (renderer.active_square === Point(x, y)) { - context.fillStyle = config.active_square; - } - - context.fillRect(cc.x1, cc.y1, cc.rss, cc.rss); - - // Update renderer.squares each draw - our list of clickable coordinates. - - renderer.squares.push({x1: cc.x1, y1: cc.y1, x2: cc.x2, y2: cc.y2, point: Point(x, y)}); - } - } - }; - - renderer.draw_piece = (o) => { - let cc = renderer.canvas_coords(o.x, o.y); - context.drawImage(images[o.piece], cc.x1, cc.y1, cc.rss, cc.rss); - }; - - renderer.draw_arrow_line = (o) => { // Doesn't draw the arrowhead - let cc1 = renderer.canvas_coords(o.x1, o.y1); - let cc2 = renderer.canvas_coords(o.x2, o.y2); - context.strokeStyle = o.colour; - context.fillStyle = o.colour; - context.beginPath(); - context.moveTo(cc1.cx, cc1.cy); - context.lineTo(cc2.cx, cc2.cy); - context.stroke(); - }; - - renderer.draw_ranking = (o) => { // Does draw the arrowhead - let cc = renderer.canvas_coords(o.x, o.y); - context.fillStyle = o.colour; - context.beginPath(); - context.arc(cc.cx, cc.cy, 12, 0, 2 * Math.PI); - context.fill(); - context.fillStyle = "black"; - context.fillText(`${o.rank}`, cc.cx, cc.cy + 1); - }; - - renderer.draw_normal = () => { - - context.lineWidth = 8; - context.textAlign = "center"; - context.textBaseline = "middle"; - context.font = config.rank_font; - - renderer.draw_board(config.light_square, config.dark_square); - - let pieces = []; - - for (let x = 0; x < 8; x++) { - for (let y = 0; y < 8; y++) { - if (renderer.pos.state[x][y] === "") { - continue; - } - pieces.push({ - fn: renderer.draw_piece, - piece: renderer.pos.state[x][y], - colour: renderer.pos.state[x][y].toUpperCase() === renderer.pos.state[x][y] ? "w" : "b", - x: x, - y: y - }); - } - } - - let info_list = renderer.info_table.sorted(); - - let arrows = []; - let rankings = Object.create(null); - - if (info_list.length > 0) { - - let best_nodes = info_list[0].n; - - for (let i = 0; i < info_list.length; i++) { - - let [x1, y1] = XY(info_list[i].move.slice(0, 2)); - let [x2, y2] = XY(info_list[i].move.slice(2, 4)); - - if (info_list[i].n >= best_nodes * config.node_display_threshold) { - - let loss = 0; - - if (typeof info_list[0].winrate === "number" && typeof info_list[i].winrate === "number") { - loss = info_list[0].winrate - info_list[i].winrate; - } - - let colour; - - if (i === 0) { - colour = config.best_colour; - } else if (loss > config.terrible_move_threshold) { - colour = config.terrible_colour; - } else if (loss > config.bad_move_threshold) { - colour = config.bad_colour; - } else { - colour = config.good_colour; - } - - arrows.push({ - fn: renderer.draw_arrow_line, - colour: colour, - x1: x1, - y1: y1, - x2: x2, - y2: y2 - }); - - // We only draw the best ranking for each particular target square... - - if (rankings[info_list[i].move.slice(2, 4)] === undefined) { - rankings[info_list[i].move.slice(2, 4)] = { - fn: renderer.draw_ranking, - colour: colour, - rank: i + 1, - x: x2, - y: y2 - }; - } - } - } - } - - // It looks best if the longest arrows are drawn underneath. Manhattan distance is good enough. - - arrows.sort((a, b) => { - if (Math.abs(a.x2 - a.x1) + Math.abs(a.y2 - a.y1) < Math.abs(b.x2 - b.x1) + Math.abs(b.y2 - b.y1)) { - return 1; - } - if (Math.abs(a.x2 - a.x1) + Math.abs(a.y2 - a.y1) > Math.abs(b.x2 - b.x1) + Math.abs(b.y2 - b.y1)) { - return -1; - } - return 0; - }); - - let drawables = []; - - for (let o of pieces) { - if (o.colour !== renderer.pos.active) { - drawables.push(o); - } - } - - drawables = drawables.concat(arrows); - - for (let o of pieces) { - if (o.colour === renderer.pos.active) { - drawables.push(o); - } - } - - drawables = drawables.concat(Object.values(rankings)); - - for (let o of drawables) { - o.fn(o); - } - }; - - renderer.draw = () => { - renderer.draw_infobox(); - renderer.draw_normal(); - }; - - renderer.draw_loop = () => { - renderer.draw(); - setTimeout(renderer.draw_loop, 500); - }; - - renderer.load_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); - return renderer; -} - -// ------------------------------------------------------------------------------------------------ - -let renderer = make_renderer(); - -if (config && config.warn_filename) { - renderer.err_receive(`Nibbler says: You should rename config.json.example to config.json`); - renderer.err_receive(""); -} - -ipcRenderer.on("call", (event, msg) => { - if (typeof msg === "string") { - renderer[msg](); - } else if (typeof msg === "object" && msg.fn && msg.args) { - renderer[msg.fn](...msg.args); - } else { - console.log("Bad call, msg was..."); - console.log(msg); - } -}); - -ipcRenderer.on("toggle", (event, cfgvar) => { - config[cfgvar] = !config[cfgvar]; - renderer.draw(); -}); - -ipcRenderer.on("set", (event, msg) => { - config[msg.key] = msg.value; - renderer.draw(); -}); - -canvas.addEventListener("mousedown", (event) => { - renderer.canvas_click(event); -}); - -// Setup return key on FEN box... -fenbox.onkeydown = (event) => { - console.log(event); - if (event.key === "Enter") { - renderer.load_fen(fenbox.value); - } -}; - -function draw_after_images_load() { - if (loads === 12) { - renderer.draw_loop(); - } else { - setTimeout(draw_after_images_load, 25); - } -} - -draw_after_images_load(); From ea2102de8b4927fe952e7747f380a68b1b45721f Mon Sep 17 00:00:00 2001 From: fohristiwhirl Date: Fri, 7 Jun 2019 16:07:45 +0100 Subject: [PATCH 26/29] Delete engine_controller.js --- engine_controller.js | 60 -------------------------------------------- 1 file changed, 60 deletions(-) delete mode 100644 engine_controller.js diff --git a/engine_controller.js b/engine_controller.js deleted file mode 100644 index 7d9bf53a..00000000 --- a/engine_controller.js +++ /dev/null @@ -1,60 +0,0 @@ -"use strict"; - -function NewEngineController(path) { - - // Full of closures. - - let controller = Object.create(null); - - let info_table = NewInfoTable(); - let failed = false; - let running = false; - let pos = null; - let readyok_required = 0; - let output_queue = []; - - let exe = child_process.spawn(config.path); - exe.on("error", (err) => { - failed = true; - }); - - let scanner = readline.createInterface({ - input: exe.stdout, - output: undefined, - terminal: false - }); - - scanner.on("line", (line) => { - if (line.includes("readyok") && readyok_required > 0) { - readyok_required--; - } - if (readyok_required > 0) { - Log("(ignored) < " + line); - return; - } - Log("< " + line); - receive(line); - }); - - let send = (msg) => { - try { - msg = msg.trim(); - exe.stdin.write(msg); - exe.stdin.write("\n"); - Log("--> " + msg); - if (msg === "isready") { - readyok_required++; - } - } catch (err) { - // pass - } - }; - - let controller.set_pos = (p) => { - - pos = p; - - output_queue.push("stop"); - output_queue.push("isready"); - - From 492d74de164b84e649337a592c05e50fc44fb021 Mon Sep 17 00:00:00 2001 From: fohristiwhirl Date: Fri, 7 Jun 2019 16:08:15 +0100 Subject: [PATCH 27/29] 0.4.0-rc1 --- package-lock.json | 5 ----- package.json | 5 ++++- 2 files changed, 4 insertions(+), 6 deletions(-) delete mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index b13d9b83..00000000 --- a/package-lock.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "Nibbler", - "version": "0.3.7", - "lockfileVersion": 1 -} diff --git a/package.json b/package.json index a21ac7b1..9a1febd4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Nibbler", - "version": "0.3.7", + "version": "0.4.0-rc1", "author": "Fohristiwhirl", "description": "Leela Chess Zero (Lc0) interface", "main": "main.js", @@ -10,5 +10,8 @@ }, "scripts": { "pack": "electron-builder --dir" + }, + "build": { + "electronVersion": "5.0.2" } } From 9fea83fb3e0ec19c524bc63a07fe59f09bfaa800 Mon Sep 17 00:00:00 2001 From: fohristiwhirl Date: Fri, 7 Jun 2019 16:24:41 +0100 Subject: [PATCH 28/29] Update 8_renderer.js --- 8_renderer.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/8_renderer.js b/8_renderer.js index 97199d2f..a67a69b9 100644 --- a/8_renderer.js +++ b/8_renderer.js @@ -644,7 +644,10 @@ function make_renderer() { renderer.pv_click = (i, n) => { + console.log(`pv_click(${i}, ${n})`); + if (i < 0 || i >= renderer.clickable_pv_lines.length) { + console.log("pv_click() failed due to i ===", i); return; } From 0b7530abd7e466cc13907ea53bdf6020ee178b81 Mon Sep 17 00:00:00 2001 From: fohristiwhirl Date: Fri, 7 Jun 2019 16:26:08 +0100 Subject: [PATCH 29/29] Create package-lock.json --- package-lock.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..e0a28a05 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,5 @@ +{ + "name": "Nibbler", + "version": "0.4.0-rc1", + "lockfileVersion": 1 +}