github.com/frankkopp/FrankyGo@v1.0.3/internal/uci/uci.go (about)

     1  //
     2  // FrankyGo - UCI chess engine in GO for learning purposes
     3  //
     4  // MIT License
     5  //
     6  // Copyright (c) 2018-2020 Frank Kopp
     7  //
     8  // Permission is hereby granted, free of charge, to any person obtaining a copy
     9  // of this software and associated documentation files (the "Software"), to deal
    10  // in the Software without restriction, including without limitation the rights
    11  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    12  // copies of the Software, and to permit persons to whom the Software is
    13  // furnished to do so, subject to the following conditions:
    14  //
    15  // The above copyright notice and this permission notice shall be included in all
    16  // copies or substantial portions of the Software.
    17  //
    18  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    19  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    20  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    21  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    22  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    23  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    24  // SOFTWARE.
    25  //
    26  
    27  // Package uci contains the UciHandler data structure and functionality to
    28  // handle the UCI protocol communication between the Chess User Interface
    29  // and the chess engine.
    30  package uci
    31  
    32  import (
    33  	"bufio"
    34  	"bytes"
    35  	"fmt"
    36  	golog "log"
    37  	"os"
    38  	"path/filepath"
    39  	"regexp"
    40  	"strconv"
    41  	"strings"
    42  	"time"
    43  
    44  	"github.com/op/go-logging"
    45  	"golang.org/x/text/language"
    46  	"golang.org/x/text/message"
    47  
    48  	"github.com/frankkopp/FrankyGo/internal/config"
    49  	myLogging "github.com/frankkopp/FrankyGo/internal/logging"
    50  	"github.com/frankkopp/FrankyGo/internal/movegen"
    51  	"github.com/frankkopp/FrankyGo/internal/moveslice"
    52  	"github.com/frankkopp/FrankyGo/internal/position"
    53  	"github.com/frankkopp/FrankyGo/internal/search"
    54  	. "github.com/frankkopp/FrankyGo/internal/types"
    55  	"github.com/frankkopp/FrankyGo/internal/uciInterface"
    56  	"github.com/frankkopp/FrankyGo/internal/util"
    57  	"github.com/frankkopp/FrankyGo/internal/version"
    58  )
    59  
    60  var out = message.NewPrinter(language.German)
    61  var log *logging.Logger
    62  
    63  // UciHandler handles all communication with the chess ui via UCI
    64  // and controls options and search.
    65  // Create an instance with NewUciHandler()
    66  type UciHandler struct {
    67  	InIo       *bufio.Scanner
    68  	OutIo      *bufio.Writer
    69  	myMoveGen  *movegen.Movegen
    70  	mySearch   *search.Search
    71  	myPosition *position.Position
    72  	myPerft    *movegen.Perft
    73  	uciLog     *logging.Logger
    74  }
    75  
    76  // ///////////////////////////////////////////////////////////
    77  // Public
    78  // ///////////////////////////////////////////////////////////
    79  
    80  // NewUciHandler creates a new UciHandler instance.
    81  // Input / Output io can be replaced by changing the instance's
    82  // InIo and OutIo members.
    83  //  Example:
    84  // 		u.InIo = bufio.NewScanner(os.Stdin)
    85  //		u.OutIo = bufio.NewWriter(os.Stdout)
    86  func NewUciHandler() *UciHandler {
    87  	if log == nil {
    88  		log = myLogging.GetLog()
    89  	}
    90  	u := &UciHandler{
    91  		InIo:       bufio.NewScanner(os.Stdin),
    92  		OutIo:      bufio.NewWriter(os.Stdout),
    93  		myMoveGen:  movegen.NewMoveGen(),
    94  		mySearch:   search.NewSearch(),
    95  		myPosition: position.NewPosition(),
    96  		myPerft:    movegen.NewPerft(),
    97  		uciLog:     getUciLog(),
    98  	}
    99  	var uciDriver uciInterface.UciDriver
   100  	uciDriver = u
   101  	u.mySearch.SetUciHandler(uciDriver)
   102  	return u
   103  }
   104  
   105  // Loop starts the main loop to receive commands through
   106  // input stream (pipe or user)
   107  func (u *UciHandler) Loop() {
   108  	u.loop()
   109  }
   110  
   111  // Command handles a single line of UCI protocol aka command.
   112  // Returns the uci response as string output.
   113  // Mostly useful for debugging and unit testing.
   114  func (u *UciHandler) Command(cmd string) string {
   115  	tmp := u.OutIo
   116  	buffer := new(bytes.Buffer)
   117  	u.OutIo = bufio.NewWriter(buffer)
   118  	u.handleReceivedCommand(cmd)
   119  	_ = u.OutIo.Flush()
   120  	u.OutIo = tmp
   121  	return buffer.String()
   122  }
   123  
   124  // SendReadyOk tells the UciDriver to send the uci response "readyok" to the UCI user interface
   125  func (u *UciHandler) SendReadyOk() {
   126  	u.send("readyok")
   127  }
   128  
   129  // SendInfoString send a arbitrary string to the UCI user interface
   130  func (u *UciHandler) SendInfoString(info string) {
   131  	u.send(out.Sprintf("info string %s", info))
   132  }
   133  
   134  // SendIterationEndInfo sends information about the last search depth iteration to the UCI ui
   135  func (u *UciHandler) SendIterationEndInfo(depth int, seldepth int, value Value, nodes uint64, nps uint64, time time.Duration, pv moveslice.MoveSlice) {
   136  	u.send(fmt.Sprintf("info depth %d seldepth %d multipv 1 score %s nodes %d nps %d time %d pv %s",
   137  		depth, seldepth, value.String(), nodes, nps, time.Milliseconds(), pv.StringUci()))
   138  }
   139  
   140  // SendSearchUpdate sends a periodically update about search stats to the UCI ui
   141  func (u *UciHandler) SendSearchUpdate(depth int, seldepth int, nodes uint64, nps uint64, time time.Duration, hashfull int) {
   142  	u.send(fmt.Sprintf("info depth %d seldepth %d nodes %d nps %d time %d hashfull %d",
   143  		depth, seldepth, nodes, nps, time.Milliseconds(), hashfull))
   144  }
   145  
   146  // SendAspirationResearchInfo sends information about Aspiration researches to the UCI ui
   147  func (u *UciHandler) SendAspirationResearchInfo(depth int, seldepth int, value Value, bound string, nodes uint64, nps uint64, time time.Duration, pv moveslice.MoveSlice) {
   148  	u.send(fmt.Sprintf("info depth %d seldepth %d %s multipv 1 score %s nodes %d nps %d time %d pv %s",
   149  		depth, seldepth, value.String(), bound, nodes, nps, time.Milliseconds(), pv.StringUci()))
   150  }
   151  
   152  // SendCurrentRootMove sends the currently searched root move to the UCI ui
   153  func (u *UciHandler) SendCurrentRootMove(currMove Move, moveNumber int) {
   154  	u.send(fmt.Sprintf("info currmove %s currmovenumber %d", currMove.StringUci(), moveNumber))
   155  }
   156  
   157  // SendCurrentLine sends a periodically update about the currently searched variation ti the UCI ui
   158  func (u *UciHandler) SendCurrentLine(moveList moveslice.MoveSlice) {
   159  	u.send(fmt.Sprintf("info currline %s", moveList.StringUci()))
   160  }
   161  
   162  // SendResult send the search result to the UCI ui after the search has ended are has been stopped
   163  func (u *UciHandler) SendResult(bestMove Move, ponderMove Move) {
   164  	var resultStr strings.Builder
   165  	resultStr.WriteString("bestmove ")
   166  	resultStr.WriteString(bestMove.StringUci())
   167  	if ponderMove != MoveNone {
   168  		resultStr.WriteString(" ponder ")
   169  		resultStr.WriteString(ponderMove.StringUci())
   170  	}
   171  	u.send(resultStr.String())
   172  }
   173  
   174  // ///////////////////////////////////////////////////////////
   175  // Private
   176  // ///////////////////////////////////////////////////////////
   177  
   178  func (u *UciHandler) loop() {
   179  	// infinite loop until "quit" command is received
   180  	for {
   181  		log.Debugf("Waiting for command:")
   182  		// read from stdin or other in stream
   183  		for u.InIo.Scan() {
   184  			if u.handleReceivedCommand(u.InIo.Text()) {
   185  				// quit command received
   186  				return
   187  			}
   188  			log.Debugf("Waiting for command:")
   189  		}
   190  	}
   191  }
   192  
   193  var regexWhiteSpace = regexp.MustCompile("\\s+")
   194  
   195  func (u *UciHandler) handleReceivedCommand(cmd string) bool {
   196  	if len(cmd) == 0 {
   197  		return false
   198  	}
   199  	log.Debugf("Received command: %s", cmd)
   200  	u.uciLog.Infof("<< %s", cmd)
   201  	// find command and execute by calling command function
   202  	tokens := regexWhiteSpace.Split(cmd, -1)
   203  	strings.TrimSpace(tokens[0])
   204  	switch tokens[0] {
   205  	case "quit":
   206  		return true
   207  	case "uci":
   208  		u.uciCommand()
   209  	case "setoption":
   210  		u.setOptionCommand(tokens)
   211  	case "isready":
   212  		u.isReadyCommand()
   213  	case "ucinewgame":
   214  		u.uciNewGameCommand()
   215  	case "position":
   216  		u.positionCommand(tokens)
   217  	case "go":
   218  		u.goCommand(tokens)
   219  	case "stop":
   220  		u.stopCommand()
   221  	case "ponderhit":
   222  		u.ponderHitCommand()
   223  	case "register":
   224  		u.registerCommand()
   225  	case "debug":
   226  		u.debugCommand()
   227  	case "perft":
   228  		u.perftCommand(tokens)
   229  	case "noop":
   230  	default:
   231  		log.Warningf("Error: Unknown command: %s", cmd)
   232  	}
   233  	log.Debugf("Processed command: %s", cmd)
   234  	return false
   235  }
   236  
   237  // command handler when the "uci" cmd has been received.
   238  // Responds with "id" and "options"
   239  func (u *UciHandler) uciCommand() {
   240  	u.send("id name FrankyGo " + version.Version())
   241  	u.send("id author Frank Kopp, Germany")
   242  	options := uciOptions.GetOptions()
   243  	for _, o := range *options {
   244  		u.send(o)
   245  	}
   246  	u.send("uciok")
   247  }
   248  
   249  // the set option command reads the option name and the optional value
   250  // and checks if the uci option exists. If it does its new value will
   251  // be stored and its handler function will be called
   252  func (u *UciHandler) setOptionCommand(tokens []string) {
   253  	name := ""
   254  	value := ""
   255  	if len(tokens) > 1 && tokens[1] == "name" {
   256  		i := 2
   257  		for i < len(tokens) && tokens[i] != "value" {
   258  			name += tokens[i] + " "
   259  			i++
   260  		}
   261  		name = strings.TrimSpace(name)
   262  		if len(tokens) > i && tokens[i] == "value" && len(tokens) > i+1 {
   263  			value += tokens[i+1]
   264  		}
   265  	} else {
   266  		msg := "Command 'setoption' is malformed"
   267  		u.SendInfoString(msg)
   268  		log.Warning(msg)
   269  		return
   270  	}
   271  	o, found := uciOptions[name]
   272  	if found {
   273  		o.CurrentValue = value
   274  		o.HandlerFunc(u, o)
   275  	} else {
   276  		msg := out.Sprintf("Command 'setoption': No such option '%s'", name)
   277  		u.SendInfoString(msg)
   278  		log.Warning(msg)
   279  		return
   280  	}
   281  }
   282  
   283  // requests the isready status from the Search which in turn might
   284  // initialize itself
   285  func (u *UciHandler) isReadyCommand() {
   286  	u.mySearch.IsReady()
   287  }
   288  
   289  // ponderhit signals that the move which was suggested as ponder move
   290  // has been made by the opponent.
   291  func (u *UciHandler) ponderHitCommand() {
   292  	u.mySearch.PonderHit()
   293  }
   294  
   295  // sends a stop signal to search or perft
   296  func (u *UciHandler) stopCommand() {
   297  	u.mySearch.StopSearch()
   298  	u.myPerft.Stop()
   299  }
   300  
   301  // starts a perft test with the given depth
   302  func (u *UciHandler) perftCommand(tokens []string) {
   303  	depth := 4 // default
   304  	var err error = nil
   305  	if len(tokens) > 1 {
   306  		depth, err = strconv.Atoi(tokens[1])
   307  		if err != nil {
   308  			log.Warningf("Can't perft on depth='%s'", tokens[1])
   309  		}
   310  	}
   311  	depth2 := depth
   312  	if len(tokens) > 2 {
   313  		tmp, err := strconv.Atoi(tokens[2])
   314  		if err != nil {
   315  			log.Warningf("Can't use second perft depth2='%s'", tokens[2])
   316  		} else {
   317  			depth2 = tmp
   318  		}
   319  	}
   320  	go u.myPerft.StartPerftMulti(position.StartFen, depth, depth2, true)
   321  }
   322  
   323  // starts a search after reading in the search limits provided
   324  func (u *UciHandler) goCommand(tokens []string) {
   325  	searchLimits, err := u.readSearchLimits(tokens)
   326  	if err {
   327  		return
   328  	}
   329  	// start the search
   330  	u.mySearch.StartSearch(*u.myPosition, *searchLimits)
   331  }
   332  
   333  // sets the current position as given by the uci command
   334  func (u *UciHandler) positionCommand(tokens []string) {
   335  	// build initial position
   336  	fen := position.StartFen
   337  	i := 1
   338  	switch tokens[i] {
   339  	case "startpos":
   340  		i++
   341  	case "fen":
   342  		i++
   343  		var fenb strings.Builder
   344  		for i < len(tokens) && tokens[i] != "moves" {
   345  			fenb.WriteString(tokens[i])
   346  			fenb.WriteString(" ")
   347  			i++
   348  		}
   349  		fen = strings.TrimSpace(fenb.String())
   350  		if len(fen) > 0 {
   351  			break
   352  		}
   353  		// fen empty fall through to err msg
   354  		fallthrough
   355  	default:
   356  		msg := out.Sprintf("Command 'position' malformed. %s", tokens)
   357  		u.SendInfoString(msg)
   358  		log.Warning(msg)
   359  		return
   360  	}
   361  	u.myPosition, _ = position.NewPositionFen(fen)
   362  
   363  	// check for moves to make
   364  	if i < len(tokens) {
   365  		if tokens[i] == "moves" {
   366  			i++
   367  			for i < len(tokens) && tokens[i] != "moves" {
   368  				move := u.myMoveGen.GetMoveFromUci(u.myPosition, tokens[i])
   369  				if move.IsValid() {
   370  					u.myPosition.DoMove(move)
   371  				} else {
   372  					msg := out.Sprintf("Command 'position' malformed. Invalid move '%s' (%s)", tokens[i], tokens)
   373  					u.SendInfoString(msg)
   374  					log.Warning(msg)
   375  					return
   376  				}
   377  				i++
   378  			}
   379  		} else {
   380  			msg := out.Sprintf("Command 'position' malformed moves. %s", tokens)
   381  			u.SendInfoString(msg)
   382  			log.Warning(msg)
   383  			return
   384  		}
   385  	}
   386  	log.Debugf("New position: %s", u.myPosition.StringFen())
   387  }
   388  
   389  // Signals the search to stop a running search and that a new game should
   390  // be started. Usually this means resetting all search related data e.g.
   391  // hash tables etc.
   392  func (u *UciHandler) uciNewGameCommand() {
   393  	u.myPosition = position.NewPosition()
   394  	u.mySearch.NewGame()
   395  }
   396  
   397  // will not be implemented
   398  func (u *UciHandler) debugCommand() {
   399  	msg := "Command 'debug' not implemented"
   400  	u.SendInfoString(msg)
   401  	log.Warning(msg)
   402  }
   403  
   404  // will not be implemented
   405  func (u *UciHandler) registerCommand() {
   406  	msg := "Command 'register' not implemented"
   407  	u.SendInfoString(msg)
   408  	log.Warning(msg)
   409  }
   410  
   411  func (u *UciHandler) readSearchLimits(tokens []string) (*search.Limits, bool) {
   412  	searchLimits := search.NewSearchLimits()
   413  	i := 1
   414  	for i < len(tokens) {
   415  		var err error = nil
   416  		switch tokens[i] {
   417  		case "moves":
   418  			i++
   419  			for i < len(tokens) {
   420  				move := u.myMoveGen.GetMoveFromUci(u.myPosition, tokens[i])
   421  				if move.IsValid() {
   422  					searchLimits.Moves.PushBack(move)
   423  					i++
   424  				} else {
   425  					break
   426  				}
   427  			}
   428  		case "infinite":
   429  			i++
   430  			searchLimits.Infinite = true
   431  		case "ponder":
   432  			i++
   433  			searchLimits.Ponder = true
   434  		case "depth":
   435  			i++
   436  			searchLimits.Depth, err = strconv.Atoi(tokens[i])
   437  			if err != nil {
   438  				msg := out.Sprintf("UCI command go malformed. Depth value not an number: %s", tokens[i])
   439  				u.SendInfoString(msg)
   440  				log.Warning(msg)
   441  				return nil, true
   442  			}
   443  			i++
   444  		case "nodes":
   445  			i++
   446  			parseInt, err := strconv.ParseInt(tokens[i], 10, 64)
   447  			if err != nil {
   448  				msg := out.Sprintf("UCI command go malformed. Nodes value not an number: %s", tokens[i])
   449  				u.SendInfoString(msg)
   450  				log.Warning(msg)
   451  				return nil, true
   452  			}
   453  			searchLimits.Nodes = uint64(parseInt)
   454  			i++
   455  		case "mate":
   456  			i++
   457  			searchLimits.Mate, err = strconv.Atoi(tokens[i])
   458  			if err != nil {
   459  				msg := out.Sprintf("UCI command go malformed. Mate value not an number: %s", tokens[i])
   460  				u.SendInfoString(msg)
   461  				log.Warning(msg)
   462  				return nil, true
   463  			}
   464  			i++
   465  		case "movetime":
   466  			// UCI wants moveTime but STS test suite uses movetime - this catches this
   467  			fallthrough
   468  		case "moveTime":
   469  			i++
   470  			parseInt, err := strconv.ParseInt(tokens[i], 10, 64)
   471  			if err != nil {
   472  				msg := out.Sprintf("UCI command go malformed. MoveTime value not an number: %s", tokens[i])
   473  				u.SendInfoString(msg)
   474  				log.Warning(msg)
   475  				return nil, true
   476  			}
   477  			searchLimits.MoveTime = time.Duration(parseInt * 1_000_000)
   478  			searchLimits.TimeControl = true
   479  			i++
   480  		case "wtime":
   481  			i++
   482  			parseInt, err := strconv.ParseInt(tokens[i], 10, 64)
   483  			if err != nil {
   484  				msg := out.Sprintf("UCI command go malformed. WhiteTime value not an number: %s", tokens[i])
   485  				u.SendInfoString(msg)
   486  				log.Warning(msg)
   487  				return nil, true
   488  			}
   489  			searchLimits.WhiteTime = time.Duration(parseInt * 1_000_000)
   490  			searchLimits.TimeControl = true
   491  			i++
   492  		case "btime":
   493  			i++
   494  			parseInt, err := strconv.ParseInt(tokens[i], 10, 64)
   495  			if err != nil {
   496  				msg := out.Sprintf("UCI command go malformed. Black value not an number: %s", tokens[i])
   497  				u.SendInfoString(msg)
   498  				log.Warning(msg)
   499  				return nil, true
   500  			}
   501  			searchLimits.BlackTime = time.Duration(parseInt * 1_000_000)
   502  			searchLimits.TimeControl = true
   503  			i++
   504  		case "winc":
   505  			i++
   506  			parseInt, err := strconv.ParseInt(tokens[i], 10, 64)
   507  			if err != nil {
   508  				msg := out.Sprintf("UCI command go malformed. WhiteInc value not an number: %s", tokens[i])
   509  				u.SendInfoString(msg)
   510  				log.Warning(msg)
   511  				return nil, true
   512  			}
   513  			searchLimits.WhiteInc = time.Duration(parseInt * 1_000_000)
   514  			i++
   515  		case "binc":
   516  			i++
   517  			parseInt, err := strconv.ParseInt(tokens[i], 10, 64)
   518  			if err != nil {
   519  				msg := out.Sprintf("UCI command go malformed. BlackInc value not an number: %s", tokens[i])
   520  				u.SendInfoString(msg)
   521  				log.Warning(msg)
   522  				return nil, true
   523  			}
   524  			searchLimits.BlackInc = time.Duration(parseInt * 1_000_000)
   525  			i++
   526  		case "movestogo":
   527  			i++
   528  			searchLimits.MovesToGo, err = strconv.Atoi(tokens[i])
   529  			if err != nil {
   530  				msg := out.Sprintf("UCI command go malformed. Movestogo value not an number: %s", tokens[i])
   531  				u.SendInfoString(msg)
   532  				log.Warning(msg)
   533  				return nil, true
   534  			}
   535  			i++
   536  		default:
   537  			msg := out.Sprintf("UCI command go malformed. Invalid subcommand: %s", tokens[i])
   538  			u.SendInfoString(msg)
   539  			log.Warning(msg)
   540  			return nil, true
   541  		}
   542  	}
   543  	// sanity check / minimum settings
   544  	if !(searchLimits.Infinite ||
   545  		searchLimits.Ponder ||
   546  		searchLimits.Depth > 0 ||
   547  		searchLimits.Nodes > 0 ||
   548  		searchLimits.Mate > 0 ||
   549  		searchLimits.TimeControl) {
   550  
   551  		msg := out.Sprintf("UCI command go malformed. No effective limits set %s", tokens)
   552  		u.SendInfoString(msg)
   553  		log.Warning(msg)
   554  		return nil, true
   555  	}
   556  	// sanity check time control
   557  	if searchLimits.TimeControl && searchLimits.MoveTime == 0 {
   558  		if u.myPosition.NextPlayer() == White && searchLimits.WhiteTime == 0 {
   559  			msg := out.Sprintf("UCI command go invalid. White to move but time for white is zero! %s", tokens)
   560  			u.SendInfoString(msg)
   561  			log.Warning(msg)
   562  			return nil, true
   563  		} else if u.myPosition.NextPlayer() == Black && searchLimits.BlackTime == 0 {
   564  			msg := out.Sprintf("UCI command go invalid. Black to move but time for white is zero! %s", tokens)
   565  			u.SendInfoString(msg)
   566  			log.Warning(msg)
   567  			return nil, true
   568  		}
   569  	}
   570  	return searchLimits, false
   571  }
   572  
   573  // getUciLog returns an instance of a special Logger preconfigured for
   574  // myLogging all UCI protocol communication to os.Stdout or file
   575  // Format is very simple "time UCI <uci command>"
   576  func getUciLog() *logging.Logger {
   577  	// create logger
   578  	uciLog := logging.MustGetLogger("UCI ")
   579  
   580  	// Stdout backend
   581  	uciFormat := logging.MustStringFormatter(`%{time:15:04:05.000} UCI %{message}`)
   582  	backend1 := logging.NewLogBackend(os.Stdout, "", golog.Lmsgprefix)
   583  	backend1Formatter := logging.NewBackendFormatter(backend1, uciFormat)
   584  	uciBackEnd1 := logging.AddModuleLevel(backend1Formatter)
   585  	uciBackEnd1.SetLevel(logging.DEBUG, "")
   586  	uciLog.SetBackend(uciBackEnd1)
   587  
   588  	// File backend
   589  	programName, _ := os.Executable()
   590  	exeName := strings.TrimSuffix(filepath.Base(programName), ".exe")
   591  
   592  	// find log path
   593  	logPath, err := util.ResolveFolder(config.Settings.Log.LogPath)
   594  	if err != nil {
   595  		golog.Println("Log folder could not be found:", err)
   596  		return uciLog
   597  	}
   598  	logFilePath := filepath.Join(logPath, exeName+"_uci.log")
   599  
   600  	// create file backend
   601  	uciLogFile, err := os.OpenFile(logFilePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
   602  	if err != nil {
   603  		golog.Println("Logfile could not be created:", err)
   604  		return uciLog
   605  	}
   606  	backend2 := logging.NewLogBackend(uciLogFile, "", golog.Lmsgprefix)
   607  	backend2Formatter := logging.NewBackendFormatter(backend2, uciFormat)
   608  	uciBackEnd2 := logging.AddModuleLevel(backend2Formatter)
   609  	uciBackEnd2.SetLevel(logging.DEBUG, "")
   610  	// multi := logging2.SetBackend(uciBackEnd1, uciBackEnd2)
   611  	uciLog.SetBackend(uciBackEnd2)
   612  	uciLog.Infof("Log %s started at %s:", uciLogFile.Name(), time.Now().String())
   613  	return uciLog
   614  }
   615  
   616  // sends any string to the UCI user interface
   617  func (u *UciHandler) send(s string) {
   618  	u.uciLog.Infof(">> %s", s)
   619  	_, _ = u.OutIo.WriteString(s + "\n")
   620  	_ = u.OutIo.Flush()
   621  }