I'm trying to create a chess engine eval bar for React. For this I've added stockfish.js to my project and using the web worker api, initialized the engine inside a useEffect that gets re-rendered each time the fen changes. The problem is, if I change the fen before it reaches the max depth, the engine keeps switching back and forth between the new and the old position and eventually gives the evaluation for the old fen position. This totally breaks the functionality of the engine, because it should always analyze the last position given to it. I tried using the stop() method but it just wouldn't work. How can I make the engine forget the old fen and just focus on the new position each time the fen updates? Here's the code:
engine.js:
class Engine {
constructor() {
this.stockfish = new Worker("/stockfish.js");
this.onMessage = callback => {
this.stockfish.addEventListener("message", e => {
const data = e.data;
console.log(data);
const depthMatch = data?.match(/info depth (\d+)/);
const depth = depthMatch ? parseInt(depthMatch[1]) : null;
const cpMatch = data?.match(/score cp (-?\d+)/);
const cp = cpMatch ? parseInt(cpMatch[1]) : null;
const bestMove = data?.match(/bestmove\s+(\S+)/)?.[1];
callback({ data, depth, cp, bestMove });
});
};
this.stockfish.postMessage("uci");
this.stockfish.postMessage("isready");
}
evaluatePosition(fen, depth) {
this.stockfish.postMessage(`position fen ${fen}`);
this.stockfish.postMessage(`go depth ${depth}`);
}
new() {
this.stockfish.postMessage("ucinewgame");
}
stop() {
this.stockfish.postMessage("stop");
}
quit() {
this.stockfish.postMessage("quit");
}
}
export default Engine;
EvalBar.js:
import { useEffect, useState } from "react";
import Engine from "@/utils/engine";
export default function EvalBar({ fen, on, turn }) {
const [depth, setDepth] = useState(0);
const [evalNum, setEvalNum] = useState(0);
const [barWidth, setBarWidth] = useState(164);
useEffect(() => {
const engine = new Engine();
engine.evaluatePosition(fen, 20);
engine.onMessage(({ depth, cp }) => {
if (depth) {
setDepth(depth);
}
if (cp) {
const num = cp / 100;
if (turn === "w") {
setEvalNum(num.toFixed(1));
setBarWidth(cp / 5 + 163);
} else if (turn === "b") {
setEvalNum((num * -1).toFixed(1));
setBarWidth((cp * -1) / 5 + 163);
}
}
});
}, [fen]);
return on ? (
<>
<p className="eval-depth">D{depth}</p>
<p className="eval-num">{evalNum}</p>
<div className="black-bar"></div>
<div className="white-bar" style={{ height: `${barWidth}px` }}></div>
<div className="half-marker"></div>
</>
) : null;
}
Solved (thanks to @Bob and @Robert Moore and @pmoleri) by calling the engine class in another useEffect and then storing it in a useState for later use:
import { useEffect, useState } from "react";
import Engine from "@/utils/engine";
export default function EvalBar({ fen, on, turn }) {
const [depth, setDepth] = useState(0);
const [evalNum, setEvalNum] = useState(0);
const [barWidth, setBarWidth] = useState(164);
const [engine, setEngine] = useState();
useEffect(() => {
const engine = new Engine();
setEngine(engine);
}, []);
useEffect(() => {
engine?.evaluatePosition(fen, 20);
engine?.onMessage(({ depth, cp }) => {
if (depth) {
setDepth(depth);
}
if (cp) {
const num = cp / 100;
if (turn === "w") {
setEvalNum(num.toFixed(1));
setBarWidth(cp / 5 + 163);
} else if (turn === "b") {
setEvalNum((num * -1).toFixed(1));
setBarWidth((cp * -1) / 5 + 163);
}
}
});
}, [fen]);
return on ? (
<>
<p className="eval-depth">D{depth}</p>
<p className="eval-num">{evalNum}</p>
<div className="black-bar"></div>
<div className="white-bar" style={{ height: `${barWidth}px` }}></div>
<div className="half-marker"></div>
</>
) : null;
}