diff --git a/chess/board.go b/chess/board.go index ade7ff2..91253f4 100644 --- a/chess/board.go +++ b/chess/board.go @@ -7,14 +7,16 @@ import ( "image/png" "log" "os" + "strings" ) type Board struct { tiles [8][8]*Piece + turn Color } func NewBoard() *Board { - board := &Board{} + board := &Board{turn: Light} board.mustSetPiece(Rook, Light, "a1") board.mustSetPiece(Knight, Light, "b1") @@ -112,12 +114,100 @@ func (b *Board) SetPiece(name PieceName, color Color, position string) error { return nil } +func (b *Board) Parse(pgn string) error { + var ( + moves = strings.Split(pgn, " ") + err error + ) + + for _, move := range moves { + if err = b.Move(move); err != nil { + return err + } + } + + return nil +} + +func (b *Board) Move(position string) error { + var ( + name PieceName + err error + ) + + // TODO: implement remaining moveset of pieces + if len(position) == 2 { + name = Pawn + } + + switch name { + case Pawn: + err = b.movePawn(position) + default: + err = fmt.Errorf("invalid move: %s", position) + } + + if err != nil { + return err + } + + if b.turn == Light { + b.turn = Dark + } else { + b.turn = Light + } + + return nil +} + +func (b *Board) movePawn(position string) error { + var ( + x int + y int + piece *Piece + err error + ) + + if x, y, err = getXY(position); err != nil { + return err + } + + // TODO: implement diagonal pawn attacks + + for yi := 0; yi < 8; yi++ { + piece = b.tiles[x][yi] + if piece != nil && piece.Name == Pawn && piece.Color == b.turn { + // TODO: assert move is valid: + // * 2 moves from start position + // * 1 move otherwise + // * diagonal if attacking + b.tiles[x][y] = piece + b.tiles[x][yi] = nil + return nil + } + } + + return nil +} + func (b *Board) mustSetPiece(name PieceName, color Color, position string) { if err := b.SetPiece(name, color, position); err != nil { log.Fatalf("cannot set piece %s: %v", name, err) } } +func (b *Board) At(position string) *Piece { + var ( + x int + y int + err error + ) + if x, y, err = getXY(position); err != nil { + return nil + } + return b.tiles[x][y] +} + func getXY(position string) (int, int, error) { var ( posX rune diff --git a/chess/board_test.go b/chess/board_test.go new file mode 100644 index 0000000..4231e8b --- /dev/null +++ b/chess/board_test.go @@ -0,0 +1,93 @@ +package chess_test + +import ( + "fmt" + "os" + "path" + "testing" + + "github.com/ekzyis/sn-chess/chess" + "github.com/stretchr/testify/assert" +) + +func init() { + // change working directory to the root of the project + // so assets/ can be found + wd, _ := os.Getwd() + os.Chdir(path.Dir(wd)) +} + +func TestBoardInitial(t *testing.T) { + b := chess.NewBoard() + + assertPiece(t, b, "a1", chess.Rook, chess.Light) + assertPiece(t, b, "b1", chess.Knight, chess.Light) + assertPiece(t, b, "c1", chess.Bishop, chess.Light) + assertPiece(t, b, "d1", chess.Queen, chess.Light) + assertPiece(t, b, "e1", chess.King, chess.Light) + assertPiece(t, b, "f1", chess.Bishop, chess.Light) + assertPiece(t, b, "g1", chess.Knight, chess.Light) + assertPiece(t, b, "h1", chess.Rook, chess.Light) + + assertPiece(t, b, "a2", chess.Pawn, chess.Light) + assertPiece(t, b, "b2", chess.Pawn, chess.Light) + assertPiece(t, b, "c2", chess.Pawn, chess.Light) + assertPiece(t, b, "d2", chess.Pawn, chess.Light) + assertPiece(t, b, "e2", chess.Pawn, chess.Light) + assertPiece(t, b, "f2", chess.Pawn, chess.Light) + assertPiece(t, b, "g2", chess.Pawn, chess.Light) + assertPiece(t, b, "h2", chess.Pawn, chess.Light) + + assertPiece(t, b, "a8", chess.Rook, chess.Dark) + assertPiece(t, b, "b8", chess.Knight, chess.Dark) + assertPiece(t, b, "c8", chess.Bishop, chess.Dark) + assertPiece(t, b, "d8", chess.Queen, chess.Dark) + assertPiece(t, b, "e8", chess.King, chess.Dark) + assertPiece(t, b, "f8", chess.Bishop, chess.Dark) + assertPiece(t, b, "g8", chess.Knight, chess.Dark) + assertPiece(t, b, "h8", chess.Rook, chess.Dark) + + assertPiece(t, b, "a7", chess.Pawn, chess.Dark) + assertPiece(t, b, "b7", chess.Pawn, chess.Dark) + assertPiece(t, b, "c7", chess.Pawn, chess.Dark) + assertPiece(t, b, "d7", chess.Pawn, chess.Dark) + assertPiece(t, b, "e7", chess.Pawn, chess.Dark) + assertPiece(t, b, "f7", chess.Pawn, chess.Dark) + assertPiece(t, b, "g7", chess.Pawn, chess.Dark) + assertPiece(t, b, "h7", chess.Pawn, chess.Dark) +} + +func TestBoardMovePawn(t *testing.T) { + b := chess.NewBoard() + + b.Move("e4") + + assertNoPiece(t, b, "e2") + assertPiece(t, b, "e4", chess.Pawn, chess.Light) + + b.Move("e5") + + assertNoPiece(t, b, "e7") + assertPiece(t, b, "e5", chess.Pawn, chess.Dark) +} + +func assertPiece(t *testing.T, b *chess.Board, position string, name chess.PieceName, color chess.Color) { + p := b.At(position) + + c := "white" + if color == chess.Dark { + c = "black" + } + + msg := fmt.Sprintf("expected %s %s at %s", c, name, position) + + assert.NotNil(t, p, msg) + assert.Equal(t, name, p.Name, msg) + assert.Equal(t, color, p.Color, msg) +} + +func assertNoPiece(t *testing.T, b *chess.Board, position string) { + p := b.At(position) + + assert.Nil(t, p, "expected no piece at %s", position) +} diff --git a/go.mod b/go.mod index 6023b10..92af81a 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,13 @@ module github.com/ekzyis/sn-chess go 1.23.0 -require golang.org/x/image v0.20.0 +require ( + github.com/stretchr/testify v1.9.0 + golang.org/x/image v0.20.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum index 135955a..91a2d70 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,12 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw= golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=