Before diving into making the UI, we need to have a list of specifications. I finalized upon the following:

  1. Ability to switch/select the side
  2. Computer mode/2 player mode
  3. Ask the computer to play/think for you
  4. Basic options such as printing the board, displaying help, exiting etc.

We’ll not develop any fancy UI here, but just interact through the terminal. Actually, a slight modification of what we are building here can be used to connect to an existing chess UI such as xboard/winboard. But I digress….so let’s delve into what we are gonna do.

We need to parse the moves a user enters into an object of the move class(and vice-versa). A move in chess indicated by something like <square_initial><square_final>, eg. e2e4 means that the piece at e2 will move to e4. In the light of this knowledge, let us begin by converting square to string conversion (and it’s vice-versa).

std::map<std::pair<int, int&gt;, std::string&gt; square_to_string_map;
std::map<std::string, std::pair<int, int&gt;&gt; string_to_square_map;

void populate_square_move_maps() {
    std::string letters[] = {"a", "b", "c", "d", "e", "f", "g", "h"};
    std::string numbers[] = {"1", "2", "3", "4", "5", "6", "7", "8"};
    
    // Label the squares in the order in which we print them
    // a8, b8, ...
    // ...
    // a1, b1, ...
    // The variables i and j do the specify the rank/file 
    // They only help with printing the board
    for(int i = 0; i < 8; i++) {
        for(int j = 0; j < 8; j++) {
            std::pair<int, int&gt; square = {i, j};
            
            int rank = 7 - i;
            int file = j;
            std::string file_name = letters[file];
            std::string rank_name = numbers[rank];
            std::string square_name = file_name + rank_name;

            square_to_string_map.insert({square, square_name});
            string_to_square_map.insert({square_name, square});
        }
    }
}

We define two maps, string_to_square and square_to_string. See how the way we populate these maps is consistent with the way in which we define them. In case you don’t remember that, here it is..(I mention this explicitly because we have used something different from the usual convention).

    0  1  2  3  4  5  6  7
0  a8 -- -- -- -- -- -- h8                          
1  -- -- -- -- -- -- -- --           
2  -- -- -- -- -- -- -- --                      
3  -- -- -- -- -- -- -- --                              
4  a4 -- -- -- -- -- -- h4 
5  -- -- -- -- -- -- -- --
6  -- -- -- -- -- -- -- --          
7  a1 b1 c1 d1 e1 f1 g1 h1

Some squares have been labelled for a better understanding. How exactly is this function used? Well, in every move we generate(apart from castling, the from and to squares are populated, and can therefore be used to uniquely define a move. We implement a method a obtain the string representation of a move by appending the string representations of the initial and final squares.

std::string get_move_string() {
    std::string move_string;
    if(castle_code == NO_CASTLE) {
        move_string = square_to_string_map[init_pos] + square_to_string_map[final_pos];
    } else {
        if(castle_code == WHITE_KING_SIDE_CASTLE || BLACK_KING_SIDE_CASTLE) {
            move_string = "0-0";
        } else {
            move_string = "0-0-0";
        }
    }
    return move_string;
}

Note that we have handled the castling case by explicitly defining two strings; one for king side castling and one for queen side castling.

So how do we parse moves supplied by the user? The approach I took is this:

  1. Generate a list of all legal moves in the given position
  2. Obtain the string representation of these moves.
  3. If the user’s input matches this string, then play the move; else log an error.

The above approach avoids the cumbersome task of constructing a move from the string, which is very complicated(since you have to look at the board to find out what kind of move it is – capture, promotion, en passant etc.). The logic given here is translated into code as follows:

move parse_move_from_string(std::string move_string, bool &flag) {
    auto movelist = generate_all_moves();
    for(move m: movelist) {
        if(m.get_move_string() == move_string) {
            flag = true;
            return m;
        }
    }
    return move({INVALID, INVALID}, {INVALID, INVALID});
}

The flag helps us know whether a valid move exists. You’ll come to know about it once you see how it’s used exactly(which is gonna come up shortly).

Next, we make a game loop. A game loop is an infinite loop in which one takes input from the user, processes this input to change the game state and then generates the required output. In our case, this is as simple as parsing some commands. At the start of this article, I mentioned certain designed specifications(switching sides, modes etc.); here is the code for some of the simple commands.

while(true) {
    std::cin &gt;&gt; input;
    if(input == "") {
        continue;
    }

    else if(input == "help") {
        message = "\n";
        std::cout << "\n";
        display_help();
    }

    else if(input == "print") {
        message = "";
        board.print();
    }

    else if(input == "think") {
        message = "";
        think = true;
    }

    else if(input == "exit") {
        message = "EXIT command received. Exiting...";
        break;
    }

    ....
}

These are pretty self-explanatory, and some call certain auxiliary functions. Note how the think flag is set to true(this will be of use later). For changing the mode too, we take input and simply set a variable called ‘computer_brain'(to be used later as well). Ditto for side(set var ‘user_side’).

else if(input == "mode") {
    std::cin >> input;
    if(input == "f") {
        computer_brain = false;
        message = "You are in 2 player mode now!\n";
    } else if(input == "c") {
        message = "You are playing against the computer now!\n";
        computer_brain = true;
    } else {
        message = "Invalid mode! Existing mode not changed!\n";
    }
}

else if(input == "side") {
    std::cin >> input;
    if(input == "w") {
        user_side = WHITE;
        message = "You have chosen WHITE\n"; 
    } else if(input == "b") {
        user_side = BLACK;
        message = "You have chosen BLACK\n"; 
    } else {
        message = "Invalid side! Existing user player side not changed!\n";
    }
}

To play the moves, we use the 3 step logic of generating the list of moves, converting them to strings and seeing if the user’s input matches any one.

else if(input == "move") {
    std::cin >> input;
    bool is_move_valid = false;
    int side = board.get_curr_side();
    move m = board.parse_move_from_string(input, is_move_valid);
    if(is_move_valid) {
        board.make_move(m);
        game_end_flag = board.is_end_of_game();
        board.print();
        message = "Played " + input + "\n\n";
    } else {
        message = "Invalid move entered! Please enter a valid move!\n\n";
    }
}

Till now no other commands apart from move have explicitly changed the game state. Remember those 3 variables we had set earlier? Well, those are used to determine when the computer has to play. This block is put towards the end of the game loop.

if((computer_brain && user_side != board.get_curr_side()) || think) {
    auto movelist = board.generate_all_moves();
    // The current logic is to choose a random index.
    // We will implement an AI later.
    board.make_move(movelist[rand() % movelist.size()]);
    game_end_flag = board.is_end_of_game();
    board.print();
    message = "Played " + movelist[0].get_move_string() + "\n\n";
    think = false;
}

The if condition here translates to : if the computer mode is activated and side to play is computer, then play a move. Also if the user asks for a hint even though it’s their turn, still play a move. The think flag is set to false at the end of the if block. Inside this block we are gonna have to call our chess engine’s core function – the function which determines the best move. For now, we just choose randomly from the list of all possible moves.

Finally, we check for end of game conditions and set the output message(which is printed at the end of the loop).

switch(game_end_flag) {
    case NO_END_OF_GAME:
        break;
    case CHECKMATE:
        message = "Checkmate! ";
        if(board.get_curr_side() == BLACK) {
            message += "WHITE";
        } else {
            message += "BLACK";
        }
        message += " wins! Congrats :-)";
        break;
    case STALEMATE:
        message = "Stalemate! The king is not in check and there are no vaild moves!";
        break;
    case INSUFFICIENT_MATERIAL_DRAW:
        message = "Draw due to insufficient material";
        break;
    case THREE_MOVE_DRAW:
        message = "Game drawn. The position has been repeated 3 times.";
        break;
    case FIFTY_MOVE_DRAW:
        message = "Draw by the fifty move rule!";
        break;
    default:
        message = "End of Game Type Unknown! Exiting...";
}

The complete code can be seen here.

That completes our user interface, along with a skeleton of where the AI function has to be put. Try running this program and playing with it maybe(or add some more interesting commands?). Here’s an output sample, do take a look at it.

Oh….and last time, we hadn’t implemented draw by repetition of the same position 3 times(I am still not doing that :p). Here’s a hint for you: use position hashes or store the position itself. That’s it for this time. In the last and final article of this series, we will see how to implement the core part of our application, i.e. the part which will make the computer play ‘intelligent’ moves. See you next time!

Advertisements

About the Author Pranav Deshpande

Software Developer interested in AI, Reinforcement Learning and game programming.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s