uci

package
v1.9.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 14, 2022 License: MIT Imports: 12 Imported by: 23

README

uci

Introduction

uci is a client package for Universal Chess Interface protocol (UCI) compliant engines (such as Stockfish).

Installation

uci requires an engine to be useful. On macOS you can install Stockfish with:

brew install stockfish

All installation methods are listed on Stockfish's Download Page

Supported Commands

Engine's Run method takes commands that implement the Cmd interface. Here is a supported commands:

Command Type Description
uci CmdUCI tell engine to use the uci
isready CmdIsReady this command can be used to wait for the engine to be ready again or to ping the engine to find out if it is still alive
setoption CmdSetOption this is sent to the engine when the user wants to change the internal parameters
ucinewgame CmdNewGame this is sent to the engine when the next search (started with "position" and "go") will be from a different game.
position CmdPosition set up the position described in fenstring on the internal board and play the moves on the internal chess board.
go CmdGo start calculating on the current position set up with the "position" command.
stop CmdStop stop calculating as soon as possible
ponderhit CmdPonderHit This will be sent if the engine was told to ponder on the same move the user has played
quit CmdQuit quit the program as soon as possible

Example Stockfish v. Stockfish

package main

import (
	"fmt"
	"time"

	"github.com/notnil/chess"
	"github.com/notnil/chess/uci"
)

func main() {
	// set up engine to use stockfish exe
	eng, err := uci.New("stockfish")
	if err != nil {
		panic(err)
	}
	defer eng.Close()
	// initialize uci with new game
	if err := eng.Run(uci.CmdUCI, uci.CmdIsReady, uci.CmdUCINewGame); err != nil {
		panic(err)
	}
	// have stockfish play speed chess against itself (10 msec per move)
	game := chess.NewGame()
	for game.Outcome() == chess.NoOutcome {
		cmdPos := uci.CmdPosition{Position: game.Position()}
		cmdGo := uci.CmdGo{MoveTime: time.Second / 100}
		if err := eng.Run(cmdPos, cmdGo); err != nil {
			panic(err)
		}
		move := eng.SearchResults().BestMove
		if err := game.Move(move); err != nil {
			panic(err)
		}
	}
	fmt.Println(game.String())
	// Output: 
	// 1.c4 c5 2.Nf3 e6 3.Nc3 Nc6 4.d4 cxd4 5.Nxd4 Nf6 6.a3 d5 7.cxd5 exd5 8.Bf4 Bc5 9.Ndb5 O-O 10.Nc7 d4 11.Na4 Be7 12.Nxa8 Bf5 13.g3 Qd5 14.f3 Rxa8 15.Bg2 Rd8 16.b4 Qe6 17.Nc5 Bxc5 18.bxc5 Nd5 19.O-O Nc3 20.Qd2 Nxe2+ 21.Kh1 d3 22.Bd6 Qd7 23.Rab1 h6 24.a4 Re8 25.g4 Bg6 26.a5 Ncd4 27.Qb4 Qe6 28.Qxb7 Nc2 29.Qxa7 Ne3 30.Rb8 Nxf1 31.Qb6 d2 32.Rxe8+ Qxe8 33.Qb3 Ne3 34.h3 Bc2 35.Qxc2 Nxc2 36.Kh2 d1=Q 37.h4 Qg1+ 38.Kh3 Ne1 39.h5 Qxg2+ 40.Kh4 Nxf3#  0-1
}

Documentation

Overview

Example
package main

import (
	"fmt"
	"time"

	"github.com/notnil/chess"
	"github.com/notnil/chess/uci"
)

var StockfishPath string

func main() {
	// set up engine to use stockfish exe
	eng, err := uci.New(StockfishPath)
	if err != nil {
		panic(err)
	}
	defer eng.Close()
	// initialize uci with new game
	if err := eng.Run(uci.CmdUCI, uci.CmdIsReady, uci.CmdUCINewGame); err != nil {
		panic(err)
	}
	// have stockfish play speed chess against itself (10 msec per move)
	game := chess.NewGame()
	for game.Outcome() == chess.NoOutcome {
		cmdPos := uci.CmdPosition{Position: game.Position()}
		cmdGo := uci.CmdGo{MoveTime: time.Second / 100}
		if err := eng.Run(cmdPos, cmdGo); err != nil {
			panic(err)
		}
		move := eng.SearchResults().BestMove
		if err := game.Move(move); err != nil {
			panic(err)
		}
	}
	fmt.Println(game.String())
}
Output:

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// CmdUCI corresponds to the "uci" command:
	// tell engine to use the uci (universal chess interface),
	// this will be send once as a first command after program boot
	// to tell the engine to switch to uci mode.
	// After receiving the uci command the engine must identify itself with the "id" command
	// and sent the "option" commands to tell the GUI which engine settings the engine supports if any.
	// After that the engine should sent "uciok" to acknowledge the uci mode.
	// If no uciok is sent within a certain time period, the engine task will be killed by the GUI.
	CmdUCI = cmdNoOptions{Name: "uci", F: func(e *Engine) error {
		e.id = map[string]string{}
		e.options = map[string]Option{}
		scanner := bufio.NewScanner(e.out)
		for scanner.Scan() {
			text := e.readLine(scanner)
			k, v, err := parseIDLine(text)
			if err == nil {
				e.id[k] = v
				continue
			}
			o := &Option{}
			err = o.UnmarshalText([]byte(text))
			if err == nil {
				e.options[o.Name] = *o
				continue
			}
			if text == "uciok" {
				break
			}
		}
		return nil
	}}

	// CmdIsReady corresponds to the "isready" command:
	// this is used to synchronize the engine with the GUI. When the GUI has sent a command or
	// multiple commands that can take some time to complete,
	// this command can be used to wait for the engine to be ready again or
	// to ping the engine to find out if it is still alive.
	// E.g. this should be sent after setting the path to the tablebases as this can take some time.
	// This command is also required once before the engine is asked to do any search
	// to wait for the engine to finish initializing.
	// This command must always be answered with "readyok" and can be sent also when the engine is calculating
	// in which case the engine should also immediately answer with "readyok" without stopping the search.
	CmdIsReady = cmdNoOptions{Name: "isready", F: func(e *Engine) error {
		scanner := bufio.NewScanner(e.out)
		for scanner.Scan() {
			text := e.readLine(scanner)
			if text == "readyok" {
				break
			}
		}
		return nil
	}}

	// CmdUCINewGame corresponds to the "ucinewgame" command:
	// this is sent to the engine when the next search (started with "position" and "go") will be from
	// a different game. This can be a new game the engine should play or a new game it should analyse but
	// also the next position from a testsuite with positions only.
	// If the GUI hasn't sent a "ucinewgame" before the first "position" command, the engine shouldn't
	// expect any further ucinewgame commands as the GUI is probably not supporting the ucinewgame command.
	// So the engine should not rely on this command even though all new GUIs should support it.
	// As the engine's reaction to "ucinewgame" can take some time the GUI should always send "isready"
	// after "ucinewgame" to wait for the engine to finish its operation.
	CmdUCINewGame = cmdNoOptions{Name: "ucinewgame", F: func(e *Engine) error {
		return nil
	}}

	// CmdPonderHit corresponds to the "ponderhit" command:
	// the user has played the expected move. This will be sent if the engine was told to ponder on the same move
	// the user has played. The engine should continue searching but switch from pondering to normal search.
	CmdPonderHit = cmdNoOptions{Name: "ponderhit", F: func(e *Engine) error {
		return nil
	}}

	// CmdStop corresponds to the "stop" command:
	// stop calculating as soon as possible,
	// don't forget the "bestmove" and possibly the "ponder" token when finishing the search
	CmdStop = cmdNoOptions{Name: "stop", F: func(e *Engine) error {
		return nil
	}}

	// CmdQuit (shouldn't be used directly as its handled by Engine.Close()) corresponds to the "quit" command:
	// quit the program as soon as possible
	CmdQuit = cmdNoOptions{Name: "quit", F: func(e *Engine) error {
		return nil
	}}
)

Functions

func Debug

func Debug(e *Engine)

Debug is an option for the New function to add logging for debugging. This will log all output to and from the chess engine.

func Logger

func Logger(logger *log.Logger) func(e *Engine)

Logger is an option for the New function to customize the logger. The logger is only used if the Debug option is also used.

Types

type Cmd

type Cmd interface {
	fmt.Stringer
	ProcessResponse(e *Engine) error
}

Cmd is a UCI compliant command

type CmdGo

type CmdGo struct {
	SearchMoves    []*chess.Move
	Ponder         bool
	WhiteTime      time.Duration
	BlackTime      time.Duration
	WhiteIncrement time.Duration
	BlackIncrement time.Duration
	MovesToGo      int
	Depth          int
	Nodes          int
	Mate           int
	MoveTime       time.Duration
	Infinite       bool
}

CmdGo corresponds to the "go" command: start calculating on the current position set up with the "position" command. There are a number of commands that can follow this command, all will be sent in the same string. If one command is not send its value should be interpreted as it would not influence the search.

  • searchmoves .... restrict search to this moves only Example: After "position startpos" and "go infinite searchmoves e2e4 d2d4" the engine should only search the two moves e2e4 and d2d4 in the initial position.
  • ponder start searching in pondering mode. Do not exit the search in ponder mode, even if it's mate! This means that the last move sent in in the position string is the ponder move. The engine can do what it wants to do, but after a "ponderhit" command it should execute the suggested move to ponder on. This means that the ponder move sent by the GUI can be interpreted as a recommendation about which move to ponder. However, if the engine decides to ponder on a different move, it should not display any mainlines as they are likely to be misinterpreted by the GUI because the GUI expects the engine to ponder on the suggested move.
  • wtime white has x msec left on the clock
  • btime black has x msec left on the clock
  • winc white increment per move in mseconds if x > 0
  • binc black increment per move in mseconds if x > 0
  • movestogo there are x moves to the next time control, this will only be sent if x > 0, if you don't get this and get the wtime and btime it's sudden death
  • depth search x plies only.
  • nodes search x nodes only,
  • mate search for a mate in x moves
  • movetime search exactly x mseconds
  • infinite search until the "stop" command. Do not exit the search without being told so in this mode!

func (CmdGo) ProcessResponse

func (CmdGo) ProcessResponse(e *Engine) error

ProcessResponse implements the Cmd interface

func (CmdGo) String

func (cmd CmdGo) String() string

type CmdPosition

type CmdPosition struct {
	Position *chess.Position
	Moves    []*chess.Move
}

CmdPosition corresponds to the "position" command: set up the position described in fenstring on the internal board and play the moves on the internal chess board. if the game was played from the start position the string "startpos" will be sent Note: no "new" command is needed. However, if this position is from a different game than the last position sent to the engine, the GUI should have sent a "ucinewgame" inbetween.

func (CmdPosition) ProcessResponse

func (CmdPosition) ProcessResponse(e *Engine) error

ProcessResponse implements the Cmd interface

func (CmdPosition) String

func (cmd CmdPosition) String() string

type CmdSetOption

type CmdSetOption struct {
	Name  string
	Value string
}

CmdSetOption corresponds to the "setoption" command: this is sent to the engine when the user wants to change the internal parameters of the engine. For the "button" type no value is needed. One string will be sent for each parameter and this will only be sent when the engine is waiting. The name of the option in should not be case sensitive and can inludes spaces like also the value. The substrings "value" and "name" should be avoided in and to allow unambiguous parsing, for example do not use = "draw value". Here are some strings for the example below:

 "setoption name Nullmove value true\n"
"setoption name Selectivity value 3\n"
 "setoption name Style value Risky\n"
 "setoption name Clear Hash\n"
 "setoption name NalimovPath value c:\chess\tb\4;c:\chess\tb\5\n"

func (CmdSetOption) ProcessResponse

func (cmd CmdSetOption) ProcessResponse(e *Engine) error

ProcessResponse implements the Cmd interface

func (CmdSetOption) String

func (cmd CmdSetOption) String() string

type Engine

type Engine struct {
	// contains filtered or unexported fields
}

Engine represents a UCI compliant chess engine (e.g. Stockfish, Shredder, etc.). Engine is safe for concurrent use.

func New

func New(path string, opts ...func(e *Engine)) (*Engine, error)

New constructs an engine from the executable path (found using exec.LookPath). New also starts running the executable process in the background. Once created the Engine can be controlled via the Run method.

func (*Engine) Close

func (e *Engine) Close() error

Close releases readers, writers, and processes associated with the Engine. It also invokes the CmdQuit to signal the engine to terminate.

func (*Engine) ID

func (e *Engine) ID() map[string]string

ID returns the id values returned from the most recent CmdUCI invocation. It includes key value data such as the following: id name Stockfish 12 id author the Stockfish developers (see AUTHORS file)

func (*Engine) Options

func (e *Engine) Options() map[string]Option

Options returns exposed options from the most recent CmdUCI invocation. It includes data such as the following: option name Debug Log File type string default option name Contempt type spin default 24 min -100 max 100 option name Analysis Contempt type combo default Both var Off var White var Black var Both option name Threads type spin default 1 min 1 max 512 option name Hash type spin default 16 min 1 max 33554432 option name Clear Hash type button option name Ponder type check default false option name MultiPV type spin default 1 min 1 max 500 option name Skill Level type spin default 20 min 0 max 20 option name Move Overhead type spin default 10 min 0 max 5000 option name Slow Mover type spin default 100 min 10 max 1000 option name nodestime type spin default 0 min 0 max 10000 option name UCI_Chess960 type check default false option name UCI_AnalyseMode type check default false option name UCI_LimitStrength type check default false option name UCI_Elo type spin default 1350 min 1350 max 2850 option name UCI_ShowWDL type check default false option name SyzygyPath type string default <empty> option name SyzygyProbeDepth type spin default 1 min 1 max 100 option name Syzygy50MoveRule type check default true option name SyzygyProbeLimit type spin default 7 min 0 max 7 option name Use NNUE type check default true option name EvalFile type string default nn-82215d0fd0df.nnue

func (*Engine) Run

func (e *Engine) Run(cmds ...Cmd) error

Run runs the set of Cmds in the order given and returns an error if any of the commands fails. Except for CmdStop (usually paired with CmdGo's infinite option) all commands block via mutux until completed.

func (*Engine) SearchResults

func (e *Engine) SearchResults() SearchResults

SearchResults returns results from the most recent CmdGo invocation. It includes data such as the following: info depth 21 seldepth 31 multipv 1 score cp 39 nodes 862438 nps 860716 hashfull 409 tbhits 0 time 1002 pv e2e4 bestmove e2e4 ponder c7c5

type Info

type Info struct {
	Depth             int
	Seldepth          int
	PV                []*chess.Move
	Multipv           int
	Time              time.Duration
	Nodes             int
	Score             Score
	CurrentMove       *chess.Move
	CurrentMoveNumber int
	Hashfull          int
	NPS               int
	TBHits            int
	CPULoad           int
}

Info corresponds to the "info" engine output: the engine wants to send infos to the GUI. This should be done whenever one of the info has changed. The engine can send only selected infos and multiple infos can be send with one info command, e.g. "info currmove e2e4 currmovenumber 1" or

"info depth 12 nodes 123456 nps 100000".

Also all infos belonging to the pv should be sent together e.g. "info depth 2 score cp 214 time 1242 nodes 2124 nps 34928 pv e2e4 e7e5 g1f3" I suggest to start sending "currmove", "currmovenumber", "currline" and "refutation" only after one second to avoid too much traffic. Additional info:

  • depth search depth in plies
  • seldepth selective search depth in plies, if the engine sends seldepth there must also a "depth" be present in the same string.
  • time the time searched in ms, this should be sent together with the pv.
  • nodes x nodes searched, the engine should send this info regularly
  • pv ... the best line found
  • multipv this for the multi pv mode. for the best move/pv add "multipv 1" in the string when you send the pv. in k-best mode always send all k variants in k strings together.
  • score
  • cp the score from the engine's point of view in centipawns.
  • mate mate in y moves, not plies. If the engine is getting mated use negativ values for y.
  • lowerbound the score is just a lower bound.
  • upperbound the score is just an upper bound.
  • currmove currently searching this move
  • currmovenumber currently searching move number x, for the first move x should be 1 not 0.
  • hashfull the hash is x permill full, the engine should send this info regularly
  • nps x nodes per second searched, the engine should send this info regularly
  • tbhits x positions where found in the endgame table bases
  • cpuload the cpu usage of the engine is x permill.
  • string any string str which will be displayed be the engine, if there is a string command the rest of the line will be interpreted as .
  • refutation ... move is refuted by the line ... , i can be any number >= 1. Example: after move d1h5 is searched, the engine can send "info refutation d1h5 g6h5" if g6h5 is the best answer after d1h5 or if g6h5 refutes the move d1h5. if there is norefutation for d1h5 found, the engine should just send "info refutation d1h5" The engine should only send this if the option "UCI_ShowRefutations" is set to true.
  • currline ... this is the current line the engine is calculating. is the number of the cpu if the engine is running on more than one cpu. = 1,2,3.... if the engine is just using one cpu, can be omitted. If is greater than 1, always send all k lines in k strings together. The engine should only send this if the option "UCI_ShowCurrLine" is set to true.

func (*Info) UnmarshalText

func (info *Info) UnmarshalText(text []byte) error

UnmarshalText implements the encoding.TextUnmarshaler interface and parses data like the following: info depth 24 seldepth 32 multipv 1 score cp 29 nodes 5130101 nps 819897 hashfull 967 tbhits 0 time 6257 pv d2d4

type Option

type Option struct {
	Name    string
	Type    OptionType
	Default string
	Min     string
	Max     string
	Vars    []string
}

Option corresponds to the "option" engine output: This command tells the GUI which parameters can be changed in the engine.

	This should be sent once at engine startup after the "uci" and the "id" commands
	if any parameter can be changed in the engine.
	The GUI should parse this and build a dialog for the user to change the settings.
	Note that not every option needs to appear in this dialog as some options like
	"Ponder", "UCI_AnalyseMode", etc. are better handled elsewhere or are set automatically.
	If the user wants to change some settings, the GUI will send a "setoption" command to the engine.
	Note that the GUI need not send the setoption command when starting the engine for every option if
	it doesn't want to change the default value.
	For all allowed combinations see the example below,
	as some combinations of this tokens don't make sense.
	One string will be sent for each parameter.
	* name
		The option has the name id.
		Certain options have a fixed value for , which means that the semantics of this option is fixed.
		Usually those options should not be displayed in the normal engine options window of the GUI but
		get a special treatment. "Pondering" for example should be set automatically when pondering is
		enabled or disabled in the GUI options. The same for "UCI_AnalyseMode" which should also be set
		automatically by the GUI. All those certain options have the prefix "UCI_" except for the
		first 6 options below. If the GUI get an unknown Option with the prefix "UCI_", it should just
		ignore it and not display it in the engine's options dialog.
		*  = Hash, type is spin
			the value in MB for memory for hash tables can be changed,
			this should be answered with the first "setoptions" command at program boot
			if the engine has sent the appropriate "option name Hash" command,
			which should be supported by all engines!
			So the engine should use a very small hash first as default.
		*  = NalimovPath, type string
			this is the path on the hard disk to the Nalimov compressed format.
			Multiple directories can be concatenated with ";"
		*  = NalimovCache, type spin
			this is the size in MB for the cache for the nalimov table bases
			These last two options should also be present in the initial options exchange dialog
			when the engine is booted if the engine supports it
		*  = Ponder, type check
			this means that the engine is able to ponder.
			The GUI will send this whenever pondering is possible or not.
			Note: The engine should not start pondering on its own if this is enabled, this option is only
			needed because the engine might change its time management algorithm when pondering is allowed.
		*  = OwnBook, type check
			this means that the engine has its own book which is accessed by the engine itself.
			if this is set, the engine takes care of the opening book and the GUI will never
			execute a move out of its book for the engine. If this is set to false by the GUI,
			the engine should not access its own book.
		*  = MultiPV, type spin
			the engine supports multi best line or k-best mode. the default value is 1
		*  = UCI_ShowCurrLine, type check, should be false by default,
			the engine can show the current line it is calculating. see "info currline" above.
		*  = UCI_ShowRefutations, type check, should be false by default,
			the engine can show a move and its refutation in a line. see "info refutations" above.
		*  = UCI_LimitStrength, type check, should be false by default,
			The engine is able to limit its strength to a specific Elo number,
		   This should always be implemented together with "UCI_Elo".
		*  = UCI_Elo, type spin
			The engine can limit its strength in Elo within this interval.
			If UCI_LimitStrength is set to false, this value should be ignored.
			If UCI_LimitStrength is set to true, the engine should play with this specific strength.
		   This should always be implemented together with "UCI_LimitStrength".
		*  = UCI_AnalyseMode, type check
		   The engine wants to behave differently when analysing or playing a game.
		   For example when playing it can use some kind of learning.
		   This is set to false if the engine is playing a game, otherwise it is true.
		 *  = UCI_Opponent, type string
		   With this command the GUI can send the name, title, elo and if the engine is playing a human
		   or computer to the engine.
		   The format of the string has to be [GM|IM|FM|WGM|WIM|none] [|none] [computer|human]
		   Example:
		   "setoption name UCI_Opponent value GM 2800 human Gary Kasparow"
		   "setoption name UCI_Opponent value none none computer Shredder"
	* type
		The option has type t.
		There are 5 different types of options the engine can send
		* check
			a checkbox that can either be true or false
		* spin
			a spin wheel that can be an integer in a certain range
		* combo
			a combo box that can have different predefined strings as a value
		* button
			a button that can be pressed to send a command to the engine
		* string
			a text field that has a string as a value,
			an empty string has the value ""
	* default
		the default value of this parameter is x
	* min
		the minimum value of this parameter is x
	* max
		the maximum value of this parameter is x
	* var
		a predefined value of this parameter is x
	Example:
    Here are 5 strings for each of the 5 possible types of options
	   "option name Nullmove type check default true\n"
      "option name Selectivity type spin default 2 min 0 max 4\n"
	   "option name Style type combo default Normal var Solid var Normal var Risky\n"
	   "option name NalimovPath type string default c:\\n"
	   "option name Clear Hash type button\n"

func (*Option) UnmarshalText

func (o *Option) UnmarshalText(text []byte) error

UnmarshalText implements the encoding.TextUnmarshaler interface and parses data like the following: option name EvalFile type string default nn-82215d0fd0df.nnue

type OptionType

type OptionType string

OptionType corresponds to the "option"'s type engine output: * type The option has type t. There are 5 different types of options the engine can send

  • check a checkbox that can either be true or false
  • spin a spin wheel that can be an integer in a certain range
  • combo a combo box that can have different predefined strings as a value
  • button a button that can be pressed to send a command to the engine
  • string a text field that has a string as a value, an empty string has the value ""
const (
	// OptionNoType indicates no option type
	OptionNoType OptionType = "notype"
	// OptionCheck indicates check option type
	OptionCheck OptionType = "check"
	// OptionSpin indicates spin option type
	OptionSpin OptionType = "spin"
	// OptionCombo indicates combo option type
	OptionCombo OptionType = "combo"
	// OptionButton indicates button option type
	OptionButton OptionType = "button"
	// OptionString indicates string option type
	OptionString OptionType = "string"
)

type Score

type Score struct {
	CP         int
	Mate       int
	LowerBound bool
	UpperBound bool
}

Score corresponds to the "info"'s score engine output:

  • score
  • cp the score from the engine's point of view in centipawns.
  • mate mate in y moves, not plies. If the engine is getting mated use negativ values for y.
  • lowerbound the score is just a lower bound.
  • upperbound the score is just an upper bound.

type SearchResults

type SearchResults struct {
	BestMove *chess.Move
	Ponder   *chess.Move
	Info     Info
}

SearchResults is the result from the most recent CmdGo invocation. It includes data such as the following: info depth 21 seldepth 31 multipv 1 score cp 39 nodes 862438 nps 860716 hashfull 409 tbhits 0 time 1002 pv e2e4 bestmove e2e4 ponder c7c5

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL