ПРИМЕНЕНИЕ АЛГОРИТМА ПОИСКА В ШИРИНУ ПРИ РАЗРАБОТКЕ ВИДЕОИГР

IX Международный конкурс научно-исследовательских и творческих работ учащихся
Старт в науке

ПРИМЕНЕНИЕ АЛГОРИТМА ПОИСКА В ШИРИНУ ПРИ РАЗРАБОТКЕ ВИДЕОИГР

Латыпов Д.В. 1
1МАОУ Одинцовский лицей №6 им. А.С. Пушкина
Ананьева Е.В. 1
1МАОУ Одинцовский лицей №6 им. А.С. Пушкина
Автор работы награжден дипломом победителя I степени
Текст работы размещён без изображений и формул.
Полная версия работы доступна во вкладке "Файлы работы" в формате PDF

Аннотация работы

Название проекта: «Применение алгоритма поиска в ширину при разработке видеоигр».

Автор проекта: Латыпов Дмитрий, ученик 9 «В» класса.

Научный руководитель: Ананьева Елена Васильевна.

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

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

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

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

Третья часть работы содержит в себе экономическую часть и включает в себя расчет рентабельности разработанной игры.

Приложения включают в себя описание непосредственной реализации алгоритма и его демонстрационного проекта с помощью выбранных программных и языковых средств.

Заключительная часть работы подводит итог полученных результатов по итогу работы над проектом

Введение.

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

Новизна и актуальность: на сегодняшний день более миллиарда людей регулярно играют в видеоигры [1]. Ежегодно выходят сотни, а то и тысячи самых разнообразных видеоигр. Масштаб игр разнится от простейшего тетриса в консольном окне до целых армий в гигантских искусственных мирах, жанр – от головоломок до ролевых игр, а бюджет разработки – от отсутствующего как такового до десятков миллионов долларов. Игры выпускаются на самых разных платформах: от компьютеров и ноутбуков до игровых приставок и мобильных телефонов.

Безусловно, все это говорит о том, что разработка новых игр востребована, а игровая индустрия активно продолжает развиваться.

Практическая значимость: в процессе работы над созданием компьютерной игры, был изучен новый язык программирования и пакет программ Visual Studio 2019.

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

Разработанность проблемы: наибольшее количество исследований, посвящённых компьютерным играм, произведено в Америке и Западной Европе — в связи с наиболее развитой индустрией компьютерных игр. В 2011 году в США видеоигры официально признали видом искусства [2].

Западными исследователями виртуальных компьютерных игр являются Гэллоувэй А.Р., занимающийся истоками виртуальных компьютерных игр, проблемой реализма в них и соотношением феноменов виртуальных компьютерных игр и театра, Фромм Дж., изучающая перемены в структурах досуга детей, произошедшие с распространением компьютерных игр, Джуул Дж. и Линдли A.C., занимающиеся проблемой наличия нарратива в виртуальных компьютерных играх и другие.

Основными российскими исследователями феномена компьютерных игр являются Игнатьев М.Б., изучающий соотношение виртуальных компьютерных игр с актёрской игрой и литературой, Степанцова O.A., изучающая особенности субкультур компьютерных игроков, Бурлаков И.В, Гудимов В.В. и Петрусь (Кузнецов) Г.Г, рассматривающие истоки и содержание феномена виртуальных компьютерных игр с позиций психоанализа и выявления архетипов коллективного бессознательного.

Цель: создать компьютерную игру “FASTRUNNER” с применением алгоритма поиска в ширину в графе в программе Visual Studio 2019.

Задачи:

Изучить работу с программой Visual Studio 2019 (интегрированная среда разработки программного обеспечения).

Разработать в программе компьютерную игру “FASTRUNNER”.

Возможные области применения и назначение: игра предназначена для детей младшего и среднего школьного возраста. Игра "FASTRUNNER" развивает зрительное восприятие и произвольное внимание. Программа может быть использована также любым пользователем для отвлечения от основной работы с целью отдыха.

Этапы работы над проектом:

I этап – подготовительный (сбор информации, маркетинговые исследования, обучение). Сентябрь – ноябрь 2019 года.

Принятие решения о создании видеоигры с использованием программы Visual Studio 2019.

Изучение литературы и видеоуроков по теме Visual Studio 2019. Поиск своей ниши на рынке.

Обучение языку программирования.

II этап – реализация проекта. Декабрь 2019 - январь 2020 г.

Формирование информации для наполнения страниц сайта.

Разработка дизайна, структуры и цифрового кода компьютерной игры.

Тестирование работы компьютерной игры.

III этап – организационно-обобщающий. Февраль - март 2020 г.

Публикация, распространение и размещение на личной странице социальных сетей автора игры в сети Интернет.

Итоги на каждом этапе:

I этап: поставлены задачи и цели, изучен язык программирования Visual Studio 2019.

II этап: разработан дизайн, структура и цифровой код компьютерной игры. Проведено тестирование и отладка программы.

III этап: создана компьютерная игра “FASTRUNNER” с применением алгоритма поиска в ширину в графе.

Материально-техническое обеспечение проекта: компьютер, интернет, специальная литература, специализированное программное обеспечение: Adobe Photoshop CS6 и Visual Studio 2019.

1. Теоретический раздел.

Принцип алгоритма поиска в ширину.

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

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

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

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

Создание игровых уровней.

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

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

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

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

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

Описание программы Visual Studio 2019.

При разработке и создании видеоигры использовался пакет Visual Studio 2019. Он использует стандартные библиотеки для работы с графикой, файловой системой и системой ввода\вывода.

Программа логически разбита на четыре части:

Основной код программы

Набор функций для работы с графикой

Вспомогательные функции и обработка файлов

Алгоритмы движения объектов игры в том числе и поиск в ширину

Основной код программы main.cxx состоит из инициализации окна, в котором работает программа, отрисовки стартового экрана и главного игрового цикла while(), который является движком игры.

Игровой движок.

Игровой движок – совокупность программных продуктов и/или библиотек, являющихся центральной частью архитектуры одной или более видеоигр. Игровой движок представляет собой технологический фундамент, обеспечивающий базовые возможности, необходимый для работы игры. К функционалу игровых движков относят систему рендеринга (визуализации), физический движок, воспроизведение звука и музыки, управление искусственным интеллектом и анимациями персонажей, сетевой код, управление памятью и многие другие системы [3].

Термин игровой движок появился в 1990-ых годах с ростом популярности таких игр, как Doom и Quake. На сегодняшний день игровые движки освобождают разработчиков от необходимости реализовывать существенную часть базового функционала и позволяют им заниматься написанием более высокоуровневого кода, специфического именно для создаваемой ими игры. При использовании готового решения сокращаются затраты времени и средств на реализацию игры.

Игровой движок в том или ином виде существует практически у любой современной игры. Многие из них являются закрытыми, т.е. создаются компанией-разработчиком и не распространяются публично. Другие движки платно лицензируются третьим лицам, а некоторые из них являются бесплатными или условно-бесплатными [4].

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

2. Практический раздел.

2.1. Постановка задачи.

Написать компьютерную игру «FASTRUNNER» с применением алгоритма поиска в ширину в графе.

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

На поле также находятся препятствия в виде стен и других объектов, затрудняющих достижение цели. Игрок должен создавать карты для игры самостоятельно в специальном редакторе, встроенном в игру. Общая схема программы представлена в Приложении 1.

void showMenu()

Отрисовывает меню

void showMap()

Отрисовывает карту

void drawMonster(introw, intcol)

Отрисовывает главного героя

void drawSkeleton(introw, intcol)

Отрисовывает врага

void wall()

Отрисовывает стену

 

Инциализция игрового

2.2. Набор функций для работы с графикой.

Код функции void drawMonster(int row, int col) и код функции void wall() представлены в Приложениях 2 и 3.

Код функции void drawSkeleton(int row, int col) представлен в приложении 4.

Код функции void showMenu() представлен в приложении 5.

2.3. Вспомогательные функции и обработка файлов.

bool fexists(constchar* filename)

Проверка на существование файла

void fill2DArrayFromTxt()

Загрузка карты из текстового файла

bool gameOver()

Проверка на проигрыш

ostream& writemap(...)

Сохранение карты в файл

void setMapNumber()

Функция сканирует кол-во карт

void fillMap()

Отрисовывает объекты на карте

void initialMonsterPlace()

Находит местоположение монстра

void clickChecker()

Обрабатывает клики в окне

Код функции void fillMap() и код функции void setMapNumber() представлены в Приложениях 6 и 7.

2.4. Функции алгоритмов движения объектов игры, в том числе и поиск в ширину.

Point getNextStep()

Функция алгоритма поиска в ширину

void smartMoveSkeleton()

Достижение врагом героя

void moveSkeleton()

Движение скелета в разные стороны

Код функции void smartMoveSkeleton() представлен в Приложении 8.

2.5. Используемые библиотеки.

#include <iostream>

Функция ввода\вывода

#include <string>

Функция работы со строками

#include <queue>

Подключает очередь

#include<ctime>

Для рандома карты

#include<fstream>

Функция работы с файлами

#include"graphics.h"

Внешняя графическая библиотека

2.6. Инструкция по установке и системные требования.

Игра поставляется в виде zip архива. Для использования программы необходим любой архиватор, в том числе встроенный в операционную систему. Распаковать программу рекомендуется в директорию с другим программным обеспечением. Например, для самой популярной операционной системы Windows это:

C:/Program Files/

Для запуска игры нужно открыть файл FastRunner.exe

Рекомендуемые системные требования:

Операционная система Windows 7 и выше.

Процессор Intel Core i3 ивыше.

ОЗУ 4ГБ и выше с учетом требований самой OS.

Любая современная видеокарта.

100МБ свободного места на жестком диске.

Клавиатура и компьютерная мышь.

Для работы программа в дополнительных настройках не нуждается.

2.7. Инструкция пользователя.

Запуская игру, открывается меню (Приложение 9).

Меню состоит из:

Название игры

Кнопка начала игры

Кнопка создания карты

Кнопка выхода из меню

Рассмотрим два из их отдельно (2,3) и начнем с кнопки создания карты.

2.8. Создание карты.

Думая над тем, как устроить создание карты, я перебирал в голове множество вариантов. Я считаю, что чем больше пользователь завлечен в игру, тем интереснее ему играть. Поэтому я сделал так, чтобы он сам конструировал карту, на которой хотел бы играть. Для этого ему надо зайти в часть меню под названием “MAP CREATOR”, и вот что будет изображаться (Приложение 10).

Поле создания карты поле для игры представляет собой квадрат 15x15 клеток.

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

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

После установки всех препятствий, сделанная нами карта готова к началу игры (Приложение 13).

Теперь можно начинать играть. Но для начала нужно сохранить карту, нажав “пробел” и выйти в главное меню с помощью “backspace”. И, сделанный мною рандомайзер карт, выберет случайную карту, из тех которые я сохранил ранее, чтобы придать игре интригу и вызвать еще больший интерес.

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

2.9. Создание главного героя.

В любой компьютерной игре нельзя обойтись без главного героя. В моей игре “FastRunner” – это героический огр с мечом и щитом. Его можно расположить в любом месте карты, нажав на колесико мышки в первый раз.

Главный герой компьютерной игры был создан с помощью программы Adobe Photoshop CS6 (Приложение 14).

2.10. Создание врагов.

Согласитесь, что игра без зловещего злодея не интересна. И чтобы сделать ее более увлекательной, я решил добавить скелета – опасного и быстрого врага.

Главный злодей компьютерной игры был также создан с помощью программы Adobe Photoshop CS6 (Приложение 15).

2.11. Начало игры.

При нажатии на кнопку “START GAME”, начинается игра, и она находится в режиме паузы. Чтобы начать играть нужно нажать на любую из стрелочек на клавиатуре. (W,A,S,D) (Приложение 16).

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

2.12. Тестирование и отладка компьютерной игры.

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

Несмотря на существование нескольких путей достижения игрока, скелет выбрал наикратчайший и тем самы успешно достиг цели.

После проигрыша отображается данная надпись Game Over (Приложение 18).

3. Экономический раздел.

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

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

Если предположить, что в среднем мою неизвестную игру будут приобретать около 7 человек в месяц. За год игру приобретут 7*12 = 84 человека.

Посчитаем рентабельность по формуле:

Pr / TC = ROE (прибыль / затраты = рентабельность)

При стоимости 200 рублей посчитаем прибыль: 200 * 84 = 16 800 рублей.

Затраты на разработку компьютерной игры составили 1028 рублей 72 копейки. В затраты входит: амортизация компьютерной техники и оплата электроэнергии.

Рассчитаем:

Амортизация 37000/5/12=616 рублей

Электроэнергия 1,1 кВт – 5 часов; 10 часов в неделю = 2,2 кВт; Потребление за период создания проекта 4*2,2*7=61,6 кВт.

Стоимость 1 кВт = 6,7 рублей.

6,7*61,6=412,72 рублей

412,72+616=1028,72

16800/1028,72=16,3

Рентабельность = 16,3

4. Заключение.

В результате проделанной работы, была успешно разработана и протестирована компьютерная игра “FastRunner”, в основе которой используется алгоритм поиска в ширину в графе, который активно используется в таких приложениях как “Яндекс.Навигатор”.

Я освоил навыки разработки программного обеспеченения с нуля и до работающего прототипа. Также, я улучшил не только свои навыки программирования, но и работы с графикой, многократно используя программу “Photoshop” для создания и редактирования картинок, а также анимаций.

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

Список литературы и Интернет-ресурсов:

More than 1.2 billion people are playing games [Электронный ресурс] / Dean Takahashi // GamesBeat. – Режим доступа: http://venturebeat.com/2013/11/25/more-than-1-2-billion-people-areplaying-games/ (дата обращения 20.05.2016) – яз. англ.

Supreme Court sees video games as art [Электронный ресурс] / John D. Sutter // CNN.com. – Режим доступа: http://edition.cnn.com/2011/TECH/gaming.gadgets/06/27/supreme.court.vid eo.game.art/ (дата обращения 20.05.2016) – яз. англ.

Пишем игровой движок [Электронный ресурс] // Хабрахабр. – Режим доступа: https://habrahabr.ru/post/102930/ (дата обращения 20.05.2016) – яз. рус.

Unreal Engine FAQ [Электронный ресурс] // Epic Games : Часто задаваемые вопросы и ответы по движку Unreal Engine 4. – Режим доступа: https://www.unrealengine.com/faq (дата обращения 20.05.2016) – яз. англ.

https://sibsutis.ru/upload/abe/Diplom_Provotorov_Public.pdf

https://урок.рф/library_kids/proekt_sozdanie_kompyuternoj_igri_135845.html

https://www.dissercat.com/content/kompyuternye-virtualnye-igry

 

 

 

 

 

Приложение 1.

Рисунок 1. Общая схема программы.

Приложение 2

Рисунок 2. Исходный код функции drawMonster().

Приложение 3

Рисунок 3. Исходный код функции wall().

 

 

 

Приложение 4.

Рисунок 4. Исходный код функции drawSkeleton().

Приложение 5

Рисунок 5. Исходный код функции showMenu().

Приложение 6

Рисунок 6. Исходный код функции fillMap().

Приложение 7

Рисунок 7. Исходный код функции setMapNumber().

 

Приложение 8

Рисунок 8. Исходный код функции smartMoveSkeleton().

 

 

 

 

 

 

 

Приложение 9

 

1

4

2

3

 

Рисунок 9. Меню компьютерной игры.

 

Приложение 10

Рисунок 9. Поле создания карты.

Приложение 11

Рисунок 10. Кирпичная стена.

Приложение 12

Рисунок 11. Линия кирпичей.

Приложение 13

Рисунок 12. Карта.

 

Приложение 14

Рисунок 13. Главный герой.

Приложение 15

Рисунок 14. Главный злодей.

Приложение 16

Стена

Главный злодей

 

Главный герой

Игровое поле

Рисунок 15 Игровое поле.

 

 

 

 

 

 

 

 

 

 

Приложение 17

Рисунок 16. Тестирование.

 

Приложение 18

Рисунок 17. Экран завершения игры.

Приложение 19.

 

Программный код компьютерной игры.

#include <iostream>

#include <string>

#include <queue>

#include "graphics.h"

#include "myGraffics.h"

#include "tools.h"

#include "move.h"

#include <ctime>

#include <fstream>

#define UPARROW 72

#define RIGHTARROW 77

#define DOWNARROW 80

#define LEFTARROW 75

#define SPACEBAR 32

#define WALL 1

#define BACKSPACE 8

#define PAUSE 32

#define CELLSIZE 60

#define UP 1

#define RIGHT 2

#define DOWN 3

#define LEFT 4

#define CELLNUMBER 15

#define SKELETON 8

#define MONSTER 9

enum modes { MENU, GAME, EDITOR, WIN, LOSE };

int direction = RIGHT;

int directionSkeleton = RIGHT;

int monRow = 0, monCol = 0;

bool monsterColour = false;

int mode = MENU;

int row1Line, col1Line, row2Line, col2Line;

int flag = 0;

bool sFlag = false;

int editMap[CELLNUMBER][CELLNUMBER] = { 0 };

int map[CELLNUMBER][CELLNUMBER] = { 0 };

int mapNumber = 1;

int speed = 150;

int skeletonRow = 0, skeletonCol = 0;

int flagSkeletonMove = 0;

int timerEndScreen = 0;

int k = 0;

using namespace std;

struct Point

{

int row, col;

};

template<typename T, int height, int width>

std::ostream& writemap(std::ostream& os, T(&map)[height][width])

{

for (int i = 0; i < height; ++i)

{

for (int j = 0; j < width; ++j)

{

os << map[i][j] << " ";

}

os << "\n";

}

return os;

}

bool fexists(const char* filename)

{

ifstream ifile(filename);

return (bool)ifile;

}

// ?????? ????? + 1 ? mapNumber(? ????? ??????? ?????)

void setMapNumber() {

char str[10];

while (1) {

sprintf(str, "map%d.txt", mapNumber);

if (fexists(str)) {

mapNumber++;

}

else {

break;

}

}

}

void drawSkeleton(int row, int col) {

int x1, y1, x2, y2;

x1 = (col * CELLSIZE) + 81;

y1 = (row * CELLSIZE) + 81;

switch (directionSkeleton) {

case UP:

readimagefile("skeletonUp.jpg", x1, y1, x1 + CELLSIZE - 2, y1 + CELLSIZE - 2);

break;

case RIGHT:

readimagefile("skeletonRight.jpg", x1, y1, x1 + CELLSIZE - 2, y1 + CELLSIZE - 2);

break;

case DOWN:

readimagefile("skeletonDown.jpg", x1, y1, x1 + CELLSIZE - 2, y1 + CELLSIZE - 2);

break;

case LEFT:

readimagefile("skeletonLeft.jpg", x1, y1, x1 + CELLSIZE - 2, y1 + CELLSIZE - 2);

break;

}

}

void drawMonster(int row, int col) {

int x1, y1;

x1 = (col * CELLSIZE) + 81;

y1 = (row * CELLSIZE) + 81;

switch (direction) {

case UP:

readimagefile("monsterUp.jpg", x1, y1, x1 + CELLSIZE - 2, y1 + CELLSIZE - 2);

break;

case RIGHT:

readimagefile("monsterRight.jpg", x1, y1, x1 + CELLSIZE - 2, y1 + CELLSIZE - 2);

break;

case DOWN:

readimagefile("monsterDown.jpg", x1, y1, x1 + CELLSIZE - 2, y1 + CELLSIZE - 2);

break;

case LEFT:

readimagefile("monsterLeft.jpg", x1, y1, x1 + CELLSIZE - 2, y1 + CELLSIZE - 2);

break;

}

}

void showMenu() {

setfillstyle(1, LIGHTGRAY);

bar(0, 0, 1600, 1000);

settextstyle(0, 0, 20);

setcolor(CYAN);

setbkcolor(LIGHTGRAY);

outtextxy(550, 100, "FASTRUNNER");

setcolor(BLACK);

settextstyle(0, 0, 17);

outtextxy(550, 250, "START GAME");

outtextxy(550, 350, "MAP CREATOR");

outtextxy(550, 450, "EXIT");

}

void showEditMap() {

for (int i = 0; i < CELLNUMBER; i++) {

for (int j = 0; j < CELLNUMBER; j++) {

cout << editMap[i][j] << " ";

}

cout << endl;

}

cout << endl << endl;

}

void showMap() {

setfillstyle(1, LIGHTGRAY);

bar(0, 0, 1600, 1000);

for (int i = 0; i < CELLNUMBER + 1; i++) {

line(80 + (i * CELLSIZE), 80, 80 + (i * CELLSIZE), 80 + CELLNUMBER * CELLSIZE);

line(80, 80 + (i * CELLSIZE), 80 + CELLNUMBER * CELLSIZE, 80 + (i * CELLSIZE));

}

}

//?????? ??????????? ?????????

void wall(int row, int col) {

int x1, y1, x2, y2;

x1 = (col * CELLSIZE) + 81;

y1 = (row * CELLSIZE) + 81;

readimagefile("brickWall.jpg", x1, y1, x1 + CELLSIZE - 2, y1 + CELLSIZE - 2);

}

void lightGrayWall(int row, int col) {

int x1, y1, x2, y2;

x1 = (col * CELLSIZE) + 81;

y1 = (row * CELLSIZE) + 81;

setfillstyle(1, LIGHTGRAY);

bar(x1, y1, x1 + CELLSIZE - 1, y1 + CELLSIZE - 1);

}

//?????? ????? ?? ???????

void drawLine(int row1, int col1, int row2, int col2) {

if (row2 > row1 && col2 == col1) {

for (int i = row1; i <= row2; i++) {

wall(i, col1);

editMap[i][col1] = 1;

}

}

else if (row2 < row1 && col2 == col1) {

for (int i = row2; i <= row1; i++) {

wall(i, col1);

editMap[i][col1] = 1;

}

}

else if (col2 > col1 && row2 == row1) {

for (int i = col1; i <= col2; i++) {

wall(row1, i);

editMap[row1][i] = 1;

}

}

else if (col2 < col1 && row2 == row1) {

for (int i = col2; i <= col1; i++) {

wall(row1, i);

editMap[row1][i] = 1;

}

}

else if (row2 + col2 == row1 + col1) {

if (row1 > row2) {

swap(row1, row2);

swap(col1, col2);

}

for (int i = row1, j = col1; i <= row2; i++, j--) {

wall(i, j);

editMap[i][j] = 1;

}

}

else if (col1 - row1 == col2 - row2) {

if (row1 > row2) {

swap(row1, row2);

swap(col1, col2);

}

for (int i = row1, j = col1; i <= row2; i++, j++) {

wall(i, j);

editMap[i][j] = 1;

}

}

}

void redMonsterAnimate(int st) {

cout << "I am inside redMonsterAnimate " << direction;

int x1, y1;

x1 = (monCol * CELLSIZE) + 81;

y1 = (monRow * CELLSIZE) + 81;

switch (direction) {

case UP:

if (monsterColour == false && st == 4) {

readimagefile("monsterUpRed.jpg", x1, y1, x1 + CELLSIZE - 2, y1 + CELLSIZE - 2);

monsterColour = true;

}

else {

readimagefile("monsterUp.jpg", x1, y1, x1 + CELLSIZE - 2, y1 + CELLSIZE - 2);

monsterColour = false;

}

break;

case RIGHT:

if (monsterColour == false && st == 3) {

readimagefile("monsterRightRed.jpg", x1, y1, x1 + CELLSIZE - 2, y1 + CELLSIZE - 2);

monsterColour = true;

}

else {

readimagefile("monsterRight.jpg", x1, y1, x1 + CELLSIZE - 2, y1 + CELLSIZE - 2);

monsterColour = false;

}

break;

case DOWN:

if (monsterColour == false && st == 4) {

readimagefile("monsterDownRed.jpg", x1, y1, x1 + CELLSIZE - 2, y1 + CELLSIZE - 2);

monsterColour = true;

}

else {

readimagefile("monsterDown.jpg", x1, y1, x1 + CELLSIZE - 2, y1 + CELLSIZE - 2);

monsterColour = false;

}

break;

case LEFT:

if (monsterColour == false && st == 3) {

readimagefile("monsterLeftRed.jpg", x1, y1, x1 + CELLSIZE - 2, y1 + CELLSIZE - 2);

monsterColour = true;

}

else {

readimagefile("monsterLeft.jpg", x1, y1, x1 + CELLSIZE - 2, y1 + CELLSIZE - 2);

monsterColour = false;

}

break;

}

}

void fill2DArrayFromTxt() {

srand(time(0));

ifstream file("map" + to_string((rand() % (mapNumber - 1)) + 1) + ".txt");

if (!file.is_open())return;

for (int i = 0; i < CELLNUMBER; ++i) {

for (int j = 0; j < CELLNUMBER; ++j) {

file >> map[i][j];

}

}

int x = 0;

}

void fillMap() {

for (int i = 0; i < CELLNUMBER; i++) {

for (int j = 0; j < CELLNUMBER; j++) {

if (map[i][j] == 1) {

wall(i, j);

}

if (map[i][j] == SKELETON) {

drawSkeleton(i, j);

skeletonRow = i;

skeletonCol = j;

}

}

}

}

void initialMonsterPlace() {

for (int i = 0; i < CELLNUMBER; i++) {

for (int j = 0; j < CELLNUMBER; j++) {

if (map[i][j] == 9) {

monRow = i;

monCol = j;

return;

}

}

}

}

//?????????? ?????

void clickChecker() {

if (ismouseclick(WM_LBUTTONDOWN)) {

int x, y;

getmouseclick(WM_LBUTTONDOWN, x, y);

cout << x << " " << y << endl;

if (mode == MENU) {

//MAP CREATOR CLICK

if (x >= 545 && x <= 940 && y >= 352 && y <= 390) {

mode = EDITOR;

showMap();

}

else if (x >= 545 && x <= 940 && y >= 250 && y <= 300) {

mode = GAME;

showMap();

fill2DArrayFromTxt();

fillMap();

initialMonsterPlace();

drawMonster(monRow, monCol);

getch();

}else if ( x >= 545 && x <= 940 && y >= 454 && y <= 480) {

k = 1;

}

}

else if (mode == EDITOR) {

int row, col;

row = (y - 80) / CELLSIZE;

col = (x - 80) / CELLSIZE;

if ((row <= CELLSIZE - 1 && row >= 0) && (col <= CELLSIZE - 1 && col >= 0)) {

wall(row, col);

editMap[row][col] = 1;

}

}

showEditMap();

}

else if (ismouseclick(WM_RBUTTONDOWN)) {

int x, y;

getmouseclick(WM_RBUTTONDOWN, x, y);

if (mode == EDITOR) {

if (flag == 0) {

cout << "startLine";

row1Line = (y - 80) / CELLSIZE;

col1Line = (x - 80) / CELLSIZE;

wall(row1Line, col1Line);

editMap[row1Line][col1Line] = 1;

flag = 1;

}

else {

cout << "EndLine";

row2Line = (y - 80) / CELLSIZE;

col2Line = (x - 80) / CELLSIZE;

drawLine(row1Line, col1Line, row2Line, col2Line);

flag = 0;

}

showEditMap();

}

}

else if (ismouseclick(WM_MBUTTONDOWN)) {

int x, y;

getmouseclick(WM_MBUTTONDOWN, x, y);

cout << x << y;

if (mode == EDITOR) {

if (!sFlag) {

drawMonster((y - 80) / CELLSIZE, (x - 80) / CELLSIZE);

editMap[(y - 80) / 60][(x - 80) / 60] = 9;

sFlag = true;

}

else {

drawSkeleton((y - 80) / CELLSIZE, (x - 80) / CELLSIZE);

editMap[(y - 80) / 60][(x - 80) / 60] = 8;

}

}

}

}

void showPath(Point** paths) {

for (int i = 0; i < CELLNUMBER; i++) {

for (int j = 0; j < CELLNUMBER; j++) {

//cout << paths[i][j].row << "," << paths[i][j].col << "\t";

}

//cout << endl;

}

}

Point getNextStep() {

Point paths[CELLNUMBER][CELLNUMBER];

queue <Point> q;

for (int i = 0; i < CELLNUMBER; i++) {

for (int j = 0; j < CELLNUMBER; j++) {

paths[i][j].row = paths[i][j].col = -1;

}

}

q.push({ skeletonRow , skeletonCol });

paths[q.front().row][q.front().col] = q.front();

Point currentCell;

bool isMonsterFound = false;

while (!q.empty()) {

currentCell = q.front();

//???????? ?? ??????? ? ?????

if (currentCell.row != 0 && map[currentCell.row - 1][currentCell.col] != 1 && paths[currentCell.row - 1][currentCell.col].row == -1) {

//??????? ?????? ???????? ??? ????????? ???????? ? paths ?????? ? ??? ??????

paths[currentCell.row - 1][currentCell.col] = currentCell;

//????????? ? queue ??????? ?????

if (map[currentCell.row - 1][currentCell.col] != 9) {

q.push({ currentCell.row - 1, currentCell.col });

}

else {

isMonsterFound = true;

currentCell.row--;

break;

}

}

if (currentCell.col != CELLNUMBER - 1 && map[currentCell.row][currentCell.col + 1] != 1 && paths[currentCell.row][currentCell.col + 1].row == -1) {

paths[currentCell.row][currentCell.col + 1] = currentCell;

if (map[currentCell.row][currentCell.col + 1] != 9) {

q.push({ currentCell.row, currentCell.col + 1 });

}

else {

isMonsterFound = true;

currentCell.col++;

break;

}

}

if (currentCell.col != 0 && map[currentCell.row][currentCell.col - 1] != 1 && paths[currentCell.row][currentCell.col - 1].row == -1) {

paths[currentCell.row][currentCell.col - 1] = currentCell;

if (map[currentCell.row][currentCell.col - 1] != 9) {

q.push({ currentCell.row, currentCell.col - 1 });

}

else {

isMonsterFound = true;

currentCell.col--;

break;

}

}

if (currentCell.row != CELLNUMBER - 1 && map[currentCell.row + 1][currentCell.col] != 1 && paths[currentCell.row + 1][currentCell.col].row == -1) {

paths[currentCell.row + 1][currentCell.col] = currentCell;

if (map[currentCell.row + 1][currentCell.col] != 9) {

q.push({ currentCell.row + 1, currentCell.col });

}

else {

isMonsterFound = true;

currentCell.row++;

break;

}

}

q.pop();

}

cout << "START: ";

//????????????? ????

while (map[paths[currentCell.row][currentCell.col].row][paths[currentCell.row][currentCell.col].col] != 8) {

cout << currentCell.row << "," << currentCell.col << "; ";

Point tmp = currentCell;

currentCell.row = paths[tmp.row][tmp.col].row;

currentCell.col = paths[tmp.row][tmp.col].col;

}

cout << "END" << endl;

return currentCell;

}

void smartMoveSkeleton() {

Point tmp = getNextStep();

map[skeletonRow][skeletonCol] = 0;

lightGrayWall(skeletonRow, skeletonCol);

//?????????? ? ????? ??????? ?????? ???????? ??????

if (skeletonRow < tmp.row) {

directionSkeleton = DOWN;

}

else if (skeletonRow > tmp.row) {

directionSkeleton = UP;

}

else if (skeletonCol > tmp.col) {

directionSkeleton = LEFT;

}

else if (skeletonCol < tmp.col) {

directionSkeleton = RIGHT;

}

skeletonRow = tmp.row;

skeletonCol = tmp.col;

drawSkeleton(skeletonRow, skeletonCol);

map[skeletonRow][skeletonCol] = 8;

cout << skeletonRow << ";" << skeletonCol << endl;

}

void moveSkeleton() {

if (mode != GAME) {

return;

}

lightGrayWall(skeletonRow, skeletonCol);

map[skeletonRow][skeletonCol] = 0;

if (directionSkeleton == RIGHT && skeletonCol < CELLNUMBER - 1 && map[skeletonRow][skeletonCol + 1] == 0) {

drawSkeleton(skeletonRow, ++skeletonCol);

}

else if (directionSkeleton == RIGHT && (skeletonCol == CELLNUMBER - 1 || map[skeletonRow][skeletonCol + 1] != 0)) {

directionSkeleton = LEFT;

drawSkeleton(skeletonRow, --skeletonCol);

}

else if (directionSkeleton == LEFT && skeletonCol > 0 && map[skeletonRow][skeletonCol - 1] == 0) {

drawSkeleton(skeletonRow, --skeletonCol);

}

else if (directionSkeleton == LEFT && (map[skeletonRow][skeletonCol - 1] == 0 || map[skeletonRow][skeletonCol - 1] != 0)) {

directionSkeleton = RIGHT;

drawSkeleton(skeletonRow, ++skeletonCol);

}

else {

cout << skeletonCol << " ";

cout << directionSkeleton << endl;

}

map[skeletonRow][skeletonCol] = 8;

}

void showMapToConsole() {

for (int i = 0; i < CELLNUMBER; i++) {

for (int j = 0; j < CELLNUMBER; j++) {

cout << map[i][j] << " ";

}

cout << endl;

}

}

bool gameOver() {//???????????? ???????

return (skeletonRow != 0 && map[skeletonRow - 1][skeletonCol] == 9) ||

(skeletonCol != 0 && map[skeletonRow][skeletonCol - 1] == 9) ||

(skeletonCol != 4 && map[skeletonRow][skeletonCol + 1] == 9) ||

(skeletonRow != 4 && map[skeletonRow + 1][skeletonCol] == 9);

}

void gameOverCheck() {

//?????? ?????, ? ?????? ??????

if (skeletonRow == monRow && skeletonCol + 1 == monCol) {

showMapToConsole();

cout << "GAME OVER";

int st = 1;

while (1) {

int x1, y1;

x1 = (skeletonCol * CELLSIZE) + 81;

y1 = (skeletonRow * CELLSIZE) + 81;

if (st == 1) {

lightGrayWall(skeletonRow, skeletonCol);

readimagefile("skeletonRight.jpg", x1, y1, x1 + CELLSIZE - 2, y1 + CELLSIZE - 2);

st = 2;

}

else if (st == 2) {

lightGrayWall(skeletonRow, skeletonCol);

readimagefile("skeletonAttackRight1.jpg", x1, y1, x1 + CELLSIZE - 2, y1 + CELLSIZE - 2);

st = 3;

}

else if (st == 3) {

lightGrayWall(skeletonRow, skeletonCol);

readimagefile("skeletonAttackRight2.jpg", x1, y1, x1 + CELLSIZE - 2, y1 + CELLSIZE - 2);

st = 1;

}

redMonsterAnimate(st);

delay(281);

timerEndScreen++;

if (timerEndScreen == 10) {

mode = LOSE;

break;

}

}

//?????? ??????, ? ?????? ?????

}

else if (skeletonRow == monRow && skeletonCol - 1 == monCol) {

showMapToConsole();

cout << "GAME OVER";

int st = 1;

while (1) {

int x1, y1;

x1 = (skeletonCol * CELLSIZE) + 81;

y1 = (skeletonRow * CELLSIZE) + 81;

if (st == 1) {

lightGrayWall(skeletonRow, skeletonCol);

readimagefile("skeletonLeft.jpg", x1, y1, x1 + CELLSIZE - 2, y1 + CELLSIZE - 2);

st = 2;

}

else if (st == 2) {

lightGrayWall(skeletonRow, skeletonCol);

readimagefile("skeletonAttackLeft1.jpg", x1, y1, x1 + CELLSIZE - 2, y1 + CELLSIZE - 2);

st = 3;

}

else if (st == 3) {

lightGrayWall(skeletonRow, skeletonCol);

readimagefile("skeletonAttackLeft2.jpg", x1, y1, x1 + CELLSIZE - 2, y1 + CELLSIZE - 2);

st = 1;

}

redMonsterAnimate(st);

delay(281);

timerEndScreen++;

if (timerEndScreen == 10) {

mode = LOSE;

break;

}

}

}

//?????? ??????, ? ?????? ?????

else if (skeletonCol == monCol && skeletonRow + 1 == monRow) {

showMapToConsole();

cout << "GAME OVER";

int st = 1;

while (1) {

int x1, y1;

x1 = (skeletonCol * CELLSIZE) + 81;

y1 = (skeletonRow * CELLSIZE) + 81;

if (st == 1) {

lightGrayWall(skeletonRow, skeletonCol);

readimagefile("skeletonDown.jpg", x1, y1, x1 + CELLSIZE - 2, y1 + CELLSIZE - 2);

st = 2;

}

else if (st == 2) {

lightGrayWall(skeletonRow, skeletonCol);

readimagefile("skeletonAttackDown1.jpg", x1, y1, x1 + CELLSIZE - 2, y1 + CELLSIZE - 2);

st = 3;

}

else if (st == 3) {

lightGrayWall(skeletonRow, skeletonCol);

readimagefile("skeletonAttackDown2.jpg", x1, y1, x1 + CELLSIZE - 2, y1 + CELLSIZE - 2);

st = 4;

}

else if (st == 4) {

lightGrayWall(skeletonRow, skeletonCol);

readimagefile("skeletonAttackDown3.jpg", x1, y1, x1 + CELLSIZE - 2, y1 + CELLSIZE - 2);

st = 1;

}

redMonsterAnimate(st);

delay(225);

timerEndScreen++;

if (timerEndScreen == 13) {

mode = LOSE;

break;

}

}

}

//?????? ?????, ? ?????? ??????

else if (skeletonCol == monCol && skeletonRow - 1 == monRow) {

showMapToConsole();

cout << "GAME OVER";

int st = 1;

while (1) {

int x1, y1;

x1 = (skeletonCol * CELLSIZE) + 81;

y1 = (skeletonRow * CELLSIZE) + 81;

if (st == 1) {

lightGrayWall(skeletonRow, skeletonCol);

readimagefile("skeletonUp.jpg", x1, y1, x1 + CELLSIZE - 2, y1 + CELLSIZE - 2);

st = 2;

}

else if (st == 2) {

lightGrayWall(skeletonRow, skeletonCol);

readimagefile("skeletonAttackUp1.jpg", x1, y1, x1 + CELLSIZE - 2, y1 + CELLSIZE - 2);

st = 3;

}

else if (st == 3) {

lightGrayWall(skeletonRow, skeletonCol);

readimagefile("skeletonAttackUp2.jpg", x1, y1, x1 + CELLSIZE - 2, y1 + CELLSIZE - 2);

st = 4;

}

else if (st == 4) {

lightGrayWall(skeletonRow, skeletonCol);

readimagefile("skeletonAttackUp3.jpg", x1, y1, x1 + CELLSIZE - 2, y1 + CELLSIZE - 2);

st = 1;

}

redMonsterAnimate(st);

delay(225);

timerEndScreen++;

if (timerEndScreen == 13) {

mode = LOSE;

break;

}

}

}

}

int main()

{

initwindow(1600, 1000, "SPRINTER");

setMapNumber();

showMenu();

while (1) {

int t = time(0);

clickChecker();

if (k == 1) {

return 0;

}

if (kbhit()) {

int code = getch();

if (code == 0) {

code = getch();

}

while (kbhit()) {

getch();

}

if (mode == EDITOR) {

if (code == SPACEBAR) {

cout << "MAP SAVING" << endl;

std::fstream of("map" + to_string(mapNumber) + ".txt", std::ios::out | std::ios::app);

mapNumber++;

cout << mapNumber;

if (of.is_open())

{

writemap(of, editMap);

writemap(std::cout, editMap);

of.close();

}

}

else if (code == BACKSPACE) {

mode = MENU;

showMenu();

}

}

else if (mode == GAME) {

if (code == RIGHTARROW) {

direction = RIGHT;

}

else if (code == UPARROW) {

direction = UP;

}

else if (code == LEFTARROW) {

direction = LEFT;

}

else if (code == DOWNARROW) {

direction = DOWN;

}

if (code == BACKSPACE) {

mode = MENU;

showMenu();

}

if (code == PAUSE) {

setcolor(RED);

outtextxy(1000, 100, "PAUSE");

getch();

setcolor(LIGHTGRAY);

outtextxy(1000, 100, "PAUSE");

setcolor(RED);

}

}

if (code == 27) {

return 0;

}

}

if (mode == GAME) {

//???????? ??????

if (direction == RIGHT && monCol < CELLNUMBER - 1 && map[monRow][monCol + 1] == 0) {

lightGrayWall(monRow, monCol);

map[monRow][monCol] = 0;

monCol++;

map[monRow][monCol] = 9;

drawMonster(monRow, monCol);

}

else if (direction == UP && monRow > 0 && map[monRow - 1][monCol] == 0) {

lightGrayWall(monRow, monCol);

map[monRow][monCol] = 0;

monRow--;

map[monRow][monCol] = 9;

drawMonster(monRow, monCol);

}

else if (direction == LEFT && monCol > 0 && map[monRow][monCol - 1] == 0) {

lightGrayWall(monRow, monCol);

map[monRow][monCol] = 0;

monCol--;

map[monRow][monCol] = 9;

drawMonster(monRow, monCol);

}

else if (direction == DOWN && monRow < CELLNUMBER - 1 && map[monRow + 1][monCol] == 0) {

lightGrayWall(monRow, monCol);

map[monRow][monCol] = 0;

monRow++;

map[monRow][monCol] = 9;

drawMonster(monRow, monCol);

}

}

if (mode == GAME && flagSkeletonMove == 1) {

gameOverCheck();

smartMoveSkeleton();

}

if (mode == LOSE) {

setfillstyle(1, BLACK);

bar(0, 0, 1600, 1000);

readimagefile("GAMEOVER.jpg", 50, 75, 1550, 225);

int st = 1;

while (1) {

if (st == 1) {

readimagefile("Skull1.jpg", 631, 295, 968, 815);

st = 2;

}

else if (st == 2) {

readimagefile("Skull2.jpg", 631, 295, 968, 815);

st = 3;

}

else if (st == 3) {

readimagefile("Skull3.jpg", 631, 295, 968, 815);

st = 1;

}

delay(350);

}

}

flagSkeletonMove = (flagSkeletonMove == 1) ? 0 : 1;

int gameTime = time(0) - t;

delay(speed);

}

return 0;

}

Просмотров работы: 87