nissy-core

The "engine" of nissy, including the H48 optimal solver.
git clone https://git.tronto.net/nissy-core
Download | Log | Files | Refs | README | LICENSE

commit fcf5a819ef52ab3069f687e6943599fa3ccf96b3
parent cf2896324e60e2dc3890c658378499383100e79c
Author: Sebastiano Tronto <sebastiano@tronto.net>
Date:   Wed, 28 May 2025 12:17:55 +0200

Web solver is usable

Diffstat:
Mweb/http/index.html | 21++++++++++++++++++---
Mweb/http/nissyapp.mjs | 134++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Mweb/worker.mjs | 89++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
3 files changed, 222 insertions(+), 22 deletions(-)

diff --git a/web/http/index.html b/web/http/index.html @@ -9,12 +9,27 @@ <body> <input id="scrambleText" placeholder="Type the scramble here..."> <select id="solverSelector"> - <option value="h48h0k4" selected="selected">h48 h=0 k=4 (59Mb)</option> - <option value="h48h3k2">h48 h=3 k=2 (283Mb)</option> - <option value="h48h7k2">h48 h=7 k=2 (3.6Gb)</option> + <option value="h48h0k4">h48 h=0 k=4 (59Mb)</option> + <option value="h48h3k2" selected="selected">h48 h=3 k=2 (283Mb)</option> + <!--option value="h48h7k2">h48 h=7 k=2 (3.6Gb)</option--> </select> <button id="solveButton">Solve</button> + <div id="confirmDownload" style="display:none"> + <p id="confirmDownloadText"> + For this solver to work, a large data table is required. + You can either download it or generate it locally. + </p> + <p id="confirmDownloadExtraText"> + In either case, this data will be stored locally for future use. You + can delete it at any time by clearing your browser's data. + </p> + <button id="confirmDownloadCancel">Cancel</button> + <button id="confirmDownloadConfirm">Download</button> + <button id="confirmDownloadGenerate">Generate locally</button> + </div> <p id="resultsLabel"></p> <p id="results"></p> + <button id="toggleShowLog">Show nissy log messages</button> + <p id="logPane" style="display:none"></p> </body> </html> diff --git a/web/http/nissyapp.mjs b/web/http/nissyapp.mjs @@ -7,30 +7,149 @@ var scrField = document.getElementById("scrambleText"); var resultsLabel = document.getElementById("resultsLabel"); var resultsText = document.getElementById("results"); var solverSelector = document.getElementById("solverSelector"); +var toggleLog = document.getElementById("toggleShowLog"); +var logPane = document.getElementById("logPane"); var lastCallbackId = 0; var callbacks = new Map(); // Values: { f: function, arg: object } var worker = new Worker("./worker.mjs", { type: "module" }); worker.onmessage = (event) => { if (event.data.command == "log") { - console.log(event.data.object); // TODO + logPane.innerText += event.data.object; + console.log(event.data.object); } else if (!callbacks.has(event.data.id)) { console.log("[nissy app] Unknown callback " + event.data.id + " for command " + event.data.command); } else { var callback = callbacks.get(event.data.id); + callbacks.delete(event.data.id); callback.f(callback.arg, event.data.arg); } }; -function updateResults(label, results, buttonState) { +function updateResults(label, results, enable) { resultsLabel.innerText = label; resultsText.innerText = results; - solveButton.disable = !buttonState; + solveButton.disable = !enable; + solverSelector.disable = !enable; } +var logVisible = false; +toggleLog.addEventListener("click", () => { + logVisible = !logVisible; + if (logVisible) { + logPane.style.display = "block"; + toggleLog.innerText = "Hide nissy log messages"; + } else { + logPane.style.display = "none"; + toggleLog.innerText = "Show nissy log messages"; + } +}); + solveButton.addEventListener("click", () => { - updateResults("Loading solutions...", "", false); + const solver = solverSelector.options[solverSelector.selectedIndex].value; + const scramble = scrField.value; + loadDataThenSolve(solver, scramble); + + const solveCallbackId = ++lastCallbackId; + callbacks.set(solveCallbackId, { + f: (callbackArg, loadResult) => { + if (loadResult.success) { + startSolve(callbackArg.solver, callbackArg.scramble); + } else { + updateResults("", "", true); + } + }, + arg: { solver: solver, scramble: scramble } + }); + + const downloadCallbackId = ++lastCallbackId; +}); + +function loadDataThenSolve(solver, scramble) { + updateResults("Loading solver " + solver + "...", "", false); + + const callbackId = ++lastCallbackId; + callbacks.set(callbackId, { + f: (callbackArg, loadResult) => { + if (loadResult.success) + startSolve(callbackArg.solver, callbackArg.scramble); + else + askDownloadThenSolve(callbackArg.solver, callbackArg.scramble); + }, + arg: { solver: solver, scramble: scramble } + }); + + worker.postMessage({ + command: "load solver data", + id: callbackId, + arg: solver + }); +} + +function askDownloadThenSolve(solver, scramble) { + var confirmDiv = document.getElementById("confirmDownload"); + var cancel = document.getElementById("confirmDownloadCancel"); + var download = document.getElementById("confirmDownloadConfirm"); + var generate = document.getElementById("confirmDownloadGenerate"); + const cleanup = () => { + confirmDiv.style.display = "none"; + + // Remove event listeners by cloning the buttons + cancel.outerHTML = cancel.outerHTML; + download.outerHTML = download.outerHTML; + generate.outerHTML = generate.outerHTML; + }; + + updateResults("", "", false); + confirmDiv.style.display = "block"; + + cancel.addEventListener("click", cleanup); + + download.addEventListener("click", () => { + cleanup(); + downloadDataThenSolve(solver, scramble); + }); + + generate.addEventListener("click", () => { + cleanup(); + generateDataThenSolve(solver, scramble); + }); +} + +const downloadDataThenSolve = (solver, scramble) => + downloadOrGenerateThenSolve(solver, scramble, true); + +const generateDataThenSolve = (solver, scramble) => + downloadOrGenerateThenSolve(solver, scramble, false); + +function downloadOrGenerateThenSolve(solver, scramble, download) { + const msg = download ? "Downloading data for " : "Generating data for "; + const command = download ? "download solver data" : "generate solver data"; + + updateResults(msg + solver + "...", "", false); + + const callbackId = ++lastCallbackId; + callbacks.set(callbackId, { + f: (callbackArg, loadResult) => { + if (loadResult.success) + startSolve(callbackArg.solver, callbackArg.scramble); + else + updateResults("Failed", loadResult.message, true); + }, + arg: { solver: solver, scramble: scramble } + }); + + worker.postMessage({ + command: command, + id: callbackId, + arg: solver + }); +} + +function startSolve(solver, scramble) { + updateResults("Solving...", "", false); + const callbackId = ++lastCallbackId; callbacks.set(callbackId, { f: (callbackArg, solveResult) => { @@ -46,12 +165,13 @@ solveButton.addEventListener("click", () => { }, arg: "" // Currently unused }); + worker.postMessage({ command: "solve", id: callbackId, arg: { - solver: solverSelector.options[solverSelector.selectedIndex].value, - scramble: scrField.value, + solver: solver, + scramble: scramble, minmoves: 0, maxmoves: 20, maxsolutions: 1, @@ -59,4 +179,4 @@ solveButton.addEventListener("click", () => { threads: window.navigator.hardwareConcurrency, } }); -}); +} diff --git a/web/worker.mjs b/web/worker.mjs @@ -8,8 +8,10 @@ function log(message) { nissy.setLogger(nissy._addCallbackFunction(log)); const commands = [ - { name: "solve", exec: solve }, - { name: "download solver data", exec: downloadSolverData }, + { name: "load solver data", exec: loadSolverDataFromStorage }, + { name: "download solver data", exec: downloadSolverData }, + { name: "generate solver data", exec: generateSolverData }, + { name: "solve", exec: solve }, ]; // Message structure: @@ -25,6 +27,7 @@ onmessage = (event) => { for (var i = 0; i < commands.length; i++) { if (commands[i].name == event.data.command) { commands[i].exec(event.data.id, event.data.arg); + console.log("[worker] Received '" + commands[i].name + "'"); return; } } @@ -32,10 +35,41 @@ onmessage = (event) => { log("[nissy worker] unknown command " + event.data.command); }; +// Load solver data from storage. +// Argument: string (the name of the solver) +async function loadSolverDataFromStorage(id, solver) { + const async_return = (success, message = "") => postMessage({ + command: "load solver data", + id: id, + arg: { + success: success, + message: message + } + }); + + if (!(await nissy.isSolverAvailable(solver))) { + async_return(false, "Error: solver " + arg.solver + + " is not available in this version of nissy."); + return; + } + + if (await nissy.isSolverLoaded(solver)) { + async_return(true); + return; + } + + if (await nissy.initSolverFromStorage(solver)) { + async_return(true); + } else { + async_return(false, "Could not read data for " + solver + + " from local storage"); + } +} + // Download solver data. -// Argument: string +// Argument: string (the name of the solver) async function downloadSolverData(id, solver) { - const downloadSolverDataReturn = (success, message = "") => postMessage({ + const async_return = (success, message = "") => postMessage({ command: "download solver data", id: id, arg: { @@ -44,10 +78,41 @@ async function downloadSolverData(id, solver) { } }); - if (await nissy.initSolverDownload(arg.solver, "/tables")) { - downloadSolverDataReturn(true); + if (!(await nissy.isSolverAvailable(solver))) { + async_return(false, "Error: solver " + arg.solver + + " is not available in this version of nissy."); + return; + } + + if (await nissy.initSolverDownload(solver, "/tables")) { + async_return(true); + } else { + async_return(false, "Error retrieving solver data"); + } +} + +// Generate solver data locally. +// Argument: string (the name of the solver) +async function generateSolverData(id, solver) { + const async_return = (success, message = "") => postMessage({ + command: "generate solver data", + id: id, + arg: { + success: success, + message: message + } + }); + + if (!(await nissy.isSolverAvailable(solver))) { + async_return(false, "Error: solver " + arg.solver + + " is not available in this version of nissy."); + return; + } + + if (await nissy.initSolverGenerate(solver)) { + async_return(true); } else { - downloadSolverDataReturn(false, "Error retrieving the solver data"); + async_return(false, "Error generating solver data"); } } @@ -62,7 +127,7 @@ async function downloadSolverData(id, solver) { // threads: number // } async function solve(id, arg) { - const solveReturn = (success, solutions = [], message = "") => postMessage({ + const async_return = (success, solutions = [], message = "") => postMessage({ command: "solve", id: id, arg: { @@ -73,14 +138,14 @@ async function solve(id, arg) { }); if (!(await nissy.isSolverAvailable(arg.solver))) { - solveReturn(false, [], "Error: solver " + arg.solver + + async_return(false, [], "Error: solver " + arg.solver + " is not available in this version of nissy."); return; } if (!(await nissy.isSolverLoaded(arg.solver)) && !(await nissy.initSolverFromStorage(arg.solver))) { - solveReturn(false, [], "Error: solver " + arg.solver + " has not been " + + async_return(false, [], "Error: solver " + arg.solver + " has not been " + "loaded. Its data must be donwloaded or generated before using it."); return; } @@ -106,9 +171,9 @@ async function solve(id, arg) { //TODO: add solution lenght? var solutions = result.solutions == "" ? [] : result.solutions.split("\n"); solutions.pop(); // Solver always returns string ending in newline - solveReturn(true, solutions); + async_return(true, solutions); } else { - solveReturn(false, [], + async_return(false, [], "Error while solving (error " + result.err.value + ")"); } };