231 lines
4.6 KiB
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
|
|
}
|
|
}
|