diff --git a/firebase.json b/firebase.json index 6583d11..7de8874 100644 --- a/firebase.json +++ b/firebase.json @@ -21,7 +21,7 @@ ] }, { - "source": "/engines/stockfish-wasm/stockfish-nnue-16.js", + "source": "/engines/**", "headers": [ { "key": "Cross-Origin-Embedder-Policy", @@ -30,6 +30,19 @@ { "key": "Cross-Origin-Opener-Policy", "value": "same-origin" + }, + { + "key": "Cache-Control", + "value": "public, max-age=3600, immutable" + } + ] + }, + { + "source": "/engines/stockfish-16-wasm/nn-5af11540bbfe.nnue", + "headers": [ + { + "key": "Cache-Control", + "value": "public, max-age=31536000, immutable" } ] }, diff --git a/next.config.js b/next.config.js index 64106d3..e03c8dd 100644 --- a/next.config.js +++ b/next.config.js @@ -26,7 +26,7 @@ const nextConfig = (phase) => ], }, { - source: "/engines/stockfish-wasm/stockfish-nnue-16.js", + source: "/engines/:blob*", headers: [ { key: "Cross-Origin-Embedder-Policy", diff --git a/public/engines/stockfish.js b/public/engines/stockfish-11.js similarity index 100% rename from public/engines/stockfish.js rename to public/engines/stockfish-11.js diff --git a/public/engines/stockfish-16-wasm/nn-5af11540bbfe.nnue b/public/engines/stockfish-16-wasm/nn-5af11540bbfe.nnue new file mode 100644 index 0000000..6690c9e Binary files /dev/null and b/public/engines/stockfish-16-wasm/nn-5af11540bbfe.nnue differ diff --git a/public/engines/stockfish-16-wasm/stockfish-nnue-16-single.js b/public/engines/stockfish-16-wasm/stockfish-nnue-16-single.js new file mode 100644 index 0000000..a65eee4 --- /dev/null +++ b/public/engines/stockfish-16-wasm/stockfish-nnue-16-single.js @@ -0,0 +1,14 @@ +/*! + * Stockfish.js 16 (c) 2023, Chess.com, LLC + * https://github.com/nmrugg/stockfish.js + * License: GPLv3 + * + * Based on stockfish.wasm (c) + * Niklas Fiekas + * Hiroshi Ogawa + * https://github.com/niklasf/stockfish.wasm + * https://github.com/hi-ogawa/Stockfish + * + * Based on Stockfish (c) T. Romstad, M. Costalba, J. Kiiski, G. Linscott and other contributors. + * https://github.com/official-stockfish/Stockfish + */!function(){var t,n,r,e,o,a;function i(){function e(e){var i,D,I,n,t;e=e||{},(i=i||(void 0!==e?e:{})).ready=new Promise(function(e,n){D=e,I=n}),"undefined"==typeof XMLHttpRequest&&(global.XMLHttpRequest=function(){var t,r={open:function(e,n){t=n},send:function(){require("fs").readFile(t,function(e,n){r.readyState=4,e?(console.error(e),r.status=404,r.onerror(e)):(r.status=200,r.response=n,r.onreadystatechange(),r.onload())})}};return r}),"undefined"!=typeof global&&"[object process]"===Object.prototype.toString.call(global.process)&&"undefined"!=typeof fetch&&(fetch=null),i.postCustomMessage=function(e){if("undefined"!=typeof PThread)for(var n of PThread.ba)n.postMessage({cmd:"custom",userData:e})},i.queue=(t=[],{get:async function(){return 0>10,56320|1023&i)))):r+=String.fromCharCode(i)}return r}function C(e){return e?K(R,e,void 0):""}function O(e,n,t,r){if(0>6}else{if(a<=65535){if(r<=t+2)break;n[t++]=224|a>>12}else{if(r<=t+3)break;n[t++]=240|a>>18,n[t++]=128|a>>12&63}n[t++]=128|a>>6&63}n[t++]=128|63&a}}n[t]=0}}function J(e){for(var n=0,t=0;t>2]=n,T[e+4>>2]=n/4294967296|0}function de(r,t,o,a,n){function i(e){var n=0,t=0;e&&(t=w.response?w.response.byteLength:0,n=j(t),R.set(new Uint8Array(w.response),n)),T[r+12>>2]=n,U(r+16,t)}if(d=T[r+8>>2]){var e=C(d),u=(u=C(g=r+112))||"GET",s=T[g+52>>2],c=T[g+56>>2],f=!!T[g+60>>2],l=T[g+68>>2],p=T[g+72>>2],d=T[g+76>>2],m=T[g+80>>2],y=T[g+84>>2],g=T[g+88>>2],h=!!(1&s),v=!!(2&s),s=!!(64&s),l=l?C(l):void 0,p=p?C(p):void 0,_=m?C(m):void 0,w=new XMLHttpRequest;if(w.withCredentials=f,w.open(u,e,!s,l,p),s||(w.timeout=c),w.Z=e,w.responseType="arraybuffer",m&&w.overrideMimeType(_),d)for(;(u=T[d>>2])&&(e=T[d+4>>2]);)d+=8,u=C(u),e=C(e),w.setRequestHeader(u,e);pe.push(w),T[r+0>>2]=pe.length,d=y&&g?R.slice(y,y+g):null,w.onload=function(e){i(h&&!v);var n=w.response?w.response.byteLength:0;U(r+24,0),n&&U(r+32,n),x[r+40>>1]=w.readyState,x[r+42>>1]=w.status,w.statusText&&O(w.statusText,R,r+44,64),200<=w.status&&w.status<300?t&&t(r,w,e):o&&o(r,w,e)},w.onerror=function(e){i(h);var n=w.status;U(r+24,0),U(r+32,w.response?w.response.byteLength:0),x[r+40>>1]=w.readyState,x[r+42>>1]=n,o&&o(r,w,e)},w.ontimeout=function(e){o&&o(r,w,e)},w.onprogress=function(e){var n=h&&v&&w.response?w.response.byteLength:0,t=0;h&&v&&(t=j(n),R.set(new Uint8Array(w.response),t)),T[r+12>>2]=t,U(r+16,n),U(r+24,e.loaded-n),U(r+32,e.total),x[r+40>>1]=w.readyState,3<=w.readyState&&0===w.status&&0>1]=w.status,w.statusText&&O(w.statusText,R,r+44,64),a&&a(r,w,e),t&&Ue(t)},w.onreadystatechange=function(e){x[r+40>>1]=w.readyState,2<=w.readyState&&(x[r+42>>1]=w.status),n&&n(r,w,e)};try{w.send(d)}catch(e){o&&o(r,w,e)}}else o(r,0,"no url specified!")}function P(e,n){if(!m)if(n)e();else try{e()}catch(e){if(!(e instanceof He)&&"unwind"!==e)throw e&&"object"==typeof e&&e.stack&&d("exception thrown: "+[e,e.stack]),e}}function me(n,e,t,r){var o=L;if(o){var a=C(T[n+112+64>>2]||T[n+8>>2]);try{var i=o.transaction(["FILES"],"readwrite").objectStore("FILES").put(e,a);i.onsuccess=function(){x[n+40>>1]=4,x[n+42>>1]=200,O("OK",R,n+44,64),t(n,0,a)},i.onerror=function(e){x[n+40>>1]=4,x[n+42>>1]=413,O("Payload Too Large",R,n+44,64),r(n,0,e)}}catch(e){r(n,0,e)}}else r(n,0,"IndexedDB not available!")}var ye,ge={};function he(){if(!ye){var e,n={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:("object"==typeof navigator&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8",_:u||"./this.program"};for(e in ge)void 0===ge[e]?delete n[e]:n[e]=ge[e];var t=[];for(e in n)t.push(e+"="+n[e]);ye=t}return ye}function S(e){return 0==e%4&&(0!=e%100||0==e%400)}function ve(e,n){for(var t=0,r=0;r<=n;t+=e[r++]);return t}var E=[31,29,31,30,31,30,31,31,30,31,30,31],_e=[31,28,31,30,31,30,31,31,30,31,30,31];function we(e,n){for(e=new Date(e.getTime());0r-e.getDate())){e.setDate(e.getDate()+n);break}n-=r-e.getDate()+1,e.setDate(1),t<11?e.setMonth(t+1):(e.setMonth(0),e.setFullYear(e.getFullYear()+1))}return e}function be(e,n,t,r){function o(e,n,t){for(e="number"==typeof e?e.toString():e||"";e.length>2];for(c in r={X:g[r>>2],W:g[r+4>>2],O:g[r+8>>2],N:g[r+12>>2],M:g[r+16>>2],F:g[r+20>>2],P:g[r+24>>2],R:g[r+28>>2],ea:g[r+32>>2],V:g[r+36>>2],Y:f?C(f):""},t=C(t),f={"%c":"%a %b %d %H:%M:%S %Y","%D":"%m/%d/%y","%F":"%Y-%m-%d","%h":"%b","%r":"%I:%M:%S %p","%R":"%H:%M","%T":"%H:%M:%S","%x":"%m/%d/%y","%X":"%H:%M:%S","%Ec":"%c","%EC":"%C","%Ex":"%m/%d/%y","%EX":"%H:%M:%S","%Ey":"%y","%EY":"%Y","%Od":"%d","%Oe":"%e","%OH":"%H","%OI":"%I","%Om":"%m","%OM":"%M","%OS":"%S","%Ou":"%u","%OU":"%U","%OV":"%V","%Ow":"%w","%OW":"%W","%Oy":"%y"})t=t.replace(new RegExp(c,"g"),f[c]);var l,p,d="Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),m="January February March April May June July August September October November December".split(" ");for(c in f={"%a":function(e){return d[e.P].substring(0,3)},"%A":function(e){return d[e.P]},"%b":function(e){return m[e.M].substring(0,3)},"%B":function(e){return m[e.M]},"%C":function(e){return a((e.F+1900)/100|0,2)},"%d":function(e){return a(e.N,2)},"%e":function(e){return o(e.N,2," ")},"%g":function(e){return s(e).toString().substring(2)},"%G":s,"%H":function(e){return a(e.O,2)},"%I":function(e){return 0==(e=e.O)?e=12:12n?0:(y.set(c,e),c.length-1)}function Se(e){try{e()}catch(e){w(e)}}var M=0,F=null,Ee=0,Me=[],Fe={},De={},Ie=0,Ae=null,Ce=[],Oe=[];function Re(t){var e,r={};for(e in t)!function(e){var n=t[e];r[e]="function"==typeof n?function(){Me.push(e);try{return n.apply(null,arguments)}catch(e){if(-1===e.message.indexOf("unreachable"))throw e}finally{m||(Me.pop()!==e&&w("Assertion failed: undefined"),F&&1===M&&0===Me.length&&(M=0,Se(i._asyncify_stop_unwind),"undefined"!=typeof Fibers&&Fibers.fa(),Ae)&&(Ae(),Ae=null))}}:n}(e);return r}function xe(e){var t,r,n,o;if(!m)return 0===M?(r=t=!1,e(function(e){var n;!m&&(Ee=e||0,t=!0,r)&&(M=2,Se(function(){i._asyncify_start_rewind(F)}),"undefined"!=typeof Browser&&Browser.T.U&&Browser.T.resume(),n=(0,i.asm[De[g[F+8>>2]]])(),F||(e=Ce,Ce=[],e.forEach(function(e){e(n)})))}),r=!0,t||(M=1,e=j(4108),n=e+12,g[e>>2]=n,g[e+4>>2]=n+4096,n=Me[0],void 0===(o=Fe[n])&&(o=Ie++,Fe[n]=o,De[o]=n),g[e+8>>2]=o,F=e,Se(function(){i._asyncify_start_unwind(F)}),"undefined"!=typeof Browser&&Browser.T.U&&Browser.T.pause())):2===M?(M=0,Se(i._asyncify_stop_rewind),Ue(F),F=null,Oe.forEach(function(e){P(e)})):w("invalid state: "+M),Ee}!function(n){try{var e=indexedDB.open("emscripten_filesystem",1)}catch(e){return n()}e.onupgradeneeded=function(e){(e=e.target.result).objectStoreNames.contains("FILES")&&e.deleteObjectStore("FILES"),e.createObjectStore("FILES")},e.onsuccess=function(e){e=e.target.result,L=e,ie()},e.onerror=function(e){n()}}(function(){L=!1,ie()}),"undefined"!=typeof ENVIRONMENT_IS_FETCH_WORKER&&ENVIRONMENT_IS_FETCH_WORKER||ae();var Te,Le={f:function(){return 0},p:function(){return 0},q:function(){},i:function(e){delete pe[e-1]},a:function(){w()},h:function(e,n){if(0===e)e=Date.now();else{if(1!==e&&4!==e)return g[Pe()>>2]=28,-1;e=le()}return g[n>>2]=e/1e3|0,g[n+4>>2]=e%1e3*1e6|0,0},k:function(){return!l},o:function(e,n,t){R.copyWithin(e,n,n+t)},d:function(){w("OOM")},b:function(t){xe(function(e){var n;n=e,setTimeout(function(){P(n)},t)})},j:function(e,t,n,r,o){function a(e,n){me(e,n.response,function(e){P(function(){p?k.apply(null,[p,e]):t&&t(e)},w)},function(e){P(function(){p?k.apply(null,[p,e]):t&&t(e)},w)})}function i(e){P(function(){y?k.apply(null,[y,e]):o&&o(e)},w)}function u(e){P(function(){d?k.apply(null,[d,e]):n&&n(e)},w)}function s(e){P(function(){m?k.apply(null,[m,e]):r&&r(e)},w)}function c(e){P(function(){p?k.apply(null,[p,e]):t&&t(e)},w)}var f=e+112,l=C(f),p=T[f+36>>2],d=T[f+40>>2],m=T[f+44>>2],y=T[f+48>>2],g=T[f+52>>2],h=!!(4&g),v=!!(32&g),_=!!(16&g),w=!!(64&g);if("EM_IDB_STORE"===l)l=T[f+84>>2],me(e,R.slice(l,l+T[f+88>>2]),c,u);else if("EM_IDB_DELETE"===l){var b=e;var S=c;var E=u;g=L;if(g){f=T[b+112+64>>2];f=C(f=f||T[b+8>>2]);try{var M=g.transaction(["FILES"],"readwrite").objectStore("FILES").delete(f);M.onsuccess=function(e){e=e.target.result,T[b+12>>2]=0,U(b+16,0),U(b+24,0),U(b+32,0),x[b+40>>1]=4,x[b+42>>1]=200,O("OK",R,b+44,64),S(b,0,e)},M.onerror=function(e){x[b+40>>1]=4,x[b+42>>1]=404,O("Not Found",R,b+44,64),E(b,0,e)}}catch(e){E(b,0,e)}}else E(b,0,"IndexedDB not available!")}else if(_){if(v)return 0;de(e,h?a:c,u,s,i)}else{var F=e;var D=c;var I=v?u:h?function(e){de(e,a,u,s,i)}:function(e){de(e,c,u,s,i)};l=L;if(l){g=T[F+112+64>>2];g=C(g=g||T[F+8>>2]);try{var A=l.transaction(["FILES"],"readonly").objectStore("FILES").get(g);A.onsuccess=function(e){var n,t;e.target.result?(n=(e=e.target.result).byteLength||e.length,t=j(n),R.set(new Uint8Array(e),t),T[F+12>>2]=t,U(F+16,n),U(F+24,0),U(F+32,n),x[F+40>>1]=4,x[F+42>>1]=200,O("OK",R,F+44,64),D(F,0,e)):(x[F+40>>1]=4,x[F+42>>1]=404,O("Not Found",R,F+44,64),I(F,0,"no data"))},A.onerror=function(e){x[F+40>>1]=4,x[F+42>>1]=404,O("Not Found",R,F+44,64),I(F,0,e)}}catch(e){I(F,0,e)}}else I(F,0,"IndexedDB not available!")}return e},l:function(){return n=async()=>{var e=await i.queue.get(),n=J(e)+1,t=j(n);return O(e,R,t,n),t},xe(function(e){n().then(e)});var n},u:function(r,o){var a=0;return he().forEach(function(e,n){var t=o+a;for(n=g[r+4*n>>2]=t,t=0;t>0]=e.charCodeAt(t);y[n>>0]=0,a+=e.length+1}),0},v:function(e,n){var t=he(),r=(g[e>>2]=t.length,0);return t.forEach(function(e){r+=e.length+1}),g[n>>2]=r,0},c:function(e){Ye(e)},g:function(){return 0},s:function(e,n,t,r){return e=fe.aa(e),n=fe.$(e,n,t),g[r>>2]=n,0},n:function(){},r:function(e,n,t,r){for(var o=0,a=0;a>2],u=g[n+(8*a+4)>>2],s=0;s>2]=o,0},m:function(){i.pauseQueue()},t:be,e:function(){i.unpauseQueue()}},Ue=(!function(){function n(e){e=Re(e=e.exports),i.asm=e,e=i.asm.w.buffer,i.HEAP8=y=new Int8Array(e),i.HEAP16=new Int16Array(e),i.HEAP32=g=new Int32Array(e),i.HEAPU8=R=new Uint8Array(e),i.HEAPU16=x=new Uint16Array(e),i.HEAPU32=T=new Uint32Array(e),i.HEAPF32=new Float32Array(e),i.HEAPF64=new Float64Array(e),ee.unshift(i.asm.x),ie()}function t(e){n(e.instance)}function r(e){return(f||!B&&!l||"function"!=typeof fetch?Promise.resolve().then(se):fetch(h,{credentials:"same-origin"}).then(function(e){if(e.ok)return e.arrayBuffer();throw"failed to load wasm binary file at '"+h+"'"}).catch(se)).then(function(e){return WebAssembly.instantiate(e,o)}).then(e,function(e){d("failed to asynchronously prepare wasm: "+e),w(e)})}var o={a:Le};if(ae(),i.instantiateWasm)try{var e=i.instantiateWasm(o,n);return Re(e)}catch(e){return d("Module.instantiateWasm callback failed with error: "+e)}(f||"function"!=typeof WebAssembly.instantiateStreaming||ue()||"function"!=typeof fetch?r(t):fetch(h,{credentials:"same-origin"}).then(function(e){return WebAssembly.instantiateStreaming(e,o).then(t,function(e){return d("wasm streaming compile failed: "+e),d("falling back to ArrayBuffer instantiation"),r(t)})})).catch(I)}(),i.___wasm_call_ctors=function(){return(i.___wasm_call_ctors=i.asm.x).apply(null,arguments)},i._main=function(){return(i._main=i.asm.y).apply(null,arguments)},i._free=function(){return(Ue=i._free=i.asm.z).apply(null,arguments)}),j=(i._stop=function(){return(i._stop=i.asm.A).apply(null,arguments)},i._ponderhit=function(){return(i._ponderhit=i.asm.B).apply(null,arguments)},i._malloc=function(){return(j=i._malloc=i.asm.C).apply(null,arguments)}),Pe=i.___errno_location=function(){return(Pe=i.___errno_location=i.asm.D).apply(null,arguments)},je=i.stackAlloc=function(){return(je=i.stackAlloc=i.asm.E).apply(null,arguments)},k=i.dynCall_vi=function(){return(k=i.dynCall_vi=i.asm.G).apply(null,arguments)},ke=i.dynCall_v=function(){return(ke=i.dynCall_v=i.asm.H).apply(null,arguments)};function He(e){this.name="ExitStatus",this.message="Program terminated with exit("+e+")",this.status=e}function Ne(a){function e(){if(!Te&&(Te=!0,i.calledRun=!0,!m)){if(b(ee),b(ne),D(i),i.onRuntimeInitialized&&i.onRuntimeInitialized(),qe){var n=a,e=i._main,t=(n=n||[]).length+1,r=je(4*(t+1));g[r>>2]=Q(u);for(var o=1;o>2)+o]=Q(n[o-1]);g[(r>>2)+t]=0;try{Ye(e(t,r))}catch(e){e instanceof He||"unwind"==e||((n=e)&&"object"==typeof e&&e.stack&&(n=[e,e.stack]),d("exception thrown: "+n),s(1,e))}}if(i.postRun)for("function"==typeof i.postRun&&(i.postRun=[i.postRun]);i.postRun.length;)n=i.postRun.shift(),te.unshift(n);b(te)}}if(a=a||N,!(0 { useEffect(() => { if (!engineName) return; + if (engineName.includes("stockfish_16") && !Stockfish16.isSupported()) { + return; + } + const engine = pickEngine(engineName); engine.init().then(() => { setEngine(engine); @@ -25,6 +30,12 @@ export const useEngine = (engineName: EngineName | undefined) => { const pickEngine = (engine: EngineName): UciEngine => { switch (engine) { case EngineName.Stockfish16: - return new Stockfish16(); + return new Stockfish16(false); + case EngineName.Stockfish16NNUE: + return new Stockfish16(true); + case EngineName.Stockfish11: + return new Stockfish11(); + default: + throw new Error(`Engine ${engine} does not exist ?!`); } }; diff --git a/src/lib/engine/stockfish11.ts b/src/lib/engine/stockfish11.ts new file mode 100644 index 0000000..8ce2997 --- /dev/null +++ b/src/lib/engine/stockfish11.ts @@ -0,0 +1,8 @@ +import { EngineName } from "@/types/enums"; +import { UciEngine } from "./uciEngine"; + +export class Stockfish11 extends UciEngine { + constructor() { + super(EngineName.Stockfish11, "engines/stockfish-11.js"); + } +} diff --git a/src/lib/engine/stockfish16.ts b/src/lib/engine/stockfish16.ts index 411a2e0..ee6318f 100644 --- a/src/lib/engine/stockfish16.ts +++ b/src/lib/engine/stockfish16.ts @@ -2,17 +2,29 @@ import { EngineName } from "@/types/enums"; import { UciEngine } from "./uciEngine"; export class Stockfish16 extends UciEngine { - constructor() { - const isWasmSupported = Stockfish16.isWasmSupported(); + constructor(nnue?: boolean) { + if (!Stockfish16.isSupported()) { + throw new Error("Stockfish 16 is not supported"); + } - const enginePath = isWasmSupported - ? "engines/stockfish-wasm/stockfish-nnue-16.js" - : "engines/stockfish.js"; + const isMultiThreadSupported = Stockfish16.isMultiThreadSupported(); + if (!isMultiThreadSupported) console.log("Single thread mode"); - super(EngineName.Stockfish16, enginePath); + const enginePath = isMultiThreadSupported + ? "engines/stockfish-16-wasm/stockfish-nnue-16.js" + : "engines/stockfish-16-wasm/stockfish-nnue-16-single.js"; + + const customEngineInit = async () => { + await this.sendCommands( + [`setoption name Use NNUE value ${!!nnue}`, "isready"], + "readyok" + ); + }; + + super(EngineName.Stockfish16, enginePath, customEngineInit); } - public static isWasmSupported() { + public static isSupported() { return ( typeof WebAssembly === "object" && WebAssembly.validate( @@ -20,4 +32,8 @@ export class Stockfish16 extends UciEngine { ) ); } + + public static isMultiThreadSupported() { + return SharedArrayBuffer !== undefined; + } } diff --git a/src/lib/engine/uciEngine.ts b/src/lib/engine/uciEngine.ts index 48d2e15..273ffbc 100644 --- a/src/lib/engine/uciEngine.ts +++ b/src/lib/engine/uciEngine.ts @@ -20,10 +20,16 @@ export abstract class UciEngine { private engineName: EngineName; private multiPv = 3; private skillLevel: number | undefined = undefined; + private customEngineInit?: () => Promise; - constructor(engineName: EngineName, enginePath: string) { + constructor( + engineName: EngineName, + enginePath: string, + customEngineInit?: () => Promise + ) { this.engineName = engineName; this.worker = new Worker(enginePath); + this.customEngineInit = customEngineInit; console.log(`${engineName} created`); } @@ -31,6 +37,7 @@ export abstract class UciEngine { public async init(): Promise { await this.sendCommands(["uci"], "uciok"); await this.setMultiPv(this.multiPv, true); + await this.customEngineInit?.(); this.ready = true; console.log(`${this.engineName} initialized`); } @@ -94,7 +101,7 @@ export abstract class UciEngine { await this.sendCommands(["stop", "isready"], "readyok"); } - private async sendCommands( + protected async sendCommands( commands: string[], finalMessage: string, onNewMessage?: (messages: string[]) => void diff --git a/src/sections/analysis/reviewPanelBody/index.tsx b/src/sections/analysis/reviewPanelBody/index.tsx index 404c113..769520b 100644 --- a/src/sections/analysis/reviewPanelBody/index.tsx +++ b/src/sections/analysis/reviewPanelBody/index.tsx @@ -1,11 +1,15 @@ import { Icon } from "@iconify/react"; import { Grid, List, Typography } from "@mui/material"; import { useAtomValue } from "jotai"; -import { boardAtom, engineMultiPvAtom, gameAtom } from "../states"; +import { + boardAtom, + engineMultiPvAtom, + engineNameAtom, + gameAtom, +} from "../states"; import LineEvaluation from "./lineEvaluation"; import { useCurrentPosition } from "../hooks/useCurrentPosition"; import { LineEval } from "@/types/eval"; -import { EngineName } from "@/types/enums"; import EngineSettingsButton from "@/sections/engineSettings/engineSettingsButton"; import Accuracies from "./accuracies"; import MoveInfo from "./moveInfo"; @@ -13,7 +17,8 @@ import Opening from "./opening"; export default function ReviewPanelBody() { const linesNumber = useAtomValue(engineMultiPvAtom); - const position = useCurrentPosition(EngineName.Stockfish16); + const engineName = useAtomValue(engineNameAtom); + const position = useCurrentPosition(engineName); const game = useAtomValue(gameAtom); const board = useAtomValue(boardAtom); diff --git a/src/sections/analysis/reviewPanelHeader/analyzeButton.tsx b/src/sections/analysis/reviewPanelHeader/analyzeButton.tsx index fb3e2c8..200c1b1 100644 --- a/src/sections/analysis/reviewPanelHeader/analyzeButton.tsx +++ b/src/sections/analysis/reviewPanelHeader/analyzeButton.tsx @@ -2,6 +2,7 @@ import { Icon } from "@iconify/react"; import { engineDepthAtom, engineMultiPvAtom, + engineNameAtom, evaluationProgressAtom, gameAtom, gameEvalAtom, @@ -11,11 +12,11 @@ import { getEvaluateGameParams } from "@/lib/chess"; import { useGameDatabase } from "@/hooks/useGameDatabase"; import { LoadingButton } from "@mui/lab"; import { useEngine } from "@/hooks/useEngine"; -import { EngineName } from "@/types/enums"; import { logAnalyticsEvent } from "@/lib/firebase"; export default function AnalyzeButton() { - const engine = useEngine(EngineName.Stockfish16); + const engineName = useAtomValue(engineNameAtom); + const engine = useEngine(engineName); const [evaluationProgress, setEvaluationProgress] = useAtom( evaluationProgressAtom ); @@ -49,7 +50,7 @@ export default function AnalyzeButton() { } logAnalyticsEvent("analyze_game", { - engine: EngineName.Stockfish16, + engine: engineName, depth: engineDepth, multiPv: engineMultiPv, nbPositions: params.fens.length, diff --git a/src/sections/analysis/states.ts b/src/sections/analysis/states.ts index e9e01f2..e8b89a0 100644 --- a/src/sections/analysis/states.ts +++ b/src/sections/analysis/states.ts @@ -1,3 +1,4 @@ +import { EngineName } from "@/types/enums"; import { CurrentPosition, GameEval } from "@/types/eval"; import { Chess } from "chess.js"; import { atom } from "jotai"; @@ -11,6 +12,7 @@ export const boardOrientationAtom = atom(true); export const showBestMoveArrowAtom = atom(true); export const showPlayerMoveIconAtom = atom(true); +export const engineNameAtom = atom(EngineName.Stockfish16); export const engineDepthAtom = atom(16); export const engineMultiPvAtom = atom(3); export const evaluationProgressAtom = atom(0); diff --git a/src/sections/engineSettings/engineSettingsDialog.tsx b/src/sections/engineSettings/engineSettingsDialog.tsx index 08f5517..e3c484f 100644 --- a/src/sections/engineSettings/engineSettingsDialog.tsx +++ b/src/sections/engineSettings/engineSettingsDialog.tsx @@ -14,9 +14,15 @@ import { Typography, Grid, } from "@mui/material"; -import { engineDepthAtom, engineMultiPvAtom } from "../analysis/states"; +import { + engineNameAtom, + engineDepthAtom, + engineMultiPvAtom, +} from "../analysis/states"; import ArrowOptions from "./arrowOptions"; import { useAtomLocalStorage } from "@/hooks/useAtomLocalStorage"; +import { Stockfish16 } from "@/lib/engine/stockfish16"; +import { useEffect } from "react"; interface Props { open: boolean; @@ -32,6 +38,16 @@ export default function EngineSettingsDialog({ open, onClose }: Props) { "engine-multi-pv", engineMultiPvAtom ); + const [engineName, setEngineName] = useAtomLocalStorage( + "engine-name", + engineNameAtom + ); + + useEffect(() => { + if (!Stockfish16.isSupported()) { + setEngineName(EngineName.Stockfish11); + } + }, [setEngineName]); return ( @@ -40,8 +56,10 @@ export default function EngineSettingsDialog({ open, onClose }: Props) { - Stockfish 16 is the only engine available now, more engine choices - will come soon ! + Stockfish 16 Lite (HCE) is the default engine. It offers the best + balance between speed and strength. Stockfish 16 is the strongest + engine available, but please note that it requires a one time download + of 40MB. } - value={EngineName.Stockfish16} - disabled={true} - sx={{ width: 200 }} + value={engineName} + onChange={(e) => setEngineName(e.target.value as EngineName)} + sx={{ width: 280, maxWidth: "100%" }} > {Object.values(EngineName).map((engine) => ( - + {engineLabel[engine]} ))} @@ -104,5 +130,7 @@ export default function EngineSettingsDialog({ open, onClose }: Props) { } const engineLabel: Record = { - [EngineName.Stockfish16]: "Stockfish 16", + [EngineName.Stockfish16]: "Stockfish 16 Lite (HCE)", + [EngineName.Stockfish16NNUE]: "Stockfish 16 (40MB download)", + [EngineName.Stockfish11]: "Stockfish 11", }; diff --git a/src/sections/play/board.tsx b/src/sections/play/board.tsx index abf0921..a295b95 100644 --- a/src/sections/play/board.tsx +++ b/src/sections/play/board.tsx @@ -5,11 +5,12 @@ import { playerColorAtom, isGameInProgressAtom, gameDataAtom, + enginePlayNameAtom, } from "./states"; import { useChessActions } from "@/hooks/useChessActions"; import { useEffect, useMemo } from "react"; import { useScreenSize } from "@/hooks/useScreenSize"; -import { Color, EngineName } from "@/types/enums"; +import { Color } from "@/types/enums"; import { useEngine } from "@/hooks/useEngine"; import { uciMoveParams } from "@/lib/chess"; import Board from "@/components/board"; @@ -17,7 +18,8 @@ import { useGameData } from "@/hooks/useGameData"; export default function BoardContainer() { const screenSize = useScreenSize(); - const engine = useEngine(EngineName.Stockfish16); + const engineName = useAtomValue(enginePlayNameAtom); + const engine = useEngine(engineName); const game = useAtomValue(gameAtom); const playerColor = useAtomValue(playerColorAtom); const { makeMove: makeGameMove } = useChessActions(gameAtom); diff --git a/src/sections/play/gameSettings/gameSettingsDialog.tsx b/src/sections/play/gameSettings/gameSettingsDialog.tsx index e5ddf6a..2c59fa4 100644 --- a/src/sections/play/gameSettings/gameSettingsDialog.tsx +++ b/src/sections/play/gameSettings/gameSettingsDialog.tsx @@ -24,10 +24,13 @@ import { playerColorAtom, isGameInProgressAtom, gameAtom, + enginePlayNameAtom, } from "../states"; import { useChessActions } from "@/hooks/useChessActions"; import { playGameStartSound } from "@/lib/sounds"; import { logAnalyticsEvent } from "@/lib/firebase"; +import { Stockfish16 } from "@/lib/engine/stockfish16"; +import { useEffect } from "react"; interface Props { open: boolean; @@ -39,6 +42,10 @@ export default function GameSettingsDialog({ open, onClose }: Props) { "engine-skill-level", engineSkillLevelAtom ); + const [engineName, setEngineName] = useAtomLocalStorage( + "engine-play-name", + enginePlayNameAtom + ); const [playerColor, setPlayerColor] = useAtom(playerColorAtom); const setIsGameInProgress = useSetAtom(isGameInProgressAtom); const { reset: resetGame } = useChessActions(gameAtom); @@ -55,12 +62,18 @@ export default function GameSettingsDialog({ open, onClose }: Props) { setIsGameInProgress(true); logAnalyticsEvent("play_game", { - engine: EngineName.Stockfish16, + engine: engineName, skillLevel, playerColor, }); }; + useEffect(() => { + if (!Stockfish16.isSupported()) { + setEngineName(EngineName.Stockfish11); + } + }, [setEngineName]); + return ( @@ -68,8 +81,10 @@ export default function GameSettingsDialog({ open, onClose }: Props) { - Stockfish 16 is the only engine available now, more engine choices - will come soon ! + Stockfish 16 Lite (HCE) is the default engine. It offers the best + balance between speed and strength. Stockfish 16 is the strongest + engine available, but please note that it requires a one time download + of 40MB. } - value={EngineName.Stockfish16} - disabled={true} - sx={{ width: 200 }} + value={engineName} + onChange={(e) => setEngineName(e.target.value as EngineName)} + sx={{ width: 280, maxWidth: "100%" }} > {Object.values(EngineName).map((engine) => ( - + {engineLabel[engine]} ))} @@ -145,5 +168,7 @@ export default function GameSettingsDialog({ open, onClose }: Props) { } const engineLabel: Record = { - [EngineName.Stockfish16]: "Stockfish 16", + [EngineName.Stockfish16]: "Stockfish 16 Lite (HCE)", + [EngineName.Stockfish16NNUE]: "Stockfish 16 (40MB download)", + [EngineName.Stockfish11]: "Stockfish 11", }; diff --git a/src/sections/play/states.ts b/src/sections/play/states.ts index e92daac..290a914 100644 --- a/src/sections/play/states.ts +++ b/src/sections/play/states.ts @@ -1,4 +1,4 @@ -import { Color } from "@/types/enums"; +import { Color, EngineName } from "@/types/enums"; import { CurrentPosition } from "@/types/eval"; import { Chess } from "chess.js"; import { atom } from "jotai"; @@ -6,5 +6,6 @@ import { atom } from "jotai"; export const gameAtom = atom(new Chess()); export const gameDataAtom = atom({}); export const playerColorAtom = atom(Color.White); +export const enginePlayNameAtom = atom(EngineName.Stockfish16); export const engineSkillLevelAtom = atom(1); export const isGameInProgressAtom = atom(false); diff --git a/src/types/enums.ts b/src/types/enums.ts index dee0780..9af50c9 100644 --- a/src/types/enums.ts +++ b/src/types/enums.ts @@ -6,6 +6,8 @@ export enum GameOrigin { export enum EngineName { Stockfish16 = "stockfish_16", + Stockfish16NNUE = "stockfish_16_nnue", + Stockfish11 = "stockfish_11", } export enum MoveClassification {