shithub: chessfs

ref: eb8f0cbd6b03c83d0ec53c73ce721dae347217de
dir: /cmd/uciconnect/uciconnect.go/

View raw version
package main

import (
	"fmt"
	"flag"
	"log"
	"os"
	"strings"
	"time"

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

const (
	DefaultThink	=	"0.25s"
	DefaultWait		=	"0.5s"
)

type GameDir struct {
	dir		string
	player	string
}

func NewGameDir(dir, player string) (*GameDir, error) {
	var err error
	_, err = os.Stat(dir)
	if err != nil {
		return nil, fmt.Errorf("game dir stat: %v", err)
	}
	_, err = os.Stat(dir + "/ctl")
	if err != nil {
		return nil, fmt.Errorf("game dir stat ctl: %v", err)
	}
	_, err = os.Stat(dir + "/fen")
	if err != nil {
		return nil, fmt.Errorf("game dir stat fen: %v", err)
	}
	_, err = os.Stat(dir + "/" + player)
	if err != nil {
		return nil, fmt.Errorf("game dir stat %s: %v", player, err)
	}
	return &GameDir { dir, player }, nil
}

func (g *GameDir) GetNew() (bool, error) {
	ctl, err := os.ReadFile(g.dir + "/ctl")
	if err != nil {
		return false, err
	}

	ctllines := strings.Split(string(ctl), "\n")
	if ctllines[0] == "new" {
		return true, nil
	}
	return false, nil
}

func (g *GameDir) GetOngoing() (bool, error) {
	ctl, err := os.ReadFile(g.dir + "/ctl")
	if err != nil {
		return false, err
	}

	ctllines := strings.Split(string(ctl), "\n")
	if ctllines[0] == "ongoing" {
		return true, nil
	}
	return false, nil
}

func (g *GameDir) GetTurn() (string, error) {
	ctl, err := os.ReadFile(g.dir + "/ctl")
	if err != nil {
		return "", err
	}

	ctllines := strings.Split(string(ctl), "\n")
	turn := strings.Split(ctllines[1], "'")
	if len(turn) != 2 {
		return "", fmt.Errorf("malformed player turn line")
	}

	return turn[0], nil
}

func (g *GameDir) GetBoard() (*chess.Game, error) {
	fenf, err := os.ReadFile(g.dir + "/fen")
	if err != nil {
		return nil, err
	}

	fens := strings.Split(string(fenf), "\n")
	if len(fens) < 1 {
		return nil, fmt.Errorf("fen file empty")
	}

	fen, err := chess.FEN(fens[0])
	if err != nil {
		return nil, err
	}

	return chess.NewGame(fen), nil
}

func (g *GameDir) MakeMove(move string) error {
	err := os.WriteFile(g.dir + "/" + g.player, []byte(move), os.FileMode(os.O_WRONLY))
	if err != nil {
		return err
	}
	return nil
}

func main() {
	player := flag.String("player", "white", "which player to play")
	gameDir := flag.String("dir", "", "game directory")
	thinks := flag.String("think", DefaultThink, "thinking time")
	waits := flag.String("wait", DefaultWait, "waiting time")
	flag.Parse()

	if flag.NArg() == 0 {
		log.Fatalf("no command supplied")
		flag.Usage()
		os.Exit(1)
	}
	command := strings.Join(flag.Args(), " ")

	if *player != "white" && *player != "black" {
		log.Fatalf("player can either be black or white\n")
		os.Exit(1)
	}

	think, err := time.ParseDuration(*thinks)
	if err != nil {
		log.Fatalf("%v\n", err)
		os.Exit(1)
	}
	wait, err := time.ParseDuration(*waits)
	if err != nil {
		log.Fatalf("%v\n", err)
		os.Exit(1)
	}

	log.Printf("opening game dir %s\n", *gameDir)
	game, err := NewGameDir(*gameDir, *player)
	if err != nil {
		log.Fatalf("%v\n", err)
		os.Exit(1)
	}

	log.Printf("starting engine\n")
	engine, err := uci.New(command)
	if err != nil {
		log.Fatalf("error starting engine: %v\n", err)
		os.Exit(1)
	}

	notation := chess.LongAlgebraicNotation{}
	var playable = true
	var isNew bool
	var isOngoing bool
	isNew, err = game.GetNew()
	if err != nil {
		log.Fatalf("error: %v\n", err)
		os.Exit(1)
	}
	isOngoing, err = game.GetOngoing()
	if err != nil {
		log.Fatalf("error: %v\n", err)
		os.Exit(1)
	}
	playable = isNew || isOngoing
	for playable {
		isOngoing, err = game.GetOngoing()
		if err != nil {
			log.Fatalf("error getting status: %v\n", err)
			os.Exit(1)
		}
		if !isOngoing {
			time.Sleep(wait)
			continue
		}

		turn, err := game.GetTurn()
		if err != nil {
			log.Fatalf("error getting turn: %v\n", err)
			os.Exit(1)
		}

		if turn != *player {
			time.Sleep(wait)
			continue
		}
		board, err := game.GetBoard()
		if err != nil {
			log.Fatalf("error getting game: %v\n", err)
			os.Exit(1)
		}

		cmdPos := uci.CmdPosition{Position: board.Position()}
		cmdGo := uci.CmdGo{MoveTime: think}
		if err := engine.Run(cmdPos, cmdGo); err != nil {
			log.Fatalf("error thinking: %v\n", err)
			os.Exit(1)
		}
		move := engine.SearchResults().BestMove

		err = board.Move(move)
		if err != nil {
			log.Fatalf("error attempting client move: %v\n", err)
			os.Exit(1)
		}

		moves := board.Moves()
		positions := board.Positions()
		err = game.MakeMove(notation.Encode(positions[len(moves) - 1], moves[len(moves) - 1]))
		if err != nil {
			log.Fatalf("error moving: %v\n", err)
			os.Exit(1)
		}

		time.Sleep(wait)
		playable, err = game.GetOngoing()
		if err != nil {
			log.Fatalf("error getting status: %v\n", err)
			os.Exit(1)
		}
		playable = isOngoing
	}
}