DEV Community

Cover image for Chess Engine In C #4: From Strings To Board Positions
Victor Chigbo
Victor Chigbo

Posted on • Edited on

Chess Engine In C #4: From Strings To Board Positions

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;
}
Enter fullscreen mode Exit fullscreen mode

 

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,

A chessboard from Arena v3.5.1
 

is represented by this string:

char fen[] = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
Enter fullscreen mode Exit fullscreen mode

 

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";
Enter fullscreen mode Exit fullscreen mode

 

A white pawn moves to e4
 

If a black pawn moves to c6, everything will look likes this:

A white pawn moves to e4

fen = "rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR w KQkq c6 0 2";
Enter fullscreen mode Exit fullscreen mode

 

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"
Enter fullscreen mode Exit fullscreen mode

 

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);
}
Enter fullscreen mode Exit fullscreen mode

 

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";
Enter fullscreen mode Exit fullscreen mode

 

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);
}
Enter fullscreen mode Exit fullscreen mode

 

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;
}
Enter fullscreen mode Exit fullscreen mode

 

Once it is executed, the console will look like this:

Board Representation of START_FEN & FEN_1

Board Representation of START_FEN & FEN_1

 

Board Representation of FEN_2 & FEN_3

Board Representation of FEN_2 & FEN_3


 

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)