Add checks

This commit is contained in:
ekzyis 2024-09-26 09:34:23 +02:00
parent aa072ca4aa
commit 908cc1cfda
2 changed files with 274 additions and 6 deletions

View File

@ -17,7 +17,7 @@ import (
type Board struct { type Board struct {
tiles [8][8]*Piece tiles [8][8]*Piece
turn Color turn Color
moves []string Moves []string
} }
func NewBoard() *Board { func NewBoard() *Board {
@ -240,7 +240,7 @@ func (b *Board) SetPiece(name PieceName, color Color, position string) error {
func (b *Board) AlgebraicNotation() string { func (b *Board) AlgebraicNotation() string {
var text string var text string
for i, m := range b.moves { for i, m := range b.Moves {
if i%2 == 0 { if i%2 == 0 {
text += fmt.Sprintf("%d. %s", i/2+1, m) text += fmt.Sprintf("%d. %s", i/2+1, m)
} else { } else {
@ -292,12 +292,9 @@ func (b *Board) Move(move string) error {
} }
// TODO: parse ambiguous captures for all pieces // TODO: parse ambiguous captures for all pieces
// TODO: parse checks e.g. e5+
// TODO: parse checkmates e.g. e5# // TODO: parse checkmates e.g. e5#
// TODO: parse O-O as kingside castle and O-O-O as queenside castle // TODO: parse O-O as kingside castle and O-O-O as queenside castle
// TODO: make sure pinned pieces cannot move // TODO: make sure pinned pieces cannot move
// TODO: make sure king is not in check after move
// ( this avoids moving into check and moving a piece that exposes the king to check e.g. pinned pieces )
move_ := func() error { move_ := func() error {
@ -330,13 +327,23 @@ func (b *Board) Move(move string) error {
return err return err
} }
// is current player in check after move?
if b.InCheck() {
return fmt.Errorf("invalid move %s: king is in check", move)
}
if b.turn == Light { if b.turn == Light {
b.turn = Dark b.turn = Dark
} else { } else {
b.turn = Light b.turn = Light
} }
b.moves = append(b.moves, move) // make sure the move is marked as a check if it was
if b.InCheck() && !strings.HasSuffix(move, "+") {
move += "+"
}
b.Moves = append(b.Moves, move)
return nil return nil
} }
@ -485,6 +492,248 @@ func (b *Board) validateMove(search PieceName, fromX int, fromY int) func(*Piece
} }
} }
func (b *Board) InCheck() bool {
var (
kingX, kingY int
p *Piece
x, y int
)
outerLoop:
// find king
for y = 0; y < 8; y++ {
for x = 0; x < 8; x++ {
p := b.getPiece(x, y)
if p != nil && p.Name == King && p.Color == b.turn {
kingX = x
kingY = y
break outerLoop
}
}
}
// check if any piece can attack king
// ^
x = kingX
y = kingY
for y = kingY - 1; y >= 0; y-- {
p = b.getPiece(x, y)
if p == nil {
continue
}
if p.Color != b.turn && (p.Name == Rook || p.Name == Queen) {
return true
}
// some other piece is blocking the way
break
}
// ^>
x = kingX
y = kingY
for x, y = kingX+1, kingY-1; x < 8 && y >= 0; x, y = x+1, y-1 {
p = b.getPiece(x, y)
if p == nil {
continue
}
if p.Color != b.turn && (p.Name == Bishop || p.Name == Queen) {
return true
}
break
}
// >
x = kingX
y = kingY
for x = kingX + 1; x < 8; x++ {
p = b.getPiece(x, y)
if p == nil {
continue
}
if p.Color != b.turn && (p.Name == Rook || p.Name == Queen) {
return true
}
break
}
// v>
x = kingX
y = kingY
for x, y = kingX+1, kingY+1; x < 8 && y < 8; x, y = x+1, y+1 {
p = b.getPiece(x, y)
if p == nil {
continue
}
if p.Color != b.turn && (p.Name == Bishop || p.Name == Queen) {
return true
}
break
}
// v
x = kingX
y = kingY
for y = kingY + 1; y < 8; y++ {
p = b.getPiece(x, y)
if p == nil {
continue
}
if p.Color != b.turn && (p.Name == Rook || p.Name == Queen) {
return true
}
break
}
// <v
x = kingX
y = kingY
for x, y = kingX-1, kingY+1; x >= 0 && y < 8; x, y = x-1, y+1 {
p = b.getPiece(x, y)
if p == nil {
continue
}
if p.Color != b.turn && (p.Name == Bishop || p.Name == Queen) {
return true
}
break
}
// <
x = kingX
y = kingY
for x = kingX - 1; x >= 0; x-- {
p = b.getPiece(x, y)
if p == nil {
continue
}
if p.Color != b.turn && (p.Name == Rook || p.Name == Queen) {
return true
}
break
}
// <^
x = kingX
y = kingY
for x, y = kingX-1, kingY-1; x >= 0 && y >= 0; x, y = x-1, y-1 {
p = b.getPiece(x, y)
if p == nil {
continue
}
if p.Color != b.turn && (p.Name == Bishop || p.Name == Queen) {
return true
}
break
}
// check for knights
x = kingX + 1
y = kingY - 2
p = b.getPiece(x, y)
if p != nil && p.Color != b.turn && p.Name == Knight {
return true
}
x = kingX + 2
y = kingY - 1
p = b.getPiece(x, y)
if p != nil && p.Color != b.turn && p.Name == Knight {
return true
}
x = kingX + 2
y = kingY + 1
p = b.getPiece(x, y)
if p != nil && p.Color != b.turn && p.Name == Knight {
return true
}
x = kingX + 1
y = kingY + 2
p = b.getPiece(x, y)
if p != nil && p.Color != b.turn && p.Name == Knight {
return true
}
x = kingX - 1
y = kingY + 2
p = b.getPiece(x, y)
if p != nil && p.Color != b.turn && p.Name == Knight {
return true
}
x = kingX - 2
y = kingY + 1
p = b.getPiece(x, y)
if p != nil && p.Color != b.turn && p.Name == Knight {
return true
}
x = kingX - 2
y = kingY - 1
p = b.getPiece(x, y)
if p != nil && p.Color != b.turn && p.Name == Knight {
return true
}
x = kingX - 1
y = kingY - 2
p = b.getPiece(x, y)
if p != nil && p.Color != b.turn && p.Name == Knight {
return true
}
// check for pawns
x = kingX - 1
if b.turn == Light {
y = kingY - 1
} else {
y = kingY + 1
}
p = b.getPiece(x, y)
if p != nil && p.Color != b.turn && p.Name == Pawn {
return true
}
x = kingX + 1
if b.turn == Light {
y = kingY - 1
} else {
y = kingY + 1
}
p = b.getPiece(x, y)
if p != nil && p.Color != b.turn && p.Name == Pawn {
return true
}
return false
}
func (b *Board) movePawn(position string, fromX int, fromY int, promotion string) error { func (b *Board) movePawn(position string, fromX int, fromY int, promotion string) error {
var ( var (
toX int toX int

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"os" "os"
"path" "path"
"strings"
"testing" "testing"
"github.com/ekzyis/chessbot/chess" "github.com/ekzyis/chessbot/chess"
@ -453,6 +454,24 @@ func TestBoardMoveKingInvalid(t *testing.T) {
assertMoveError(t, b, "Ke3", "no king found that can move to e3") assertMoveError(t, b, "Ke3", "no king found that can move to e3")
} }
func TestBoardCheck(t *testing.T) {
t.Parallel()
b := chess.NewBoard()
assert.False(t, b.InCheck())
assertParse(t, b, "e4 e5 Qh5 Nc6 Qxf7")
assert.True(t, b.InCheck())
assert.True(t, strings.HasSuffix(b.Moves[len(b.Moves)-1], "+"), "check move should end with +")
assertMoveError(t, b, "Nf6", "invalid move Nf6: king is in check")
assertMoveError(t, b, "Ke7", "invalid move Ke7: king is in check")
assertParse(t, b, "Kxf7")
}
func assertParse(t *testing.T, b *chess.Board, moves string) { func assertParse(t *testing.T, b *chess.Board, moves string) {
assert.NoError(t, b.Parse(moves)) assert.NoError(t, b.Parse(moves))
} }