Как создать змейку на c в visual studio
Не так давно мы разбирали, как искусственный интеллект учится играть в змейку. А теперь мы сами сделаем такую игру, чтобы ей могли насладиться обычные люди. Что нам понадобится:
- HTML, чтобы можно было играть прямо в браузере;
- CSS для украшений;
- JavaScript для самой игры.
Логика игры
У классической змейки правила простые:
- есть поле из клеточек, где случайным образом появляется еда;
- есть змейка, которая всё время двигается и которой мы можем управлять;
- если змейка на своём пути встречает еду — еда исчезает, появляется в новом месте, а сама змейка удлиняется на одну клеточку;
- если змейка врежется в стену или в саму себя, игра заканчивается.
Чтобы играть было проще, мы сделаем так, чтобы змейка не врезалась в стенки, а проходила сквозь них. Если что — сможете это сами потом настроить в коде, когда захотите посложнее.
Последовательность наших действий будет такой:
- Делаем пустую HTML-страницу.
- Настраиваем внешний вид с помощью CSS.
- Рисуем игровое поле.
- Пишем скрипт, который и будет отвечать за всю игру.
Делаем HTML-страницу
С этим всё просто: берём стандартный код и сохраняем его как файл snake.html .
Это даст нам пустую страницу, которую мы сейчас немного настроим стилями.
Настраиваем внешний вид
За внешний вид на странице у нас отвечает раздел <style> , поэтому мы просто добавим в него CSS-код:
Теперь у нас на странице нет лишних отступов, зато всё по центру, есть чёрный фон и граница вокруг игрового поля. Самое время создать само игровое поле.
Рисуем игровое поле
Поле делается очень просто:
<canvas width="400" height="400"></canvas>
400 пикселей в ширину, столько же в высоту, название поля — game. Этого достаточно, чтобы браузер отобразил холст с такими размерами и позволил нам на нём рисовать.
Пишем скрипт
1. Зададим все переменные, которые нам понадобятся.
2. Сделаем генератор случайных чисел. Он нам понадобится, чтобы размещать еду на поле случайным образом.
// Делаем генератор случайных чисел в заданном диапазоне
3. Напишем основной игровой цикл, который будет работать бесконечно.
4. Сделаем управление стрелочками на клавиатуре.
5. Запускаем игру. Для этого достаточно запустить предыдущий бесконечный цикл, поэтому пишем:
6. Наслаждаемся результатом:
Чтобы у вас тоже получилось такое, просто скопируйте готовый код, сохраните его как HTML-файл и откройте в браузере.Как улучшить
Этот код — самая простая реализация змейки, и игру можно сделать ещё лучше:
- выводить количество набранных очков;
- сделать так, чтобы нельзя было проходить сквозь стены;
- добавить препятствия;
- поставить таймер — кто больше соберёт еды за 5 минут;
- добавить вторую змейку и играть вдвоём.
Проголосуйте за тот вариант, который вам больше всего нравится, в комментариях, или сделайте свою змейку, где всё это будет одновременно.
Учиться легче, когда вы имеете дело с играми. Создав несложную игру на любом языке программирования, вы получите отличный опыт, который пригодится вам в будущем, если вы собираетесь стать программистом.
Для создания вам необходимы базовые знания языка. Однако, даже если вы имеете мало опыта программирования на C++, я постараюсь подробно описать основные моменты разработки.
Для работы с C++ вам нужно скачать Visual Studio - это среда , разработанная компанией Microsoft.
После скачивания вы создаете пустой проект и начинаете писать программный код , в котором вы заложите отрисовку , логику, управление нашей Змейки.
Сразу скажу, что на создание даже такой простой игры как Змейка у малоопытного программиста может уйти немало времени , но по завершении создания вы сможете неплохо продвинуться в этой сфере и , возможно , полюбить программирование . Сам процесс создания я буду излагать в нескольких статьях на моем канале.
ЧАСТЬ 1:
Окей, для начала подключим к нашей программе основную библиотеку для ввода и вывода ( Вывод осуществляется, как правило, с помощью перегруженного оператора сдвига влево (<<), а ввод – с помощью оператора сдвига вправо (>>)) Это основная библиотека, только благодаря в дальнейшем мы сможем отрисовать карту для игры, саму змейку и фрукты, появляющиеся на карте.
Дальше мы создаем основные функции. Отдельные функции будут отвечать за логику, отрисовку и управление игры. Название каждой функции будет говорить за ее назначение в программе.
Так, функция Setup (англ.установить) будет содержать в себе переменные, благодаря которым мы зададим змейке начальное положение и настроим случайное появление фрукта на карте.
Функция Draw (англ.рисовать) будет отвечать за отрисовку карты, змейки и фрукта в консоли с ее постоянным обновлением(анимацией).
Функция Input (англ.ввод) будет отвечать за управление нашей змейкой, которое заключается в назначении клавиш, отвечающих за перемещение.
Функция Logic (англ.логика) будет содержать переменные, описывающие логику игры (конец игры, если змейка врежется в стенку, увеличение длины тела при поедании фрукта и т.п.)
Ну и основная функция main будет включать в себя все функции для отображения игры на консоли до тех пор пока игра не завершена.
Как вы уже заметили мы ввели логическую переменную GameOver (конец игры), пока она принимает значения False(ложь) игра будет продолжаться, поэтому все функции будут выполняться.
Теперь нам надо заполнить каждую функцию, чтобы они имели смысл. Но перед тем, как заполнять одну из функций введем глобальные переменные:
const int width (ширина ) и (height) можно поставить любые значения, эти константы зададут размер игрового поля для змейки. При тесте игры мы сможем в любой момент изменить эти параметры не меняя логику и целостность игры.
Параметры x,y будут отвечать за положение змейки на горизонтали и вертикали ( т.к. змейка в двухмерном пространстве(на плоскости)) , аналогично параметры fruitX и fruitY будут отвечать за положение фрукта.
Дальше идет перечисление enum (англ. enumeration) / с помощью перечислений мы присвоим параметрам Stop, LEFT,RIGHT,UP,DOWN целые значения ( первому параметру Stop присвоится 0, второму LEFT - 1, и т.д. DOWN - 4)
Переходим в функцию Setup.
Изначально игра не окончена, поэтому GameOver = false; , иначе при запуске консоли программа завершит процесс выполнения. eDirection или dir (направление при запуске игры будет 0, то есть змейка будет стоять на месте, 0 в перечислениях принимал параметр Stop. x,y отвечают за положение змейки, при делении высоты и ширины пополам мы получим, что змейка будет находиться в центре игрового поля при запуске. Положение фрукта же случайно , поэтому приплетаем функцию rand() с остатком от деления на высоту вертикальной составляющей и с остатком от деления на ширину горизонтальной составляющей, так фрукт будет появляться в случайном месте на карте. Score( счетчик очков ) изначально равен нулю. Тут все ясно.
Сразу перейдем к функции Draw для отрисовки карты и объектов на консоли. Пожалуй, это самая сложная и интересная функция. Для того, чтобы картинка стала обновляться и создалась анимация движения нам необходимо прописать команду system("cls") ; Дальше заполняем первую верхнюю строчку(верхний край) нашего игрового поля. Это можно реализовать с помощью цикла for. Верхний край заполнится дефисами и будет символизировать стенку шириной width. По окончании цикла переходим на вторую строку консоли с помощью cout<<endl;
Дальше нам необходимо отрисовать остальную часть поля, использую уже вложенный цикл ( for вложен в for ) вложенный цикл отрисует змейку( решетка ), фрукт ( F ), и левую и правую стенку ( | ). Суть в том, что в каждой строке левая стенка находится на первой позиции(нулевой элемент строки), а правая на позиции ширина - 1(элемент равный ширине строки).
На позиции x,y находится змейка , рисуем любой элемент, я использовал решетку, на позиции fruitX,fruitY рисуем фрукт , я использовал букву F . Строки №40-45 отрисуют нижнюю границу поля, аналогично верхней, только ниже на расстоянии height (высоты).
На этом этапе, если вы все правильно усвоили и безошибочно написали вы увидите следующую картинку в консоли:
Как человеку, выросшему во времена дискет и 56 Кбит модемов, мне всегда нравились небольшие программы. Я мог поместить много небольших программ на дискету, которую носил с собой. Если программа не помещалась на моем гибком диске, я начинал думать, почему: много графики? Музыка? Программа сложная или просто раздулась?
В наши дни дисковое пространство стало настолько дешевым (а огромные флешки настолько вездесущими), что люди отказались от оптимизации размера.
Единственная область, где размер еще имеет значение — это передача: при передаче программы по проводу мегабайты приравниваются к секундам. Быстрое соединение на 100 Мбит может пропускать только 12 мегабайт в секунду в лучшем случае. Когда на другом конце провода находится человек, ожидающий завершения загрузки, разница между пятью и одной секундами может оказать существенное влияние на восприятие. Человек может зависеть от времени передачи либо напрямую: он загружает программу по сети, либо косвенно — бессерверная служба развертывается для ответа на веб-запрос.
Люди обычно воспринимают что-то быстрее 0,1 секунды как мгновенное. 3 секунды — примерно предел непрерывности потока пользователя, и вам было бы трудно удержать пользователя после 10 секунд.
Хотя меньший размер больше не является существенным, он всё равно лучше.
Что такое автономность?
Игра меньше 8 Кб
Мы создадим клон змейки:
Не интересна игровая механика? Не стесняйтесь переходить к интересным частям, где мы сжимаем игру с 65 мегабайт до 8 килобайт за 9 шагов, прокрутите до графика.
Игра будет работать в текстовом режиме, и мы используем поле рисования символов, чтобы нарисовать змею. Я уверен, что Vulcan или DirectX намного веселее, но мы справимся и с System.Console .
Игра без выделения памяти
Структура игры
Начнем со структуры буфера кадров. Буфер кадров — это компонент, содержащий пиксели (или в данном случае — символы), отображаемые на экране:
Мы предоставляем методы установки отдельных пикселей, очистки и визуализации содержимого буфера кадров System.Console . Шаг рендеринга особых случаев несколько символов, так что мы получаем красочный вывод без необходимости отслеживать цвет каждого пикселя буфера кадров.
Мы не можем переборщить с размером фиксированного массива, потому что как часть структуры массив должен жить в стеке, а стеки, как правило, ограничены небольшим количеством байтов (обычно 1 Мб на поток) и 40*20*2 width*height* sizeof(char) — допустимое число.
Этот генератор не слишком хорош, но нам не нужно ничего сложного. Теперь пишем обёртку для логики игры:
Состояния, которые должна отслеживать змейка:
- Координаты каждого пикселя тела.
- Длина змейки.
- Текущее направление движения.
- Прошлое направление для случая, когда нужно нарисовать символ изгиба вместо прямой линии.
Структура предоставляет метод расширения змеи на один элемент (возвращает false, если змея уже находится на полной длине), метод HitTest для теста столкновений пикселя тела, отрисовки змейки во FrameBuffer и метод обновления положения змеи как ответ на игровой тик (возвращает false , если змея съела себя). Существует также свойство, чтобы установить направление змеи.
Мы используем тот же трюк с фиксированным массивом, что и в буфере кадров, чтобы не использовать new . Это означает, что максимальная длина змеи должна быть постоянной времени компиляции. Последнее, что нам нужно — игровой цикл:
Мы используем генератор случайных чисел для генерации случайного положения и направления змеи. Мы случайным образом размещаем еду на игровой поверхности так, чтобы она не перекрывала змею, и запускаем цикл игры. Внутри игрового цикла мы просим змею обновить свое положение и проверить, съела ли она сама себя. Затем рисуем змею, проверяем клавиатуру на ввод, тестируем змею с едой и отображаем всё на консоль. Давайте посмотрим, где мы находимся с точки зрения размера.
Я поместил игру в репозиторий GitHub, чтобы вы могли следить за ней. Файл проекта собирает игру в различных конфигурациях в зависимости от переданного при публикации свойства Mode . Чтобы создать конфигурацию по умолчанию с помощью CoreCLR, выполните:
Il Linker
С этой настройкой игра сжимается до 25 МБ. Хорошее сокращение, но оно далеко от нашей цели.
IL Linker имеет более агрессивные настройки, не выставляемые публично, и они могут работать дальше, но в конце концов, мы ограничимся размером самой среды выполнения CoreCLR coreclr.dll в 5,3 Мбайт. Возможно, мы зашли в тупик на пути к игре на 8 Кб?
В отличие от CoreCLR, Mono также зависит от распространяемой библиотеки среды выполнения Visual C++, недоступной в установке Windows по умолчанию: чтобы сохранить автономность приложения, нам нужно зашить эту библиотеку в приложение. Это увеличивает объем ещё на один мегабайт или около того.
Мы, вероятно, сможем сделать приложение меньше, добавив Il Linker, но тогда столкнемся с той же проблемой, что и с CoreCLR — размером среды выполнения mono-2.0-sgen.dll , он составляет 5,9 МБ. Плюс размер библиотек времени выполнения C++ поверх него. Это предел оптимизаций уровня IL.
Среда выполнения
Давайте посмотрим, где мы находимся теперь с конфигурацией CoreRT по умолчанию:
4,7 МБ. Файл пока самый маленький, но этого недостаточно.
Умеренная экономия размера в CoreRT
Сейчас мы на уровне 4,3 МБ.
Включение сильной экономии в CoreRT
Я сгруппировал еще несколько вариантов компиляции в режим “сильной экономии”. Режим удаляет поддержку возможностей, важных для многих приложений, но не для нашей змейки. Мы удаляем:
Мы достигли 3,0 МБ, это 5% от начального размера, но у CoreRT есть еще один трюк.
Отключение рефлексии
Сейчас мы на уровне 1,2 МБ. Оверхед на рефлексию довольно значителен.
Пачкаем руки
Как мы уже видели ранее, CoreRT — это набор библиотек времени выполнения в сочетании с опережающим компилятором. Что делать, если мы заменим библиотеки времени выполнения с минимальным переопределением? Мы решили не использовать сборщик мусора, и это делает работу намного более выполнимой. Начнём с простого:
Мы просто переопределили Thread.Sleep и Environment.TickCount64 (для Windows), избегая всех зависимостей от существующей библиотеки времени выполнения. Делаем то же самое для подмножества System.Console , используемого игрой:
Пересоберём игру с заменой фреймворка:
Неудивительно, что это не слишком эффективно. Заменяемые API уже относительно легки, переписывание только добавляет пару килобайт, о которых не стоит упоминать. Но это важная ступенька к последнему шагу нашего путешествия.
Замена библиотек среды выполнения
Оставшиеся 1,2 МБ кода и данных в игре — это поддержка вещей, которые мы не видим, но они есть, они готовы, если нам понадобятся. Есть сборщик мусора, поддержка обработки исключений, код для форматирования и печати трассировок стека на консоль, когда происходит необработанное исключение, и многие другие вещи под капотом.
Компилятор может обнаружить, что ничего этого не требуется, и избежать их генерации, но то, что мы пытаемся сделать, настолько странно, что неплохо добавить функции компилятора для его поддержки. Способ избежать этого — просто предоставить альтернативную библиотеку времени выполнения. Начнем с переопределения минимальной версии базовых типов:
/noconfig , /nostdlib , и /runtimemetadataversion — волшебные параметры, необходимыми для компиляции чего-то, определяющего System.Object . Я выбрал расширение .ilexe , потому что .exe мы используем для готового продукта.
Перестроим IL с добавленным кодом и повторно запустим ILC.
Теперь у нас есть zerosnake.obj — стандартный объектный файл, который ничем не отличается от объектных файлов, создаваемых другими нативными компиляторами, такими как C или C++. Последний шаг — связать его. Воспользуемся link.exe , он должен быть в “x64 Native Tools Command Prompt”. Возможно, вам потребуется установить средства разработки C/C++ в Visual Studio.
Имя символа __managed__Main является контрактом с компилятором — это имя управляемой точки входа программы, созданной ILC. Но команда не работает:
Некоторые из этих символов кажутся знакомыми: компоновщик не знает, где искать вызываемые API Windows. Добавим библиотеки для них:
Выглядит лучше, всего 4 неразрешенных символа:
Остальные отсутствующие символы — это помощники, которые компилятор ожидает найти в библиотеке времени выполнения. Их отсутствие обнаруживается только во время связывания, потому что эти помощники обычно реализуются в сборке, и компилятор ссылается на них только по их символическому имени в отличие от других типов и методов, необходимых компилятору и предоставленных нами выше.
После перестроения исходного кода с этими изменениями и повторного запуска ILC, связывание, наконец, будет успешным. Мы сейчас на 27 килобайтах. Игра работает!
Возня с линкером
Оставшиеся килобайты можно получить с помощью трюков, которые используют разработчики нативного кода, чтобы уменьшить свои приложения. Мы собираемся:
- Отключить инкрементное связывание.
- Обрезать информацию о релокации.
- Объединить похожие разделы внутри исполняемого файла.
- Установить внутреннее выравнивание в небольшое значение
8176 байт! Игра все еще работает и, что интересно, она полностью отлаживаема. Вы можете отключить оптимизацию в ILC, чтобы сделать исполняемый файл еще более отладочным: просто удалите аргумент --Os .
Ещё меньше?
Исполняемый файл ещё содержит несущественные данные — компилятор ILC просто не предоставляет параметры командной строки, отключающие их генерацию.
Одна из этих избыточных структур данных — информация GC для отдельных методов. В CoreRT есть точный сборщик мусора, требующий, чтобы каждый метод описывал, где находятся ссылки на кучу GC в каждой инструкции тела метода.
Поскольку у нас нет сборщика мусора, эти данные не нужны. Другие среды выполнения — например Mono — используют консервативный сборщик, не требующий этих данных. Он просто предполагает, что любая часть стека и регистров процессора может быть ссылкой GC. Консервативный сборщик торгует производительностью GC ради экономии размера. Точный сборщик CoreRT также может работать в консервативном режиме, но он еще не подключен. Это потенциальное будущее дополнение, которое мы могли бы использовать, чтобы сделать программу ещё меньше. Может, однажды мы сможем сделать упрощенную версию нашей игры в 512 байт загрузочного сектора. А до тех пор — счастливого кода!
Установите Visual Studio бесплатно со страницы скачиваемых материалов Visual Studio, если еще не сделали этого.
Установите Visual Studio бесплатно со страницы скачиваемых материалов Visual Studio, если еще не сделали этого.
На некоторых снимках экрана в этом учебнике используется темная тема. Если вы не используете темную тему, но хотите переключиться на нее, см. страницу Персонализация интегрированной среды разработки и редактора Visual Studio.
Установите Visual Studio бесплатно со страницы скачиваемых материалов Visual Studio 2022, если еще не сделали этого.
Создание проекта
Откройте Visual Studio 2017.
В верхней строке меню последовательно выберите Файл > Создать > Проект.
Запустите Visual Studio.
На начальном экране выберите Создать проект.
Затем нажмите кнопку Изменить в Visual Studio Installer. Вам может быть предложено сохранить результаты работы; в таком случае сделайте это. Выберите Продолжить, чтобы установить рабочую нагрузку. После этого вернитесь к шагу 2 в процедуре Создание проекта.
В поле Имя проекта окна Настроить новый проект введите HelloWorld. Затем нажмите Создать.
Новый проект открывается в Visual Studio.
Запустите Visual Studio.
В окне запуска выберите Создание нового проекта.
Затем нажмите кнопку Изменить в Visual Studio Installer. Вам может быть предложено сохранить результаты работы; в таком случае сделайте это. Выберите Продолжить, чтобы установить рабочую нагрузку. После этого вернитесь к шагу 2 в процедуре Создание проекта.
В поле Имя проекта окна Настроить новый проект введите HelloWorld. Затем выберите Создать.
Новый проект открывается в Visual Studio.
Создание приложения
Добавление кнопки на форму
Выберите Панель элементов, чтобы открыть всплывающее окно "Панель элементов".
(Если параметр для всплывающего окна Панель элементов отсутствует, его можно открыть в строке меню. Для этого выберите Вид > Панель элементов. Либо нажмите клавиши CTRL+ALT+X.)
Выберите значок Закрепить, чтобы закрепить окно Панель элементов.
Выберите элемент управления Кнопка и перетащите его на форму.
В окне Свойства найдите Текст, измените имя с button1 на Click this и нажмите клавишу ВВОД.
(Если окно Свойства не отображается, его можно открыть в строке меню.) Для этого выберите Вид > Окно свойств. Или нажмите клавишу F4.)
В разделе Конструктор окна Свойства измените имя с button1 на btnClickThis и нажмите клавишу ВВОД.
Если список в окне Свойства был упорядочен по алфавиту, button1 появится в разделе Привязки данных.
Добавление метки на форму
Теперь, когда мы добавили элемент управления ''Кнопка'' для создания действия, давайте добавим элемент управления "Метка", куда можно отправлять текст.
Выберите элемент управления Метка в окне Панель элементов, а затем перетащите его на форму и расположите под кнопкой Нажмите это.
В разделе Конструктор или Привязки данных окна Свойства измените имя label1 на lblHelloWorld и нажмите клавишу ВВОД.
Добавление кода на форму
В окне Form1.cs [Проект] дважды щелкните кнопку Нажмите это, чтобы открыть окно Form1.cs.
(Кроме того, можно развернуть узел Form1.cs в обозревателе решений, а затем выбрать Form1.)
В окне Form1.cs после строки private void введите lblHelloWorld.Text = "Hello World!"; , как показано на следующем снимке экрана:
Запуск приложения
Будет выполнено несколько операций. В интегрированной среде разработки Visual Studio откроются окна Средства диагностики и Вывод. Кроме того, вне этой среды откроется диалоговое окно Form1. Оно будет содержать вашу кнопку Нажмите это и текст label1.
Закройте диалоговое окно Form1, чтобы завершить работу приложения.
Создание приложения
Добавление кнопки на форму
Щелкните Панель элементов, чтобы открыть всплывающее окно "Панель элементов".
(Если параметр для всплывающего окна Панель элементов отсутствует, его можно открыть в строке меню. Для этого выберите Вид > Панель элементов. Либо нажмите клавиши CTRL+ALT+X.)
Щелкните значок Закрепить, чтобы закрепить окно Панель элементов.
Выберите элемент управления Кнопка и перетащите его на форму.
В окне Свойства найдите элемент Текст, измените имя с Button1 на Click this , а затем нажмите клавишу ВВОД.
(Если окно Свойства не отображается, его можно открыть в строке меню.) Для этого выберите Вид > Окно свойств. Или нажмите клавишу F4.)
В разделе Проектирование окна Свойства измените имя с Button1 на btnClickThis , а затем нажмите клавишу ВВОД.
Если список был упорядочен по алфавиту в окне Свойства, Button1 появится в разделе (DataBindings) .
Добавление метки на форму
Теперь, когда мы добавили элемент управления ''Кнопка'' для создания действия, давайте добавим элемент управления "Метка", куда можно отправлять текст.
Выберите элемент управления Метка в окне Панель элементов, а затем перетащите его на форму и расположите под кнопкой Нажмите это.
В разделе Проект или (DataBindings) окна Свойства измените имя Label1 на lblHelloWorld и нажмите клавишу ВВОД.
Добавление кода на форму
В окне Form1.cs [Проект] дважды щелкните кнопку Нажмите это, чтобы открыть окно Form1.cs.
(Кроме того, можно развернуть узел Form1.cs в обозревателе решений, а затем выбрать Form1.)
В окне Form1.cs после строки private void введите lblHelloWorld.Text = "Hello World!"; , как показано на следующем снимке экрана:
Запуск приложения
Будет выполнено несколько операций. В интегрированной среде разработки Visual Studio откроются окна Средства диагностики и Вывод. Кроме того, вне этой среды откроется диалоговое окно Form1. Оно будет содержать вашу кнопку Нажмите это и текст Label1.
Закройте диалоговое окно Form1, чтобы завершить работу приложения.
Следующие шаги
Для получения дополнительных сведений перейдите к следующему руководству:
Читайте также: