My question: In proc $(p: Position); string I want to get p.game50 as a decimal string representing the integer. Problem is I have specialized conversions to string for the two types Square and Bitboard which are both a kind of integer. What is the right way to solve the problem?
chess.nim(51, 14) Error: ambiguous call; both chess.$(sq: Square) [proc declared in /home/urban/src/chess/chess.nim(8, 6)] and chess.$(bb: Bitboard) [proc declared in /home/urban/src/chess/chess.nim(14, 6)] match for: (uint16)
I keep the source at https://github.com/Koistinen/chess
# Copyright 2022 Urban Koistinen - GNU Affero
import strutils
type Square* = range[0..63]
proc square*(file, rank: int):Square = rank*8+file
proc rank*(sq: Square):int=sq shr 3
proc file*(sq: Square):int=sq and 7
proc `$`* (sq: Square):string =
result.add("abcdefgh"[sq.file])
result.add("12345678"[sq.rank])
type Bitboard* = uint64
proc bitboard* (sq: Square):Bitboard = 1u shl sq
proc `$`* (bb: Bitboard): string =
for rank in countdown(7,0):
if rank<7: result.add('\n')
for file in countup(0,7):
if 0 == (bb and bitboard(square(file, rank))):
result.add('-')
else:
result.add('+')
type Position* = object # 8*8 bytes
so: array[0..1, Bitboard]
pawns, knights, bishops, rooks, queens: Bitboard
game50: uint16
halfmoves: uint16
kings: array[0..1, uint8]
ep: uint8
castling: uint8
proc `$`*(p: Position): string =
var oc: Bitboard = p.so[0] or p.so[1]
for rank in countdown(7,0):
for file in countup(0,7):
var sq: Bitboard = bitboard(square(file, rank))
if 0 == (oc and sq):
result.add(if 1 == (1 and (rank+file)): '-' else: ' ')
else:
var pieceChar: char = 'k'
if sq == (sq and p.knights): pieceChar = 'n'
elif sq == (sq and p.bishops): pieceChar = 'b'
elif sq == (sq and p.rooks): pieceChar = 'r'
elif sq == (sq and p.queens): pieceChar = 'q'
elif sq == (sq and p.pawns): pieceChar = 'p'
if sq == (sq and p.so[0]):
pieceChar = (pieceChar.int - ' '.int).char
result.add(pieceChar)
result.add('\n')
result.add("game50: ")
result.add($p.game50)
result.add(" halfmoves: ")
# result.add($p.halfmoves)
result.add(" epsquare: ")
if 0 < p.ep: result.add($p.ep.Square)
else: result.add("no")
result.add(" castling: ")
# result.add($p.castling)
proc emptyPosition: Position =
result.so[0] = 0
result.so[1] = 0
result.pawns = 0
result.knights = 0
result.bishops = 0
result.rooks = 0
result.queens = 0
result.game50 = 0
result.halfmoves = 0
result.kings[0] = 64 # missing white king is illegal position
result.kings[1] = 64 # missing black king is illegal position
result.ep = 0
result.castling = 0xf
proc addPiece*(p: Position, piece: char, sq: Square): Position =
result = p
var bb: Bitboard = bitboard(sq)
var side: int = if isLowerAscii(piece): 1 else: 0
result.so[side] = p.so[side] or bb
case piece.toLowerAscii
of 'p': result.pawns = p.pawns or bb
of 'n': result.knights = p.knights or bb
of 'b': result.bishops = p.bishops or bb
of 'r': result.rooks = p.rooks or bb
of 'q': result.queens = p.queens or bb
of 'k':
assert result.kings[side] == 64 # no previous king
result.kings[side] = sq.uint8
else: assert false
proc fen(s: string): Position =
result = emptyPosition()
var board: string = s
var rank: int = 7
var file: int = 0
for c in board:
assert file < 8 or c == '/'
case c
of '/':
dec rank
file = 0
assert rank >= 0
of '1'..'8':
inc(file, c.ord - '0'.ord)
of 'k','K','q','Q','r','R','b','B','n','N','p','P':
result = result.addPiece(c, square(file, rank))
inc file
else: assert false
proc startingPosition: Position =
fen("rnbqkbnr/pppppppp/////PPPPPPPP/RNBQKBNR")
type Move* = object
fr: uint8 # 64..127 promotion, 128..191 ep, 192..256 castling
to: uint8 # high 2 bits say promotion piece in case of promotion
proc isPromotion(mv: Move):bool = 64 <= mv.fr and mv.fr <= 127
when isMainModule:
echo bitboard(square(0,1)) or bitboard(square(2,2))
echo square(0,1)
echo square(2,2)
var mv: Move
mv.fr = 12
mv.to = 28
echo isPromotion(mv)
var p = startingPosition()
echo "white pieces:"
echo p.so[0]
echo "black pieces:"
echo p.so[1]
echo "pawns:"
echo p.pawns
echo "queens:"
echo p.queens
echo "rooks:"
echo p.rooks
echo "bishops:"
echo p.bishops
echo "bishops:"
echo p.bishops
echo "knights:"
echo p.knights
echo "kings: ", p.kings[0].Square, ", ", p.kings[1].Square
p = fen("8/p7/1P6/1r3p1k/7P/3R1KP1/8/8") # b - - 0 0
echo "8/p7/1P6/1r3p1k/7P/3R1KP1/8/8 b - - 0 0"
echo "white pieces:"
echo p.so[0]
echo "black pieces:"
echo p.so[1]
echo "pawns:"
echo p.pawns
echo "queens:"
echo p.queens
echo "rooks:"
echo p.rooks
echo "bishops:"
echo p.bishops
echo "bishops:"
echo p.bishops
echo "knights:"
echo p.knights
echo p
I would declare Square as distinct that's enough to get it to compile, but you could declare BitBoard distinct as well, it might save you from some silly bugs in the future.
type Square* = distinct range[0..63]
proc square*(file, rank: int):Square = (rank*8+file).Square
proc rank*(sq: Square):int=sq.int div 8
proc file*(sq: Square):int=sq.int and 7
proc `$`* (sq: Square):string =
result.add("abcdefgh"[sq.file])
result.add("12345678"[sq.rank])
type Bitboard* = uint64
proc bitboard* (sq: Square):Bitboard = 1u shl sq.uint64
...
Thanks shirleyquirk!
Without Bitboard declared distinct, a silly error I get is it is printed as a Bitboard. Adding in the procs I want to use looks like a good enough solution.
This is getting to much like the complications of Java or C++. Maybe it is better to skip Square and Bitboard types and just use uint8 and uint64 instead or have them as aliases.
I want the code to be readable, not have many lines that make the code harder to understand in most circumstances.
With a completely new type such as Position, there seems to be no complication because there are no predefined functions that I would want to work that will get mixed up. I think the "distinct type" part of the manual explains how it works. (just found it)
Keeping range for addPiece looks like a good idea!
xigoi, yes, that is how I am now thinking of it.
I'll use it when it makes sense. What I was hoping for was something like:
proc `$`*(bb: distinct Bitboard): string = ...
working and meaning the type is distinct for that function, but I understand that kind of thing might mess up the language or cause bad code to be written.
I am happy enough using a separate function for formatting bitboards.
I notice it will compile without warning when you don't call it. Is there any case when distinct should be used that way? What does it mean? How would you call it?