chessfs/cmd/uciconnect/uciconnect.go

231 lines
4.6 KiB
Go

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