mirror of
https://github.com/MSWS/Chess.git
synced 2025-12-05 21:30:23 -08:00
Initial commit - All files
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
chess
|
||||
65
board/board.go
Normal file
65
board/board.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package board
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Single byte to store 2D coordinates,
|
||||
// as the max value we need to store in a given
|
||||
// dimension is 8.
|
||||
// We store the X coordinate in the upper 4 bits,
|
||||
// and the Y coordinate in the lower 4 bits
|
||||
type Coordinate byte
|
||||
|
||||
func (coord Coordinate) GetCoords() (byte, byte) {
|
||||
return byte(coord) >> 4, byte(coord) & 0b1111
|
||||
}
|
||||
|
||||
func CreateCoordInt(x int, y int) Coordinate {
|
||||
return CreateCoordByte(byte(x), byte(y))
|
||||
}
|
||||
|
||||
func CreateCoordByte(x byte, y byte) Coordinate {
|
||||
return Coordinate(x<<4 + y)
|
||||
}
|
||||
|
||||
type Castlability struct {
|
||||
CanQueenSide bool
|
||||
CanKingSide bool
|
||||
}
|
||||
|
||||
// A board that represents a given game.
|
||||
// Represents all data that is stored in a FEN string.
|
||||
// i.e. given a Board, you can find the FEN, and vice-versa
|
||||
type Board struct {
|
||||
// The game's pieces, in [row][col]
|
||||
// with the first row, first column being the bottom
|
||||
// left of the board from white's perspective (i.e. a1)
|
||||
Board *[8][8]Piece
|
||||
Active Piece
|
||||
WhiteCastling Castlability
|
||||
BlackCastling Castlability
|
||||
EnPassant Coordinate
|
||||
HalfMoves int
|
||||
FullMoves int
|
||||
}
|
||||
|
||||
func FromFEN(str string) (*Board, error) {
|
||||
result := Board{}
|
||||
|
||||
records := strings.Split(str, " ")
|
||||
|
||||
if len(records) != 6 {
|
||||
return nil, errors.New("malformed FEN string, expected 6 records")
|
||||
}
|
||||
|
||||
board, err := GenerateBoard(records[0])
|
||||
|
||||
if err != nil {
|
||||
return &result, err
|
||||
}
|
||||
|
||||
result.Board = board
|
||||
return &result, nil
|
||||
}
|
||||
128
board/board_test.go
Normal file
128
board/board_test.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package board
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
func TestGetCoords(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := map[string]struct {
|
||||
input byte
|
||||
x, y byte
|
||||
}{
|
||||
"Zero": {
|
||||
input: 0b0000_0000,
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
"Middle": {
|
||||
input: 3<<4 + 3,
|
||||
x: 3,
|
||||
y: 3,
|
||||
},
|
||||
"Last": {
|
||||
input: 7<<4 + 7,
|
||||
x: 7,
|
||||
y: 7,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
coord := Coordinate(test.input)
|
||||
|
||||
x, y := coord.GetCoords()
|
||||
|
||||
if x != test.x {
|
||||
t.Errorf("Expected x to be %x, got %x", test.x, x)
|
||||
}
|
||||
if y != test.y {
|
||||
t.Errorf("Expected y to be %x, got %x", test.y, y)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateCoordInt(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
x int
|
||||
y int
|
||||
expected byte
|
||||
}{
|
||||
"Zero": {
|
||||
x: 0,
|
||||
y: 0,
|
||||
expected: 0b0000_0000,
|
||||
},
|
||||
"Middle": {
|
||||
x: 4,
|
||||
y: 4,
|
||||
expected: 4<<4 + 4,
|
||||
},
|
||||
"Last": {
|
||||
x: 7,
|
||||
y: 7,
|
||||
expected: 7<<4 + 7,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
coord := CreateCoordInt(test.x, test.y)
|
||||
|
||||
if byte(coord) != test.expected {
|
||||
t.Errorf("Expected (%d, %d) to become %x, got %x",
|
||||
test.x, test.y, test.expected, coord)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFromFEN(t *testing.T) {
|
||||
startPos := "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
|
||||
|
||||
startRow := [8]Piece{Rook, Knight, Bishop, Queen, King, Bishop, Knight, Rook}
|
||||
pawnRow := [8]Piece{Pawn, Pawn, Pawn, Pawn, Pawn, Pawn, Pawn, Pawn}
|
||||
|
||||
startBoard := &Board{
|
||||
Board: &[8][8]Piece{
|
||||
startRow, // White starting row
|
||||
pawnRow, // White pawn row
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
pawnRow, // Black pawn row
|
||||
startRow, // Black starting row
|
||||
},
|
||||
}
|
||||
|
||||
markRowColor(&startBoard.Board[0], White)
|
||||
markRowColor(&startBoard.Board[1], White)
|
||||
markRowColor(&startBoard.Board[6], Black)
|
||||
markRowColor(&startBoard.Board[7], Black)
|
||||
|
||||
resultBoard, err := FromFEN(startPos)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("encountered error when parsing from fen: %v", err)
|
||||
}
|
||||
|
||||
if !cmp.Equal(*startBoard, *resultBoard) {
|
||||
t.Errorf("Boards are not equal, expected %v, got %v", *startBoard, *resultBoard)
|
||||
}
|
||||
}
|
||||
|
||||
func markRowColor(row *[8]Piece, color Piece) {
|
||||
for index, piece := range row {
|
||||
if piece == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
row[index] = row[index] | color
|
||||
}
|
||||
}
|
||||
92
board/fen.go
Normal file
92
board/fen.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package board
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GenerateBoard(str string) (*[8][8]Piece, error) {
|
||||
board := &[8][8]Piece{}
|
||||
ranks := strings.Split(str, "/")
|
||||
|
||||
if len(ranks) != 8 {
|
||||
return board, fmt.Errorf("invalid number of rows (%d)", len(ranks))
|
||||
}
|
||||
|
||||
for rank, row := range ranks {
|
||||
result, err := GenerateRow(row)
|
||||
|
||||
if err != nil {
|
||||
return board, err
|
||||
}
|
||||
|
||||
board[7-rank] = *result
|
||||
}
|
||||
|
||||
return board, nil
|
||||
}
|
||||
|
||||
func GenerateRow(str string) (*[8]Piece, error) {
|
||||
row := &[8]Piece{}
|
||||
if len(str) == 0 {
|
||||
return row, nil
|
||||
}
|
||||
|
||||
column := 0
|
||||
|
||||
for _, c := range str {
|
||||
if column > len(row) {
|
||||
return row, errors.New("out of bounds column")
|
||||
}
|
||||
|
||||
if c >= '1' && c <= '8' {
|
||||
column += int(c - '0')
|
||||
continue
|
||||
}
|
||||
|
||||
piece, err := GetPiece(c)
|
||||
|
||||
if err != nil {
|
||||
return row, err
|
||||
}
|
||||
|
||||
row[column] = piece
|
||||
column++
|
||||
}
|
||||
|
||||
return row, nil
|
||||
}
|
||||
|
||||
func GetPiece(c rune) (Piece, error) {
|
||||
white := true
|
||||
if c > 'Z' {
|
||||
white = false
|
||||
c = 'A' + (c - 'a')
|
||||
}
|
||||
var result Piece
|
||||
switch c {
|
||||
case 'P':
|
||||
result = Pawn
|
||||
case 'N':
|
||||
result = Knight
|
||||
case 'B':
|
||||
result = Bishop
|
||||
case 'R':
|
||||
result = Rook
|
||||
case 'Q':
|
||||
result = Queen
|
||||
case 'K':
|
||||
result = King
|
||||
}
|
||||
|
||||
if result == 0 {
|
||||
return 0, fmt.Errorf("unknown piece type: %c (%d)", c, c)
|
||||
}
|
||||
|
||||
if !white {
|
||||
result |= Black
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
117
board/fen_test.go
Normal file
117
board/fen_test.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package board
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestGenerateBoard(t *testing.T) {
|
||||
startRow := [8]Piece{Rook, Knight, Bishop, Queen, King, Bishop, Knight, Rook}
|
||||
pawnRow := [8]Piece{Pawn, Pawn, Pawn, Pawn, Pawn, Pawn, Pawn, Pawn}
|
||||
|
||||
board := [8][8]Piece{
|
||||
startRow, // White starting row
|
||||
pawnRow, // White pawn row
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
pawnRow, // Black pawn row
|
||||
startRow, // Black starting row
|
||||
}
|
||||
|
||||
markRowColor(&board[0], White)
|
||||
markRowColor(&board[1], White)
|
||||
markRowColor(&board[6], Black)
|
||||
markRowColor(&board[7], Black)
|
||||
|
||||
tests := map[string]struct {
|
||||
input string
|
||||
result [8][8]Piece
|
||||
}{
|
||||
"Starting": {
|
||||
input: "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR",
|
||||
result: board,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
result, err := GenerateBoard(test.input)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if *result != test.result {
|
||||
t.Errorf("boards are not equal, expected %v, got %v",
|
||||
test.result, *result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateRow(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
input string
|
||||
result [8]Piece
|
||||
}{
|
||||
"Empty Row": {
|
||||
input: "8",
|
||||
result: [8]Piece{},
|
||||
},
|
||||
"All Pawns": {
|
||||
input: "pppppppp",
|
||||
result: [8]Piece{Pawn | Black, Pawn | Black, Pawn | Black, Pawn | Black, Pawn | Black, Pawn | Black, Pawn | Black, Pawn | Black},
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
result, err := GenerateRow(test.input)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if *result != test.result {
|
||||
t.Errorf("rows do not match, expected %v, got %v",
|
||||
test.result, *result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPiece(t *testing.T) {
|
||||
tests := []struct {
|
||||
input rune
|
||||
result Piece
|
||||
}{
|
||||
{input: 'K', result: King | White},
|
||||
{input: 'Q', result: Queen | White},
|
||||
{input: 'R', result: Rook | White},
|
||||
{input: 'B', result: Bishop | White},
|
||||
{input: 'N', result: Knight | White},
|
||||
{input: 'P', result: Pawn | White},
|
||||
{input: 'k', result: King | Black},
|
||||
{input: 'q', result: Queen | Black},
|
||||
{input: 'r', result: Rook | Black},
|
||||
{input: 'b', result: Bishop | Black},
|
||||
{input: 'n', result: Knight | Black},
|
||||
{input: 'p', result: Pawn | Black},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(string(test.input), func(t *testing.T) {
|
||||
result, err := GetPiece(test.input)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("encountered unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if result != test.result {
|
||||
t.Errorf("expected %x, got %x", test.result, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
16
board/pieces.go
Normal file
16
board/pieces.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package board
|
||||
|
||||
type Piece byte
|
||||
|
||||
const (
|
||||
White Piece = iota
|
||||
Black Piece = 1 << (iota - 1)
|
||||
|
||||
King
|
||||
Queen
|
||||
Rook
|
||||
Bishop
|
||||
Knight
|
||||
Pawn
|
||||
f
|
||||
)
|
||||
5
go.mod
Normal file
5
go.mod
Normal file
@@ -0,0 +1,5 @@
|
||||
module github.com/msws/chess
|
||||
|
||||
go 1.22.8
|
||||
|
||||
require github.com/google/go-cmp v0.6.0 // indirect
|
||||
2
go.sum
Normal file
2
go.sum
Normal file
@@ -0,0 +1,2 @@
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
Reference in New Issue
Block a user