mirror of
https://github.com/MSWS/Chess.git
synced 2025-12-05 21:30:23 -08:00
feat: Implement UCI engine with command processing loop
``` Introduce UCI engine and improve codebase functionality - Add `CreateMoveUCI` function in `board/move.go` for parsing UCI strings into moves with error handling for invalid inputs. - Refactor `main.go` to use the `uci` package for implementing a chess engine, replacing legacy functionality and optimizing input handling with `bufio`. - Add a new `Engine` interface implementation in `uci/engine.go` for handling UCI interactions, including methods for engine initialization, copy protection, and position parsing. - Add `.vscode` to the `.gitignore` file to exclude editor-specific configurations. - Add unit tests in `uci/engine_test.go` and `main_test.go` for verifying the UCI engine and main module functionality. ``` [board/move.go] - Added a new function `CreateMoveUCI` for creating a move using the Universal Chess Interface (UCI) string format. - The function constructs move coordinates and handles promotion if included in the UCI string. - Error handling added for invalid promotion piece inputs, with a panic in case of an error. [main.go] - Replaced usage of the `fmt` package with `bufio` for input handling. - Removed logic related to chess board initialization and move generation. - Introduced new usage of the `uci` package to create and manage a chess engine. - Implemented a loop to continuously read commands from `stdin`. - Removed legacy functionality, including FEN parsing, move generation, and `Perft` calculations. - Added a goroutine to asynchronously handle and process commands using the UCI engine. [main_test.go] - Added a new test file for the main package. [uci/engine.go] - Added a new implementation of an `Engine` interface for handling UCI (Universal Chess Interface) interactions. - Defined the `MyEngine` struct with fields for reader, writer, a chess game object, and a debug flag. - Implemented `Engine` interface methods: - `CopyProtection`: Responds with a "copyprotection ok" message. - `Init`: Outputs engine metadata such as name and author and indicates UCI readiness. - `Debug`: Toggles debug mode. - `SetOption`: Stub method added but not yet implemented. - `Position`: Parses a FEN string to initialize a chess game and handles errors in debug mode. - Introduced utility methods for printing through the engine's output stream. - Provided a constructor function `NewEngine` to initialize a `MyEngine` instance. [.gitignore] - Added `.vscode` to the ignored files list. [uci/engine_test.go] - Added a test file with unit tests for the UCI engine. - Implemented a `base` helper function to initialize the engine and establish pipe connections. - Added a test case `TestInit` to validate the engine's initialization: - Ensures that initialization output is read from the pipe. - Checks for errors during read operations. - Verifies the output matches the expected initialization string.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
chess
|
||||
.vscode
|
||||
|
||||
@@ -163,6 +163,24 @@ func (board Game) CreateMoveAlgebra(algebra string) Move {
|
||||
return board.createMoveFromTarget(piece, algebra[1:])
|
||||
}
|
||||
|
||||
func (board Game) CreateMoveUCI(uci string) Move {
|
||||
from := CreateCoordAlgebra(uci[:2])
|
||||
to := CreateCoordAlgebra(uci[2:])
|
||||
|
||||
move := board.CreateMove(from, to)
|
||||
|
||||
if len(uci) == 5 {
|
||||
piece, err := GetPiece(rune(uci[4]))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
move.promotionTo = piece
|
||||
}
|
||||
|
||||
return move
|
||||
}
|
||||
|
||||
func (board Game) createMoveFromTarget(piece Piece, algebra string) Move {
|
||||
if len(algebra) == 3 {
|
||||
target := CreateCoordAlgebra(algebra[1:])
|
||||
|
||||
39
main.go
39
main.go
@@ -1,46 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"bufio"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/msws/chess/board"
|
||||
"github.com/msws/chess/uci"
|
||||
)
|
||||
|
||||
func main() {
|
||||
board, err := board.FromFEN("r3k2r/p1pPqpb1/1n3np1/1b2N3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R b KQkq - 0 2")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
engine := uci.NewEngine(reader, os.Stdout)
|
||||
|
||||
baseMoves := board.GetMoves()
|
||||
for {
|
||||
cmd, err := reader.ReadString('\n')
|
||||
|
||||
depth := 1
|
||||
total := 0
|
||||
|
||||
println(strings.Join(os.Args, ","))
|
||||
|
||||
if len(os.Args) == 2 {
|
||||
val, err := strconv.Atoi(os.Args[1])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
depth = val
|
||||
|
||||
go engine.Print(cmd)
|
||||
}
|
||||
|
||||
// fmt.Printf("Depth: %d\n", depth)
|
||||
|
||||
for _, move := range baseMoves {
|
||||
board.MakeMove(move)
|
||||
|
||||
perft := board.Perft(depth - 1)
|
||||
total += perft
|
||||
fmt.Printf("%v%v: %d\n", move.From.GetAlgebra(), move.To.GetAlgebra(), perft)
|
||||
|
||||
board.UndoMove()
|
||||
}
|
||||
|
||||
fmt.Printf("Depth: %d, (%d)", depth, total)
|
||||
}
|
||||
|
||||
1
main_test.go
Normal file
1
main_test.go
Normal file
@@ -0,0 +1 @@
|
||||
package main
|
||||
67
uci/engine.go
Normal file
67
uci/engine.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package uci
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/msws/chess/board"
|
||||
)
|
||||
|
||||
type Engine interface {
|
||||
Debug(bool bool)
|
||||
Init()
|
||||
CopyProtection()
|
||||
SetOption(name string, id string, value string)
|
||||
Print(message string)
|
||||
Position(fen string, moves []string)
|
||||
}
|
||||
|
||||
type MyEngine struct {
|
||||
in io.Reader
|
||||
out io.Writer
|
||||
game *board.Game
|
||||
debug bool
|
||||
}
|
||||
|
||||
// CopyProtection implements Engine.
|
||||
func (e *MyEngine) CopyProtection() {
|
||||
e.Println("copyprotection ok")
|
||||
}
|
||||
|
||||
func (e *MyEngine) Init() {
|
||||
e.Println("id name GoChess2")
|
||||
e.Println("id author MS")
|
||||
e.Println("uciok")
|
||||
}
|
||||
|
||||
func NewEngine(in io.Reader, out io.Writer) Engine {
|
||||
return &MyEngine{
|
||||
in: in,
|
||||
out: out,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *MyEngine) Print(message string) {
|
||||
e.out.Write([]byte(message))
|
||||
}
|
||||
|
||||
func (e *MyEngine) Println(message string) {
|
||||
e.Print(message + "\n")
|
||||
}
|
||||
|
||||
func (e *MyEngine) Debug(bool bool) {
|
||||
e.debug = bool
|
||||
}
|
||||
|
||||
func (e *MyEngine) SetOption(name string, id string, value string) {
|
||||
}
|
||||
|
||||
func (e *MyEngine) Position(fen string, moves []string) {
|
||||
game, err := board.FromFEN(fen)
|
||||
if err != nil {
|
||||
if e.debug {
|
||||
e.Print(err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
e.game = game
|
||||
}
|
||||
34
uci/engine_test.go
Normal file
34
uci/engine_test.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package uci
|
||||
|
||||
import (
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func base() (Engine, io.Reader, io.Writer) {
|
||||
in, out := io.Pipe()
|
||||
e := NewEngine(in, out)
|
||||
|
||||
return e, in, out
|
||||
}
|
||||
|
||||
func TestInit(t *testing.T) {
|
||||
e, in, _ := base()
|
||||
|
||||
go e.Init()
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
n, err := in.Read(buf)
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
t.Error("No data read")
|
||||
}
|
||||
|
||||
if string(buf[:n]) != "id name GoChess2\n" {
|
||||
t.Error("Unexpected output, got:", string(buf[:n]))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user