Switch to better mechanism to keep track of pins
This commit is contained in:
parent
b0ebdc02a6
commit
1a89b437fa
|
@ -19,16 +19,7 @@ import std/strutils
|
||||||
import std/strformat
|
import std/strformat
|
||||||
import std/sequtils
|
import std/sequtils
|
||||||
|
|
||||||
|
|
||||||
type
|
type
|
||||||
# Useful type aliases
|
|
||||||
Location* = tuple[row, col: int8]
|
|
||||||
|
|
||||||
Attacked = seq[tuple[source, dest: Location]]
|
|
||||||
|
|
||||||
Pieces = tuple[king: Location, queens: seq[Location], rooks: seq[Location],
|
|
||||||
bishops: seq[Location], knights: seq[Location],
|
|
||||||
pawns: seq[Location]]
|
|
||||||
|
|
||||||
PieceColor* = enum
|
PieceColor* = enum
|
||||||
## A piece color enumeration
|
## A piece color enumeration
|
||||||
|
@ -38,7 +29,7 @@ type
|
||||||
|
|
||||||
PieceKind* = enum
|
PieceKind* = enum
|
||||||
## A chess piece enumeration
|
## A chess piece enumeration
|
||||||
Empty = '\0', # No piece
|
Empty = 0'i8, # No piece
|
||||||
Bishop = 'b',
|
Bishop = 'b',
|
||||||
King = 'k'
|
King = 'k'
|
||||||
Knight = 'n',
|
Knight = 'n',
|
||||||
|
@ -60,13 +51,24 @@ type
|
||||||
# Castling metadata
|
# Castling metadata
|
||||||
CastleLong,
|
CastleLong,
|
||||||
CastleShort,
|
CastleShort,
|
||||||
XRay, # Move is an X-ray attack
|
|
||||||
# Pawn promotion metadata
|
# Pawn promotion metadata
|
||||||
PromoteToQueen,
|
PromoteToQueen,
|
||||||
PromoteToRook,
|
PromoteToRook,
|
||||||
PromoteToBishop,
|
PromoteToBishop,
|
||||||
PromoteToKnight
|
PromoteToKnight
|
||||||
|
|
||||||
|
# Useful type aliases
|
||||||
|
Location* = tuple[row, col: int8]
|
||||||
|
|
||||||
|
Attacked = seq[tuple[source, target: Location]]
|
||||||
|
Pinned = seq[tuple[source, target, direction: Location]]
|
||||||
|
|
||||||
|
Pieces = tuple[king: Location, queens: seq[Location], rooks: seq[Location],
|
||||||
|
bishops: seq[Location], knights: seq[Location],
|
||||||
|
pawns: seq[Location]]
|
||||||
|
|
||||||
|
CountData = tuple[nodes: uint64, captures: uint64, castles: uint64, checks: uint64, promotions: uint64, enPassant: uint64, checkmates: uint64]
|
||||||
|
|
||||||
Move* = object
|
Move* = object
|
||||||
## A chess move
|
## A chess move
|
||||||
piece*: Piece
|
piece*: Piece
|
||||||
|
@ -91,13 +93,12 @@ type
|
||||||
# every 2 ply
|
# every 2 ply
|
||||||
fullMoveCount: int16
|
fullMoveCount: int16
|
||||||
# En passant target square (see https://en.wikipedia.org/wiki/En_passant)
|
# En passant target square (see https://en.wikipedia.org/wiki/En_passant)
|
||||||
# If en passant is not possible, both the row and
|
|
||||||
# column of the position will be set to -1
|
|
||||||
enPassantSquare*: Move
|
enPassantSquare*: Move
|
||||||
# Locations of all pieces
|
# Locations of all pieces
|
||||||
pieces: tuple[white: Pieces, black: Pieces]
|
pieces: tuple[white: Pieces, black: Pieces]
|
||||||
# Potential attacking moves for black and white
|
# Potential attacking moves for black and white
|
||||||
attacked: tuple[white: Attacked, black: Attacked]
|
attacked: tuple[white: Attacked, black: Attacked]
|
||||||
|
pinned: tuple[white: Pinned, black: Pinned]
|
||||||
# Has any piece been captured to reach this position?
|
# Has any piece been captured to reach this position?
|
||||||
captured: Piece
|
captured: Piece
|
||||||
# Active color
|
# Active color
|
||||||
|
@ -127,6 +128,8 @@ proc makeMove*(self: ChessBoard, move: Move): Move {.discardable.}
|
||||||
proc makeMove*(self: ChessBoard, startSquare, targetSquare: Location): Move {.discardable.}
|
proc makeMove*(self: ChessBoard, startSquare, targetSquare: Location): Move {.discardable.}
|
||||||
func emptyMove*: Move {.inline.} = Move(startSquare: emptyLocation(), targetSquare: emptyLocation(), piece: emptyPiece())
|
func emptyMove*: Move {.inline.} = Move(startSquare: emptyLocation(), targetSquare: emptyLocation(), piece: emptyPiece())
|
||||||
func `+`*(a, b: Location): Location = (a.row + b.row, a.col + b.col)
|
func `+`*(a, b: Location): Location = (a.row + b.row, a.col + b.col)
|
||||||
|
func `-`*(a: Location): Location = (-a.row, -a.col)
|
||||||
|
func `-`*(a, b: Location): Location = (a.row - b.row, a.col - b.col)
|
||||||
func isValid*(a: Location): bool {.inline.} = a.row in 0..7 and a.col in 0..7
|
func isValid*(a: Location): bool {.inline.} = a.row in 0..7 and a.col in 0..7
|
||||||
proc generateMoves(self: ChessBoard, location: Location): seq[Move]
|
proc generateMoves(self: ChessBoard, location: Location): seq[Move]
|
||||||
proc isAttacked*(self: ChessBoard, loc: Location, color: PieceColor = None): bool
|
proc isAttacked*(self: ChessBoard, loc: Location, color: PieceColor = None): bool
|
||||||
|
@ -136,6 +139,13 @@ proc doMove(self: ChessBoard, move: Move)
|
||||||
proc pretty*(self: ChessBoard): string
|
proc pretty*(self: ChessBoard): string
|
||||||
proc spawnPiece(self: ChessBoard, location: Location, piece: Piece)
|
proc spawnPiece(self: ChessBoard, location: Location, piece: Piece)
|
||||||
proc updateAttackedSquares(self: ChessBoard)
|
proc updateAttackedSquares(self: ChessBoard)
|
||||||
|
proc getPinnedDirections(self: ChessBoard, loc: Location): seq[Location]
|
||||||
|
|
||||||
|
|
||||||
|
proc extend[T](self: var seq[T], other: openarray[T]) {.inline.} =
|
||||||
|
for x in other:
|
||||||
|
self.add(x)
|
||||||
|
|
||||||
|
|
||||||
# Due to our board layout, directions of movement are reversed for white/black so
|
# Due to our board layout, directions of movement are reversed for white/black so
|
||||||
# we need these helpers to avoid going mad with integer tuples and minus signs
|
# we need these helpers to avoid going mad with integer tuples and minus signs
|
||||||
|
@ -146,7 +156,7 @@ func bottomLeftDiagonal(color: PieceColor): Location {.inline.} = (if color == W
|
||||||
func bottomRightDiagonal(color: PieceColor): Location {.inline.} = (if color == White: (1, 1) else: (-1, -1))
|
func bottomRightDiagonal(color: PieceColor): Location {.inline.} = (if color == White: (1, 1) else: (-1, -1))
|
||||||
func leftSide(color: PieceColor): Location {.inline.} = (if color == White: (0, -1) else: (0, 1))
|
func leftSide(color: PieceColor): Location {.inline.} = (if color == White: (0, -1) else: (0, 1))
|
||||||
func rightSide(color: PieceColor): Location {.inline.} = (if color == White: (0, 1) else: (0, -1))
|
func rightSide(color: PieceColor): Location {.inline.} = (if color == White: (0, 1) else: (0, -1))
|
||||||
func topSide(color: PieceColor): Location {.inline.} = (-1, 0)
|
func topSide(color: PieceColor): Location {.inline.} = (if color == White: (-1, 0) else: (1, 0))
|
||||||
func bottomSide(color: PieceColor): Location {.inline.} = (if color == White: (1, 0) else: (-1, 0))
|
func bottomSide(color: PieceColor): Location {.inline.} = (if color == White: (1, 0) else: (-1, 0))
|
||||||
func forward(color: PieceColor): Location {.inline.} = (if color == White: (-1, 0) else: (1, 0))
|
func forward(color: PieceColor): Location {.inline.} = (if color == White: (-1, 0) else: (1, 0))
|
||||||
func doublePush(color: PieceColor): Location {.inline.} = (if color == White: (-2, 0) else: (2, 0))
|
func doublePush(color: PieceColor): Location {.inline.} = (if color == White: (-2, 0) else: (2, 0))
|
||||||
|
@ -275,7 +285,19 @@ proc newChessboard: ChessBoard =
|
||||||
enPassantSquare: emptyMove(),
|
enPassantSquare: emptyMove(),
|
||||||
move: emptyMove(),
|
move: emptyMove(),
|
||||||
turn: White,
|
turn: White,
|
||||||
fullMoveCount: 1)
|
fullMoveCount: 1,
|
||||||
|
pieces: (white: (king: emptyLocation(),
|
||||||
|
queens: @[],
|
||||||
|
rooks: @[],
|
||||||
|
bishops: @[],
|
||||||
|
knights: @[],
|
||||||
|
pawns: @[]),
|
||||||
|
black: (king: emptyLocation(),
|
||||||
|
queens: @[],
|
||||||
|
rooks: @[],
|
||||||
|
bishops: @[],
|
||||||
|
knights: @[],
|
||||||
|
pawns: @[])))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -532,7 +554,6 @@ func getPiece*(self: ChessBoard, square: string): Piece =
|
||||||
return self.getPiece(square.algebraicToLocation())
|
return self.getPiece(square.algebraicToLocation())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func isCapture*(move: Move): bool {.inline.} =
|
func isCapture*(move: Move): bool {.inline.} =
|
||||||
## Returns whether the given move is a capture
|
## Returns whether the given move is a capture
|
||||||
## or not
|
## or not
|
||||||
|
@ -555,9 +576,9 @@ proc inCheck*(self: ChessBoard, color: PieceColor = None): bool =
|
||||||
color = self.getActiveColor()
|
color = self.getActiveColor()
|
||||||
case color:
|
case color:
|
||||||
of White:
|
of White:
|
||||||
return self.isAttacked(self.position.pieces.white.king, color)
|
return self.isAttacked(self.position.pieces.white.king, Black)
|
||||||
of Black:
|
of Black:
|
||||||
return self.isAttacked(self.position.pieces.black.king, color)
|
return self.isAttacked(self.position.pieces.black.king, White)
|
||||||
else:
|
else:
|
||||||
# Unreachable
|
# Unreachable
|
||||||
discard
|
discard
|
||||||
|
@ -608,38 +629,40 @@ proc canCastle*(self: ChessBoard, color: PieceColor = None): tuple[queen, king:
|
||||||
|
|
||||||
if result.king:
|
if result.king:
|
||||||
# Short castle
|
# Short castle
|
||||||
var location = loc
|
var
|
||||||
|
location = loc
|
||||||
|
otherPiece: Piece
|
||||||
while true:
|
while true:
|
||||||
location = location + kingSide
|
location = location + kingSide
|
||||||
if not location.isValid():
|
if not location.isValid():
|
||||||
break
|
break
|
||||||
if self.grid[location.row, location.col].color == color:
|
otherPiece = self.grid[location.row, location.col]
|
||||||
# Blocked by own piece
|
|
||||||
|
if otherPiece.color == None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if otherPiece.color == color.opposite() or otherPiece.kind != Rook or self.isAttacked(location, color.opposite()):
|
||||||
result.king = false
|
result.king = false
|
||||||
break
|
break
|
||||||
# Square is attacked or blocked by enemy piece
|
|
||||||
if self.isAttacked(location, color) or self.grid[location.row, location.col].color != None:
|
|
||||||
result.king = false
|
|
||||||
break
|
|
||||||
# Square is occupied by our rook: we're done. No need to check the color or type of it (because
|
|
||||||
# if it weren't the right color, castling rights would've already been lost and we wouldn't
|
|
||||||
# have got this far)
|
|
||||||
if location == color.kingSideRook():
|
if location == color.kingSideRook():
|
||||||
break
|
break
|
||||||
# Square is empty and not attacked. Keep going
|
|
||||||
|
|
||||||
if result.queen:
|
if result.queen:
|
||||||
# Long castle
|
# Long castle
|
||||||
var location = loc
|
var
|
||||||
|
location = loc
|
||||||
|
otherPiece: Piece
|
||||||
while true:
|
while true:
|
||||||
location = location + queenSide
|
location = location + queenSide
|
||||||
if not location.isValid():
|
if not location.isValid():
|
||||||
break
|
break
|
||||||
if self.grid[location.row, location.col].color == color:
|
otherPiece = self.grid[location.row, location.col]
|
||||||
result.queen = false
|
|
||||||
break
|
if otherPiece.color == None:
|
||||||
if self.isAttacked(location, color) or self.grid[location.row, location.col].color != None:
|
continue
|
||||||
result.queen = false
|
|
||||||
|
if otherPiece.color == color.opposite() or otherPiece.kind != Rook or self.isAttacked(location, color.opposite()):
|
||||||
|
result.king = false
|
||||||
break
|
break
|
||||||
if location == color.queenSideRook():
|
if location == color.queenSideRook():
|
||||||
break
|
break
|
||||||
|
@ -684,6 +707,17 @@ proc generatePawnMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||||
if diagonal.isValid() and self.grid[diagonal.row, diagonal.col].color == piece.color.opposite() and self.grid[diagonal.row, diagonal.col].kind != King:
|
if diagonal.isValid() and self.grid[diagonal.row, diagonal.col].color == piece.color.opposite() and self.grid[diagonal.row, diagonal.col].kind != King:
|
||||||
locations.add(diagonal)
|
locations.add(diagonal)
|
||||||
flags.add(Capture)
|
flags.add(Capture)
|
||||||
|
var
|
||||||
|
newLocation: Location
|
||||||
|
newLocations: seq[Location]
|
||||||
|
let pins = self.getPinnedDirections(location)
|
||||||
|
for pin in pins:
|
||||||
|
newLocation = location + pin
|
||||||
|
# Pin direction is legal
|
||||||
|
if newLocation in locations:
|
||||||
|
newLocations.add(newLocation)
|
||||||
|
if pins.len() > 0:
|
||||||
|
locations = newLocations
|
||||||
|
|
||||||
var targetPiece: Piece
|
var targetPiece: Piece
|
||||||
for (target, flag) in zip(locations, flags):
|
for (target, flag) in zip(locations, flags):
|
||||||
|
@ -698,10 +732,10 @@ proc generatePawnMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||||
|
|
||||||
proc generateSlidingMoves(self: ChessBoard, location: Location): seq[Move] =
|
proc generateSlidingMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||||
## Generates moves for the sliding piece in the given location
|
## Generates moves for the sliding piece in the given location
|
||||||
var
|
let piece = self.grid[location.row, location.col]
|
||||||
piece = self.grid[location.row, location.col]
|
|
||||||
doAssert piece.kind in [Bishop, Rook, Queen], &"generateSlidingMoves called on a {piece.kind}"
|
doAssert piece.kind in [Bishop, Rook, Queen], &"generateSlidingMoves called on a {piece.kind}"
|
||||||
var directions: seq[Location] = @[]
|
var directions: seq[Location] = @[]
|
||||||
|
|
||||||
# Only check in the right directions for the chosen piece
|
# Only check in the right directions for the chosen piece
|
||||||
if piece.kind in [Bishop, Queen]:
|
if piece.kind in [Bishop, Queen]:
|
||||||
directions.add(piece.color.topLeftDiagonal())
|
directions.add(piece.color.topLeftDiagonal())
|
||||||
|
@ -713,6 +747,11 @@ proc generateSlidingMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||||
directions.add(piece.color.bottomSide())
|
directions.add(piece.color.bottomSide())
|
||||||
directions.add(piece.color.rightSide())
|
directions.add(piece.color.rightSide())
|
||||||
directions.add(piece.color.leftSide())
|
directions.add(piece.color.leftSide())
|
||||||
|
let pinned = self.getPinnedDirections(location)
|
||||||
|
if pinned.len() > 0:
|
||||||
|
# If a sliding piece is pinned then it can only
|
||||||
|
# move along the pinning direction(s)
|
||||||
|
directions = pinned
|
||||||
for direction in directions:
|
for direction in directions:
|
||||||
# Slide in this direction as long as it's possible
|
# Slide in this direction as long as it's possible
|
||||||
var
|
var
|
||||||
|
@ -795,6 +834,11 @@ proc generateKnightMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||||
piece.color.bottomRightKnightMove(long=false),
|
piece.color.bottomRightKnightMove(long=false),
|
||||||
piece.color.topLeftKnightMove(long=false),
|
piece.color.topLeftKnightMove(long=false),
|
||||||
piece.color.topRightKnightMove(long=false)]
|
piece.color.topRightKnightMove(long=false)]
|
||||||
|
let pinned = self.getPinnedDirections(location)
|
||||||
|
if pinned.len() > 0:
|
||||||
|
# Knight is pinned: can't move!
|
||||||
|
return @[]
|
||||||
|
|
||||||
for direction in directions:
|
for direction in directions:
|
||||||
# Jump to this square
|
# Jump to this square
|
||||||
let square: Location = location + direction
|
let square: Location = location + direction
|
||||||
|
@ -851,11 +895,11 @@ proc getAttackers*(self: ChessBoard, square: Location, color = None): seq[Locati
|
||||||
case color:
|
case color:
|
||||||
of White:
|
of White:
|
||||||
for attack in self.position.attacked.black:
|
for attack in self.position.attacked.black:
|
||||||
if attack.dest == square:
|
if attack.target == square:
|
||||||
result.add(attack.source)
|
result.add(attack.source)
|
||||||
of Black:
|
of Black:
|
||||||
for attack in self.position.attacked.white:
|
for attack in self.position.attacked.white:
|
||||||
if attack.dest == square:
|
if attack.target == square:
|
||||||
result.add(attack.source)
|
result.add(attack.source)
|
||||||
else:
|
else:
|
||||||
# Unreachable
|
# Unreachable
|
||||||
|
@ -866,18 +910,18 @@ proc getAttackers*(self: ChessBoard, square: Location, color = None): seq[Locati
|
||||||
# getAttackers)
|
# getAttackers)
|
||||||
proc isAttacked*(self: ChessBoard, loc: Location, color: PieceColor = None): bool =
|
proc isAttacked*(self: ChessBoard, loc: Location, color: PieceColor = None): bool =
|
||||||
## Returns whether the given location is attacked
|
## Returns whether the given location is attacked
|
||||||
## by the given opponent
|
## by the given color
|
||||||
var color = color
|
var color = color
|
||||||
if color == None:
|
if color == None:
|
||||||
color = self.getActiveColor()
|
color = self.getActiveColor().opposite()
|
||||||
case color:
|
case color:
|
||||||
of White:
|
|
||||||
for attack in self.position.attacked.black:
|
|
||||||
if attack.dest == loc:
|
|
||||||
return true
|
|
||||||
of Black:
|
of Black:
|
||||||
for attack in self.position.attacked.black:
|
for attack in self.position.attacked.black:
|
||||||
if attack.dest == loc:
|
if attack.target == loc:
|
||||||
|
return true
|
||||||
|
of White:
|
||||||
|
for attack in self.position.attacked.white:
|
||||||
|
if attack.target == loc:
|
||||||
return true
|
return true
|
||||||
of None:
|
of None:
|
||||||
discard
|
discard
|
||||||
|
@ -885,12 +929,12 @@ proc isAttacked*(self: ChessBoard, loc: Location, color: PieceColor = None): boo
|
||||||
|
|
||||||
proc isAttacked*(self: ChessBoard, square: string): bool =
|
proc isAttacked*(self: ChessBoard, square: string): bool =
|
||||||
## Returns whether the given square is attacked
|
## Returns whether the given square is attacked
|
||||||
## by its opponent
|
## by the current
|
||||||
return self.isAttacked(square.algebraicToLocation())
|
return self.isAttacked(square.algebraicToLocation())
|
||||||
|
|
||||||
|
|
||||||
func addAttack(self: ChessBoard, attack: tuple[source, dest: Location], color: PieceColor) {.inline.} =
|
func addAttack(self: ChessBoard, attack: tuple[source, target: Location], color: PieceColor) {.inline.} =
|
||||||
if attack.source.isValid() and attack.dest.isValid():
|
if attack.source.isValid() and attack.target.isValid():
|
||||||
case color:
|
case color:
|
||||||
of White:
|
of White:
|
||||||
self.position.attacked.white.add(attack)
|
self.position.attacked.white.add(attack)
|
||||||
|
@ -900,18 +944,33 @@ func addAttack(self: ChessBoard, attack: tuple[source, dest: Location], color: P
|
||||||
discard
|
discard
|
||||||
|
|
||||||
|
|
||||||
proc updatePawnAttacks(self: ChessBoard) =
|
proc getPinnedDirections(self: ChessBoard, loc: Location): seq[Location] =
|
||||||
|
let piece = self.grid[loc.row, loc.col]
|
||||||
|
case piece.color:
|
||||||
|
of None:
|
||||||
|
discard
|
||||||
|
of White:
|
||||||
|
for pin in self.position.pinned.black:
|
||||||
|
if pin.target == loc:
|
||||||
|
result.add(pin.direction)
|
||||||
|
of Black:
|
||||||
|
for pin in self.position.pinned.white:
|
||||||
|
if pin.target == loc:
|
||||||
|
result.add(pin.direction)
|
||||||
|
|
||||||
|
|
||||||
|
proc updatePawnAttacks(self: ChessBoard) {.thread.} =
|
||||||
## Internal helper of updateAttackedSquares
|
## Internal helper of updateAttackedSquares
|
||||||
for loc in self.position.pieces.white.pawns:
|
for loc in self.position.pieces.white.pawns:
|
||||||
# Pawns are special in how they capture (i.e. the
|
# Pawns are special in how they capture (i.e. the
|
||||||
# squares they can move to do not match the squares
|
# squares they can move to do not match the squares
|
||||||
# they can capture on. Sneaky fucks)
|
# they can capture on. Sneaky fucks)
|
||||||
self.addAttack((loc, loc + White.topRightDiagonal()), White)
|
self.addAttack((loc, loc + White.topRightDiagonal()), White)
|
||||||
self.addAttack((loc, loc + White.topRightDiagonal()), White)
|
self.addAttack((loc, loc + White.topLeftDiagonal()), White)
|
||||||
# We do the same thing for black
|
# We do the same thing for black
|
||||||
for loc in self.position.pieces.black.pawns:
|
for loc in self.position.pieces.black.pawns:
|
||||||
self.addAttack((loc, loc + Black.topRightDiagonal()), Black)
|
self.addAttack((loc, loc + Black.topRightDiagonal()), Black)
|
||||||
self.addAttack((loc, loc + Black.topRightDiagonal()), Black)
|
self.addAttack((loc, loc + Black.topLeftDiagonal()), Black)
|
||||||
|
|
||||||
|
|
||||||
proc updateKingAttacks(self: ChessBoard) =
|
proc updateKingAttacks(self: ChessBoard) =
|
||||||
|
@ -951,25 +1010,27 @@ proc updateKnightAttacks(self: ChessBoard) =
|
||||||
self.addAttack((loc, loc + Black.bottomRightKnightMove(long=false)), Black)
|
self.addAttack((loc, loc + Black.bottomRightKnightMove(long=false)), Black)
|
||||||
|
|
||||||
|
|
||||||
proc getSlidingAttacks(self: ChessBoard, loc: Location): Attacked =
|
proc getSlidingAttacks(self: ChessBoard, loc: Location): tuple[attacks: Attacked, pins: Pinned] =
|
||||||
## Internal helper of updateSlidingAttacks
|
## Internal helper of updateSlidingAttacks
|
||||||
var
|
var
|
||||||
directions: seq[Location] = @[]
|
directions: seq[Location] = @[]
|
||||||
square: Location = loc
|
|
||||||
otherPiece: Piece
|
|
||||||
let piece = self.grid[loc.row, loc.col]
|
let piece = self.grid[loc.row, loc.col]
|
||||||
if piece.kind in [Bishop, Queen]:
|
if piece.kind in [Bishop, Queen]:
|
||||||
directions.add(piece.color.topLeftDiagonal())
|
directions.add(piece.color.topLeftDiagonal())
|
||||||
directions.add(piece.color.topRightDiagonal())
|
directions.add(piece.color.topRightDiagonal())
|
||||||
directions.add(piece.color.bottomLeftDiagonal())
|
directions.add(piece.color.bottomLeftDiagonal())
|
||||||
directions.add(piece.color.bottomRightDiagonal())
|
directions.add(piece.color.bottomRightDiagonal())
|
||||||
|
|
||||||
if piece.kind in [Queen, Rook]:
|
if piece.kind in [Queen, Rook]:
|
||||||
directions.add(piece.color.topSide())
|
directions.add(piece.color.topSide())
|
||||||
directions.add(piece.color.bottomSide())
|
directions.add(piece.color.bottomSide())
|
||||||
directions.add(piece.color.rightSide())
|
directions.add(piece.color.rightSide())
|
||||||
directions.add(piece.color.leftSide())
|
directions.add(piece.color.leftSide())
|
||||||
|
|
||||||
for direction in directions:
|
for direction in directions:
|
||||||
|
var
|
||||||
square = loc
|
square = loc
|
||||||
|
otherPiece: Piece
|
||||||
# Slide in this direction as long as it's possible
|
# Slide in this direction as long as it's possible
|
||||||
while true:
|
while true:
|
||||||
square = square + direction
|
square = square + direction
|
||||||
|
@ -977,46 +1038,71 @@ proc getSlidingAttacks(self: ChessBoard, loc: Location): Attacked =
|
||||||
if not square.isValid():
|
if not square.isValid():
|
||||||
break
|
break
|
||||||
otherPiece = self.grid[square.row, square.col]
|
otherPiece = self.grid[square.row, square.col]
|
||||||
# A piece is in the way: we cannot proceed
|
# Target square is attacked (even if a friendly piece
|
||||||
# any further
|
# is present, because in this case we're defending
|
||||||
if otherPiece.color notin [piece.color.opposite(), None]:
|
# it)
|
||||||
|
result.attacks.add((loc, square))
|
||||||
|
# Empty square, keep going
|
||||||
|
if otherPiece.color == None:
|
||||||
|
continue
|
||||||
|
if otherPiece.color == piece.color.opposite and otherPiece.kind != King:
|
||||||
|
# We found an enemy piece that is not
|
||||||
|
# the enemy king. We don't break out
|
||||||
|
# immediately because we first want
|
||||||
|
# to check if we've pinned a piece
|
||||||
|
var
|
||||||
|
otherSquare: Location = square
|
||||||
|
behindPiece: Piece
|
||||||
|
while true:
|
||||||
|
otherSquare = otherSquare + direction
|
||||||
|
if not otherSquare.isValid():
|
||||||
|
break
|
||||||
|
behindPiece = self.grid[otherSquare.row, otherSquare.col]
|
||||||
|
if behindPiece.color == None:
|
||||||
|
continue
|
||||||
|
if behindPiece.color == piece.color.opposite and behindPiece.kind == King:
|
||||||
|
# The enemy king is behind this enemy piece: pin it in
|
||||||
|
# this direction relative to them (that's why we have the
|
||||||
|
# minus sign: up for us is down for them and vice versa)
|
||||||
|
result.pins.add((loc, square, -direction))
|
||||||
|
else:
|
||||||
|
break
|
||||||
break
|
break
|
||||||
# Target square is attacked
|
|
||||||
result.add((loc, square))
|
|
||||||
|
|
||||||
|
|
||||||
proc updateSlidingAttacks(self: ChessBoard) =
|
proc updateSlidingAttacks(self: ChessBoard) =
|
||||||
## Internal helper of updateAttackedSquares
|
## Internal helper of updateAttackedSquares
|
||||||
|
|
||||||
var
|
var data: tuple[attacks: Attacked, pins: Pinned]
|
||||||
directions: seq[Location]
|
|
||||||
piece: Piece
|
|
||||||
# Bishops
|
|
||||||
for loc in self.position.pieces.white.bishops:
|
for loc in self.position.pieces.white.bishops:
|
||||||
for attack in self.getSlidingAttacks(loc):
|
data = self.getSlidingAttacks(loc)
|
||||||
self.addAttack(attack, White)
|
self.position.attacked.white.extend(data.attacks)
|
||||||
for loc in self.position.pieces.black.bishops:
|
self.position.pinned.white.extend(data.pins)
|
||||||
for attack in self.getSlidingAttacks(loc):
|
|
||||||
self.addAttack(attack, Black)
|
|
||||||
# Rooks
|
|
||||||
for loc in self.position.pieces.white.rooks:
|
for loc in self.position.pieces.white.rooks:
|
||||||
for attack in self.getSlidingAttacks(loc):
|
data = self.getSlidingAttacks(loc)
|
||||||
self.addAttack(attack, White)
|
self.position.attacked.white.extend(data.attacks)
|
||||||
for loc in self.position.pieces.black.rooks:
|
self.position.pinned.white.extend(data.pins)
|
||||||
for attack in self.getSlidingAttacks(loc):
|
|
||||||
self.addAttack(attack, Black)
|
|
||||||
# Queens
|
|
||||||
for loc in self.position.pieces.white.queens:
|
for loc in self.position.pieces.white.queens:
|
||||||
for attack in self.getSlidingAttacks(loc):
|
data = self.getSlidingAttacks(loc)
|
||||||
self.addAttack(attack, White)
|
self.position.attacked.white.extend(data.attacks)
|
||||||
|
self.position.pinned.white.extend(data.pins)
|
||||||
|
for loc in self.position.pieces.black.bishops:
|
||||||
|
data = self.getSlidingAttacks(loc)
|
||||||
|
self.position.attacked.black.extend(data.attacks)
|
||||||
|
self.position.pinned.black.extend(data.pins)
|
||||||
|
for loc in self.position.pieces.black.rooks:
|
||||||
|
data = self.getSlidingAttacks(loc)
|
||||||
|
self.position.attacked.black.extend(data.attacks)
|
||||||
|
self.position.pinned.black.extend(data.pins)
|
||||||
for loc in self.position.pieces.black.queens:
|
for loc in self.position.pieces.black.queens:
|
||||||
for attack in self.getSlidingAttacks(loc):
|
data = self.getSlidingAttacks(loc)
|
||||||
self.addAttack(attack, Black)
|
self.position.attacked.black.extend(data.attacks)
|
||||||
|
self.position.pinned.black.extend(data.pins)
|
||||||
|
|
||||||
|
|
||||||
proc updateAttackedSquares(self: ChessBoard) =
|
proc updateAttackedSquares(self: ChessBoard) =
|
||||||
## Updates internal metadata about which squares
|
## Updates internal metadata about which squares
|
||||||
## are attacked. Called internally by doMove
|
## are attacked
|
||||||
|
|
||||||
self.position.attacked.white.setLen(0)
|
self.position.attacked.white.setLen(0)
|
||||||
self.position.attacked.black.setLen(0)
|
self.position.attacked.black.setLen(0)
|
||||||
|
@ -1024,6 +1110,7 @@ proc updateAttackedSquares(self: ChessBoard) =
|
||||||
self.updatePawnAttacks()
|
self.updatePawnAttacks()
|
||||||
# Sliding pieces
|
# Sliding pieces
|
||||||
self.updateSlidingAttacks()
|
self.updateSlidingAttacks()
|
||||||
|
# Knights
|
||||||
self.updateKnightAttacks()
|
self.updateKnightAttacks()
|
||||||
# Kings
|
# Kings
|
||||||
self.updateKingAttacks()
|
self.updateKingAttacks()
|
||||||
|
@ -1147,11 +1234,7 @@ proc updateLocations(self: ChessBoard, move: Move) =
|
||||||
## the pieces on the board after a move
|
## the pieces on the board after a move
|
||||||
if move.isCapture():
|
if move.isCapture():
|
||||||
self.position.captured = self.grid[move.targetSquare.row, move.targetSquare.col]
|
self.position.captured = self.grid[move.targetSquare.row, move.targetSquare.col]
|
||||||
try:
|
|
||||||
self.removePiece(move.targetSquare, attack=false)
|
self.removePiece(move.targetSquare, attack=false)
|
||||||
except AssertionDefect:
|
|
||||||
echo move
|
|
||||||
raise
|
|
||||||
|
|
||||||
# Update the positional metadata of the moving piece
|
# Update the positional metadata of the moving piece
|
||||||
self.movePiece(move)
|
self.movePiece(move)
|
||||||
|
@ -1246,7 +1329,6 @@ proc doMove(self: ChessBoard, move: Move) =
|
||||||
self.spawnPiece(move.targetSquare, Piece(kind: Rook, color: move.piece.color))
|
self.spawnPiece(move.targetSquare, Piece(kind: Rook, color: move.piece.color))
|
||||||
of PromoteToQueen:
|
of PromoteToQueen:
|
||||||
self.spawnPiece(move.targetSquare, Piece(kind: Queen, color: move.piece.color))
|
self.spawnPiece(move.targetSquare, Piece(kind: Queen, color: move.piece.color))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
discard
|
discard
|
||||||
let previous = self.position
|
let previous = self.position
|
||||||
|
@ -1493,16 +1575,35 @@ proc pretty*(self: ChessBoard): string =
|
||||||
result &= "\x1b[0m"
|
result &= "\x1b[0m"
|
||||||
|
|
||||||
|
|
||||||
proc countLegalMoves*(self: ChessBoard, ply: int, verbose: bool = false, current: int = 0): tuple[nodes: int, captures: int, castles: int, checks: int, promotions: int] =
|
proc perft*(self: ChessBoard, ply: int, verbose: bool = false): CountData =
|
||||||
## Counts (and debugs) the number of legal positions reached after
|
## Counts (and debugs) the number of legal positions reached after
|
||||||
## the given number of ply
|
## the given number of ply
|
||||||
|
var verbose = verbose
|
||||||
if ply == 0:
|
if ply == 0:
|
||||||
result = (1, 0, 0, 0, 0)
|
result = (1, 0, 0, 0, 0, 0, 0)
|
||||||
else:
|
else:
|
||||||
var
|
let moves = self.generateAllMoves()
|
||||||
before: string
|
if len(moves) == 0:
|
||||||
for move in self.generateAllMoves():
|
inc(result.checkmates)
|
||||||
before = self.pretty()
|
for move in moves:
|
||||||
|
if verbose:
|
||||||
|
let canCastle = self.canCastle(move.piece.color)
|
||||||
|
#echo "\x1Bc"
|
||||||
|
echo &"Ply: {self.position.plyFromRoot}"
|
||||||
|
echo &"Move: {move.startSquare.locationToAlgebraic()}{move.targetSquare.locationToAlgebraic()}, from ({move.startSquare.row}, {move.startSquare.col}) to ({move.targetSquare.row}, {move.targetSquare.col})"
|
||||||
|
echo &"Turn: {move.piece.color}"
|
||||||
|
echo &"Piece: {move.piece.kind}"
|
||||||
|
echo &"Flag: {move.flag}"
|
||||||
|
echo &"In check: {(if self.inCheck(): \"yes\" else: \"no\")}"
|
||||||
|
echo &"Can castle:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
|
||||||
|
echo "Before:\n"
|
||||||
|
echo self.pretty()
|
||||||
|
try:
|
||||||
|
discard readLine(stdin)
|
||||||
|
except IOError:
|
||||||
|
discard
|
||||||
|
except EOFError:
|
||||||
|
discard
|
||||||
self.doMove(move)
|
self.doMove(move)
|
||||||
case move.flag:
|
case move.flag:
|
||||||
of Capture:
|
of Capture:
|
||||||
|
@ -1511,41 +1612,30 @@ proc countLegalMoves*(self: ChessBoard, ply: int, verbose: bool = false, current
|
||||||
inc(result.castles)
|
inc(result.castles)
|
||||||
of PromoteToBishop, PromoteToKnight, PromoteToQueen, PromoteToRook:
|
of PromoteToBishop, PromoteToKnight, PromoteToQueen, PromoteToRook:
|
||||||
inc(result.promotions)
|
inc(result.promotions)
|
||||||
|
of EnPassant:
|
||||||
|
inc(result.enPassant)
|
||||||
else:
|
else:
|
||||||
discard
|
discard
|
||||||
if self.inCheck():
|
if self.inCheck():
|
||||||
# Opponent king is in check
|
# Opponent king is in check
|
||||||
inc(result.checks)
|
inc(result.checks)
|
||||||
if verbose:
|
if verbose:
|
||||||
let canCastle = self.canCastle(move.piece.color)
|
echo "Now:\n"
|
||||||
echo "\x1Bc"
|
|
||||||
echo &"Ply: {self.position.plyFromRoot} (move {current + result.nodes + 1})"
|
|
||||||
echo &"Move: {move.startSquare.locationToAlgebraic()}{move.targetSquare.locationToAlgebraic()} (({move.startSquare.row}, {move.startSquare.col}) -> ({move.targetSquare.row}, {move.targetSquare.col}))"
|
|
||||||
echo &"Turn: {move.piece.color}"
|
|
||||||
echo &"Piece: {move.piece.kind}"
|
|
||||||
echo &"Flag: {move.flag}"
|
|
||||||
echo &"Can castle:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
|
|
||||||
echo "\nBefore:"
|
|
||||||
echo before
|
|
||||||
echo "\nNow: "
|
|
||||||
echo self.pretty()
|
echo self.pretty()
|
||||||
echo &"\n\nTotal captures: {result.captures}"
|
|
||||||
echo &"Total castles: {result.castles}"
|
|
||||||
echo &"Total checks: {result.checks}"
|
|
||||||
echo &"Total promotions: {result.promotions}"
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
discard readLine(stdin)
|
discard readLine(stdin)
|
||||||
except IOError:
|
except IOError:
|
||||||
discard
|
discard
|
||||||
except EOFError:
|
except EOFError:
|
||||||
discard
|
discard
|
||||||
let next = self.countLegalMoves(ply - 1, verbose, result.nodes + 1)
|
let next = self.perft(ply - 1, verbose)
|
||||||
result.nodes += next.nodes
|
result.nodes += next.nodes
|
||||||
result.captures += next.captures
|
result.captures += next.captures
|
||||||
result.checks += next.checks
|
result.checks += next.checks
|
||||||
result.promotions += next.promotions
|
result.promotions += next.promotions
|
||||||
result.castles += next.castles
|
result.castles += next.castles
|
||||||
|
result.enPassant += next.enPassant
|
||||||
|
result.checkmates += next.checkmates
|
||||||
self.undoMove(move)
|
self.undoMove(move)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1607,6 +1697,6 @@ when isMainModule:
|
||||||
when compileOption("profiler"):
|
when compileOption("profiler"):
|
||||||
import nimprof
|
import nimprof
|
||||||
|
|
||||||
#b = newChessboardFromFEN("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - ")
|
# b = newChessboardFromFEN("fen")
|
||||||
echo b.countLegalMoves(3, verbose=false)
|
echo b.perft(4, verbose=true)
|
||||||
echo "All tests were successful"
|
echo "All tests were successful"
|
Loading…
Reference in New Issue