2

I can't for the life of me seem to be able to set stockfish to do anything except deliver crushing blows at level 20. Provided below is the code set up to configure the Skill Level and Depth for stockfish as well as the order of UCI commands executed by the code.

This code is in javascript, but uses UCI just like the original open-source implementation. I followed the example at: https://github.com/nmrugg/stockfish.js/blob/2b87d5d16a613f3ce05b1dd0bbb58465501ed30a/example/enginegame.js#L38.

Stockfish.js

import _ from 'lodash';

import ChessBoardState from './ChessBoardState';
import Move from './Move';

class Stockfish {
    constructor() {
        this.bestMove = null;
        this.skill = null;
        this.depth = null;
        this.isThinking = false;
        this.engineStatus = {};

        this.stockfish = new Worker('stockfish.js');
        this.stockfish.onmessage = (event) => {
            const line = event && typeof event === 'object' ? event.data : event;

            console.log('Stockfish: ', line);

            if (line === 'uciok') {
                this.engineStatus.engineLoaded = true;
            } else if (line === 'readyok') {
                this.engineStatus.engineReady = true;
            } else {
                const match = line.match('^bestmove ([a-h][1-8])([a-h][1-8])([qrbn])?');
                if (match) {
                    this.bestMove = new Move(match[1], match[2], match[3] ? match[3] : null);
                    this.isThinking = false;
                }
            }
        }

        this.stockfish.postMessage('uci');
        this.stockfish.postMessage('isready');
        this.stockfish.postMessage('ucinewgame');
    }

    isEngineLoaded() {
        return this.engineStatus.engineLoaded;
    }

    /**
     * Update engine state to fenCode.
     * @param {string} fenCode
     */
    setFEN(fenCode) {
        if (!this.engineStatus.engineLoaded) {
            throw new Error('Engine not loaded');
        }

        this.stockfish.postMessage(`position fen ${fenCode}`);
    }

    /**
     * Update engine depth
     * @param {number} depth
     */
    setDepth(depth) {
        console.log(depth);
        this.depth = _.clamp(depth, 1, 20);
    }

    /**
     * Update engine skill level
     * @param {number} skill
     */
    setSkillLevel(skillLevel) {
        if (!this.engineStatus.engineLoaded) {
            throw new Error('Engine not loaded');
        }

        skillLevel = _.clamp(skillLevel, 0, 20);
        console.log(skillLevel);
        this.stockfish.postMessage(`setoption name Skill Level value ${skillLevel}`);

        // Stockfish level 20 does not make errors (intentially), so these numbers have no effect on level 20.
        // Level 0 starts at 1
        const err_prob = Math.round((skillLevel * 6.35) + 1);
        // Level 0 starts at 10
        const max_err = Math.round((skillLevel * -0.5) + 10);

        this.stockfish.postMessage(`setoption name Skill Level Maximum Error value ${max_err}`);
        this.stockfish.postMessage(`setoption name Skill Level Probability value ${err_prob}`);

        this.skillLevel = skillLevel;
    }

    /**
     * Start churning for best move.
     * @param {ChessBoardState} chessBoardState
     * @param {number} depth
     */
    searchBestMove(chessBoardState) {
        this.bestMove = null;
        this.isThinking = true;

        this.setFEN(chessBoardState.toFEN());
        this.stockfish.postMessage(`go depth ${this.depth}`);
    }

    /**
     * Returns best move if one has been found.
     */
    getBestMove() {
        return this.isThinking ? null : this.bestMove;
    }
}

export default Stockfish

ChessGameUI.js (... used to denote unrelated code)

...
const stockfish = new Stockfish()

this.state = {
    ...
    stockfish: stockfish,
    stockfishSkillLevel: 0,
    stockfishDepth: 1,
}
...
this.intID = setInterval(() => {
    this.state.stockfish.setSkillLevel(this.state.stockfishSkillLevel);
    this.state.stockfish.setDepth(this.state.stockfishDepth);
    
    ...
    
    const bestMove = this.state.stockfish.getBestMove();
    if (!bestMove && !this.state.stockfish.isThinking) {
        this.state.stockfish.searchBestMove(this.state.chessBoardState, this.state.stockfishDepth);
    } else if (bestMove) {
        this.state.chessBoardState.move(bestMove)
        this.state.stockfish.bestMove = null;

        this.setState(prev => ({
            ...prev,
            chessBoardState: ChessBoardState.fromFEN(this.state.chessBoardState.toFEN()),
        }));
    }
}, 500);

In theory this code should attempt to aggressively set the Skill Level and Depth every half second while polling for a new bestMove from Stockfish. But the settings don't seem to have any effect on the difficulty of the engine.

UCI Commands called (in order):

uci
isready
ucinewgame
setoption name Skill Level value 0
setoption name Skill Level Maximum Error value 1
setoption name Skill Level Probability Value 10
position fen rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1
go depth 1
Justian Meyer
  • 3,623
  • 9
  • 35
  • 53

1 Answers1

3

try to use this, it works for me:

const stockfish = new Worker('/stockfish.js');
stockfish.postMessage('uci');
stockfish.postMessage('ucinewgame');
stockfish.postMessage('setoption name Skill Level value 3');
stockfish.postMessage('setoption name Skill Level Maximum Error value 600');
stockfish.postMessage('setoption name Skill Level Probability value 128');
stockfish.postMessage('position fen ' + chess.fen());
stockfish.postMessage('go depth 10');
Unsleeping
  • 31
  • 4