mirror of
https://github.com/MSWS/Chess.git
synced 2025-12-05 21:30:23 -08:00
- Enhance `testdata/integration_test.go` with improved logging and subtest naming for better clarity. - Refine castle move legality checks in `board/move.go` to ensure accurate column-based validation. [testdata/integration_test.go] - Updated log statement in `testData` function to include both the description and FEN string. - Modified subtest naming to replace slashes in the FEN string with periods. [board/move.go] - Adjusted conditional logic for castle-related moves to check for different column coordinates when determining legality. - No functional changes outside of castle move handling.
389 lines
8.1 KiB
Go
389 lines
8.1 KiB
Go
package board
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
type Move struct {
|
|
from, to Coordinate
|
|
piece Piece
|
|
capture Piece
|
|
promotionTo Piece
|
|
isEnPassant bool
|
|
}
|
|
|
|
func (move Move) IsCastle() bool {
|
|
if move.piece.GetType() != King {
|
|
return false
|
|
}
|
|
|
|
_, fromCol := move.from.GetCoords()
|
|
_, toCol := move.to.GetCoords()
|
|
|
|
if fromCol > toCol {
|
|
toCol, fromCol = fromCol, toCol
|
|
}
|
|
|
|
return toCol-fromCol > 1
|
|
}
|
|
|
|
func (move Move) String() string {
|
|
var sb strings.Builder
|
|
sb.WriteString(fmt.Sprintf("mv{%v%v", move.from, move.to))
|
|
|
|
if move.capture != 0 {
|
|
sb.WriteString(fmt.Sprintf(",X%v", move.capture))
|
|
}
|
|
if move.promotionTo != 0 {
|
|
sb.WriteString(fmt.Sprintf(",P%v", move.promotionTo))
|
|
}
|
|
|
|
sb.WriteString("}")
|
|
|
|
return sb.String()
|
|
}
|
|
|
|
func (board Game) CreateMove(from Coordinate, to Coordinate) Move {
|
|
result := Move{
|
|
from: from,
|
|
to: to,
|
|
piece: board.Get(from),
|
|
capture: board.Get(to),
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func (board Game) CreateMoveStr(from string, to string) Move {
|
|
return board.CreateMove(
|
|
CreateCoordAlgebra(from),
|
|
CreateCoordAlgebra(to),
|
|
)
|
|
}
|
|
|
|
func (game Game) GetImmediateMoves() []Move {
|
|
result := []Move{}
|
|
|
|
board := game.Board
|
|
for row := 0; row < len(board); row++ {
|
|
for col := 0; col < len(board[row]); col++ {
|
|
piece := board[row][col]
|
|
|
|
if piece == 0 || piece.GetColor() != game.Active {
|
|
continue
|
|
}
|
|
|
|
result = append(result, game.getMovesFor(CreateCoordInt(row, col))...)
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func (game Game) GetMoves() []Move {
|
|
result := []Move{}
|
|
|
|
board := game.Board
|
|
pieces := 0
|
|
for row := 0; row < len(board) && pieces < 16; row++ {
|
|
for col := 0; col < len(board[row]) && pieces < 16; col++ {
|
|
piece := board[row][col]
|
|
|
|
if piece == 0 || piece.GetColor() != game.Active {
|
|
continue
|
|
}
|
|
|
|
pieces++
|
|
|
|
psuedo := game.getMovesFor(CreateCoordInt(row, col))
|
|
legalMoves := []Move{}
|
|
|
|
for _, psuedoMove := range psuedo {
|
|
if psuedoMove.capture.GetType() == King {
|
|
continue
|
|
}
|
|
game.MakeMove(psuedoMove)
|
|
|
|
enemyMoves := game.GetImmediateMoves()
|
|
|
|
legal := true
|
|
for _, enemyMove := range enemyMoves {
|
|
if enemyMove.IsCastle() {
|
|
continue
|
|
}
|
|
if psuedoMove.IsCastle() && enemyMove.capture.GetType() == Rook {
|
|
targetRow, targetCol := psuedoMove.to.GetCoords()
|
|
enemyRow, enemyCol := enemyMove.to.GetCoords()
|
|
if targetRow != enemyRow {
|
|
continue
|
|
}
|
|
if targetCol == 0 && enemyCol == 3 {
|
|
legal = false
|
|
break
|
|
}
|
|
if targetCol == 7 && enemyCol == 5 {
|
|
legal = false
|
|
break
|
|
}
|
|
}
|
|
|
|
if enemyMove.capture.GetType() == King {
|
|
legal = false
|
|
break
|
|
}
|
|
}
|
|
|
|
if legal {
|
|
legalMoves = append(legalMoves, psuedoMove)
|
|
}
|
|
|
|
game.UndoMove()
|
|
}
|
|
|
|
result = append(result, legalMoves...)
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func (game Game) getMovesFor(coord Coordinate) []Move {
|
|
piece := game.Get(coord)
|
|
|
|
switch piece.GetType() {
|
|
case Pawn:
|
|
return game.getPawnMoves(coord)
|
|
case Knight:
|
|
return game.getKnightMoves(coord)
|
|
case Bishop:
|
|
return game.getBishopMoves(coord)
|
|
case Rook:
|
|
return game.getRookMoves(coord)
|
|
case Queen:
|
|
return game.getQueenMoves(coord)
|
|
case King:
|
|
return game.getKingMoves(coord)
|
|
default:
|
|
panic(fmt.Errorf("unknown piece type: %v", piece))
|
|
}
|
|
}
|
|
|
|
func (game Game) getPawnMoves(coord Coordinate) []Move {
|
|
piece := game.Get(coord)
|
|
moves := []Move{}
|
|
direction := 1
|
|
if piece.GetColor() == Black {
|
|
direction = -1
|
|
}
|
|
|
|
// Basic pushing
|
|
row, col := coord.GetCoords()
|
|
if piece.GetColor() == White {
|
|
if row == 1 && game.Board[row+1][col] == 0 {
|
|
moves = append(moves, game.CreateMove(coord, CreateCoordByte(row+2, col)))
|
|
}
|
|
} else {
|
|
if row == 6 && game.Board[row-1][col] == 0 {
|
|
moves = append(moves, game.CreateMove(coord, CreateCoordByte(row-2, col)))
|
|
}
|
|
}
|
|
moves = append(moves, game.CreateMove(coord, CreateCoordInt(int(row)+direction, int(col))))
|
|
moves = filter(moves, func(m Move) bool {
|
|
return m.capture == 0
|
|
})
|
|
|
|
// Capturing
|
|
for _, dx := range []int{-1, 1} {
|
|
if col+byte(dx) > 7 {
|
|
continue
|
|
}
|
|
capture := game.CreateMove(coord, CreateCoordInt(int(row)+direction, int(col)+dx))
|
|
if capture.capture != 0 && capture.piece.GetColor() != capture.capture.GetColor() {
|
|
moves = append(moves, capture)
|
|
}
|
|
}
|
|
|
|
// Promoting
|
|
for _, move := range moves {
|
|
row, _ := move.to.GetCoords()
|
|
|
|
if row == 0 || row == 7 {
|
|
// A 0 promotionTo defaults to Queen for simplicity
|
|
for _, piece := range []Piece{Knight, Bishop, Rook} {
|
|
move.promotionTo = piece | move.piece.GetColor()
|
|
moves = append(moves, move)
|
|
}
|
|
}
|
|
}
|
|
|
|
// En Passant
|
|
|
|
if game.EnPassant != nil {
|
|
enRow, enCol := (*game.EnPassant).GetCoords()
|
|
|
|
if int(enRow) == int(row)+direction {
|
|
diff := int(enCol) - int(col)
|
|
if diff == -1 || diff == 1 {
|
|
move := game.CreateMove(coord, *game.EnPassant)
|
|
move.isEnPassant = true
|
|
moves = append(moves, move)
|
|
}
|
|
}
|
|
}
|
|
|
|
return moves
|
|
}
|
|
|
|
func (game Game) getKnightMoves(coord Coordinate) []Move {
|
|
moves := []Move{}
|
|
|
|
offsets := [][]int{{-2, 1}, {-1, 2}, {1, 2}, {2, 1}, {2, -1}, {1, -2}, {-1, -2}, {-2, -1}}
|
|
sx, sy := coord.GetCoords()
|
|
|
|
for _, offset := range offsets {
|
|
tx := sx + byte(offset[0])
|
|
ty := sy + byte(offset[1])
|
|
|
|
if tx > 7 || ty > 7 {
|
|
continue
|
|
}
|
|
|
|
toCoord := CreateCoordByte(tx, ty)
|
|
moves = append(moves, game.CreateMove(coord, toCoord))
|
|
}
|
|
|
|
moves = game.filterAllies(moves)
|
|
return moves
|
|
}
|
|
|
|
func (game Game) getBishopMoves(coord Coordinate) []Move {
|
|
return game.getSlidingMovesOf(coord, [][]int{{-1, 1}, {1, 1}, {1, -1}, {-1, -1}})
|
|
}
|
|
|
|
func (game Game) getRookMoves(coord Coordinate) []Move {
|
|
return game.getSlidingMovesOf(coord, [][]int{{-1, 0}, {1, 0}, {0, -1}, {0, 1}})
|
|
}
|
|
|
|
func (game Game) getQueenMoves(coord Coordinate) []Move {
|
|
return append(game.getBishopMoves(coord), game.getRookMoves(coord)...)
|
|
}
|
|
|
|
func (game Game) getKingMoves(coord Coordinate) []Move {
|
|
moves := []Move{}
|
|
|
|
offsets := [][]int{{-1, 1}, {0, 1}, {1, 1}, {1, 0}, {1, -1}, {0, -1}, {-1, -1}, {-1, 0}}
|
|
sx, sy := coord.GetCoords()
|
|
|
|
for _, offset := range offsets {
|
|
tx := sx + byte(offset[0])
|
|
ty := sy + byte(offset[1])
|
|
|
|
if tx > 7 || ty > 7 {
|
|
continue
|
|
}
|
|
|
|
toCoord := CreateCoordByte(tx, ty)
|
|
moves = append(moves, game.CreateMove(coord, toCoord))
|
|
}
|
|
|
|
moves = game.filterAllies(moves)
|
|
return append(moves, game.getCastleMoves(coord)...)
|
|
}
|
|
|
|
func (game Game) getCastleMoves(coord Coordinate) []Move {
|
|
moves := []Move{}
|
|
castling := game.WhiteCastling
|
|
castleRow := 0
|
|
|
|
if game.Active == Black {
|
|
castling = game.BlackCastling
|
|
castleRow = 7
|
|
}
|
|
|
|
if castling.KingSide {
|
|
if game.Board[castleRow][5] == 0 && game.Board[castleRow][6] == 0 {
|
|
moves = append(moves, game.CreateMove(coord, CreateCoordInt(castleRow, 7)))
|
|
}
|
|
}
|
|
|
|
if castling.QueenSide {
|
|
for col := 1; col <= 3; col++ {
|
|
if game.Board[castleRow][col] != 0 {
|
|
return moves
|
|
}
|
|
}
|
|
|
|
moves = append(moves, game.CreateMove(coord, CreateCoordInt(castleRow, 0)))
|
|
}
|
|
return moves
|
|
}
|
|
|
|
func (game Game) getSlidingMovesOf(coord Coordinate, offsets [][]int) []Move {
|
|
moves := []Move{}
|
|
|
|
for _, offset := range offsets {
|
|
moves = append(moves, game.getSlidingMoves(coord, offset[0], offset[1])...)
|
|
}
|
|
|
|
return moves
|
|
}
|
|
|
|
func (game Game) getSlidingMoves(coord Coordinate, offsetX int, offsetY int) []Move {
|
|
moves := []Move{}
|
|
|
|
current := coord
|
|
|
|
for {
|
|
cRow, cCol := current.GetCoords()
|
|
|
|
cRow += byte(offsetY)
|
|
cCol += byte(offsetX)
|
|
|
|
if cRow > 7 || cCol > 7 {
|
|
break
|
|
}
|
|
|
|
current = CreateCoordByte(cRow, cCol)
|
|
|
|
move := game.CreateMove(coord, current)
|
|
|
|
if move.capture == 0 {
|
|
moves = append(moves, move)
|
|
continue
|
|
}
|
|
|
|
if move.piece.GetColor() != move.capture.GetColor() {
|
|
moves = append(moves, move)
|
|
}
|
|
break
|
|
}
|
|
|
|
return moves
|
|
}
|
|
|
|
func filter[T any](arr []T, predicate func(T) bool) []T {
|
|
ret := []T{}
|
|
for _, t := range arr {
|
|
if !predicate(t) {
|
|
continue
|
|
}
|
|
|
|
ret = append(ret, t)
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
func (board Game) filterEnemies(moves []Move) []Move {
|
|
return filter(moves, func(m Move) bool {
|
|
return m.capture == 0 || m.piece.GetColor() == m.capture.GetColor()
|
|
})
|
|
}
|
|
|
|
func (board Game) filterAllies(moves []Move) []Move {
|
|
return filter(moves, func(m Move) bool {
|
|
return m.capture == 0 || m.piece.GetColor() != m.capture.GetColor()
|
|
})
|
|
}
|