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

nissyapp.mjs (8411B)


      1 // Run ./build web from the main folder before running this example.
      2 // The necessary modules (including worker.mjs) will be built and / or moved
      3 // to this folder when running ./build web.
      4 
      5 var solveButton = document.getElementById("solveButton");
      6 var pauseResumeButton = document.getElementById("pauseResumeButton");
      7 var cancelSolveButton = document.getElementById("cancelSolveButton");
      8 var scrField = document.getElementById("scrambleText");
      9 var resultsLabel = document.getElementById("resultsLabel");
     10 var resultsText = document.getElementById("results");
     11 var solverSelector = document.getElementById("solverSelector");
     12 var toggleLog = document.getElementById("toggleShowLog");
     13 var logPane = document.getElementById("logPane");
     14 var minSlider = document.getElementById("minMovesSlider");
     15 var maxSlider = document.getElementById("maxMovesSlider");
     16 var minLabel = document.getElementById("minLabel");
     17 var maxLabel = document.getElementById("maxLabel");
     18 var maxSolsInput = document.getElementById("maxSolutions");
     19 var optimalInput = document.getElementById("optimalInput");
     20 
     21 var solveStatus = "run"; // For now this is global
     22 var lastCallbackId = 0;
     23 var callbacks = new Map(); // Values: { f: function, arg: object }
     24 var worker = new Worker("./worker.mjs", { type: "module" });
     25 
     26 // Periodically send status updates to the worker
     27 
     28 const sendStatusUpdateToWorker = () =>
     29   worker.postMessage({command: "update status", id: -1, arg: solveStatus });
     30 setInterval(() => sendStatusUpdateToWorker(), 500);
     31 
     32 worker.onmessage = (event) => {
     33   if (event.data.command == "log") {
     34     logPane.innerText += event.data.object;
     35     console.log(event.data.object);
     36   } else if (!callbacks.has(event.data.id)) {
     37     console.log("[nissy app] Unknown callback " + event.data.id +
     38       " for command " + event.data.command);
     39   } else {
     40     var callback = callbacks.get(event.data.id);
     41     callbacks.delete(event.data.id);
     42     callback.f(callback.arg, event.data.arg);
     43   }
     44 };
     45 
     46 function updateResults(label, results, enable) {
     47   resultsLabel.innerText = label;
     48   resultsText.innerText = results;
     49   solveButton.disabled = !enable;
     50   solverSelector.disabled = !enable;
     51   minSlider.disabled = !enable;
     52   maxSlider.disabled = !enable;
     53   maxSolsInput.disabled = !enable;
     54   optimalInput.disabled = !enable;
     55 }
     56 
     57 scrField.addEventListener("input", (e) => {
     58   const scramble = scrField.value;
     59 
     60   const callbackId = ++lastCallbackId;
     61   callbacks.set(callbackId, {
     62     f: (callbackArg, validateResult) => {
     63       if (validateResult) {
     64         scrField.style.border = "";
     65       } else {
     66         scrField.style.border = "2px solid red";
     67       }
     68     },
     69     arg: scramble
     70   });
     71 
     72   worker.postMessage({
     73     command: "validate scramble",
     74     id: callbackId,
     75     arg: scramble
     76   });
     77 });
     78 
     79 minSlider.addEventListener("input", () =>
     80   minLabel.innerText = "Minimum moves: " + minSlider.value);
     81 
     82 maxSlider.addEventListener("input", () =>
     83   maxLabel.innerText = "Maximum moves: " + maxSlider.value);
     84 
     85 function enforceMinMax() {
     86   if (this.value != "") {
     87     if (parseInt(this.value) < parseInt(this.min))
     88       this.value = this.min;
     89     if (parseInt(this.value) > parseInt(this.max))
     90       this.value = this.max;
     91   }
     92 }
     93 
     94 maxSolsInput.addEventListener("keyup", enforceMinMax);
     95 optimalInput.addEventListener("keyup", enforceMinMax);
     96 
     97 var logVisible = false;
     98 toggleLog.addEventListener("click", () => {
     99   logVisible = !logVisible;
    100   if (logVisible) {
    101     logPane.style.display = "block";
    102     toggleLog.innerText = "Hide nissy log messages";
    103   } else {
    104     logPane.style.display = "none";
    105     toggleLog.innerText = "Show nissy log messages";
    106   }
    107 });
    108 
    109 solveButton.addEventListener("click", () => {
    110   const solver = solverSelector.options[solverSelector.selectedIndex].value;
    111   const scramble = scrField.value;
    112 
    113   const callbackId = ++lastCallbackId;
    114   callbacks.set(callbackId, {
    115     f: (callbackArg, validateResult) => {
    116       if (validateResult) {
    117         loadDataThenSolve(callbackArg.solver, callbackArg.scramble);
    118       } else {
    119         updateResults("Scramble is invalid", "", true);
    120       }
    121     },
    122     arg: {
    123       solver: solver,
    124       scramble: scramble
    125     }
    126   });
    127 
    128   worker.postMessage({
    129     command: "validate scramble",
    130     id: callbackId,
    131     arg: scramble
    132   });
    133 });
    134 
    135 function loadDataThenSolve(solver, scramble) {
    136   updateResults("Loading solver " + solver + "...", "", false);
    137 
    138   const callbackId = ++lastCallbackId;
    139   callbacks.set(callbackId, {
    140     f: (callbackArg, loadResult) => {
    141       if (loadResult.success)
    142         startSolve(callbackArg.solver, callbackArg.scramble);
    143       else
    144         askDownloadThenSolve(callbackArg.solver, callbackArg.scramble);
    145     },
    146     arg: { solver: solver, scramble: scramble }
    147   });
    148 
    149   worker.postMessage({
    150     command: "load solver data",
    151     id: callbackId,
    152     arg: solver
    153   });
    154 }
    155 
    156 function askDownloadThenSolve(solver, scramble) {
    157   var confirmDiv = document.getElementById("confirmDownload");
    158   var cancel = document.getElementById("confirmDownloadCancel");
    159   var download = document.getElementById("confirmDownloadConfirm");
    160   var generate = document.getElementById("confirmDownloadGenerate");
    161   const cleanup = () => {
    162     confirmDiv.style.display = "none";
    163 
    164     // Remove event listeners by cloning the buttons
    165     cancel.outerHTML = cancel.outerHTML;
    166     download.outerHTML = download.outerHTML;
    167     generate.outerHTML = generate.outerHTML;
    168   };
    169 
    170   updateResults("", "", false);
    171   confirmDiv.style.display = "block";
    172 
    173   cancel.addEventListener("click", () => {
    174     cleanup();
    175     updateResults("", "", true);
    176   });
    177 
    178   download.addEventListener("click", () => {
    179     cleanup();
    180     downloadDataThenSolve(solver, scramble);
    181   });
    182 
    183   generate.addEventListener("click", () => {
    184     cleanup();
    185     generateDataThenSolve(solver, scramble);
    186   });
    187 }
    188 
    189 const downloadDataThenSolve = (solver, scramble) =>
    190   downloadOrGenerateThenSolve(solver, scramble, true);
    191 
    192 const generateDataThenSolve = (solver, scramble) =>
    193   downloadOrGenerateThenSolve(solver, scramble, false);
    194 
    195 function downloadOrGenerateThenSolve(solver, scramble, download) {
    196   const msg = download ? "Downloading data for " : "Generating data for ";
    197   const command = download ? "download solver data" : "generate solver data";
    198 
    199   updateResults(msg + solver + "...", "", false);
    200 
    201   const callbackId = ++lastCallbackId;
    202   callbacks.set(callbackId, {
    203     f: (callbackArg, loadResult) => {
    204       if (loadResult.success)
    205         startSolve(callbackArg.solver, callbackArg.scramble);
    206       else
    207         updateResults("Failed", loadResult.message, true);
    208     },
    209     arg: { solver: solver, scramble: scramble }
    210   });
    211 
    212   worker.postMessage({
    213     command: command,
    214     id: callbackId,
    215     arg: solver
    216   });
    217 }
    218 
    219 pauseResumeButton.addEventListener("click", (e) => {
    220   if (pauseResumeButton.innerText == "Pause") {
    221     solveStatus = "pause";
    222     pauseResumeButton.innerText = "Resume";
    223     updateResults("Solver paused - click Resume to continue", "", false);
    224   } else {
    225     solveStatus = "run";
    226     pauseResumeButton.innerText = "Pause";
    227     updateResults("Solving...", "", false);
    228   }
    229 });
    230 
    231 cancelSolveButton.addEventListener("click", (e) => {
    232   solveStatus = "stop";
    233   pauseResumeButton.innerText = "Pause";
    234 });
    235 
    236 function startSolve(solver, scramble) {
    237   updateResults("Solving...", "", false);
    238 
    239   const callbackId = ++lastCallbackId;
    240   pauseResumeButton.disabled = false;
    241   cancelSolveButton.disabled = false;
    242   solveStatus = "run";
    243 
    244   callbacks.set(callbackId, {
    245     f: (callbackArg, solveResult) => {
    246       pauseResumeButton.disabled = true;
    247       cancelSolveButton.disabled = true;
    248       pauseResumeButton.innerText = "Pause";
    249       solveStatus = "stop";
    250 
    251       if (solveResult.success) {
    252         const n = solveResult.solutions.length;
    253         const label = n == 0 ? "No solution found" :
    254           ("Found " + n + " solution" + (n == 1 ? "" : "s")) + ":";
    255         const text = solveResult.solutions.join("\n");
    256         updateResults(label, text, true);
    257       } else {
    258         updateResults("Unexpected error while solving!", "", true);
    259       }
    260     },
    261     arg: "" // Currently unused
    262   });
    263 
    264   worker.postMessage({
    265     command: "solve",
    266     id: callbackId,
    267     arg: {
    268       solver: solver,
    269       scramble: scramble,
    270       minmoves: Number(minSlider.value),
    271       maxmoves: Number(maxSlider.value),
    272       maxsolutions: Number(maxSolsInput.value),
    273       optimal: Number(optimalInput.value),
    274       threads: window.navigator.hardwareConcurrency,
    275     }
    276   });
    277 }