Шахматы на php и javascript: Показываем фигуры

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

Картинки фигур

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

Находим в интернете картинки фигур. Вам искать конечно уже не надо, можете скопировать картинки из таблички ниже. Создаём в корне нашего проекта папку "img", и ложим туда картинки с соответствующими именами.

Картинка Путь, куда надо положить картинку Название фигуры
Белый король img/king-white.svg Белый король
Чёрный король img/king-black.svg Чёрный король
Белый ферзь img/queen-white.svg Белый ферзь
Чёрный ферзь img/queen-black.svg Чёрный ферзь
Белая ладья img/rook-white.svg Белая ладья
Чёрная лалья img/rook-black.svg Чёрная ладья
Белый слон img/bishop-white.svg Белый слон
Чёрный слон img/bishop-black.svg Чёрный слон
Белый конь img/knight-white.svg Белый конь
Чёрный конь img/knight-black.svg Чёрный конь
Белая пешка img/pawn-white.svg Белая пешка
Чёрная пешка img/pawn-black.svg Чёрная пешка

Информация о картинках фигур в коде

Каждый вид фигуры мы будем обозначать числом (идентификатором). Открываем в редакторе наш файлик chess.js, и в самом начале добавляем информацию о видах фигур в виде константы FIGURES:

chess.jsconst FIGURES = {
    1: 'img/king-white.svg', // белый король
    2: 'img/king-black.svg', // чёрный король
    3: 'img/queen-white.svg', // белый ферзь
    4: 'img/queen-black.svg', // чёрный ферзь
    5: 'img/rook-white.svg', // белая ладья
    6: 'img/rook-black.svg', // чёрная ладья
    7: 'img/bishop-white.svg', // белый слон
    8: 'img/bishop-black.svg', // чёрный слон
    9: 'img/knight-white.svg', // белый конь
    10: 'img/knight-black.svg', // чёрный конь
    11: 'img/pawn-white.svg', // белая пешка
    12: 'img/pawn-black.svg', // чёрная пешка
}

Информация о позиции

Давайте определимся - как будет выглядеть процесс игры? Если сейчас не наш ход (ход программы), то мы ничего не можем сделать на доске, просто ждём, пока очередь хода перейдёт к нам. Если очередь хода наша, то мы можем кликать по клеткам доски. Если кликнули по клетке с нашей фигурой, то делаем её "текущей", т.е. мы намериваемся сделать ход этой фигурой. При этом надо как-то выделить, "подсветить" те клетки доски, куда можно переместить эту "текущую" фигуру. Пока остановимся.

Уже видно, что нам надо хранить такую информацию:

  • наш сейчас ход или не наш
  • положение всех фигур на доске (позицию)
  • допустимые ходы всех наших фигур
Информацию "наш ход или наш" будем хранить в простой булевой переменной is_our_move. С остальным чуть сложнее.

Положение фигур на доске

Вспомним картинку из предыдущей части, где показано, как мы нумерум клетки доски:

шахматная доска с пронумерованными полями от 0 до 63

Информацию о позиции будем хранить в виде массива с 64-ю элементами. Индекс массива (от 0 до 63) указывает на клетку доски. Значение массива - идентификатор вида фигуры. Напомню - виды фигур мы храним в константе FIGURES. Если в поле записан 0, значит поле пустое, на нем нет фигуры.

Допустимые ходы

Допустимые ходы будем хранить в виде хеша (ассоциативного массива). Ключи массива - номера клеток доски, откуда можно сделать какой-либо ход. Значения - массивы с номерами клеток, куда можно переместить фигуру. Далее, на примере кода, будет понятнее, чем такое словесное описание.

Заводим переменные для хранения информации

После константы FIGURES добавляем переменные для хранения всей вышеперечисленной информации:

chess.jslet is_our_move = true; // флаг, сигнализирующий о том, что сейчас наш ход
let position = []; // расположение фигур на доске
let available_moves = {}; // допустимые ходы текущего игрока - человека

Инициализация позиции

Берём нашу функцию initGame, где была всего одна строка, и добавляем ещё две - "инициализируем позицию", и "показываем позицию". Функция теперь должна выглядеть так:

chess.jsfunction initGame() {
    createBoard();
    initPosition();
    showPosition();
}

Напишем функцию initPosition для инициализации (начального заполнения) позиции. В этой функции мы заполняем ранее объявленные переменные:

chess.jsfunction initPosition() {
    is_our_move = true;
    position = [
        6,  10, 8,  4,  2,  8,  10, 6,
        12, 12, 12, 12, 12, 12, 12, 12,
        0,  0,  0,  0,  0,  0,  0,  0,
        0,  0,  0,  0,  0,  0,  0,  0,
        0,  0,  0,  0,  0,  0,  0,  0,
        0,  0,  0,  0,  0,  0,  0,  0,
        11, 11, 11, 11, 11, 11, 11, 11,
        5,  9,  7,  3,  1,  7,  9,  5
    ];
    available_moves = {
        48: [40, 32],
        49: [41, 33],
        50: [42, 34],
        51: [43, 35],
        52: [44, 36],
        53: [45, 37],
        54: [46, 38],
        55: [47, 39],
        57: [40, 42],
        62: [45, 47]
    };
}

В строке 2 мы просто говорим что сейчас наш ход.

Расстановка фигур

В строках 3 - 12 заполняем начальную позицию. В нулевом элементе массива, т.е. в поле с номером 0 (левом верхнем поле) стоит фигура, чей вид имеет идентификатор 6. Смотрим константу FIGURES, видим что 6 - это чёрная ладья. В пятой строчке подряд стоят 8 фигур с типом 12 - это чёрные пешки. На картинке ниже наглядное представление для всей позиции:

Шахматная доска с фигурами в начальной позиции и с пронумерованными полями

Допустимые ходы

В строках 13 - 24 заполняем хеш со всеми возможными ходами из начальной позиции. Для каждой из восьми пешек возможны два хода - на одну клетку вперёд, и на две клетки. И для каждого из двух коней возможны по два хода. На картинке выше, синими стрелками проиллюстрированы два примера.

48: [40, 32],

Это первый элемент хеша. Эта запись значит, что из поля с номером 48 можно переместить то что в нём находится (а там пешка) на поле с номером 40, или на поле с номером 32.

62: [45, 47]

А это последний элемент из хеша, говорит что с поля 62 (там конь), можно переместиться на поля 45 или 47.

Показываем позицию

У нас осталась не реализована функция showPosition, вызов которой мы поставили в конце функции initGame. Пришло время написать её. Нам понадобится константа BoardSize, которую мы определили внутри функции createBoard. Давайте вынесём это определение оттуда, и поставим прямо в самом начале файла chess.js

Ну и теперь сама функция для показа позиции:

chess.jsfunction showPosition() {
    cells = document.querySelectorAll('.board .cell');
    for (let i = 0; i < BoardSize**2; i += 1) {
        let figure = position[i];
        if (figure == 0) {
            continue;
        }
        let image = FIGURES[figure];
        if (!image) {
            continue;
        }
        const figure_cell = document.createElement('div');
        const image_tag = document.createElement('img');
        image_tag.src = image;
        figure_cell.appendChild(image_tag);
        figure_cell.classList.add('figure');
        cells[i].appendChild(figure_cell);
    }
}

В строке 2 в переменную cells получем клетки доски (список dom-элементов, представляющих клетки доски).

В строке 3 делаем цикл от 0 до 63 - хотим пройти по всем клеткам. В строке 4 из массива с позицией, берём вид фигуры, находящейся на текущей клетке доски (на клетке с номером i). Если вид фигуры - ноль, т.е. поле пустое, то завершает тело цикла, переходим к следующей итерации.

В строке 8 берём путь к картинке фигуры, находящейся в текущей клетке. Если по какой-то причине картинку не удалось получить, переходим к следующей итерации.

В строке 12 создаём элемент div, запоминаем его в константе figure_cell. На следующей строке создаём элемент img, потом указываем в атрибуте src путь до картинки фигуры, и в строке 15 добавляем наш новый элемент img в не менее новый figure_cell.

В строке 16 добавляем класс figure в figure_cell, и на следующей строке добавляем наконец наш готовый элемент div с картинкой внутри, в элемент, представляющий текущую клетку доски (cells[i])

Выравниваем фигуры в css

Осталось "навести красоту" - сделать чтобы изображения фигур занимали всю свою клетку. Достаточно добавить всего одно правило, не зря-же мы добавляли класс figure в figure_cell. В конец файла chess.css добавляем правило:

chess.css.figure img {
    width: 100%;
    height: 100%;
}

Полный код chess.js

Итак, на текущий момент у нас получился такой код chess.js:

chess.jsconst BoardSize = 8;

const FIGURES = {
    1: 'img/king-white.svg', // белый король
    2: 'img/king-black.svg', // чёрный король
    3: 'img/queen-white.svg', // белый ферзь
    4: 'img/queen-black.svg', // чёрный ферзь
    5: 'img/rook-white.svg', // белая ладья
    6: 'img/rook-black.svg', // чёрная ладья
    7: 'img/bishop-white.svg', // белый слон
    8: 'img/bishop-black.svg', // чёрный слон
    9: 'img/knight-white.svg', // белый конь
    10: 'img/knight-black.svg', // чёрный конь
    11: 'img/pawn-white.svg', // белая пешка
    12: 'img/pawn-black.svg', // чёрная пешка
}

let is_our_move = true; // флаг, сигнализирующий о том, что сейчас наш ход
let position = []; // расположение фигур на доске
let available_moves = {}; // допустимые ходы текущего игрока - человека

window.onload = function () {
    initGame();
}

function initGame() {
    createBoard();
    initPosition();
    showPosition();
}

function createBoard() {
    board = document.querySelector('.board');
    board.innerHTML = '';
    for (let i = 0; i < BoardSize**2; i += 1) {
        const cell = document.createElement('div');
        let is_white = (parseInt(i / BoardSize) + (i % BoardSize)) % 2 == 0;
        cell.classList.add('cell', (is_white ? 'white' : 'black'));
        board.appendChild(cell);
    }
}

function initPosition() {
    is_our_move = true;
    position = [
        6,  10, 8,  4,  2,  8,  10, 6,
        12, 12, 12, 12, 12, 12, 12, 12,
        0,  0,  0,  0,  0,  0,  0,  0,
        0,  0,  0,  0,  0,  0,  0,  0,
        0,  0,  0,  0,  0,  0,  0,  0,
        0,  0,  0,  0,  0,  0,  0,  0,
        11, 11, 11, 11, 11, 11, 11, 11,
        5,  9,  7,  3,  1,  7,  9,  5
    ];
    available_moves = {
        48: [40, 32],
        49: [41, 33],
        50: [42, 34],
        51: [43, 35],
        52: [44, 36],
        53: [45, 37],
        54: [46, 38],
        55: [47, 39],
        57: [40, 42],
        62: [45, 47]
    };
}

function showPosition() {
    cells = document.querySelectorAll('.board .cell');
    for (let i = 0; i < BoardSize**2; i += 1) {
        let figure = position[i];
        if (figure == 0) {
            continue;
        }
        let image = FIGURES[figure];
        if (!image) {
            continue;
        }
        const figure_cell = document.createElement('div');
        const image_tag = document.createElement('img');
        image_tag.src = image;
        figure_cell.appendChild(image_tag);
        figure_cell.classList.add('figure');
        cells[i].appendChild(figure_cell);
    }
}

Итоги

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