UNPKG

stockfish

Version:

The Stockfish chess engine in Web Assembly (WASM)

292 lines (255 loc) 13.9 kB
<!DOCTYPE html> <head> <meta charset="utf-8" /> <title>Stockfish NNUE JS</title> <meta name="viewport" content="width=device-width, height=device-height, initial-scale=1, maximum-scale=1, user-scalable=no" /> </head> <!-- CSS --> <style> *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; border: 0; font: inherit; } html, body, #root { height: 100%; } #root { font-family: 'mono'; font-size: 13px; } main { height: 100%; display: flex; flex-direction: column; padding: 10px; } #input { display: flex; margin-bottom: 10px; } #input #command { width: 80%; padding: 2px 0 2px 6px; border: 1px solid #ddd; border-right: 0; } #input #send { display: inline-block; padding: 3px 6px 3px 6px; background: #888; color: #fff; font-weight: bold; margin-right: 6px; cursor: pointer; } #input #examples { width: 120px; background: #eee; } #misc { margin-bottom: 10px; } #output { flex: 1 1 auto; width: 100%; border: 1px solid #ddd; padding: 10px; overflow: scroll; box-shadow: inset 1px 1px 2px #eee; font-size: 11px; } </style> <body> <div id="root"></div> <!-- Javascript --> <!-- <script src="./stockfish.js"></script> --> <script src="./mithril.2.0.4.min.js"></script> <script> const $ = (...args) => document.querySelector(...args); const formatMB = (n) => { return (n ? (n / 1e6).toPrecision(3) : '?') + 'MB'; }; const isSupported = () => { if (typeof WebAssembly !== 'object') return false; //const source = Uint8Array.from([0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 123, 3, 2, 1, 0, 7, 8, 1, 4, 116, 101, 115, 116, 0, 0, 10, 15, 1, 13, 0, 65, 0, 253, 17, 65, 0, 253, 17, 253, 186, 1, 11]); //if (typeof WebAssembly.validate !== 'function' || !WebAssembly.validate(source)) return false; if (typeof Atomics !== 'object') return false; if (typeof SharedArrayBuffer !== 'function') return false; return true; }; const RequestProgress = ({ attrs: { url, onFinishDownload } }) => { let state = 'INIT'; // 'LOADING', 'DONE', 'FAILED' let loaded = 0; let total = 0; const oninit = () => { state = 'LOADING'; m.request({ url: url, method: 'GET', responseType: 'arraybuffer', headers: { Accept: '*/*' }, config: (xhr) => { xhr.onprogress = (e) => { // TODO: // When gzip compressed, the value of "loaded/total" gets messed up. // On Chrome, "loaded" is the value after decompression, but on the other hand, // On Firefox, "loaded" is the value before decompression. loaded = e.loaded; total = e.total || Number(e.target.getResponseHeader("x-decompressed-content-length")); m.redraw(); }; } }).then( (response) => { state = 'DONE'; onFinishDownload(response); }, (e) => { console.error(e); state = 'FAILED'; onFinishDownload(null); } ) }; const view = () => { const fraction = (total == -1) ? `?MB/?MB` : `${formatMB(loaded)}/${formatMB(total)}`; return m("span", [ `${fraction} [${state}] `, m("span", { style: 'cursor: pointer;', onclick: () => window.alert('On some browsers, download size might look contradicted due to file compression.'), }, '[?]') ]); }; return { oninit, view }; }; const App = () => { let stockfish = null; let stockfish_state = 'INIT'; // 'READY', 'FAILED' let output = ''; let tail_mode = true; const examples = [ 'uci', 'go', 'go depth 20', 'go depth 25', 'stop', 'd', 'eval', 'position startpos', 'position fen r1k4r/ppp1bq1p/2n1N3/6B1/3p2Q1/8/PPP2PPP/R5K1 w - - 0 1', 'setoption name Use NNUE value true', 'setoption name Use NNUE value false', 'setoption name Threads value 1', 'setoption name Threads value 4', 'bench 16 1 20 current depth NNUE', 'bench 16 1 20 current depth classical', 'bench_eval', 'setoption name EvalFile value old-nn/nn-76a8a7ffb820.nnue', 'setoption name EvalFile value http://127.0.0.1:8080/old-nn/nn-76a8a7ffb820.nnue', 'position fen 8/7p/6p1/5p2/Q4P2/2p3P1/3r3P/2K1k3 w - - 2 44 moves a4a7', ]; const sendCommand = () => { const command = $('#command').value; if (command.length > 0) { stockfish.postMessage(command); } }; const scrollOutput = () => { if (!tail_mode) { return; } $('#output').scrollTo({ top: $('#output').scrollHeight, behavior: 'smooth' }); }; const loadStockfish = function () { var mod = { locateFile: function (path) { if (path.indexOf(".wasm") > -1) { return "stockfish.wasm"; } else { return "stockfish.js#stockfish.wasm,worker"; //console.error("TODO: CONCAT worker"); //return "stockfish.worker.js"; //const code = "(" + '"use strict";var Module={};if(typeof process==="object"&&typeof process.versions==="object"&&typeof process.versions.node==="string"){var nodeWorkerThreads=require("worker_threads");var parentPort=nodeWorkerThreads.parentPort;parentPort.on("message",function(data){onmessage({data:data})});var nodeFS=require("fs");Object.assign(global,{self:global,require:require,Module:Module,location:{href:__filename},Worker:nodeWorkerThreads.Worker,importScripts:function(f){(0,eval)(nodeFS.readFileSync(f,"utf8"))},postMessage:function(msg){parentPort.postMessage(msg)},performance:global.performance||{now:function(){return Date.now()}}})}function threadPrintErr(){var text=Array.prototype.slice.call(arguments).join(" ");console.error(text)}function threadAlert(){var text=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:text,threadId:Module["_pthread_self"]()})}var err=threadPrintErr;self.alert=threadAlert;Module["instantiateWasm"]=function(info,receiveInstance){var instance=new WebAssembly.Instance(Module["wasmModule"],info);receiveInstance(instance);Module["wasmModule"]=null;return instance.exports};function moduleLoaded(){}self.onmessage=function(e){try{if(e.data.cmd==="load"){Module["wasmModule"]=e.data.wasmModule;Module["wasmMemory"]=e.data.wasmMemory;Module["buffer"]=Module["wasmMemory"].buffer;Module["ENVIRONMENT_IS_PTHREAD"]=true;if(typeof e.data.urlOrBlob==="string"){importScripts(e.data.urlOrBlob)}else{var objectUrl=URL.createObjectURL(e.data.urlOrBlob);importScripts(objectUrl);URL.revokeObjectURL(objectUrl)}Stockfish(Module).then(function(instance){Module=instance;moduleLoaded()})}else if(e.data.cmd==="objectTransfer"){Module["PThread"].receiveObjectTransfer(e.data)}else if(e.data.cmd==="run"){Module["__performance_now_clock_drift"]=performance.now()-e.data.time;Module["__emscripten_thread_init"](e.data.threadInfoStruct,/*isMainBrowserThread=*/0,/*isMainRuntimeThread=*/0);var max=e.data.stackBase;var top=e.data.stackBase+e.data.stackSize;Module["establishStackSpace"](top,max);Module["PThread"].receiveObjectTransfer(e.data);Module["PThread"].threadInit();try{var result=Module["invokeEntryPoint"](e.data.start_routine,e.data.arg);if(Module["keepRuntimeAlive"]()){Module["PThread"].setExitStatus(result)}else{Module["PThread"].threadExit(result)}}catch(ex){if(ex==="Canceled!"){Module["PThread"].threadCancel()}else if(ex!="unwind"){if(ex instanceof Module["ExitStatus"]){if(Module["keepRuntimeAlive"]()){}else{Module["PThread"].threadExit(ex.status)}}else{Module["PThread"].threadExit(-2);throw ex}}}}else if(e.data.cmd==="cancel"){if(Module["_pthread_self"]()){Module["PThread"].threadCancel()}}else if(e.data.target==="setimmediate"){}else if(e.data.cmd==="processThreadQueue"){if(Module["_pthread_self"]()){Module["_emscripten_current_thread_process_queued_calls"]()}}else{err("worker.js received unknown command "+e.data.cmd);err(e.data)}}catch(ex){err("worker.js onmessage() captured an uncaught exception: "+ex);if(ex&&ex.stack)err(ex.stack);throw ex}};' + ")();"; //const blob = new Blob([code], {type: 'application/javascript'}) //const code = '"use strict";var Module={};if(typeof process==="object"&&typeof process.versions==="object"&&typeof process.versions.node==="string"){var nodeWorkerThreads=require("worker_threads");var parentPort=nodeWorkerThreads.parentPort;parentPort.on("message",function(data){onmessage({data:data})});var nodeFS=require("fs");Object.assign(global,{self:global,require:require,Module:Module,location:{href:__filename},Worker:nodeWorkerThreads.Worker,importScripts:function(f){(0,eval)(nodeFS.readFileSync(f,"utf8"))},postMessage:function(msg){parentPort.postMessage(msg)},performance:global.performance||{now:function(){return Date.now()}}})}function threadPrintErr(){var text=Array.prototype.slice.call(arguments).join(" ");console.error(text)}function threadAlert(){var text=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:text,threadId:Module["_pthread_self"]()})}var err=threadPrintErr;self.alert=threadAlert;Module["instantiateWasm"]=function(info,receiveInstance){var instance=new WebAssembly.Instance(Module["wasmModule"],info);receiveInstance(instance);Module["wasmModule"]=null;return instance.exports};function moduleLoaded(){}self.onmessage=function(e){try{if(e.data.cmd==="load"){Module["wasmModule"]=e.data.wasmModule;Module["wasmMemory"]=e.data.wasmMemory;Module["buffer"]=Module["wasmMemory"].buffer;Module["ENVIRONMENT_IS_PTHREAD"]=true;if(typeof e.data.urlOrBlob==="string"){importScripts(e.data.urlOrBlob)}else{var objectUrl=URL.createObjectURL(e.data.urlOrBlob);importScripts(objectUrl);URL.revokeObjectURL(objectUrl)}Stockfish(Module).then(function(instance){Module=instance;moduleLoaded()})}else if(e.data.cmd==="objectTransfer"){Module["PThread"].receiveObjectTransfer(e.data)}else if(e.data.cmd==="run"){Module["__performance_now_clock_drift"]=performance.now()-e.data.time;Module["__emscripten_thread_init"](e.data.threadInfoStruct,/*isMainBrowserThread=*/0,/*isMainRuntimeThread=*/0);var max=e.data.stackBase;var top=e.data.stackBase+e.data.stackSize;Module["establishStackSpace"](top,max);Module["PThread"].receiveObjectTransfer(e.data);Module["PThread"].threadInit();try{var result=Module["invokeEntryPoint"](e.data.start_routine,e.data.arg);if(Module["keepRuntimeAlive"]()){Module["PThread"].setExitStatus(result)}else{Module["PThread"].threadExit(result)}}catch(ex){if(ex==="Canceled!"){Module["PThread"].threadCancel()}else if(ex!="unwind"){if(ex instanceof Module["ExitStatus"]){if(Module["keepRuntimeAlive"]()){}else{Module["PThread"].threadExit(ex.status)}}else{Module["PThread"].threadExit(-2);throw ex}}}}else if(e.data.cmd==="cancel"){if(Module["_pthread_self"]()){Module["PThread"].threadCancel()}}else if(e.data.target==="setimmediate"){}else if(e.data.cmd==="processThreadQueue"){if(Module["_pthread_self"]()){Module["_emscripten_current_thread_process_queued_calls"]()}}else{err("worker.js received unknown command "+e.data.cmd);err(e.data)}}catch(ex){err("worker.js onmessage() captured an uncaught exception: "+ex);if(ex&&ex.stack)err(ex.stack);throw ex}};'; //const worker = new Worker(URL.createObjectURL(blob)) //return 'data:application/javascript,' + encodeURIComponent(code); } } }; Stockfish(mod).then(_stockfish => { stockfish = _stockfish; stockfish_state = 'READY'; stockfish.addMessageListener((line) => { output += line + '\n'; m.redraw(); }); }) }; function loadStockfishWorker() { stockfish = new Worker("stockfish.js#stockfish.wasm"); stockfish.onmessage = function (line) { output += line.data + '\n'; m.redraw(); }; } /* const onFinishDownload = (data) => { if (!data) { stockfish_state = 'FAILED'; m.redraw(); return; } loadStockfish({ wasmBinary: data }) .then(_stockfish => { stockfish = _stockfish; stockfish_state = 'READY'; stockfish.addMessageListener((line) => { output += line + '\n'; m.redraw(); }); }) .catch(e => { stockfish_state = 'FAILED'; throw e; }) .finally(() => m.redraw()); }; */ const oninit = () => { stockfish_state = 'READY'; }; const view = () => { const is_ready = stockfish_state == 'READY'; // return m("main", [ m("div#input", [ m("input#command", { placeholder: 'Input UCI Command Here', disabled: !is_ready, onkeyup: (e) => { if (e.keyCode != 13) { e.redraw = false; return; } sendCommand(); } }), m("span#send", { disabled: !is_ready, onclick: sendCommand }, 'SEND'), m("select#examples", { disabled: !is_ready, onchange: (e) => { $("#command").value = e.target.value; window.setTimeout(sendCommand, 10); } }, [ m("option", { value: '' }, "-- EXAMPLE --"), ...examples.map((ex, index) => m("option", { value: ex }, ex)), ]) ]), m("div#misc", [ //m("div", ['- download: ', m(RequestProgress, { url: './stockfish.wasm', onFinishDownload })]), m("div", `- stockfish_state: ${stockfish_state}`), m("div", [ m("span", { style: 'margin-right: 5px;' }, '- tail_mode:'), m("span", { style: 'cursor: pointer;', onclick: () => { tail_mode = !tail_mode; scrollOutput(); } }, [ tail_mode ? '[x]' : '[ ]' ]), ]), ]), m("div#output", { onupdate: scrollOutput }, m("pre", output)) ]); }; //loadStockfish(); loadStockfishWorker(); return { oninit, view }; }; if (!isSupported()) { window.alert("Your browser is not supported. For more information, please take a look at https://github.com/hi-ogawa/Stockfish/wiki."); } else { m.mount($("#root"), App); } </script> </body>