2024-04-21 11:09:12 +02:00
|
|
|
# Copyright 2024 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.
|
|
|
|
|
2024-04-16 15:24:31 +02:00
|
|
|
## Handling of moves
|
|
|
|
import pieces
|
|
|
|
|
|
|
|
|
2024-04-24 10:41:01 +02:00
|
|
|
import std/strformat
|
|
|
|
|
|
|
|
|
2024-04-16 15:24:31 +02:00
|
|
|
type
|
|
|
|
MoveFlag* = enum
|
|
|
|
## An enumeration of move flags
|
|
|
|
Default = 0'u16, # No flag
|
|
|
|
EnPassant = 1, # Move is a capture with en passant
|
|
|
|
Capture = 2, # Move is a capture
|
|
|
|
DoublePush = 4, # Move is a double pawn push
|
|
|
|
# Castling metadata
|
2024-04-23 01:50:56 +02:00
|
|
|
Castle = 8,
|
2024-04-16 15:24:31 +02:00
|
|
|
# Pawn promotion metadata
|
2024-04-23 01:50:56 +02:00
|
|
|
PromoteToQueen = 16,
|
|
|
|
PromoteToRook = 32,
|
|
|
|
PromoteToBishop = 64,
|
|
|
|
PromoteToKnight = 128
|
2024-04-16 15:24:31 +02:00
|
|
|
|
|
|
|
Move* = object
|
|
|
|
## A chess move
|
|
|
|
startSquare*: Square
|
|
|
|
targetSquare*: Square
|
|
|
|
flags*: uint16
|
|
|
|
|
2024-04-25 15:20:55 +02:00
|
|
|
MoveList* = ref object
|
2024-04-16 16:29:21 +02:00
|
|
|
## A list of moves
|
2024-04-25 15:20:55 +02:00
|
|
|
data*: array[218, Move]
|
2024-04-16 16:29:21 +02:00
|
|
|
len: int8
|
|
|
|
|
2024-04-25 15:20:55 +02:00
|
|
|
|
2024-04-16 23:45:32 +02:00
|
|
|
func `[]`*(self: MoveList, i: SomeInteger): Move =
|
|
|
|
when not defined(danger):
|
|
|
|
if i >= self.len:
|
|
|
|
raise newException(IndexDefect, &"move list access out of bounds ({i} >= {self.len})")
|
|
|
|
result = self.data[i]
|
|
|
|
|
2024-04-16 16:29:21 +02:00
|
|
|
iterator items*(self: MoveList): Move =
|
|
|
|
var i = 0
|
|
|
|
while self.len > i:
|
|
|
|
yield self.data[i]
|
|
|
|
inc(i)
|
|
|
|
|
|
|
|
|
|
|
|
iterator pairs*(self: MoveList): tuple[i: int, move: Move] =
|
|
|
|
var i = 0
|
|
|
|
for item in self:
|
|
|
|
yield (i, item)
|
2024-05-06 00:34:06 +02:00
|
|
|
inc(i)
|
2024-04-16 16:29:21 +02:00
|
|
|
|
|
|
|
|
2024-04-23 01:50:56 +02:00
|
|
|
func `$`*(self: MoveList): string =
|
|
|
|
result &= "["
|
|
|
|
for i, move in self:
|
|
|
|
result &= $move
|
|
|
|
if i < self.len:
|
|
|
|
result &= ", "
|
|
|
|
result &= "]"
|
|
|
|
|
|
|
|
|
2024-04-16 16:29:21 +02:00
|
|
|
func add*(self: var MoveList, move: Move) {.inline.} =
|
|
|
|
self.data[self.len] = move
|
|
|
|
inc(self.len)
|
|
|
|
|
|
|
|
|
2024-04-16 23:45:32 +02:00
|
|
|
func clear*(self: var MoveList) {.inline.} =
|
|
|
|
self.len = 0
|
|
|
|
|
|
|
|
|
2024-04-16 16:29:21 +02:00
|
|
|
func contains*(self: MoveList, move: Move): bool {.inline.} =
|
|
|
|
for item in self:
|
|
|
|
if move == item:
|
|
|
|
return true
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
|
|
func len*(self: MoveList): int {.inline.} = self.len
|
|
|
|
|
2024-04-16 15:24:31 +02:00
|
|
|
|
2024-04-16 23:45:32 +02:00
|
|
|
# A bunch of move creation utilities
|
2024-04-16 15:24:31 +02:00
|
|
|
|
2024-04-16 23:45:32 +02:00
|
|
|
func createMove*(startSquare, targetSquare: Square, flags: varargs[MoveFlag]): Move =
|
2024-04-16 15:24:31 +02:00
|
|
|
result = Move(startSquare: startSquare, targetSquare: targetSquare, flags: Default.uint16)
|
|
|
|
for flag in flags:
|
|
|
|
result.flags = result.flags or flag.uint16
|
|
|
|
|
2024-05-01 16:30:21 +02:00
|
|
|
|
2024-04-16 23:45:32 +02:00
|
|
|
proc createMove*(startSquare, targetSquare: string, flags: varargs[MoveFlag]): Move =
|
|
|
|
result = createMove(startSquare.toSquare(), targetSquare.toSquare(), flags)
|
|
|
|
|
|
|
|
func createMove*(startSquare, targetSquare: SomeInteger, flags: varargs[MoveFlag]): Move =
|
|
|
|
result = createMove(Square(startSquare.int8), Square(targetSquare.int8), flags)
|
|
|
|
|
|
|
|
|
|
|
|
func createMove*(startSquare: Square, targetSquare: SomeInteger, flags: varargs[MoveFlag]): Move =
|
|
|
|
result = createMove(startSquare, Square(targetSquare.int8), flags)
|
|
|
|
|
2024-04-16 15:24:31 +02:00
|
|
|
|
2024-04-20 14:51:50 +02:00
|
|
|
func nullMove*: Move {.inline.} = createMove(nullSquare(), nullSquare())
|
|
|
|
|
2024-05-01 16:30:21 +02:00
|
|
|
|
2024-04-20 14:51:50 +02:00
|
|
|
func isPromotion*(move: Move): bool {.inline.} =
|
|
|
|
## Returns whether the given move is a
|
|
|
|
## pawn promotion
|
|
|
|
for promotion in [PromoteToBishop, PromoteToKnight, PromoteToRook, PromoteToQueen]:
|
|
|
|
if (move.flags and promotion.uint16) != 0:
|
|
|
|
return true
|
|
|
|
|
|
|
|
|
|
|
|
func getPromotionType*(move: Move): MoveFlag {.inline.} =
|
|
|
|
## Returns the promotion type of the given move.
|
|
|
|
## The return value of this function is only valid
|
|
|
|
## if isPromotion() returns true
|
|
|
|
for promotion in [PromoteToBishop, PromoteToKnight, PromoteToRook, PromoteToQueen]:
|
|
|
|
if (move.flags and promotion.uint16) != 0:
|
|
|
|
return promotion
|
|
|
|
|
|
|
|
|
|
|
|
func isCapture*(move: Move): bool {.inline.} =
|
|
|
|
## Returns whether the given move is a
|
|
|
|
## cature
|
2024-04-23 01:50:56 +02:00
|
|
|
result = (move.flags and Capture.uint16) != 0
|
2024-04-20 14:51:50 +02:00
|
|
|
|
|
|
|
|
|
|
|
func isCastling*(move: Move): bool {.inline.} =
|
|
|
|
## Returns whether the given move is a
|
2024-04-23 01:50:56 +02:00
|
|
|
## castling move
|
|
|
|
result = (move.flags and Castle.uint16) != 0
|
2024-04-20 14:51:50 +02:00
|
|
|
|
|
|
|
|
|
|
|
func isEnPassant*(move: Move): bool {.inline.} =
|
|
|
|
## Returns whether the given move is an
|
|
|
|
## en passant capture
|
|
|
|
result = (move.flags and EnPassant.uint16) != 0
|
|
|
|
|
|
|
|
|
|
|
|
func isDoublePush*(move: Move): bool {.inline.} =
|
|
|
|
## Returns whether the given move is a
|
|
|
|
## double pawn push
|
|
|
|
result = (move.flags and DoublePush.uint16) != 0
|
|
|
|
|
|
|
|
|
|
|
|
func getFlags*(move: Move): seq[MoveFlag] =
|
|
|
|
## Gets all the flags of this move
|
2024-04-23 01:50:56 +02:00
|
|
|
for flag in [EnPassant, Capture, DoublePush, Castle,
|
|
|
|
PromoteToBishop, PromoteToKnight, PromoteToQueen,
|
|
|
|
PromoteToRook]:
|
2024-04-20 14:51:50 +02:00
|
|
|
if (move.flags and flag.uint16) == flag.uint16:
|
|
|
|
result.add(flag)
|
|
|
|
if result.len() == 0:
|
2024-04-24 10:41:01 +02:00
|
|
|
result.add(Default)
|
|
|
|
|
|
|
|
|
|
|
|
func `$`*(self: Move): string =
|
|
|
|
## Returns a string representation
|
|
|
|
## for the move
|
2024-05-01 16:30:21 +02:00
|
|
|
if self == nullMove():
|
|
|
|
return "null"
|
2024-04-24 10:41:01 +02:00
|
|
|
result &= &"{self.startSquare}{self.targetSquare}"
|
|
|
|
let flags = self.getFlags()
|
|
|
|
if len(flags) > 0:
|
|
|
|
result &= " ("
|
|
|
|
for i, flag in flags:
|
|
|
|
result &= $flag
|
|
|
|
if i < flags.high():
|
|
|
|
result &= ", "
|
2024-04-24 12:38:03 +02:00
|
|
|
result &= ")"
|
|
|
|
|
|
|
|
|
|
|
|
func toAlgebraic*(self: Move): string =
|
2024-05-01 16:30:21 +02:00
|
|
|
if self == nullMove():
|
|
|
|
return "null"
|
2024-04-24 12:38:03 +02:00
|
|
|
result &= &"{self.startSquare}{self.targetSquare}"
|
|
|
|
if self.isPromotion():
|
|
|
|
case self.getPromotionType():
|
|
|
|
of PromoteToBishop:
|
|
|
|
result &= "b"
|
|
|
|
of PromoteToKnight:
|
|
|
|
result &= "n"
|
|
|
|
of PromoteToQueen:
|
|
|
|
result &= "q"
|
|
|
|
of PromoteToRook:
|
|
|
|
result &= "r"
|
|
|
|
else:
|
2024-04-27 09:49:45 +02:00
|
|
|
discard
|
|
|
|
|
|
|
|
|
|
|
|
proc newMoveList*: MoveList =
|
|
|
|
new(result)
|
|
|
|
for i in 0..result.data.high():
|
|
|
|
result.data[i] = nullMove()
|