Add checks
This commit is contained in:
parent
aa072ca4aa
commit
908cc1cfda
261
chess/board.go
261
chess/board.go
|
@ -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
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue