Предыдущие части: Обработка действий игрока, Давайте уже рисовать, Чёрный квадрат, Графический контекст, Расстановка объектов, Инициализация, Функции и библиотеки, Проектирование
В предыдущей части я организовал получение события: нажатие клавиши. Это событие попадает ко мне в функцию process_game в виде объекта event, и теперь я должен его обработать. Начну с версии JavaScript. У объекта event в JavaScript есть атрибут keyCode. Это код нажатой клавиши. Это не символ, который нарисован на клавише. Это код самой клавиши. Мне нужны клавиши 1,2,3,4,6,7,8,9, но не простые, а на цифровой клавиатуре. Они имеют коды, соответственно, 97,98,99,100,102,103,104,105. Вот с этими числами я и буду сравнивать атрибут keyCode.
Да, и так как я начал реализацию финальной версии, то я разобью функцию process_game на несколько более специализированных функций.
Таким незамысловатым способом я вычислил направление, в котором должен сдвинуться игрок. Напомню, что вместо координат (x, y) игрока я храню его адрес в массиве (player_addr) – так короче и удобнее. Чтобы игрок сместился на 1 клетку вниз, надо к его адресу прибавить 22. Вверх – отнять 22. Влево – отнять 1. Вправо – прибавить 1. В общем, для каждой из клавиш, обозначающих направления, я присвоил переменной move соответствующее число, которое надо прибавить к адресу.
Дальше я проверю, что находится в клетке с этим адресом. Если там стена, игрок не может совершить ход и игра вообще не двигается. Я использую return, чтобы досрочно вернуться из функции. Если там робот или трансформатор, игрок должен погибнуть. В этом случае я присвою переменной game_mode константу GAME_FINISH (об этом – в конце) и также вернусь из функции досрочно. Если первые два условия не сработали, значит клетка пустая – это можно уже не проверять. Я удалю игрока из текущей клетки, прибавлю к его адресу смещение и запишу игрока в новую клетку:
После того как я переставил игрока, приходит очередь роботов. Перебирать их я буду перебирая клетки поля и сравнивая их c константой ROBOT. Каждый робот должен найти более близкую к игроку клетку. Затем робот проверит, что находится в этой клетке...
...И тут меня настиг оглушительный провал. Когда я запустил ранний прототип игры, роботы двигались крайне странно, а игра могла закончиться с первого же хода. Я попал в ловушку: допустим, я переставил робота из текущей клетки в следующую, но когда мой цикл перейдет в следующую клетку, я найду там робота, которого только что переставил!
Кроме того, когда в одной клетке должны столкнуться три робота, этого не происходит: первые два, столкнувшись, уничтожают друг друга, и третий приходит уже на пустую клетку. Наконец, возможен и обратный вариант: один робот должен зайти в пустую клетку, из которой только что ушел другой. Но в моем цикле другой не успевает уйти, потому что до него ещё не дошла очередь, и поэтому оба робота гибнут, хотя и не должны.
Сразу скажу, что это стандартная проблема и она решается путем создания копии массива.
var field_copy = [];
Данная инструкция создает пустой массив. И я создаю его только один раз в начале программы. У меня, таким образом, будут постоянно в наличии два массива.
Чтобы сделать копию рабочего массива, я перебираю его в цикле и копирую значения во второй массив (при добавлении элементов в пустой массив JavaScript автоматически наращивает его длину), а заодно удаляю роботов из рабочего массива после копирования.
Затем я пройдусь уже по копии в поисках роботов, и каждого робота, сделавшего ход, я перенесу обратно в рабочий массив. И все проверки столкновений я буду делать в рабочем массиве. Если в нём уже есть робот – значит, этот робот уже ходил. Но я не буду их сразу уничтожать, а добавлю еще одну константу:
const DEAD_ROBOT = 35
и буду записывать её в клетку, где столкнулись роботы. Любой робот, который попадет в эту клетку, тоже умрет. Таким образом я решаю проблему, когда в одной клетке сталкивается больше двух роботов.
А после завершения перебора я переберу рабочий массив ещё раз и обнулю все клетки с содержимым DEAD_ROBOT, то есть сделаю уборку. Заодно я смогу посчитать, сколько роботов осталось.
Роботы используют функцию get_target_addr(addr), куда передают собственный адрес, а функция возвращает им новый адрес, куда они должны переместиться. Функция переводит адрес игрока и адрес робота в координаты x,y (как это делается, уже рассказывал), и сравнивает эти координаты. Если координата x робота меньше чем x игрока, робот должен сместиться вправо. Ну и остальное аналогично.
Вот и всё, алгоритмическая часть игры полностью реализована. Но осталось ещё кое-что. Несмотря на то, что игра крутится в цикле и каждый раз рисует экран для игрока, эти экраны могут быть принципиально разные. Игрок не всегда играет: после запуска игры он видит заставку, а проиграв или победив, видит финальный экран. И в каждом экране действия игрока обрабатываются по-разному: одно и то же нажатие на клавишу может или двигать игрока по полю, или осуществлять выбор в меню (не в этой игре, а вообще).
Поэтому необходимо ввести такое понятие, как режим игры. У меня будет 3 режима: заставка, игра и финальный экран. Для их обозначения я заведу три константы и переменную game_mode:
const GAME_TITLE = 0;
const GAME_PLAY = 1;
const GAME_FINISH = 2;
var game_mode = GAME_TITLE;
В своем игровом цикле я буду проверять, в каком режиме находится игра, и вызывать свой обработчик для каждого режима. Теперь цепочка действий от момента получения события будет выглядеть так:
Для режимов GAME_TITLE и GAME_FINISH я пока не вижу целесообразным готовить какую-то особую графику или интерактив. Они нужны мне только для демонстрации. Поэтому я просто заготовил еще три игровых поля в виде массивов, где я поставил стенки таким образом, чтобы из них на экране получились буквы. И буду просто рисовать поле, как обычно. Но оно будет не для игры, а только для показа. Например, вот стартовый экран:
Я также доработал функцию рисования draw_field(), чтобы она выбирала нужное поле для рисования с учетом текущего режима игры.
Потом длинные куски кода я перенес в отдельные функции, чтобы программа выглядела лучше, кое-что подпилил, ну и собственно вот финальная живая версия на JavaScript
Конечно, по-настоящему играть в такое пока вряд ли будет интересно. Выводам из всей этой истории я посвящу десятую, заключительную часть.
Читайте дальше: Итоги и выводы