In the last article, I talked about hash keys, their use in the chess engine, and it's importance. Here, I will talk about the process of using a position string to set up a chessboard.
First of all, we create a ResetBoard function. It is going to look like this:
void ResetBoard(S_BOARD *pos) {
int index = 0;
for (index = 0; index < BOARD_SQ_NUMBER; index++) {
pos->pieces[index] = OFFBOARD;
}
for (index = 0; index < 64; ++index) {
pos->pieces[SQ120(index)] = EMPTY;
}
for (index = 0; index < 3; ++index) {
pos->bigPieces[index] = 0;
pos->majorPieces[index] = 0;
pos->minorPieces[index] = 0;
pos->pawns[index] = 0ULL;
}
for (index = 0; index < 13; ++index) {
pos->pieceNum[index] = 0;
}
pos->KingSquare[WHITE] = pos->KingSquare[BLACK] = NO_SQ;
pos->side = BOTH;
pos->enPassant = NO_SQ;
pos->fiftyMove = 0;
pos->ply = 0;
pos->hisPly = 0;
pos->castlePerm = 0;
pos->posKey = 0ULL;
}
The first loop goes through all the squares in the 10x12 mailbox and setting them to OFFBOARD. Then the second loop goes through the internal squares which make up the visible chessboard and sets them to EMPTY. The third loop simply makes everything in it 0. bigPieces. majorPieces. minorPieces .pawns. Their values become 0. Note that pawns is set to 0ULL, being a U64 variable in a program. The fourth loop does the same thing for pieceNum.
KingSquare[WHITE] & KingSquare[BLACK] are set to NO_SQ, the side is set to BOTH, enPassant is also set to NO_SQ, and the rest is set to 0 (posKey is set to 0ULL).
After all the resets are down, the chess engine can use the position string to set up the chessboard. But first, we need to understand something called Forsyth-Edwards Notation (FEN).
FEN
In the most simplest way possible, FEN is a way of representing the placement of each piece, how many spaces are on the board, which side to move, the castling permissions, if there is an available enPassant move, a counter for the Fifty Moves rules, and a counter to show how many moves have been made in the entire running game.
Basically, this image,
is represented by this string:
char fen[] = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
From left to right, the lowercase letters are for the black pieces while the UPPERCASE letters are for the white pieces. rnb stands for Rook, Knight, and Bishop. q and k stands for Queen and_King_. p, of course, stands for the Pawns. The number in between represents the numbers of empty squares in each rank. The next segment tells the engine which side is to move next. The next one contains the castling permissions, after that is the enPassant square indicator, followed by the Fifty Move counter, and finally, the general moves counter
If a piece is moved, it's former position is replaced by 1. For example, if at the start of the game, a pawn moves to e4, the fen string changes to this:
fen = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1";
If a black pawn moves to c6, everything will look likes this:
fen = "rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR w KQkq c6 0 2";
As you can see, when the pawns move to their new positions, their former positions are replaced by 1, and the 8s have transformed into 4P3 and 2p5 respectively, showing how many empty squares come before and after it from left to right.
FEN Implementation
First of all, in defs.h, we add the START_FEN constant with the fen string at the start of the game to set the chess pieces:
#define START_FEN "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
Then we create a function to parse the fen strings, basically ParseFen:
int ParseFen(char *fen, S_BOARD *pos) {
ASSERT(fen != NULL);
ASSERT(pos != NULL);
int rank = RANK_8;
int file = FILE_A;
int piece = 0;
int count = 0;
int i = 0;
int sq64 = 0;
int sq120 = 0;
ResetBoard(pos);
while ((rank >= RANK_1) && *fen) {
count = 1;
switch (*fen) {
case 'p':
piece = bP;
break;
case 'r':
piece = bR;
break;
case 'n':
piece = bN;
break;
case 'b':
piece = bB;
break;
case 'k':
piece = bK;
break;
case 'q':
piece = bQ;
break;
case 'P':
piece = wP;
break;
case 'R':
piece = wR;
break;
case 'N':
piece = wN;
break;
case 'B':
piece = wB;
break;
case 'K':
piece = wK;
break;
case 'Q':
piece = wQ;
break;
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
piece = EMPTY;
count = *fen - '0';
break;
case '/':
case ' ':
rank--;
file = FILE_A;
fen++;
continue;
default:
printf("FEN error \n");
return -1;
}
for (i = 0; i < count; i++) {
sq64 = rank * 8 + file;
sq120 = SQ120(sq64);
if (piece != EMPTY) {
pos -> pieces[sq120] = piece;
}
file++;
}
fen++;
}
ASSERT(*fen == 'w' || *fen == 'b');
pos -> side = (*fen == 'w') ? WHITE : BLACK;
fen += 2;
// * Check castling permissions
for (i = 0; i < 4; i++) {
if (*fen == ' ') {
break;
}
switch (*fen) {
case 'K':
pos -> castlePerm |= WKCA;
break;
case 'Q':
pos -> castlePerm |= WQCA;
break;
case 'k':
pos -> castlePerm |= BKCA;
break;
case 'q':
pos -> castlePerm |= BQCA;
break;
default:
break;
}
fen++;
}
fen++;
ASSERT(pos -> castlePerm >= 0 && pos -> castlePerm <= 15);
// * Check enPassant squares
if (*fen != '-') {
file = fen[0] - 'a';
rank = fen[1] - '1';
ASSERT(file >= FILE_A && file <= FILE_H);
ASSERT(rank >= RANK_1 && rank <= RANK_8);
pos -> enPassant = FR2SQ(file, rank);
}
pos -> posKey = GeneratePosKey(pos);
}
How It Works
The chess engine asserts if the fen & pos are not NULL, and if they aren't, it sets all the necessary variables to it's initial value, and resets the board.
The first loop runs while the rank is greater than or equal to RANK_1 and the pointer to fen is present. It increases the count to 1, and while it reads the characters of fen one by one, it sets the piece according to the character. If it is a number, piece is set to EMPTY, and count is set to *fen - '0'. If a space or a forward slash is met, it goes to a new rank, set the file back to FILE_A, and continue to read the fen. Once the switch statement is done executing it's job, a loop inside the while loop goes through each file in a rank If the value of piece at that point is not EMPTY, then the value of pieces in that particular square on the chessboard is set to piece. After everything is done, it goes to the next character in fen and starts the loop over again until the piece segment of fen is done.
Once all that is done, an assertion is made for the side to check its validity, and if that is true, the board's(pos) side value is set to either WHITE or BLACK. After that, we move over to the castling permissions, and because we are treating them as bits, we use bitwise OR to set them for each character.
After that, the engine will move to the enPassant value of fen, get the corresponding file and rank, and use them to set the board's enPassant value.
Once everything is done, the engine generates the position key of that fen(because each fen is a moved piece), which ends the FEN parsing function.
Printing The Board
So in order to print the board to the console, we will first make four arrays and fill them with the corresponding data:
char PieceChar[] = ".PNBRQKpnbrqk";
char SideChar[] = "wb-";
char RankChar[] = "12345678";
char FileChar[] = "abcdefgh";
Then, we create a function like this:
void PrintBoard(const S_BOARD *pos) {
int sq, rank, file, piece;
printf("\n Game Board: \n\n");
for (rank = RANK_8; rank >= RANK_1; rank--) {
printf("%d ", rank + 1);
for (file = FILE_A; file <= FILE_H; file++) {
sq = FR2SQ(file, rank);
piece = pos -> pieces[sq];
printf("%3c", PieceChar[piece]);
}
printf("\n");
}
printf("\n ");
for (file = FILE_A; file <= FILE_H; file++) {
printf("%3c", 'a' + file);
}
printf("\n");
printf("side: %c\n", SideChar[pos -> side]);
printf("enPassant: %d\n", pos -> enPassant);
printf("Castling Permissions: %c%c%c%c\n",
pos -> castlePerm & WKCA ? 'K' : '-',
pos -> castlePerm & WQCA ? 'Q' : '-',
pos -> castlePerm & BKCA ? 'k' : '-',
pos -> castlePerm & BQCA ? 'q' : '-'
);
printf("Position Key: %llX\n", pos -> posKey);
}
The first loop reads the board and prints the ranks and for each file in each rank, the pieces in them. The second loop prints the files. After that, the side to move, the enPassant square, if available, the castling permissions, and the hash key are all printed out.
To execute this, the main file is going to look like this:
#include <stdio.h>
#include <stdlib.h>
#include "defs.h"
#define FEN_1 "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1"
#define FEN_2 "rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR w KQkq c6 0 2"
#define FEN_3 "rnbqkbnr/pp1ppppp/8/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2"
int main() {
AllInit();
S_BOARD board[1];
ParseFen(START_FEN, board);
PrintBoard(board);
ParseFen(FEN_1, board);
PrintBoard(board);
ParseFen(FEN_2, board);
PrintBoard(board);
ParseFen(FEN_3, board);
PrintBoard(board);
return 0;
}
Once it is executed, the console will look like this:
If you haven't read the previous article, click here so that you can follow along and if you want to see the code, you can click here to view the Github repo.
Until the next commit....bye 👋






Top comments (0)