It is no wonder that chess is the most popular intellectual game in the world. We can trace this game to the 6th century, which has stuck because of its complex and challenging nature until these days.

The first “chess machine” was invented by the Hungarian Wolfgang Von Kempelen (Kempelen Farkas) in 1770, known as the Mechanical Turk. The inventor claimed that the machine was fully automatic. Moreover, its elaborate mechanism and the fact that it had defeated even chess masters convinced many people. But did a successful chess machine already exist in the late 18th century? The answer is no. The truth was that Wolfgang von Kempelen had hired several chess masters and hid them inside the machine. The hidden players then controlled the mechanical arms of the Turk manually. As a result, the chess machine has traveled worldwide, impressed people everywhere, and inspired them to create their own.

In the last century, the concern became creating a machine with human-like intelligence. The mathematician and computer scientist Alan Turing was amongst the first to start laying down the principles of chess AI algorithms. Many are familiar with his name thanks to the recent movie “The Imitation Game,” about how he helped crack the German ciphers encoded with the Enigma machine [4]. However, his work on artificial intelligence led him toward chess, specifically the theory of using computers to play chess.

Around 1948, he and his colleague David Gawen Champerowne wrote the first computer chess program, called Turochamp, using the following basic principles that each piece on the board was assigned a value: pawn = 1, knight = 3, bishop = 3.5, rook = 5, queen = 10, and king = 1000. The algorithm foresaw two moves to calculate every possible combination of moves and then chose the most rewarding one. He tested his algorithm by challenging one of his friends, Alick Glennie. Since computers at the time were not up to heavy computing calculations, Turing calculated the moves on paper by hand (which took him more than 30 minutes on the average per move). Unfortunately, Turing and his algorithm lost the match. Nevertheless, he constructed the earliest known computer chess program.

So now, what about you? Are you ready to build your chessboard on a webpage?

Building the chessboard

In this section, we will teach you how to build a simple chessboard using html and javascript (no css styling needed). The method used may not be the best, yet it is easy to understand.

Let us break it into simple steps and statements:

a) Creating the canvas: to draw the chessboard, we brought a white canvas element in html with a specific height and width to stick the pieces on it later. The code in html is as simple as:





<canvas width=”512” height=”512” id=”canvas”></canvas>




b) Drawing the squares:

Since we have a white canvas, we chose to draw the squares and pieces using functions in JavaScript. The squares are created by slicing the canvas into 64 identical squares alternating between black and white. The code for this is as follows:

Note: dimension = 8 is the number of files/ ranks in the chessboard and the context.globalAlpha is set to 1 to make the canvas rendering fully opaque.



function draw_board()
{
    let square_colors = ["LightGray", "DarkGray"];
    context.globalAlpha = 1;

    for (let r = 0; r < dimension; ++r)
    {
        for (let c = 0; c < dimension; ++c)
        {
            context.fillStyle = square_colors[(r + c) % 2];
            context.fillRect(
                c * square_width,
                r * square_height,
                square_width,
                square_height);
        }
    }
}


c) Drawing the pieces:

The pieces are of a complex shape; therefore, images of them are easier to insert and use. You may get the images from any source and import them into your js code. Then we can organize them into a list to put them in a loop. You can see here that we used the instance game_state to place each piece in its correct initial position. Nevertheless, it is better explained later in the article.



function draw_pieces()
{
    context.globalAlpha = 1;

    for (let r = 0; r < dimension; ++r)
    {
        for (let c = 0; c < dimension; ++c)
        {
            let piece = game_state.board[r][c];
            let piece_image = images[piece];

            if (piece != "--")
            {
                context.drawImage(
                    piece_image, c * square_width, r * square_height);
            }
        }
    }
}


Now that we have all the drawing elements, how do we link them all together?

We link the html body with an initiating function that defines the canvas elements and events.

So in html, we add the following in the body tag:



body onload="window.index.init()"


Then in the js code:



export function init()
{
    canvas = document.getElementById("canvas");
    context = canvas.getContext("2d");
    square_width = canvas.width / 8;
    square_height = canvas.height / 8;
    for (let [name, image] of Object.entries(image_map))
    {
        images[name] = new Image(64, 64);
        images[name].onload = draw_pieces;
        images[name].src = image;
    }

    draw_board();
}


The view of our chessboard on the page after building the html
The view of our chessboard on the page after building the html

Basic functionality of a chess game

To create a real chess game, we must move the piece and keep track of their positions at all times. However, this is not as simple as it sounds in programming. Let’s say we want to create a class that defines the game and let’s call it game_state. It needs to state the initial board position and the offset of each piece and move it accordingly while logging the new board position. For example, this snippet code shows the board representation and then the knight offset.



export class Game_state
{
    constructor()
    {
        this.board =
        [
            ["bR", "bN", "bB", "bQ", "bK", "bB", "bN", "bR"],
            ["bP", "bP", "bP", "bP", "bP", "bP", "bP", "bP"],
            ["--", "--", "--", "--", "--", "--", "--", "--"],
            ["--", "--", "--", "--", "--", "--", "--", "--"],
            ["--", "--", "--", "--", "--", "--", "--", "--"],
            ["--", "--", "--", "--", "--", "--", "--", "--"],
            ["wP", "wP", "wP", "wP", "wP", "wP", "wP", "wP"],
            ["wR", "wN", "wB", "wQ", "wK", "wB", "wN", "wR"]
        ];
        this.knight_offsets =
        [
            [ 2,  1],
            [ 2, -1],
            [ 1,  2],
            [ 1, -2],
            [-1,  2],
            [-1, -2],
            [-2, -1],
            [-2,  1]
        ];
    }
}


Moving the pieces: There are different types of moves in chess. Any move could be a start, end, board move, capturing a piece, promotion move, en-passant move, or castle move. Each of them should be defined. For example, an en-passant move can be made with:



if (move.en_passant_move)
        {
            const direction = this.white_to_move ? 1 : -1;

            this.board[move.end[0] + direction][move.end[1]] = "--";
        }


By now, the piece could move, but what if the move was illegal?

Each move needs to be validated. If the move is illegal, then the piece should be retracted automatically. Yet, suppose you want to make it easier for the player to commit legal moves only. In that case, you can define all the valid moves of a piece and highlight them when the player touches (clicks on) any piece to move. For example, you may check all the possible moves of a piece with a code similar to this:



get_all_possible_moves()
    {
        let moves = [];

        for (let row = 0; row < this.board.length; ++row)
        {
            for (let column = 0; column < this.board[row].length; ++column)
            {
                let player = this.board[row][column][0];
                let current_color = this.white_to_move ? "w" : "b";

                if (player == current_color)
                {
                    let piece = this.board[row][column][1];

                    this.move_methods[piece].call(this, row, column, moves);
                }
            }
        }

        return moves;
    }


Did you make a move and regret it or make a mistake? Then you need to introduce the undo button to your game.

It is easily implemented in HTML by adding the following:

<div>
	<button onclick="window.index.undo_click()">Undo</button>
</div>

In js code, you may create the undo function as a method of the game_state, which allows you to edit other attributes in game-tracking, such as making a move and game-over status. Check it below:



export function undo_click()
{
    game_state.undo_move();
    made_move = true;
    game_over = false;

    refresh_state();
}


In concept, the undo method is purely popping the last move out of the log. Nevertheless, what if the move was a promotion or en-passant? Then we need to define more conditions that cover all possibilities. Let’s look at the code snippet below. It checks the move type as promotion and takes multiple actions to revoke it, including the return of the captured piece during the move.



if (last_move.promotion_move)
        {
            const color = this.white_to_move ? "b" : "w";

            this.board[last_move.end[0]][last_move.end[1]] =
                last_move.piece_captured;
            this.board[last_move.start[0]][last_move.start[1]] = color + 'P';
        }
		

Now we have all bases covered to start playing.

But do you want to play alone? Absolutely not. Let’s create an AI opponent.

The first thing that you need to build an AI opponent is data. Let’s put it as you feed the model some inputs to teach it how to digest them and generate the specific output. For chess, these data are recorded games between real players. These recorded games are formed as Portable Game Notation (PGN) files. Fortunately, many PGN databases are available online for free, and you may use any. Yet keep in your mind that these notation files need to be parsed. Our recommendation is to use chess.pgn in Python.

So let’s consider that you already have parsed the database and are ready to use it; thus, it’s time to build our AI model. We can create a sequential model out of multiple layers using TensorFlow and Keras. As you may see below, the model consists of 2 layers of 2D Convolutional Neural Networks (CNN, Flatten, 2 layers of Dense Neural Networks (DNN), and a softmax layer. You can notice that we used the Adam optimizer and binary loss entropy function to optimize the training process.



model = models.Sequential()
model.add(layers.Conv2D(filters=32, kernel_size=(3, 3), padding='same',
                        activation='relu', input_shape=(8, 8, 12)))
model.add(layers.Conv2D(filters=32, kernel_size=(5, 5), strides=(1, 1),
                        padding='valid', activation='relu'))
model.add(layers.Flatten())
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(2))
model.add(tf.keras.layers.Softmax())
model.summary()
model.compile(optimizer='adam',
              loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
              metrics=['accuracy'])
			  

Note: after building the model and training it in Python, you can convert it into Javascript code.

After training the model, you need a function that will enable the AI engine to move a piece based on predicting the best valid move possible at a specific game state. In other words, you need to link the AI prediction with the chess functions provided earlier in the Javascript code. For example, selecting the best move will be as the following:



export function find_model_best_move(game_state, valid_moves)
{   let max_score = 0;
    let next_move = null;
    if (!model)
    {
        return next_move;
    }
    for (const move of valid_moves)
    {
        let scores;
        let score;
        // Make a valid move
        game_state.make_move(move);
        // Expand current position to 4D b/c model input requirement
        const input = tf.tensor([ game_state.get_position() ]);
        // Model predicts score (shape:(1,2)) of current position
        scores = model.predict(input).arraySync();
        console.assert(scores[0][0] + scores[0][1] >= 0.99);
        score = scores[0][game_state.white_to_move ? 0 : 1];
        if (score > max_score)
        {
            max_score = score;
            next_move = move;
        }
    }
    return next_move;
}


Don’t forget to add the button and link it with a function to enable the AI feature.

Are you wondering how to link all the files together? Well, module bundling is the solution to that.

Here we reach the end of this story. We provided tutorial-like instructions and discussion to build your own chess game on a webpage, define your desired rules, and program your ultimate opponent. Nevertheless, our description was brief in some points, but we hope our article encouraged you to go and search further.

What is your next move?

For the full code to build your own game, please check our repository on GitHub.

If you are interested in playing against our AI engine, please visit the page under Demos.

Cover image: by Felix Mittermeier on Unsplash