refacto : chess engine worker

This commit is contained in:
GuillaumeSD
2024-12-28 21:46:55 +01:00
parent fc4871ce2b
commit 2db361e2f6
9 changed files with 88 additions and 54 deletions

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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
View 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;
};