Fix algebraicToLocation and added getPiece function

This commit is contained in:
Mattia Giambirtone 2023-10-12 11:55:12 +02:00
parent 85648d883c
commit 1f940a6e60
Signed by: nocturn9x
GPG Key ID: 8270F9F467971E59
2 changed files with 153 additions and 11 deletions

View File

@ -13,12 +13,17 @@
# limitations under the License.
import ../util/matrix
export matrix
import std/strutils
import std/strformat
type
Location = tuple[row, col: int]
Pieces = tuple[king: Location, queen: Location, rooks: array[2, Location],
bishops: array[2, Location], knights: array[2, Location],
pawns: array[8, Location]]
PieceColor* = enum
None = 0,
White,
@ -36,10 +41,10 @@ type
kind*: PieceKind
Position* = object
piece*: Piece
location*: tuple[row, col: int]
location*: Location
ChessBoard* = ref object
## A chess board object
grid: Matrix[Piece]
grid*: Matrix[Piece]
# Currently active color
turn: PieceColor
# Number of half moves since
@ -55,6 +60,11 @@ type
# If en passant is not possible, both the row and
# column of the position will be set to -1
enPassantSquare: Position
# Locations of all pieces
pieces: tuple[white: Pieces, black: Pieces]
# Locations of all attacked squares
attacked: tuple[white: seq[Location], black: seq[Location]]
# Initialized only once, copied every time
var empty: seq[Piece] = @[]
@ -62,12 +72,46 @@ for _ in countup(0, 63):
empty.add(Piece(kind: Empty, color: None))
proc algebraicToPosition(s: string): tuple[row, col: int] {.inline.} =
proc algebraicToPosition(s: string): Location {.inline.} =
## Converts a square location from algebraic
## notation to its corresponding row and column
## in the chess grid (0 indexed)
assert len(s) == 2
result = (int(uint8(s[0]) - (uint8('a') - 1)), int(uint8(s[1]) - uint8('0')))
if len(s) != 2:
raise newException(ValueError, "algebraic position must be of length 2")
var s = s.toLowerAscii()
if s[0] notin 'a'..'h':
raise newException(ValueError, &"algebraic position has invalid first character ('{s[0]}')")
if s[1] notin '1'..'8':
raise newException(ValueError, &"algebraic position has invalid second character ('{s[1]}')")
let file = int(uint8(s[0]) - uint8('a'))
var rank: int
case s[1]:
of '1':
rank = 7
of '2':
rank = 6
of '3':
rank = 5
of '4':
rank = 4
of '5':
rank = 3
of '6':
rank = 2
of '7':
rank = 1
of '8':
rank = 0
else:
discard
return (rank, file)
proc getPiece*(self: ChessBoard, square: string): Piece =
## Gets the piece on the given square
## in algebraic notation
let loc = square.algebraicToPosition()
return self.grid[loc.row, loc.col]
proc `$`*(self: ChessBoard): string =
@ -84,12 +128,33 @@ proc `$`*(self: ChessBoard): string =
result &= "\n- - - - - - - -"
proc newChessboard: ChessBoard =
## Returns a new, empty chessboard
new(result)
# Initialize all positions to a known default state.
# This is useful in newChessBoardFromFEN
result.pieces.black.king = (-1, -1)
result.pieces.white.king = (-1, -1)
result.pieces.black.queen = (-1, -1)
result.pieces.white.queen = (-1, -1)
result.pieces.black.rooks = [(-1, -1), (-1, -1)]
result.pieces.white.rooks = [(-1, -1), (-1, -1)]
result.pieces.black.bishops = [(-1, -1), (-1, -1)]
result.pieces.white.bishops = [(-1, -1), (-1, -1)]
result.pieces.black.knights = [(-1, -1), (-1, -1)]
result.pieces.white.knights = [(-1, -1), (-1, -1)]
result.pieces.black.pawns = [(-1, -1), (-1, -1), (-1, -1), (-1, -1), (-1, -1), (-1, -1), (-1, -1), (-1, -1)]
result.pieces.white.pawns = [(-1, -1), (-1, -1), (-1, -1), (-1, -1), (-1, -1), (-1, -1), (-1, -1), (-1, -1)]
# Turns our flat sequence into an 8x8 grid
result.grid = newMatrixFromSeq[Piece](empty, (8, 8))
result.attacked = (@[], @[])
result.enPassantSquare = Position(piece: Piece(kind: Empty, color: None), location: (-1, -1))
proc newChessboardFromFEN*(state: string): ChessBoard =
## Initializes a chessboard with the
## state encoded by the given FEN string
new(result)
# Turns our flat sequence into an 8x8 grid
result.grid = newMatrixFromSeq[Piece](empty, (8, 8))
result = newChessboard()
var
# Current location in the grid
row = 0
@ -98,6 +163,8 @@ proc newChessboardFromFEN*(state: string): ChessBoard =
section = 0
# Current index into the FEN string
index = 0
# Temporary variable to store the piece
piece: Piece
# See https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation
while index <= state.high():
var c = state[index]
@ -110,13 +177,82 @@ proc newChessboardFromFEN*(state: string): ChessBoard =
of 0:
# Piece placement data
case c.toLowerAscii():
# Piece
of 'r', 'n', 'b', 'q', 'k', 'p':
result.grid[row, column] = Piece(kind: PieceKind(c.toLowerAscii()), color: if c.isUpperAscii(): White else: Black)
# We know for a fact these values are in our
# enumeration, so all is good
{.push.}
{.warning[HoleEnumConv]:off.}
piece = Piece(kind: PieceKind(c.toLowerAscii()), color: if c.isUpperAscii(): White else: Black)
{.pop.}
case piece.color:
of Black:
case piece.kind:
of Pawn:
# Find first empty slot in the pieces array
for i, e in result.pieces.black.pawns:
if e == (-1, -1):
result.pieces.black.pawns[i] = (row, column)
break
of Bishop:
if result.pieces.black.bishops[0] == (-1, -1):
result.pieces.black.bishops[0] = (row, column)
else:
result.pieces.black.bishops[1] = (row, column)
of Knight:
if result.pieces.black.knights[0] == (-1, -1):
result.pieces.black.knights[0] = (row, column)
else:
result.pieces.black.knights[1] = (row, column)
of Rook:
if result.pieces.black.rooks[0] == (-1, -1):
result.pieces.black.rooks[0] = (row, column)
else:
result.pieces.black.rooks[1] = (row, column)
of Queen:
result.pieces.black.queen = (row, column)
of King:
result.pieces.black.king = (row, column)
else:
discard
of White:
case piece.kind:
of Pawn:
for i, e in result.pieces.white.pawns:
if e == (-1, -1):
result.pieces.white.pawns[i] = (row, column)
break
of Bishop:
if result.pieces.white.bishops[0] == (-1, -1):
result.pieces.white.bishops[0] = (row, column)
else:
result.pieces.white.bishops[1] = (row, column)
of Knight:
if result.pieces.white.knights[0] == (-1, -1):
result.pieces.white.knights[0] = (row, column)
else:
result.pieces.white.knights[1] = (row, column)
of Rook:
if result.pieces.white.rooks[0] == (-1, -1):
result.pieces.white.rooks[0] = (row, column)
else:
result.pieces.white.rooks[1] = (row, column)
of Queen:
result.pieces.white.queen = (row, column)
of King:
result.pieces.white.king = (row, column)
else:
discard
else:
discard
result.grid[row, column] = piece
inc(column)
of '/':
# Next row
inc(row)
column = 0
of '0'..'9':
# Skip x columns
let x = int(uint8(c) - uint8('0')) - 1
if x > 7:
raise newException(ValueError, "invalid skip value (> 8) in FEN string")
@ -154,7 +290,8 @@ proc newChessboardFromFEN*(state: string): ChessBoard =
# En passant target square
case c:
of '-':
result.enPassantSquare.location = (-1, -1)
# Field is already uninitialized to the correct state
discard
else:
result.enPassantSquare.location = state[index..index+1].algebraicToPosition()
# Just for cleanliness purposes, we fill in the other positional metadata as

View File

@ -1,5 +1,10 @@
import board
var b = newDefaultChessboard()
echo b
echo b.getPiece("a2")
echo b.getPiece("a7")
echo b.getPiece("a1")
echo b.getPiece("a8")
echo newChessboardFromFEN("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1")[]