Вы когда-нибудь задумывались, можете ли вы создать свою собственную шахматную игру и играть в нее с помощью движка ИИ в браузере? Если вы это сделаете, то вам может быть интересно прочитать эту статью.

Неудивительно, что шахматы — самая популярная интеллектуальная игра в мире. Мы можем проследить эту игру до 6-го века, которая застряла из-за ее сложного и сложного характера до наших дней.

Первую шахматную машину изобрел венгр Вольфганг Фон Кемпелен (Kempelen Farkas) в 1770 году, известный как Механический турок. Изобретатель утверждал, что машина была полностью автоматической. Более того, его сложный механизм и тот факт, что он побеждал даже гроссмейстеров, убедили многих. Но существовала ли уже в конце 18 века успешная шахматная машина? Ответ - нет. Правда заключалась в том, что Вольфганг фон Кемпелен нанял нескольких шахматных мастеров и спрятал их внутри машины. Затем скрытые игроки управляли механическими руками Турка вручную. В результате шахматная машина путешествовала по всему миру, впечатляла людей повсюду и вдохновляла их на создание своих собственных.

В прошлом веке концерном стало создание машины с человеческим интеллектом. Математик и ученый-компьютерщик Алан Тьюринг был одним из первых, кто начал излагать принципы алгоритмов шахматного ИИ. Многие знакомы с его именем благодаря недавнему фильму Игра в имитацию о том, как он помог взломать немецкие шифры, закодированные с помощью машины Энигма [4]. Однако его работа над искусственным интеллектом привела его к шахматам, в частности к теории использования компьютеров для игры в шахматы.

Примерно в 1948 году он и его коллега Дэвид Гавен Шампероун написали первую компьютерную шахматную программу под названием Turochamp, используя следующие основные принципы, согласно которым каждой фигуре на доске присваивается значение: пешка = 1, конь = 3, слон = 3,5, ладья = 5, ферзь = 10 и король = 1000. Алгоритм предусматривал два хода для расчета всех возможных комбинаций ходов, а затем выбирал наиболее выгодную. Он проверил свой алгоритм, бросив вызов одному из своих друзей, Алику Гленни. Поскольку компьютеры в то время не были способны к тяжелым вычислительным вычислениям, Тьюринг вычислял ходы на бумаге вручную (что занимало у него в среднем более 30 минут на ход). К сожалению, Тьюринг и его алгоритм проиграли матч. Тем не менее, он создал самую раннюю из известных компьютерных шахматных программ.

Итак, что насчет тебя? Готовы ли вы построить свою шахматную доску на веб-странице?

Построение шахматной доски

В этом разделе мы научим вас, как построить простую шахматную доску, используя html и javascript (стиль css не требуется). Используемый метод может быть не самым лучшим, но его легко понять.

Разобьем его на простые шаги и утверждения:

а) Создание холста: для рисования шахматной доски мы принесли белый элемент холста в html с определенной высотой и шириной, чтобы потом наклеивать на него фигуры. Код в html такой же простой, как:

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

б) Рисование квадратов:
Поскольку у нас есть белый холст, мы решили рисовать квадраты и фигуры с помощью функций в JavaScript. Квадраты создаются путем разрезания холста на 64 одинаковых квадрата, чередующихся между черным и белым. Код для этого выглядит следующим образом:

Примечание: размерность = 8 – это количество файлов/рангов на шахматной доске, а параметру context.globalAlpha присвоено значение 1, чтобы рендеринг холста полностью непрозрачный.

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);
        }
    }
}

в) Вычерчивание деталей:
Детали сложной формы; поэтому их изображения легче вставлять и использовать. Вы можете получить изображения из любого источника и импортировать их в свой js-код. Затем мы можем организовать их в список, чтобы поместить их в цикл. Здесь вы можете видеть, что мы использовали экземпляр game_state, чтобы поместить каждую фигуру в правильное начальное положение. Тем не менее, это лучше объяснить позже в статье.

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);
            }
        }
    }
}

Теперь, когда у нас есть все элементы рисунка, как их связать вместе?

Мы связываем тело html с инициирующей функцией, которая определяет элементы холста и события.

Итак, в html мы добавляем в тег body следующее:

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

Затем в js-коде:

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();
}

Основные функции шахматной игры

Чтобы создать настоящую шахматную игру, мы должны двигать фигуры и постоянно следить за их позициями. Однако это не так просто, как кажется в программировании. Допустим, мы хотим создать класс, определяющий игру, и назовем его game_state. Он должен указывать начальную позицию доски и смещение каждой фигуры и перемещать ее соответствующим образом при регистрации новой позиции доски. Например, этот фрагмент кода показывает представление доски, а затем смещение коня.

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]
        ];
    }
}

Перемещение фигур. В шахматах есть разные типы ходов. Любой ход может быть стартовым, конечным, ходом доски, взятием фигуры, ходом продвижение, ходом на проходе или ходом рокировки. Каждый из них должен быть определен. Например, ход на проходе можно сделать с помощью:

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

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

К этому моменту фигура могла двигаться, но что, если этот ход был неправильным?

Каждое действие должно быть подтверждено. Если ход недопустим, то фигура должна быть убрана автоматически. Тем не менее, предположим, вы хотите облегчить игроку выполнение только разрешенных ходов. В этом случае вы можете определить все допустимые ходы фигуры и выделить их, когда игрок касается (щелкает) любую фигуру для хода. Например, вы можете проверить все возможные ходы фигуры с помощью кода, подобного этому:

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;
    }

Вы сделали шаг и пожалели об этом или совершили ошибку? Затем вам нужно добавить в игру кнопку отмены.

Его легко реализовать в HTML, добавив следующее:

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

В коде js вы можете создать функцию отмены как метод game_state, который позволяет вам редактировать другие атрибуты в отслеживании игры, такие как выполнение хода и статус окончания игры. Проверьте это ниже:

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

    refresh_state();
}

По идее, метод отмены просто выталкивает последний ход из журнала. Тем не менее, что, если переезд был повышением или проходом? Затем нам нужно определить больше условий, которые охватывают все возможности. Давайте посмотрим на фрагмент кода ниже. Он проверяет тип хода как повышение и предпринимает несколько действий для его отмены, включая возврат захваченной фигуры во время хода.

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';
        }

Теперь у нас есть все основания, чтобы начать играть.

Но хочешь ли ты играть один? Точно нет. Давайте создадим противника ИИ.

Первое, что вам нужно для создания ИИ-оппонента, — это данные. Скажем так, когда вы подаете модели некоторые входные данные, чтобы научить ее переваривать их и генерировать конкретный результат. Для шахмат эти данные представляют собой записи игр между реальными игроками. Эти записанные игры формируются в виде файлов Portable Game Notation (PGN). К счастью, многие базы данных PGN доступны онлайн бесплатно, и вы можете использовать любую. Однако имейте в виду, что эти файлы нотаций необходимо разобрать. Мы рекомендуем использовать chess.pgn в Python.

Итак, будем считать, что вы уже разобрали базу данных и готовы ее использовать; таким образом, пришло время построить нашу модель ИИ. Мы можем создать последовательную модель из нескольких слоев, используя TensorFlow и Keras. Как вы можете видеть ниже, модель состоит из 2 слоев 2D Сверточных нейронных сетей (CNN), Flatten, 2 слоев плотных нейронных сетей (DNN) и слоя softmax. Вы можете заметить, что мы использовали оптимизатор Адама и функцию бинарная энтропия потерь для оптимизации процесса обучения.

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'])

Примечание. После построения модели и ее обучения на Python вы можете преобразовать ее в код Javascript.

После обучения модели вам понадобится функция, которая позволит движку ИИ перемещать фигуру на основе предсказания наилучшего допустимого хода, возможного в определенном состоянии игры. Другими словами, вам нужно связать предсказание ИИ с шахматными функциями, предоставленными ранее в коде Javascript. Например, выбор лучшего хода будет выглядеть следующим образом:

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;
}

Не забудьте добавить кнопку и связать ее с функцией, чтобы включить функцию AI.

Вам интересно, как связать все файлы вместе? Что ж, связывание модулей — решение этой проблемы.

Вот мы и подошли к концу этой истории. Мы предоставили обучающие инструкции и обсуждение, чтобы создать свою собственную шахматную игру на веб-странице, определить желаемые правила и запрограммировать вашего главного противника. Тем не менее, в некоторых моментах наше описание было кратким, но мы надеемся, что наша статья вдохновила вас на дальнейшие поиски.

Каков ваш следующий шаг?

Полный код можно найти в нашем репозитории на GitHub.
Если вы хотите поиграть против нашего ИИ-движка, посетите страницу: