В качестве упражнения я решил воссоздать очень простую, но интересную игру, созданную в 1970-х годах. В оригинале она называлась Chase, а затем появился ремейк под названием Robots.
Я собираюсь написать её на JavaScript и Python по следующим причинам:
- JavaScript работает прямо в браузере, поэтому я могу давать ссылки на работающую игру и её исходный код.
- Чтобы сравнить два языка, и показать, что разница между ними не играет никакой роли.
Суть игрового процесса
На огороженном квадратном игровом поле располагается игрок и некоторое количество злых роботов. Роботы охотятся за игроком. Каждый раз, когда игрок делает шаг в любом направлении, все роботы тоже делают шаг, чтобы сблизиться с игроком. Как только один из роботов настигает игрока, игра окончена.
Единственное, что может сделать игрок для своего спасения – заставить роботов сталкиваться друг с другом. Столкнувшись, они разрушаются. Также на игровом поле случайным образом расположены трансформаторы под напряжением. Столкнувшись с трансформатором, робот разрушается, но игроку с ним тоже нельзя сталкиваться.
Цель игры – уничтожить всех роботов.
Начну, как всегда, с представления состояния игры в памяти компьютера.
Дело будет происходить в квадрате 22*22 клетки. Клетки по периметру квадрата будут служить "забором", чтобы игрок не мог выйти за пределы поля. То есть для перемещений игрока будет доступно поле 20*20 клеток.
Я выберу метод "от пространства", потому что он мне подходит:
- Все объекты перемещаются строго по клеткам
- Игрока, роботов и препятствия можно хранить прямо в клетках
- Каждая клетка может содержать только один объект – игрока, робота, забор или трансформатор
- Для хранения содержимого клеток понадобится массив размером 22*22 = 484 байта
- Для полного перебора клеток требуется цикл 20*20 = 400 повторений, это немного для такой игры
Далее проверю, можно ли полностью реализовать игру при таком выборе. Для этого отвечу на вопросы:
- Как представить состояние игры? В виде массива 22*22 байта. Каждый элемент массива соответствует состоянию одной клетки поля.
- Как хранить позиции игрока и роботов? Обозначу элементы массива следующим образом: 0 – пустая клетка, 10 – забор, 20 – игрок, 30 – робот, 40 – трансформатор. Также я буду хранить адрес игрока как отдельную переменную, чтобы был быстрый доступ к нему.
- Как узнать, сколько роботов осталось? Нужно перебрать все элементы массива и посчитать, сколько из них содержат число 30 (робота).
- Как обработать всех роботов? Точно так же – перебрать всех и обработать каждого по отдельности. Заодно можно их сразу и посчитать, чтобы не делать два перебора.
- Как определить, возможен ли ход игрока? На основании выбора, который совершит игрок, он должен переместиться в одну из соседних клеток, то есть его текущий адрес изменится. Я проверю, что находится по этому адресу. Если там забор (10), то ход невозможен. Если там робот (30) или трансформатор (40), то игрок погибает. Если пустая клетка, то ход возможен.
- Как обработать ход робота? Находясь в какой-то клетке, робот имеет её адрес. Зная адрес игрока, робот может вычислить, какая из соседних клеток находится ближе всего к игроку. И переходит в эту клетку. Если в этой клетке находится игрок, то игра заканчивается. Если в этой клетке трансформатор, то робот погибает. Если в этой клетке другой робот, то оба робота погибают.
- Как погибают роботы? Я просто записываю 0 в клетку, где стоял робот. Всё, клетка пустая и робота больше нет.
- Может ли игрок выйти за пределы поля? Нет, потому что оно огорожено клетками, в которые игрок не может совершить ход (пункт 5).
- Может ли робот выйти за пределы поля? Нет, потому что робот движется всегда по направлению к игроку, а игрок всегда внутри поля.
Вы можете заметить, что я не написал ещё ни строчки на JavaScript или Python, но уже решил множество вопросов. Программированием я занимаюсь именно сейчас, а не тогда, когда начну писать код.
Теперь надо решить, как отображать игру на экране, ведь без этого даже полностью рабочая игра будет бессмысленна.
Интересный момент заключается в том, что визуальную часть можно сделать какую угодно. На основу игры это никак не повлияет. Ведь в памяти будут находиться одни и те же данные, просто рисовать мы их будем по-разному. Поэтому можно сделать сначала попроще, а затем посложнее.
Я пока быстро проверил JavaScript и Python на наличие сходных графических возможностей, и нашел их. Поэтому буду рисовать поле в графическом виде, а заборы, игрока, роботов и трансформаторы буду просто обозначать прямоугольниками разных цветов. Конечно, потом всё это можно будет усовершенствовать.
Также меня интересует, как игрок будет управлять персонажем на экране. Перемещение возможно в восьми направлениях: влево, вправо, вверх, вниз, и по диагоналям. Значит, нужно 8 клавиш для управления. Наиболее удобными будут клавиши на цифровой клавиатуре: 1 это влево-вниз, 2 это вниз, 3 это вправо-вниз, 4 это влево, 6 это вправо, 7 это влево-вверх, 8 это вверх и 9 это вправо-вверх. Стало быть, игра должна уметь получать от системы информацию, какая клавиша была нажата.
Теперь я спроектирую главный цикл игры, по схеме из предыдущего выпуска, и реализую в коде каждый его пункт.
1. Старт
Здесь нужно создать в памяти поле, огороженное забором, и случайным образом поставить в нем игрока и некоторое количество роботов и трансформаторов.
2. Отображение информации
Здесь нужно нарисовать на экране внешний вид игрового поля, пользуясь графическими возможностями языка.
3. Ожидание ввода
Здесь программа должна ожидать, когда игрок нажмет одну из клавиш.
4. Обработка состояния
Получив код нажатой игроком клавиши, игра должна выяснить, может ли игрок совершить ход. Если может, то игрок перемещается в новую клетку. Вслед за этим игра перебирает всех роботов и двигает их по направлению к игроку, одновременно проверяя столкновения и уничтожая часть роботов.
5. Принятие решения
Если на поле остался игрок и хотя бы один робот, игра продолжается и переходит на пункт 2.
6. Конец игровой активности
Сюда мы попадаем, если игрок умер или все роботы уничтожены. Мы напишем на экране соответствующее сообщение для игрока. Если он захочет повторить игру, то она начнется снова с пункта 1.
Всё, осталось это написать. Каждый этап реализации вызовет кое-какие вопросы, конечно. Например, нам обязательно понадобятся функции – то, о чем я до сих пор не говорил. Поэтому буду разбирать всё по частям в следующих выпусках.
- Часть 2: Функции и библиотеки
- Часть 3: Инициализация
- Часть 4: Расстановка объектов
- Часть 5: Графический контекст
- Часть 6: Чёрный квадрат
- Часть 7: Давайте уже рисовать
- Часть 8: Обработка действий игрока
- Часть 9: Эпичный провал
- Часть 10: Итоги и выводы