bb migration: add util, update fen/fen-test + partial pos + piece
This commit is contained in:
344
src/fen.c
344
src/fen.c
@@ -1,6 +1,6 @@
|
||||
/* fen.c - fen notation.
|
||||
*
|
||||
* Copyright (C) 2021 Bruno Raoult ("br")
|
||||
* Copyright (C) 2021-2024 Bruno Raoult ("br")
|
||||
* Licensed under the GNU General Public License v3.0 or later.
|
||||
* Some rights reserved. See COPYING.
|
||||
*
|
||||
@@ -18,158 +18,264 @@
|
||||
#include <ctype.h>
|
||||
|
||||
#include <debug.h>
|
||||
#include <bug.h>
|
||||
|
||||
#include "chessdefs.h"
|
||||
#include "position.h"
|
||||
#include "board.h"
|
||||
#include "fen.h"
|
||||
#include "piece.h"
|
||||
#include "util.h"
|
||||
|
||||
/* Starting Position :
|
||||
* rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
|
||||
* After 1.e4 :
|
||||
* rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1
|
||||
* After 1... c5 :
|
||||
* rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR w KQkq c6 0 2
|
||||
* After 2. Nf3:
|
||||
* rnbqkbnr/pp1ppppp/8/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2
|
||||
*
|
||||
* 1 : White uppercase
|
||||
/* FEN description:
|
||||
* 1 : pieces on board (no space allowed):
|
||||
* - rank 8 first, '/' between ranks
|
||||
* - piece is usual piece notation(PNBRQK), black lowercase.
|
||||
* - empty: number of consecutive empty squares (digit)
|
||||
* 2 : next move (w or b)
|
||||
* 3 : Castling capabilities: "-" if none, KQ/kq if white/black can castle
|
||||
* on K or Q side
|
||||
* 4 : en-passant: if pawn just moved 2 squares, indicate target square (e.g.
|
||||
* for e2-e4 this field is e3)
|
||||
* 4 : en-passant: "-" if none. If pawn just moved 2 squares, indicate target
|
||||
* en-passant square (e.g. for e2-e4 this field is e3)
|
||||
* 5 : half moves since last capture or pawn advance (for 50 moves rule)
|
||||
* 6 : full moves, starts at 1, increments after black move
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* starting position:
|
||||
* rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
|
||||
* after 1.e4 e6 2.e5 d5
|
||||
* rnbqkbnr/ppp2ppp/4p3/3pP3/8/8/PPPP1PPP/RNBQKBNR w KQkq d6 0 3
|
||||
* 3.Nc3 Nc6 4.Rb1 Rb8 5.Nf3 h5 6.Be2
|
||||
* 1rbqkbnr/ppp2pp1/2n1p3/3pP2p/8/2N2N2/PPPPBPPP/1RBQK2R b Kk - 1 6
|
||||
* 6...Be7
|
||||
* 1rbqk1nr/ppp1bpp1/2n1p3/3pP2p/8/2N2N2/PPPPBPPP/1RBQK2R w Kk - 2 7
|
||||
* 7.Nxd5 h4 8.g4
|
||||
* 1rbqk1nr/ppp1bpp1/2n1p3/3NP3/6Pp/5N2/PPPPBP1P/1RBQK2R b Kk g3 0 8
|
||||
*/
|
||||
|
||||
// warning, we expect a valid fen input
|
||||
pos_t *fen2pos(pos_t *pos, char *fen)
|
||||
{
|
||||
char *p = fen;
|
||||
short rank, file, skip, color, bbpiece;
|
||||
piece_t piece;
|
||||
board_t *board = pos->board;
|
||||
# define SKIP_BLANK(p) for(;*(p) == ' '; (p)++)
|
||||
/* chess startup position FEN */
|
||||
const char *startfen="rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
|
||||
|
||||
/* next must follow 'piece_type' enum values (pawn is 1, king is 6) */
|
||||
static const char *pieces_str = " PNBRQK";
|
||||
/* And this one must follow 'castle' enum order (also same as usual FEN) */
|
||||
static const char *castle_str = "KQkq";
|
||||
|
||||
#define SKIP_BLANK(p) for(;isspace(*(p)); (p)++)
|
||||
|
||||
/**
|
||||
* startpos - create a game start position
|
||||
* @pos: a position pointer or NULL
|
||||
*
|
||||
* See @fen2pos function.
|
||||
*
|
||||
* @return: the pos position.
|
||||
*/
|
||||
position *startpos(position *pos)
|
||||
{
|
||||
return fen2pos(pos, startfen);
|
||||
}
|
||||
|
||||
/**
|
||||
* fen2pos - make a position from a fen string
|
||||
* @pos: a position pointer or NULL
|
||||
* @fen: a valid fen string
|
||||
*
|
||||
* If @pos is NULL, a position will be allocated with malloc(1),
|
||||
* that should be freed by caller.
|
||||
*
|
||||
* @return: the pos position, or NULL if error.
|
||||
*/
|
||||
position *fen2pos(position *pos, const char *fen)
|
||||
{
|
||||
const char *cur = fen;
|
||||
char *p;
|
||||
short rank, file, color, tmp;
|
||||
int consumed, err_line = 0, err_pos, err_char;
|
||||
position tmppos;
|
||||
|
||||
pos_clear(&tmppos);
|
||||
|
||||
pos_clear(pos);
|
||||
/* 1) get piece placement information
|
||||
*/
|
||||
for (rank = 7, file = 0; *p && *p != ' '; ++p) {
|
||||
color = isupper(*p)? WHITE: BLACK;
|
||||
char cp = toupper(*p);
|
||||
switch (cp) {
|
||||
case CHAR_PAWN:
|
||||
bbpiece = BB_PAWN;
|
||||
piece = PAWN;
|
||||
goto set_square;
|
||||
case CHAR_KNIGHT:
|
||||
bbpiece = BB_KNIGHT;
|
||||
piece = KNIGHT;
|
||||
goto set_square;
|
||||
case CHAR_BISHOP:
|
||||
bbpiece = BB_BISHOP;
|
||||
piece = BISHOP;
|
||||
goto set_square;
|
||||
case CHAR_ROOK:
|
||||
bbpiece = BB_ROOK;
|
||||
piece = ROOK;
|
||||
goto set_square;
|
||||
case CHAR_QUEEN:
|
||||
bbpiece = BB_QUEEN;
|
||||
piece = QUEEN;
|
||||
goto set_square;
|
||||
case CHAR_KING:
|
||||
bbpiece = BB_KING;
|
||||
piece = KING;
|
||||
//pos->bb[color][BB_KING] = BB(file, rank);
|
||||
//goto set_square;
|
||||
set_square:
|
||||
# ifdef DEBUG_FEN
|
||||
log_i(5, "f=%d r=%d *p=%c piece=%c color=%d\n",
|
||||
file, rank, *p, cp, color);
|
||||
# endif
|
||||
pos->bb[color][bbpiece] |= BB(file, rank);
|
||||
pos->occupied[color] |= BB(file, rank);
|
||||
SET_COLOR(piece, color);
|
||||
board[SQ88(file, rank)].piece = piece;
|
||||
board[SQ88(file, rank)].s_piece =
|
||||
piece_add(pos, piece, SQ88(file, rank));
|
||||
file++;
|
||||
break;
|
||||
case '/':
|
||||
rank--;
|
||||
file = 0;
|
||||
break;
|
||||
default:
|
||||
skip = cp - '0';
|
||||
while (skip--) {
|
||||
board[SQ88(file++, rank)].piece = EMPTY;
|
||||
}
|
||||
for (rank = 7, file = 0; *cur && !isspace(*cur); ++cur) {
|
||||
if (*cur == '/') { /* next rank */
|
||||
rank--;
|
||||
file = 0;
|
||||
continue;
|
||||
}
|
||||
if (isdigit(*cur)) { /* empty square(s) */
|
||||
file += *cur - '0';
|
||||
continue;
|
||||
}
|
||||
color = isupper(*cur)? WHITE: BLACK;
|
||||
if ((p = strchr(pieces_str, toupper(*cur)))) { /* valid piece letter */
|
||||
# ifdef DEBUG_FEN
|
||||
log_i(5, "f=%d r=%d *p=%c piece=%c color=%d ppos=%ld\n",
|
||||
file, rank, *cur, *p, color, p - pieces_str);
|
||||
# endif
|
||||
pos_set_sq(&tmppos, p - pieces_str, color, file, rank);
|
||||
file++;
|
||||
} else { /* error */
|
||||
err_line = __LINE__, err_char = *cur, err_pos = cur - fen;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
# ifdef DEBUG_FEN
|
||||
for (rank = 7; rank >= 0; --rank) {
|
||||
for (file = 0; file < 8; ++file) {
|
||||
log(5, "%02x ", board[SQ88(file, rank)].piece);
|
||||
}
|
||||
log(5, "\n");
|
||||
}
|
||||
# endif
|
||||
SKIP_BLANK(cur);
|
||||
|
||||
/* 2) next move color
|
||||
/* 2) next turn color
|
||||
*/
|
||||
SKIP_BLANK(p);
|
||||
SET_COLOR(pos->turn, *p == 'w' ? WHITE : BLACK);
|
||||
p++;
|
||||
tmppos.turn = *cur++ == 'w' ? WHITE : BLACK;
|
||||
SKIP_BLANK(cur);
|
||||
|
||||
/* 3) castle status
|
||||
/* 3) castle rights
|
||||
*/
|
||||
SKIP_BLANK(p);
|
||||
pos->castle = 0;
|
||||
if (*p != '-') {
|
||||
for (; *p && *p != ' '; ++p) {
|
||||
switch (*p) {
|
||||
case 'K':
|
||||
pos->castle |= CASTLE_WK;
|
||||
break;
|
||||
case 'k':
|
||||
pos->castle |= CASTLE_BK;
|
||||
break;
|
||||
case 'Q':
|
||||
pos->castle |= CASTLE_WQ;
|
||||
break;
|
||||
case 'q':
|
||||
pos->castle |= CASTLE_BQ;
|
||||
break;
|
||||
if (*cur == '-') {
|
||||
cur++;
|
||||
} else {
|
||||
for (; *cur && !isspace(*cur); ++cur) {
|
||||
if ((p = strchr(castle_str, *cur))) { /* valid castle letter */
|
||||
tmppos.castle |= 1 << (p - castle_str);
|
||||
} else {
|
||||
err_line = __LINE__, err_char = *cur, err_pos = cur - fen;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
}
|
||||
p++;
|
||||
SKIP_BLANK(cur);
|
||||
|
||||
/* 4) en passant
|
||||
*/
|
||||
SKIP_BLANK(p);
|
||||
pos->en_passant = 0;
|
||||
if (*p != '-') {
|
||||
//SET_F(pos->en_passant, C2FILE(*p++));
|
||||
//SET_R(pos->en_passant, C2RANK(*p++));
|
||||
pos->en_passant = SQ88(C2FILE(*p), C2RANK(*(p+1)));
|
||||
pos += 2;
|
||||
tmppos.en_passant = 0;
|
||||
if (*cur == '-') {
|
||||
cur++;
|
||||
} else {
|
||||
p++;
|
||||
tmppos.en_passant = BB(C2FILE(*cur), C2RANK(*(cur+1)));
|
||||
cur += 2;
|
||||
}
|
||||
SKIP_BLANK(cur);
|
||||
|
||||
/* 5) half moves since last capture or pawn move and
|
||||
* 6) current move number
|
||||
/* 5) half moves since last capture or pawn move (50 moves rule)
|
||||
*/
|
||||
SKIP_BLANK(p);
|
||||
//log_i(5, "pos=%d\n", (int)(p-fen));
|
||||
sscanf(p, "%hd %hd", &pos->clock_50, &pos->curmove);
|
||||
sscanf(cur, "%hd%n", &tmppos.clock_50, &consumed);
|
||||
cur += consumed;
|
||||
SKIP_BLANK(cur);
|
||||
|
||||
/* 6) current full move number, starting with 1
|
||||
*/
|
||||
printf ("remain=[%s]\n", cur);
|
||||
sscanf(cur, "%hd", &tmp);
|
||||
printf ("tmp=[%d]\n", tmp);
|
||||
if (tmp <= 0) /* fix faulty numbers*/
|
||||
tmp = 1;
|
||||
printf ("tmp2=[%d]\n", tmp);
|
||||
tmp = 2 * (tmp - 1) + (tmppos.turn == BLACK); /* plies, +1 if black turn */
|
||||
printf ("tmp3=[%d]\n", tmp);
|
||||
|
||||
tmppos.plycount = tmp;
|
||||
|
||||
# ifdef DEBUG_FEN
|
||||
log_i(5, "50 rule=%d current move=%d\n", pos->clock_50, pos->curmove);
|
||||
for (rank = 7; rank >= 0; --rank) {
|
||||
for (file = 0; file < 8; ++file) {
|
||||
log(5, "%02x ", tmppos.board[BB(file, rank)]);
|
||||
}
|
||||
log(5, "\n");
|
||||
}
|
||||
log(5, "turn=%d 50_rule=%d curply=%d\n",
|
||||
tmppos.turn, tmppos.clock_50, tmppos.plycount);
|
||||
# endif
|
||||
|
||||
end:
|
||||
if (warn(err_line, "FEN error line %d: pos=%d char=%#x(%c)\n",
|
||||
err_line, err_pos, err_char, err_char)) {
|
||||
return NULL;
|
||||
}
|
||||
if (!pos)
|
||||
pos = pos_new();
|
||||
else
|
||||
pos_clear(pos);
|
||||
*pos = tmppos;
|
||||
return pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* pos2fen - make a FEN string from a position.
|
||||
* @pos: a position pointer
|
||||
* @fen: destination FEN char*, or NULL
|
||||
*
|
||||
* If @fen is NULL, a 100 bytes memory will be allocated with malloc(1),
|
||||
* that should be freed by caller.
|
||||
*
|
||||
* Note: If @fen is given, no check is done on its length, but to
|
||||
* be on secure side, it should be at least 90 bytes. See:
|
||||
* https://chess.stackexchange.com/questions/30004/longest-possible-fen
|
||||
* For convenience, use FENSTRLEN.
|
||||
*
|
||||
* @return: the pos position, or NULL if error.
|
||||
*/
|
||||
char *pos2fen(const position *pos, char *fen)
|
||||
{
|
||||
int cur = 0;
|
||||
|
||||
if (!fen)
|
||||
fen = safe_malloc(92);
|
||||
|
||||
/* 1) position
|
||||
*/
|
||||
for (int rank = RANK_8; rank >= RANK_1; --rank) {
|
||||
printf("r=%d 1=%d\n", rank, RANK_1);
|
||||
for (int file = FILE_A; file <= FILE_H;) {
|
||||
printf(" f=%d H=%d\n", file, FILE_H);
|
||||
square sq = BB(file, rank);
|
||||
printf(" sq=%d\n", sq);
|
||||
piece piece = PIECE(pos->board[sq]);
|
||||
color color = COLOR(pos->board[sq]);
|
||||
if (pos->board[sq] == EMPTY) {
|
||||
int len = 0;
|
||||
for (; file <= FILE_H && pos->board[BB(file,rank)] == EMPTY; file++)
|
||||
len++;
|
||||
fen[cur++] = '0' + len;
|
||||
} else {
|
||||
char c = pieces_str[piece];
|
||||
fen[cur++] = color == WHITE? c: tolower(c);
|
||||
file++;
|
||||
}
|
||||
fen[cur]=0; puts(fen);
|
||||
}
|
||||
fen[cur++] = rank == RANK_1? ' ': '/';
|
||||
}
|
||||
|
||||
/* 2) next turn color
|
||||
*/
|
||||
fen[cur++] = pos->turn == WHITE? 'w': 'b';
|
||||
fen[cur++] = ' ';
|
||||
|
||||
/* 3) castle rights
|
||||
*/
|
||||
if (pos->castle == 0) {
|
||||
fen[cur++] = '-';
|
||||
} else {
|
||||
//static char *
|
||||
for (int i = 0; i < 4; ++i)
|
||||
if (pos->castle & mask(i))
|
||||
fen[cur++] = castle_str[i];
|
||||
}
|
||||
fen[cur++] = ' ';
|
||||
|
||||
/* 4) en passant
|
||||
*/
|
||||
if (!pos->en_passant) {
|
||||
fen[cur++] = '-';
|
||||
} else {
|
||||
fen[cur++] = FILE2C(BBfile(pos->en_passant));
|
||||
fen[cur++] = RANK2C(BBrank(pos->en_passant));
|
||||
}
|
||||
fen[cur++] = ' ';
|
||||
|
||||
/* 5) moves since last capture or pawn move (50 moves rule)
|
||||
* 6) current full move number, starting with 1
|
||||
*/
|
||||
sprintf(fen+cur, "%d %d", pos->clock_50,
|
||||
1 + (pos->plycount - (pos->turn == BLACK)) / 2);
|
||||
return fen;
|
||||
}
|
||||
|
Reference in New Issue
Block a user