refacto : chess engine worker
This commit is contained in:
@@ -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<UciEngine> => {
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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<UciEngine> {
|
||||
const worker = getEngineWorker("engines/stockfish-11.js");
|
||||
|
||||
return UciEngine.create(EngineName.Stockfish11, worker);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<UciEngine> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<UciEngine> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<void>;
|
||||
|
||||
constructor(
|
||||
engineName: EngineName,
|
||||
enginePath: string,
|
||||
customEngineInit?: () => Promise<void>
|
||||
) {
|
||||
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<void> {
|
||||
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<void>
|
||||
): Promise<UciEngine> {
|
||||
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) {
|
||||
|
||||
17
src/lib/engine/worker.ts
Normal file
17
src/lib/engine/worker.ts
Normal file
@@ -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;
|
||||
};
|
||||
6
src/types/engine.ts
Normal file
6
src/types/engine.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export interface EngineWorker {
|
||||
uci(command: string): void;
|
||||
listen: (data: string) => void;
|
||||
terminate?: () => void;
|
||||
setNnueBuffer?: (data: Uint8Array, index?: number) => void;
|
||||
}
|
||||
Reference in New Issue
Block a user