Игра Морской бой на JavaScript. Выстрел игрока.

Вступление.

Очередная статья из цикла «Игра Морской бой на чистом JavaScript». В этой статье мы рассмотрим:

  1. Визуальное отображение клеток, по которым стрелять нет смысла.
  2. Выстрел игрока.
  3. Обработка результата выстрела.
  4. Визуальное отображение попадания и промаха.

Прежде чем приступать к написанию алгоритма выстрела, необходимо вспомнить несколько массивов, о которых писалось в статье Игра Морской бой на JavaScript. Рандомная расстановка кораблей.

  • 1

    matrix — это двумерный массив (матрица), в которой содержится информация о местоположении кораблей эскадры. Кроме этого, в матрицу записываются реультаты выстрела (промах или попадание) и места, где кораблей точно не может быть, исходя из правил игры. Изначально, данный массив при создании заполняется нулями, которые соотвествуют пустой клетке.

    • 0 — пустая клетка;
    • 1 — палуба корабля;
    • 2 — палубы корабля не может быть по условиям игры;
    • 3 — промах;
    • 4 — попадание.

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

    1. user.matrix — соответствует игровому полю игрока;
    2. comp.matrix — соответствует игровому полю компьютера.
  • 2

    squadron — массив эскадры, в котором храниться полная информация по каждому кораблю. Данные этого массива используются для:

    • — определения корабля, в которого было попадание;
    • — уничтожен данный корабль или только повреждён (ранен);
    • — количество уничтоженных (потопленных) кораблей в эскадре.

Кроме массивов, хочу напомнить о том, что весь алгоритм игры «Морской бой», реализован в модуле Controller, содержащем объект battle. В этот объект с помощью простой литеральной нотации записаны все функции определяющие алгоритм игры и определён ряд свойств — глобальных, в области видимости модуля, переменных.

Подробнее о модуле Controller и инициализации игры «Морской бой», читайте в статье Игра Морской бой на JavaScript. Редактирование положения корабля и начало игры.

Игра «Морской бой». Блокировка клетки игрового поля и установка маркера.

Прежде чем рассматривать реализацию выстрела игрока, необходимо разобраться с блокировкой клеток игрового поля и установкой маркера на них. Напомню, таким образом отмечаются клетки игрового поля, в которых, согласно правилам игры, не могут располагаться корабли — это соседние с кораблём клетки, как по вертикали и горизонтали, так и по диагонали. У вас может возникнуть вопрос, а зачем их помечать? Это позволяет лучше оценить возможное расположение кораблей противника и облегчает выбор координат для следующего выстрела.

Игра Морской бой. Пример игрового поля во время игры.

Маркер устанавливается кликом правой кнопки мыши по клетке игрового поля компьютера, при этом клетка не должна содержать отметки попадания или промаха. Снимается маркер также — кликом правой кнопкой.
Для этой цели, мы в функции battle.init зарегистрировали обработчик события contextmenu:

Алгоритм по установке и снятию маркера на клетке игрового поля не сложен. Давайте рассмотрим его:

  • 1

    Используя функцию transformCoordinates, переводим координаты точки клика правой кнопкой мыши, в координаты матрицы.

    Запомните, это очень важно.
    Координаты точки клика отсчитываются относительно верхнего левого угла игрового поля противника.
  • 2

    Получаем коллекцию всех объектов иконок, которые в данный момент расположены на игровом поле противника — иконки попаданий, промахов и заблокированных клеток.

  • 3

    Перебираем полученную коллекцию, сравнивая координаты иконок с координатами клика.

  • 4

    Если координаты ни одной иконки не совпадут, значит клетка, по которой был сделан клик, пустая. Устанавливаем на неё заштрихованный маркер и в матрицу игрового поля компьютера по полученным координатам записываем значение ‘2’, что соответствует заблокированной клетке.

  • 5

    Если координаты совпадут и в этой клетке уже установлен заштрихованный маркер, то удаляем его, а в матрицу игрового поля компьютера по этим координатам записываем значение ‘0’, что соответствует пустой клетке.

Теперь напишем JS-код, реализующий данный алгоритм. Для этого мы создадим две функции — setEmptyCell и checkCell. Данные функции будут методами объекта battle. Запишем их в конец JS-кода этого объекта в простой литеральной нотации.

Игра «Морской бой». Получение координат выстрела игрока.

Как я писал ранее в предыдущей статье, в функции battle.init устанавливается обработчик события, который при клике по игровому полю компьютера запускает функцию shoot:

Добавим к объекту battle метод shoot. Запись, как говорилось ранее, будем осуществлять в простой литеральной нотации:

На данном этапе, мы рассмотрим работу функции shoot применительно к выстрелу игрока. Небольшие дополнения к JS-коду, относящиеся к выстрелу компьютера мы рассмотрим позже, в следующей статье, когда будем разбираться с искусственным интеллектом противника.

Аргументом функции shoot является событие, генерируемое игроком, при клике по игровому полю компьютера. По наличию данного аргумента можно определить — кто стреляет. Если выстрел был компьютера, то e === undefined
В список свойств данного события, входят pageX и pageY, которые содержат координаты клика в пикселях по оси 'X' и оси 'Y' относительно всего документа. Зная координаты сторон игрового поля компьютера (они являются свойствами экземпляра comp.FieldfieldX и fieldY), получаем координаты клика уже относительно рамки игрового поля.
Теперь мы можем преобразовать полученные координаты в координаты матрицы и извлечь значение матрицы в клетке игрового поля, по которой был сделан клик. Если значение равно 0 — значит это пустая клетка и игрок промахнулся, а если значение равно 1 — в данной клетке находится палуба корабля и было попадание.

Вот краткий алгоритм обработки выстрела игрока. Забегая вперёд, скажу, что алгоритм выстрела компьютера точно такой же, только координаты клика выдаёт искусственный интеллект, который мы с вами ещё создадим. А пока напишем JS-код, реализующий приведённый выше алгоритм:

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

Игра Морской бой. Преобразование координат выстрела.

Где:

  1. e.pageX — координата точки клика по оси Х относительно документа;
  2. comp.fieldX — координата левой границы игрового поля компьютера по оси Х;
  3. e.pageX - comp.fieldX — координата точки клика по оси Х относительно левой границы игрового поля компьютера.

По оси ‘Y’ — аналогично.

Полученные координаты точки клика переводим в координаты двумерного массива (матрицы). Для этого разность координат необходимо поделить на ширину стороны клетки, которая равна размеру палубы корабля. Размер палубы является значением свойства shipSide конструктора Field. Полученное значение округляем с недостатком или просто отбрасываем от него дробную часть, что одно и то же. Оставшаяся целая часть и есть координата двумерного массива.

Аргументами функции является событие и переменная enemy, которая в данный момент равна 'comp'.

Всё что мы изложили выше, можно описать двумя простыми формулами. Добавим в самый конец объекта battle метод transformCoordinates:

Для получения целого используем метод Math.trunc, который отбрасывает десятичную запятую и все цифры после неё. К сожалению, IE данный метод не поддерживает и нужно использовать полифилл.

Настало время перейти к самой сложной части — обработке полученного из матрицы значения.

Игра «Морской бой». Обработка результатов выстрела.

В данном разделе мы с вами рассмотрим JS-код, который обрабатывает информацию, полученную из двумерного массива matrix и, в зависимости от результата выстрела (промах, попадание, повторный обстрел одних и тех же координат), отображает игровой процесс на экране монитора.
Этот JS-код обрабатывает как выстрел игрока, так и, с небольшими дополнениями, выстрел компьютера. Поэтому постараемся разобрать его как можно подробнее.

Ещё раз вспомним, какие значения могут храниться в матрице и чему они соответствуют:

  • 0 — пустая клетка;
  • 1 — палуба корабля;
  • 2 — клетка, отмеченная игроком;
  • 3 — зафиксирован промах;
  • 4 — зафиксировано попадание.

Для вызова тех или иных функции, в зависимости от полученного значения, будем использовать конструкцию switch. Аргументом для данной конструкции будет полученное из матрицы значение val. В конец функции shoot запишем следующий JS-код:

Игра «Морской бой». Промах.

Рассмотрим алгоритм обработки промаха:

  • 1

    Визуально отображаем промах и записываем в матрицу по этим координатам значение 3.

  • 2

    Выводим сообщение о промахе.

  • 3

    Определяем, чей выстрел будет следующим.

  • 4

    Если следующий выстрел делает компьютер, то снимаем пользовательские обработчики событий click и contextmenu, чтобы в это время игрок не мог производить никаких действий. В противном случае — устанавливаем эти обработчики событий.
    Напоминаю, что в данный момент мы рассматриваем выстрел игрока, поэтому обработчики событий click и contextmenu будут удалены.

Прежде чем приступим к написанию JS-кода, давайте рассмотрим новую вспомогательную функцию showIcons, которая будет отвечать за визуальное отображение результа выстрела, а также отображать клетки, в которых гарантированно не может быть расположен корабль. Данная функция универсальная и будет использоваться, как при выстреле игрока, так и при выстреле компьютера.

Функция showIcons будет использовать три аргумента:

  1. enemy — указывает, кто на момент выстрела является противником;
  2. coords — координаты выстрела в системе координат матрицы;
  3. класс таблицы стилей ('dot', 'red-cross', 'shaded-cell'), который выводит точку, крест или заштрихованную ячейку соответственно.

Функция showIcons является методом объекта battle, поэтому запишем её в конец данного объекта:

Теперь настало время написать JavaScript, который реализует алгоритм промаха при выстреле:

Игра «Морской бой». Попадание в корабль противника.

Алгоритм обработка результата попадания в корабль противника:

  • 1

    Визуально отображаем попадание и записываем в матрицу по этим координатам значение 4

  • 2

    Перебираем массив эскадры squadron, в котором храниться информация о каждом корабле противника, и определяем корабль в который произошло попадание. После этого вносим изменения в данные массива и выводим сообщение о попадании.

  • 3

    Проверяем, все ли корабли противника потоплены. В зависимости от результата или продолжаем игру (делаем следующий выстрел), или заканчиваем, объявляя о победе игрока.

Первый пункт алгоритма подробно рассматривать не будем, т. к. его JS-код практически полностью совпадает с JS-кодом обработки промаха.

Игра «Морской бой». Поиск корабля, в который произошло попадание.

Как я писал ранее, для поиска корабля, в который произошло попадание, необходимо перебрать массив эскадры squadron. Каждым элементом данного массива является экземпляр объекта корабля, созданный с помощью конструктора Ships.

Внимание.
Более подробно об объекте корабля изложено в описании конструктора кораблей — Ships в статье «Игра Морской бой на JavaScript. Рандомная расстановка кораблей.»

На данном этапе нам понадобятся только три свойства объекта корабля:

  1. decks — количество палуб;
  2. hits — счётчик попаданий;
  3. matrix — массив с координатами каждой палубы корабля.

В процессе поиска, нужно перебрать массив matrix каждого корабля эскадры, сравнивая координаты палуб с координатами выстрела. В случае совпадения, увеличиваем счётчик попадания (hits) найденного корабля на единицу и сравниваем значение счётчика с количеством палуб. Если эти значения равны, то удаляем корабль из массива эскадры.

Теперь блок case 1 будет выглядеть следующим образом:

Игра «Морской бой». Продолжение боя и окончание игры.

Принимать решение о продолжении боя или его окончании мы будем на основании длины массива squadron — если его длина равна нулю, значит все корабли эскадры противника уничтожены. В противном случае, морской бой продолжается и игрок может совершить новый выстрел. Хочу сразу отметить, что большинство JS-кода относится к выстрелу компьютера. Подробно это будет рассмотрено в следующей статье.

Рассмотрим подробнее окончание игры при выстреле игрока.
При окончании игры необходимо совершить всего два действия:

  1. вывести текстовое сообщение поздравления с выигрышем;
  2. снять обработчики событий пользователя:
    click — выстрел;
    contextmenu — пометка клеток на игровом поле.

Напишем JS-код, который реализует описанный функционал. Разместим его в конце блока case 1:

Итак, мы подробно изучили работу JavaScript, обрабатывающего попадание при выстреле, как игрока, так и компьютера. Отдельные моменты, касающиеся только компьютера, будут рассмотренны в следующей статье.

Теперь настало время разобрать, что же произойдёт, если игрок попытается совершить выстрел по заблокированной клетке, отмеченной маркером.

Игра «Морской бой». Выстрел по заблокированной клетке.

Если игрок сделает выстрел по заблокированной клетке, не смотря на установленный маркер, то ему будет выдано предупреждение о необходимости снять блокировку, а маркер на 0.5 сек подсветится красным цветом.
JavaScript здесь несложный, поэтому отдельно описывать его не буду — достаточно будет комментариев в самом коде.

Хочу сказать, что такая ситуация возможна только при выстреле игрока. Компьютеру выстрелить по заблокированной или ранее обстрелянной клетке не даст его искусственный интеллект.

Игра «Морской бой». Выстрел по ранее обстрелянным координатам.

Если игрок выстрелил по клетке, которую обстрелял ранее и там зафиксировано попадание или промах, то игроку выдаётся предупреждение о повторном обстреле данных координат.

Добавим в конструкцию switch следующий JS-код:

На этом мы закончим рассматривать выстрел игрока.

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

Комментарии

Всего: 2 комментария
Требования при посте комментариев:
  1. Комментарии должны содержать вопросы и дополнения по статье, ответы на вопросы других пользователей.
    Комментарии содержащие обсуждение политики, будут безжалостно удаляться.
  2. Для удобства чтения Вашего кода, не забываейте его форматировать. Вы его можете подсветить код с помощью тега <pre>:
    <pre class="lang:xhtml"> - HTML;
    <pre class="lang:css"> - CSS;
    <pre class="lang:javascript"> - JavaScript.
  3. Если что-то не понятно в статье, постарайтесь указать более конкретно, что именно не понятно.
  • Привет, сколько всего частей планируешь. И когда следующая ?

    • Вальдемар Владислав Ответить 17 мая 2018 в 11:28

      В данный момент пишу статью «Выстрел компьютера». Параллельно дополняю и улучшаю ИИ компьютера. Думаю, что ещё пару недель у меня это займёт.
      Скорее всего, это будет последняя статья из цикла «Морской бой».

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *