diff --git a/public/engines/stockfish-16.1/nn-b1a57edbea57.nnue b/public/engines/stockfish-16.1/nn-b1a57edbea57.nnue deleted file mode 100644 index bac8c2c..0000000 Binary files a/public/engines/stockfish-16.1/nn-b1a57edbea57.nnue and /dev/null differ diff --git a/public/engines/stockfish-16.1/nn-baff1ede1f90.nnue b/public/engines/stockfish-16.1/nn-baff1ede1f90.nnue deleted file mode 100644 index 6469b88..0000000 Binary files a/public/engines/stockfish-16.1/nn-baff1ede1f90.nnue and /dev/null differ diff --git a/src/hooks/useEngine.ts b/src/hooks/useEngine.ts index f1a1680..04b2e07 100644 --- a/src/hooks/useEngine.ts +++ b/src/hooks/useEngine.ts @@ -16,30 +16,28 @@ export const useEngine = (engineName: EngineName | undefined) => { return; } - const engine = pickEngine(engineName); - engine.init().then(() => { - setEngine(engine); + pickEngine(engineName).then((newEngine) => { + setEngine((prev) => { + prev?.shutdown(); + return newEngine; + }); }); - - return () => { - engine.shutdown(); - }; }, [engineName]); return engine; }; -const pickEngine = (engine: EngineName): UciEngine => { +const pickEngine = (engine: EngineName): Promise => { switch (engine) { case EngineName.Stockfish16_1: - return new Stockfish16_1(false); + return Stockfish16_1.create(false); case EngineName.Stockfish16_1Lite: - return new Stockfish16_1(true); + return Stockfish16_1.create(true); case EngineName.Stockfish16: - return new Stockfish16(false); + return Stockfish16.create(false); case EngineName.Stockfish16NNUE: - return new Stockfish16(true); + return Stockfish16.create(true); case EngineName.Stockfish11: - return new Stockfish11(); + return Stockfish11.create(); } }; diff --git a/src/lib/engine/stockfish11.ts b/src/lib/engine/stockfish11.ts index 8ce2997..8897a34 100644 --- a/src/lib/engine/stockfish11.ts +++ b/src/lib/engine/stockfish11.ts @@ -1,8 +1,11 @@ import { EngineName } from "@/types/enums"; import { UciEngine } from "./uciEngine"; +import { getEngineWorker } from "./worker"; -export class Stockfish11 extends UciEngine { - constructor() { - super(EngineName.Stockfish11, "engines/stockfish-11.js"); +export class Stockfish11 { + public static async create(): Promise { + const worker = getEngineWorker("engines/stockfish-11.js"); + + return UciEngine.create(EngineName.Stockfish11, worker); } } diff --git a/src/lib/engine/stockfish16.ts b/src/lib/engine/stockfish16.ts index 541c55c..cf349db 100644 --- a/src/lib/engine/stockfish16.ts +++ b/src/lib/engine/stockfish16.ts @@ -1,9 +1,10 @@ import { EngineName } from "@/types/enums"; import { UciEngine } from "./uciEngine"; import { isMultiThreadSupported, isWasmSupported } from "./shared"; +import { getEngineWorker } from "./worker"; -export class Stockfish16 extends UciEngine { - constructor(nnue?: boolean) { +export class Stockfish16 { + public static async create(nnue?: boolean): Promise { if (!isWasmSupported()) { throw new Error("Stockfish 16 is not supported"); } @@ -15,13 +16,17 @@ export class Stockfish16 extends UciEngine { ? "engines/stockfish-16/stockfish-nnue-16.js" : "engines/stockfish-16/stockfish-nnue-16-single.js"; - const customEngineInit = async () => { - await this.sendCommands( + const customEngineInit = async ( + sendCommands: UciEngine["sendCommands"] + ) => { + await sendCommands( [`setoption name Use NNUE value ${!!nnue}`, "isready"], "readyok" ); }; - super(EngineName.Stockfish16, enginePath, customEngineInit); + const worker = getEngineWorker(enginePath); + + return UciEngine.create(EngineName.Stockfish16, worker, customEngineInit); } } diff --git a/src/lib/engine/stockfish16_1.ts b/src/lib/engine/stockfish16_1.ts index 7dbd451..137752b 100644 --- a/src/lib/engine/stockfish16_1.ts +++ b/src/lib/engine/stockfish16_1.ts @@ -1,9 +1,10 @@ import { EngineName } from "@/types/enums"; import { UciEngine } from "./uciEngine"; import { isMultiThreadSupported, isWasmSupported } from "./shared"; +import { getEngineWorker } from "./worker"; -export class Stockfish16_1 extends UciEngine { - constructor(lite?: boolean) { +export class Stockfish16_1 { + public static async create(lite?: boolean): Promise { if (!isWasmSupported()) { throw new Error("Stockfish 16.1 is not supported"); } @@ -14,10 +15,12 @@ export class Stockfish16_1 extends UciEngine { const enginePath = `engines/stockfish-16.1/stockfish-16.1${ lite ? "-lite" : "" }${multiThreadIsSupported ? "" : "-single"}.js`; + const engineName = lite + ? EngineName.Stockfish16_1Lite + : EngineName.Stockfish16_1; - super( - lite ? EngineName.Stockfish16_1Lite : EngineName.Stockfish16_1, - enginePath - ); + const worker = getEngineWorker(enginePath); + + return UciEngine.create(engineName, worker); } } diff --git a/src/lib/engine/uciEngine.ts b/src/lib/engine/uciEngine.ts index 207e22f..7c2a41c 100644 --- a/src/lib/engine/uciEngine.ts +++ b/src/lib/engine/uciEngine.ts @@ -13,33 +13,36 @@ import { computeAccuracy } from "./helpers/accuracy"; import { getWhoIsCheckmated } from "../chess"; import { getLichessEval } from "../lichess"; import { getMovesClassification } from "./helpers/moveClassification"; +import { EngineWorker } from "@/types/engine"; -export abstract class UciEngine { - private worker: Worker; +export class UciEngine { + private worker: EngineWorker; private ready = false; private engineName: EngineName; private multiPv = 3; private skillLevel: number | undefined = undefined; - private customEngineInit?: () => Promise; - constructor( - engineName: EngineName, - enginePath: string, - customEngineInit?: () => Promise - ) { + private constructor(engineName: EngineName, worker: EngineWorker) { this.engineName = engineName; - this.worker = new Worker(enginePath); - this.customEngineInit = customEngineInit; - - console.log(`${engineName} created`); + this.worker = worker; } - 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`); + public static async create( + engineName: EngineName, + worker: EngineWorker, + customEngineInit?: ( + sendCommands: UciEngine["sendCommands"] + ) => Promise + ): Promise { + const engine = new UciEngine(engineName, worker); + + await engine.sendCommands(["uci"], "uciok"); + await engine.setMultiPv(engine.multiPv, true); + await customEngineInit?.(engine.sendCommands.bind(engine)); + engine.ready = true; + + console.log(`${engineName} initialized`); + return engine; } private async setMultiPv(multiPv: number, initCase = false) { @@ -88,8 +91,8 @@ export abstract class UciEngine { public shutdown(): void { this.ready = false; - this.worker.postMessage("quit"); - this.worker.terminate(); + this.worker.uci("quit"); + this.worker.terminate?.(); console.log(`${this.engineName} shutdown`); } @@ -101,7 +104,7 @@ export abstract class UciEngine { await this.sendCommands(["stop", "isready"], "readyok"); } - protected async sendCommands( + private async sendCommands( commands: string[], finalMessage: string, onNewMessage?: (messages: string[]) => void @@ -109,18 +112,17 @@ export abstract class UciEngine { return new Promise((resolve) => { const messages: string[] = []; - this.worker.onmessage = (event) => { - const messageData: string = event.data; - messages.push(messageData); + this.worker.listen = (data) => { + messages.push(data); onNewMessage?.(messages); - if (messageData.startsWith(finalMessage)) { + if (data.startsWith(finalMessage)) { resolve(messages); } }; for (const command of commands) { - this.worker.postMessage(command); + this.worker.uci(command); } }); } @@ -138,7 +140,7 @@ export abstract class UciEngine { this.ready = false; await this.sendCommands(["ucinewgame", "isready"], "readyok"); - this.worker.postMessage("position startpos"); + this.worker.uci("position startpos"); const positions: PositionEval[] = []; for (const fen of fens) { diff --git a/src/lib/engine/worker.ts b/src/lib/engine/worker.ts new file mode 100644 index 0000000..922bf41 --- /dev/null +++ b/src/lib/engine/worker.ts @@ -0,0 +1,17 @@ +import { EngineWorker } from "@/types/engine"; + +export const getEngineWorker = (enginePath: string): EngineWorker => { + const worker = new Worker(enginePath); + + const engineWorker: EngineWorker = { + uci: (command: string) => worker.postMessage(command), + listen: () => null, + terminate: () => worker.terminate(), + }; + + worker.onmessage = (event) => { + engineWorker.listen(event.data); + }; + + return engineWorker; +}; diff --git a/src/types/engine.ts b/src/types/engine.ts new file mode 100644 index 0000000..b83f32f --- /dev/null +++ b/src/types/engine.ts @@ -0,0 +1,6 @@ +export interface EngineWorker { + uci(command: string): void; + listen: (data: string) => void; + terminate?: () => void; + setNnueBuffer?: (data: Uint8Array, index?: number) => void; +}