chess engine keeps calculating old fen position once a new fen is given

105 views Asked by At

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;
}
2

There are 2 answers

0
Daniel Mohebi On BEST ANSWER

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;
}
3
Bob On

You should add return to useEffect, it would shut it down when fen changes

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);
        }
      }
    });
    **return engine.stop**
  }, [fen]);

It should help your problem, but if that will not help change your engine to useState and store it in component