added uciconnect

This commit is contained in:
kitzman 2024-01-26 12:45:33 +00:00
parent c975372b17
commit eb8f0cbd6b
4 changed files with 255 additions and 1 deletions

12
README
View File

@ -12,6 +12,9 @@
chessterm [-font path] [-bg col] [-fg col] player directory
uciconnect [-think time] [-wait time] [-player p] -dir game
command
rc/play [-r] player directory
rc/spectate [-r] directory
@ -75,6 +78,15 @@
and foreground colors can also be set, based on which the
board is drawn.
Uciconnect can be used to connect a game directory, with a
process serving the UCI protocol via standard input and out-
put. There are two mandatory arguments: the game directory
and the command to execute. The -think arguments sets the
time the engine is allowed to find a move. Between commands
and reads, the program sleeps a duration, which can be set
via the -wait option. By default, the engine plays the white
player, which can be set with the -player flag.
Play is an interactive script which prints the board and
game status, and waits for your or your opponent's move. The
-r flag transcribes the UTF-8 piece symbols into other sym-

View File

@ -6,6 +6,8 @@ chessfs [-verbose] [-addr addr] [-srv srv] [-group gid]
chessterm [-font path] [-bg col] [-fg col] player directory
uciconnect [-think time] [-wait time] [-player p] -dir game command
rc/play [-r] player directory
rc/spectate [-r] directory
@ -90,6 +92,17 @@ from the
environment variable, or from the argument. Additionally, the background
and foreground colors can also be set, based on which the board is drawn.
.PP
Uciconnect can be used to connect a game directory, with a process serving
the UCI protocol via standard input and output. There are two mandatory
arguments: the game directory and the command to execute. The
.BI -think
arguments sets the time the engine is allowed to find a move. Between
commands and reads, the program sleeps a duration, which can be set via the
.BI -wait
option. By default, the engine plays the white player, which can be set with the
.BI -player
flag.
.PP
Play is an interactive script which prints the board and game status,
and waits for your or your opponent's move. The -r flag transcribes
the UTF-8 piece symbols into other symbols, in case the used font

View File

@ -0,0 +1,230 @@
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
}
}

View File

@ -8,7 +8,6 @@ import (
"github.com/knusbaum/go9p"
"github.com/knusbaum/go9p/fs"
// "github.com/knusbaum/go9p/proto"
)
func main() {