Initial work on Chess

This commit is contained in:
Mattia Giambirtone 2023-03-18 18:14:30 +01:00
parent 4f908c24fa
commit 9c191adedd
Signed by: nocturn9x
GPG Key ID: 8270F9F467971E59
2 changed files with 175 additions and 0 deletions

170
src/Chess/board.nim Normal file
View File

@ -0,0 +1,170 @@
# Copyright 2023 Mattia Giambirtone & All Contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import ../util/matrix
import std/strutils
type
PieceColor* = enum
None = 0,
White,
Black
PieceKind* = enum
Empty = 0, # No piece
Bishop = 'b',
King = 'k'
Knight = 'n',
Pawn = 'p',
Queen = 'q',
Rook = 'r',
Piece* = object
color*: PieceColor
kind*: PieceKind
Position* = object
piece*: Piece
location*: tuple[row, col: int]
Board* = ref object
grid: Matrix[Piece]
ply: PieceColor
# Number of half moves since
# last piece capture. Used
# for the 50-move rule
halfMoveClock: int
# Full move counter. Increments
# every 2 ply
fullMoveCount: int
# Stores metadata for castling.
castling: tuple[white, black: tuple[queen, king: bool]]
# 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: Position
# Initialized only once, copied every time
var empty: seq[Piece] = @[]
for _ in countup(0, 63):
empty.add(Piece(kind: Empty, color: None))
proc algebraicToPosition(s: string): tuple[row, col: int] {.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')))
proc newChessboardFromFEN*(state: string): Board =
## 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))
var
# Current location in the grid
row = 0
column = 0
# Current section in the FEN string
section = 0
# Current index into the FEN string
index = 0
# See https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation
while index <= state.high():
var c = state[index]
if c == ' ':
# Next section
inc(section)
inc(index)
continue
case section:
of 0:
# Piece placement data
case c.toLowerAscii():
of 'r', 'n', 'b', 'q', 'k', 'p':
result.grid[row, column] = Piece(kind: PieceKind(c.toLowerAscii()), color: if c.isUpperAscii(): White else: Black)
inc(column)
of '/':
inc(row)
column = 0
of '0'..'9':
let x = int(uint8(c) - uint8('0')) - 1
if x > 7:
raise newException(ValueError, "invalid skip value (> 8) in FEN string")
column += x
else:
raise newException(ValueError, "invalid piece identifier in FEN string")
of 1:
# Active color
case c:
of 'w':
result.ply = White
of 'b':
result.ply = Black
else:
raise newException(ValueError, "invalid next move identifier in FEN string")
of 2:
# Castling availability
case c:
of '-':
# Neither side can castle anywhere: do nothing,
# as the castling metadata is set to this state
# by default
discard
of 'K':
result.castling.white.king = true
of 'Q':
result.castling.white.queen = true
of 'k':
result.castling.black.king = true
of 'q':
result.castling.black.queen = true
else:
raise newException(ValueError, "invalid castling availability in FEN string")
of 3:
# En passant target square
case c:
of '-':
result.enPassantSquare.location = (-1, -1)
else:
result.enPassantSquare.location = state[index..index+1].algebraicToPosition()
# Just for cleanliness purposes, we fill in the other positional metadata as
# well
result.enPassantSquare.piece.color = if result.ply == Black: White else: Black
result.enPassantSquare.piece.kind = Pawn
# Square metadata is 2 bytes long
inc(index)
of 4:
# Halfmove clock
var s = ""
while not state[index].isSpaceAscii():
s.add(state[index])
inc(index)
# Backtrack so the space is seen by the
# next iteration of the loop
dec(index)
result.halfMoveClock = parseInt(s)
of 5:
# Fullmove number
var s = ""
while index <= state.high():
s.add(state[index])
inc(index)
result.fullMoveCount = parseInt(s)
else:
raise newException(ValueError, "too many fields in FEN string")
inc(index)

5
src/Chess/player.nim Normal file
View File

@ -0,0 +1,5 @@
import board
echo newChessboardFromFEN("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1")[]