как сделать игру в командной строке windows

Создаю игру на командной строке

Я решил создать игру. Какой жанр? Может рпг, платформер или симулятор чего-нибудь? Или…может лучше что-нибудь старенькое и простое? Ооо да, это время текстовых игр.

Ещё в 2017, когда я писал курсовую работу, где надо было создать что-то на Java, я понял, что буду создавать текстовую игру. И я создал. Она была размером примерно на 3000 строк кода. Операторов If в коде было дофига. Ну так вот я решил вспомнить былые времена, но написать не на джаве, а на моей любимой командной строке Windows.

Создаём текстовый файл, вводим название игры(игру я назвал Adventure of the past), затем сохраняем его с расширением bat или cmd. Я привык к bat. Затем украшаем наш батник, меняя цвет и название. Также меняем кодировку на 1251.

Я создал ещё один батник. В нём будут хранится статы героя. Всего их 6: уровень, ловкость, удача, красноречие, сила, количество жизней и урон. Изначально они все равны 1(кроме хп и урона), но с увеличением уровня игрок сможет прокачивать характеристики. Для того, чтобы значение характеристик на экране обновлялось, я сделал цикл, печатал характеристики, а потом очищал экран.

Также я сделал отдельно окно торговли. В статистику я также добавил показатель количества денег. Также я русифицировал все показатели

И последний батник, который я сегодня добавил это инвентарь. Там будут отображаться вещи игрока

Игра на данный момент весит 20 КБ. В каждом следующем посте я буду писать её вес, чтобы наблюдать прогресс так сказать) В этом посте я хотел написать о карте и о том, как будет генерироваться подземелье, однако эта тема оказалась немного посложнее и шире, чем я думал. Поэтому про это я наверное расскажу в отдельном посте. Всем пока, подписывайтесь на блог, здесь прикольно :3

Источник

Как написать игру на C++

О чем статья?

Оглавление

Что нужно знать, чтобы понять статью?

Обработка действий игрока в реальном времени

Запуск периодических событий по таймеру

Генератор случайных чисел

Изменение размера экрана консоли

Ускорение вывода текста

Вывод готовых фрагментов текста

Настройка буферизации stdio.h

Мотивация

Зачем так делать?

Почему C++?

Главная причина в том, что я сейчас веду индивидуальные занятия по C++ у одного талантливого студента. Его успехи вдохновили меня, а его вопросы показали о чем вообще нужно написать. C++ до сих пор рекомендуют как «язык для обучения». Это вселяет надежду, что статья будет полезна многим. Может быть когда-нибудь я напишу такую же статью и для других языков или для Linux, но не рассчитывайте на это. Если вы напишете сами подобный сборник советов для другого языка, то сообщите мне личным сообщением. Я добавлю ссылку на ваш труд.

Что нужно знать, чтобы понять статью?

Обработка действий игрока в реальном времени

Windows API

Поиск по ключевым словам «handle C++ event» выдало целую кучу бесполезной информации по рисованию окон в каком-то из фреймворков windows. Я все еще намерен писать простую игру в консоли, а не оконное приложение, поэтому игнорирую результаты.

Следующая попытка была «cpp event loop with console input» и мне попалась статья https://docs.microsoft.com/en-us/windows/console/reading-input-buffer-events из которой я и взял решение. Считаю это удачной находкой, потому что в процессе написания этой статьи пытался вспомнить свои запросы и даже слегка видоизмененный «event loop c++ with console application» уже не давал нужной информации. Пришлось смотреть историю поиска. Для будущих поколений я добавил сюда явный текст своих запросов, в надежде что будущие поиски будут более результативными.

Подстава в том, что даже эта статья на самом деле показывает пример блокирующего чтения «The function [ReadConsoleInput] does not return until at least one record is available to be read.» Поэтому я посмотрел на соседние статьи и нашел обзор низкоуровневых функций чтения https://docs.microsoft.com/en-us/windows/console/low-level-console-input-functions

В ней описана функция PeekConsoleInput, которая «If no records are available, the function returns immediately.», но которая при этом «Reads without removing the pending input records in an input buffer.». То есть если ее вызвать несколько раз подряд, то она получит информацию об одних и тех же событиях. К счастью там же еще описана функция FlushConsoleInputBuffer, которая удаляет все непрочитанные накопленные события. Сочетание этих двух функций позволит добиться нужного эффекта.

Я видоизменил код из найденной статьи https://docs.microsoft.com/en-us/windows/console/reading-input-buffer-events а именно:

Удалил обработку событий мыши. Если понадобится, спишите ее сами из оригинала.

Удалил колдовство над режимом консоли (функции GetConsoleMode/SetConsoleMode). Для обработки клавиатуры подходит и стандартный режим. Это позволило упростить обработку ошибок в функции ErrorExit.

Добавил сквозной счетчик итераций внешнего цикла. Чтобы было видно работу программы при отсутствии событий.

Получилось вот так:

как сделать игру в командной строке windows. 515e1a586c862259b8ade50a688ccecd. как сделать игру в командной строке windows фото. как сделать игру в командной строке windows-515e1a586c862259b8ade50a688ccecd. картинка как сделать игру в командной строке windows. картинка 515e1a586c862259b8ade50a688ccecd. Я решил создать игру. Какой жанр? Может рпг, платформер или симулятор чего-нибудь? Или…может лучше что-нибудь старенькое и простое? Ооо да, это время текстовых игр.Обработка событий клавиатуры в консоли Windows API

как сделать игру в командной строке windows. ee7849aeeea3e053dc508e3503c1c734. как сделать игру в командной строке windows фото. как сделать игру в командной строке windows-ee7849aeeea3e053dc508e3503c1c734. картинка как сделать игру в командной строке windows. картинка ee7849aeeea3e053dc508e3503c1c734. Я решил создать игру. Какой жанр? Может рпг, платформер или симулятор чего-нибудь? Или…может лучше что-нибудь старенькое и простое? Ооо да, это время текстовых игр.Результат запуска обработки событий клавиатуры в консоли Windows API

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

Чтобы собрать больше информации, я поставил точку останова в функции обработки события и понажимал разные кнопки.

Анализ результатов в отладчике

Например вот так выглядит событие при нажатии «ж».

как сделать игру в командной строке windows. 24b79e96be30e54aa370d50e0dfb6015. как сделать игру в командной строке windows фото. как сделать игру в командной строке windows-24b79e96be30e54aa370d50e0dfb6015. картинка как сделать игру в командной строке windows. картинка 24b79e96be30e54aa370d50e0dfb6015. Я решил создать игру. Какой жанр? Может рпг, платформер или симулятор чего-нибудь? Или…может лучше что-нибудь старенькое и простое? Ооо да, это время текстовых игр.

как сделать игру в командной строке windows. 24cb190bef8ef26c13936b8e776dac81. как сделать игру в командной строке windows фото. как сделать игру в командной строке windows-24cb190bef8ef26c13936b8e776dac81. картинка как сделать игру в командной строке windows. картинка 24cb190bef8ef26c13936b8e776dac81. Я решил создать игру. Какой жанр? Может рпг, платформер или симулятор чего-нибудь? Или…может лучше что-нибудь старенькое и простое? Ооо да, это время текстовых игр.

как сделать игру в командной строке windows. 5a60b18ce549892f8ee4acd5c3bab8d3. как сделать игру в командной строке windows фото. как сделать игру в командной строке windows-5a60b18ce549892f8ee4acd5c3bab8d3. картинка как сделать игру в командной строке windows. картинка 5a60b18ce549892f8ee4acd5c3bab8d3. Я решил создать игру. Какой жанр? Может рпг, платформер или симулятор чего-нибудь? Или…может лучше что-нибудь старенькое и простое? Ооо да, это время текстовых игр.

как сделать игру в командной строке windows. 015079d8a7812b86a33a8f48157db028. как сделать игру в командной строке windows фото. как сделать игру в командной строке windows-015079d8a7812b86a33a8f48157db028. картинка как сделать игру в командной строке windows. картинка 015079d8a7812b86a33a8f48157db028. Я решил создать игру. Какой жанр? Может рпг, платформер или симулятор чего-нибудь? Или…может лучше что-нибудь старенькое и простое? Ооо да, это время текстовых игр.

как сделать игру в командной строке windows. 65c16a8e7a6dc5d2d5638cf8eb3c2037. как сделать игру в командной строке windows фото. как сделать игру в командной строке windows-65c16a8e7a6dc5d2d5638cf8eb3c2037. картинка как сделать игру в командной строке windows. картинка 65c16a8e7a6dc5d2d5638cf8eb3c2037. Я решил создать игру. Какой жанр? Может рпг, платформер или симулятор чего-нибудь? Или…может лучше что-нибудь старенькое и простое? Ооо да, это время текстовых игр.

как сделать игру в командной строке windows. ad2cb29a86b7157d9ed4123100c5e953. как сделать игру в командной строке windows фото. как сделать игру в командной строке windows-ad2cb29a86b7157d9ed4123100c5e953. картинка как сделать игру в командной строке windows. картинка ad2cb29a86b7157d9ed4123100c5e953. Я решил создать игру. Какой жанр? Может рпг, платформер или симулятор чего-нибудь? Или…может лучше что-нибудь старенькое и простое? Ооо да, это время текстовых игр.

Обратите внимание, что во всех вариантах нажатия на кнопку «ж», «Ж», «;» в поле wVirtualKeyCode находится одно и то же число. Если использовать это поле, то управление не будет зависеть от раскладки и даже от зажатого шифта и капслока.

Еще важный момент, что wVirtualKeyCode для точки с запятой «;» на разных клавишах разный, но uChar.UnicodeChar у них одинаковый.

Итого с помощью Windows API мы можем:

Различать нажатые клавиши по коду клавиши независимо от раскладки

Различать нажатые клавиши по коду из Юникода независимо от их расположения на клавиатуре.

Обрабатывать нажатия на shift, ctrl, alt.

Понимать, нажат ли сейчас shift, ctrl, alt.

Отличать нажатие клавиши и ее отпускание (буду благодарен, если подберете слово получше).

Прикрутить обработку событий мыши

как сделать игру в командной строке windows. 167718dede2bf500410bb538edc405b7. как сделать игру в командной строке windows фото. как сделать игру в командной строке windows-167718dede2bf500410bb538edc405b7. картинка как сделать игру в командной строке windows. картинка 167718dede2bf500410bb538edc405b7. Я решил создать игру. Какой жанр? Может рпг, платформер или симулятор чего-нибудь? Или…может лучше что-нибудь старенькое и простое? Ооо да, это время текстовых игр.

В обработчик нажатия клавиш добавил ветвление. В нем проверяется код клавиши и увеличивается соответствующая переменная.

как сделать игру в командной строке windows. af40bfb95fa820bca23076eeb13fb588. как сделать игру в командной строке windows фото. как сделать игру в командной строке windows-af40bfb95fa820bca23076eeb13fb588. картинка как сделать игру в командной строке windows. картинка af40bfb95fa820bca23076eeb13fb588. Я решил создать игру. Какой жанр? Может рпг, платформер или симулятор чего-нибудь? Или…может лучше что-нибудь старенькое и простое? Ооо да, это время текстовых игр.

Для проверки запустил и нажал вниз 1 раз, вправо 2 раза, вверх 3 раза, влево 4 раза. Получился вывод как на картинке. Чтобы сделать скриншот, пришлось подключить отладчик.

как сделать игру в командной строке windows. a631ed6c1e1c53814a936d3a0e039f2a. как сделать игру в командной строке windows фото. как сделать игру в командной строке windows-a631ed6c1e1c53814a936d3a0e039f2a. картинка как сделать игру в командной строке windows. картинка a631ed6c1e1c53814a936d3a0e039f2a. Я решил создать игру. Какой жанр? Может рпг, платформер или симулятор чего-нибудь? Или…может лучше что-нибудь старенькое и простое? Ооо да, это время текстовых игр.

conio.h + getch

Мой студент параллельно мне нашел способ обрабатывать нажатия на кнопки с помощью комбинации функций kbhit и getch из conio.h. С помощью getch можно получить код нажатого символа, но эта функция ждет следующего нажатия. Чтобы программа при этом продолжала работать, нужно сначала вызвать kbhit. Эта функция вернет true, если нажата хотя бы одна клавиша, но при следующего нажатия ждать не будет. Если ничего не нажато, то kbhit возвращает false и программа работает дальше.

К сожалению, я сходу не разобрался, в какой кодировке этот код для кириллических символов. Я реализовал прототип с помощью conio и поэкспериментировал с нажатием клавиш. Эта библиотека игнорирует нажатие shift, ctrl, alt, caps lock. Точка с запятой «;» на клавише с «ж» вернет код «59», на клавише с «4» тоже получился код 59. У символов в разных регистрах, например «ж» и «Ж» будут разные коды. У меня получились 166 и 134 соответственно. Интересно, что стандартное преобразование «(char)key» превратило эти коды в совершенно другие символы.

как сделать игру в командной строке windows. 24b4d4294206394df6d4f4f38450346e. как сделать игру в командной строке windows фото. как сделать игру в командной строке windows-24b4d4294206394df6d4f4f38450346e. картинка как сделать игру в командной строке windows. картинка 24b4d4294206394df6d4f4f38450346e. Я решил создать игру. Какой жанр? Может рпг, платформер или симулятор чего-нибудь? Или…может лучше что-нибудь старенькое и простое? Ооо да, это время текстовых игр.Обработка событий клавиатуры conio.h как сделать игру в командной строке windows. d5b0e865a5a429135b2ecba315521b69. как сделать игру в командной строке windows фото. как сделать игру в командной строке windows-d5b0e865a5a429135b2ecba315521b69. картинка как сделать игру в командной строке windows. картинка d5b0e865a5a429135b2ecba315521b69. Я решил создать игру. Какой жанр? Может рпг, платформер или симулятор чего-нибудь? Или…может лучше что-нибудь старенькое и простое? Ооо да, это время текстовых игр.Результат запуска обработки событий с помощью conio.h

Запуск периодических событий по таймеру

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

Конкретная реализация нагуглилась с первого запроса «windows.h time milliseconds». https://stackoverflow.com/questions/17008026/windows-how-to-get-the-current-time-in-milliseconds-in-c Для очистки совести я еще попробовал поискать «cpp thread sleep» и «cpp sleep». Первый вариант получился слишком сложный, а второй недостаточно точный.

Существует несколько способов получить текущее время:

С помощью GetSystemTime из windows.h

С помощью std::chrono

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

Получение текущего времени Windows API

как сделать игру в командной строке windows. 2c67f4306bc8c24111243bb24c38afe7. как сделать игру в командной строке windows фото. как сделать игру в командной строке windows-2c67f4306bc8c24111243bb24c38afe7. картинка как сделать игру в командной строке windows. картинка 2c67f4306bc8c24111243bb24c38afe7. Я решил создать игру. Какой жанр? Может рпг, платформер или симулятор чего-нибудь? Или…может лучше что-нибудь старенькое и простое? Ооо да, это время текстовых игр.

Сделать хитрое сравнение.

Первый вариант может решить проблему только если я воспроизведу вычисление настоящего unix-timestamp, а мне лень. Например там будет сложность с количеством дней в месяце. Поэтому я сделаю менее точную версию, где «начало эпохи» будет в начале текущего месяца.

Код получается вот таким

как сделать игру в командной строке windows. 89ebf58d23f3dd1dd4be2cce0a3d2103. как сделать игру в командной строке windows фото. как сделать игру в командной строке windows-89ebf58d23f3dd1dd4be2cce0a3d2103. картинка как сделать игру в командной строке windows. картинка 89ebf58d23f3dd1dd4be2cce0a3d2103. Я решил создать игру. Какой жанр? Может рпг, платформер или симулятор чего-нибудь? Или…может лучше что-нибудь старенькое и простое? Ооо да, это время текстовых игр.Получение текущей даты Windows API

При запуске получается примерно такой вывод:

как сделать игру в командной строке windows. 4c2b7851920f988727b6c5bc7404f6d1. как сделать игру в командной строке windows фото. как сделать игру в командной строке windows-4c2b7851920f988727b6c5bc7404f6d1. картинка как сделать игру в командной строке windows. картинка 4c2b7851920f988727b6c5bc7404f6d1. Я решил создать игру. Какой жанр? Может рпг, платформер или симулятор чего-нибудь? Или…может лучше что-нибудь старенькое и простое? Ооо да, это время текстовых игр.Результат получения времени из Windows API

Теперь добавляю дополнительную переменную для хранения времени предыдущего обновления (prevTime) и периода между обновлениями (delay).

как сделать игру в командной строке windows. cdcb101ee4727f9794369360a858ef76. как сделать игру в командной строке windows фото. как сделать игру в командной строке windows-cdcb101ee4727f9794369360a858ef76. картинка как сделать игру в командной строке windows. картинка cdcb101ee4727f9794369360a858ef76. Я решил создать игру. Какой жанр? Может рпг, платформер или симулятор чего-нибудь? Или…может лучше что-нибудь старенькое и простое? Ооо да, это время текстовых игр.

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

как сделать игру в командной строке windows. b3f79ae5b1cb1cc979c1310b385183e0. как сделать игру в командной строке windows фото. как сделать игру в командной строке windows-b3f79ae5b1cb1cc979c1310b385183e0. картинка как сделать игру в командной строке windows. картинка b3f79ae5b1cb1cc979c1310b385183e0. Я решил создать игру. Какой жанр? Может рпг, платформер или симулятор чего-нибудь? Или…может лучше что-нибудь старенькое и простое? Ооо да, это время текстовых игр.

При запуске получается примерно такой вывод.

как сделать игру в командной строке windows. d011b06f8ee804b98aee7713cf2a0f10. как сделать игру в командной строке windows фото. как сделать игру в командной строке windows-d011b06f8ee804b98aee7713cf2a0f10. картинка как сделать игру в командной строке windows. картинка d011b06f8ee804b98aee7713cf2a0f10. Я решил создать игру. Какой жанр? Может рпг, платформер или симулятор чего-нибудь? Или…может лучше что-нибудь старенькое и простое? Ооо да, это время текстовых игр.

Таким образом команда вывода слова «tick» будет выполняться раз в 300 миллисекунд. После нее нужно помещать логику будущей игры.

Получение текущего времени std::chrono

Реализация с помощью std::chrono получилась значительно проще. Но в ней используется непривычное для обычных людей запись времени. Там нет количества часов с начала дня, минут с начала часа и секунд с начала минуты. С помощью std::chrono можно получить количество миллисекунд с «начала эпохи». То есть с 1 января 1970 года. Это получается огромное целое число. Например 1610827417491. В отличие от «количества миллисекунд с начала секунды», это число всегда увеличивается. Поэтому не возникнет необходимости беспокоиться о переполнении часов.

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

как сделать игру в командной строке windows. 8ab2b3017d3277634888311814edcc73. как сделать игру в командной строке windows фото. как сделать игру в командной строке windows-8ab2b3017d3277634888311814edcc73. картинка как сделать игру в командной строке windows. картинка 8ab2b3017d3277634888311814edcc73. Я решил создать игру. Какой жанр? Может рпг, платформер или симулятор чего-нибудь? Или…может лучше что-нибудь старенькое и простое? Ооо да, это время текстовых игр.Получение текущего времени std::chrono

Константа PRIu64 нужна для форматированного вывода значения типа uint64_t с помощью printf. Для её использования нужно подключить inttypes.h Хотелось бы, конечно, не подключать лишних библиотек, но времени на обход именно этой библиотеки у меня не было.

Вывод получился вот таким

как сделать игру в командной строке windows. 45e434c5f28381884f134c1b66971558. как сделать игру в командной строке windows фото. как сделать игру в командной строке windows-45e434c5f28381884f134c1b66971558. картинка как сделать игру в командной строке windows. картинка 45e434c5f28381884f134c1b66971558. Я решил создать игру. Какой жанр? Может рпг, платформер или симулятор чего-нибудь? Или…может лучше что-нибудь старенькое и простое? Ооо да, это время текстовых игр.Вывод от получения текущего времени std::chrono

Что с этим можно сделать? Навскидку мне в голову пришло:

Симуляция гравитации в платформере. Каждые Х миллисекунд персонаж должен падать на 1 символ.

Полет пуль. Каждые Х миллисекунд передвинь пулю по направлению выстрела.

Движение змейки. Каждые Х миллисекунд передвинь сегменты змейки на один символ от головы.

как сделать игру в командной строке windows. image loader. как сделать игру в командной строке windows фото. как сделать игру в командной строке windows-image loader. картинка как сделать игру в командной строке windows. картинка image loader. Я решил создать игру. Какой жанр? Может рпг, платформер или симулятор чего-нибудь? Или…может лучше что-нибудь старенькое и простое? Ооо да, это время текстовых игр.

На картинке ниже вывод, который получается в такой программе время. Обратите внимание на чередование фраз «Each 225» и «Each 300».

как сделать игру в командной строке windows. image loader. как сделать игру в командной строке windows фото. как сделать игру в командной строке windows-image loader. картинка как сделать игру в командной строке windows. картинка image loader. Я решил создать игру. Какой жанр? Может рпг, платформер или симулятор чего-нибудь? Или…может лучше что-нибудь старенькое и простое? Ооо да, это время текстовых игр.

Генератор случайных чисел

Если немного копнуть в алгоритм генераторов случайных чисел, то можно обнаружить, что они не такие уж случайные. Поэтому используют более точный термин Генератор псевдо-случайных чисел или ГПСЧ.

По запросу «с++ rand» и «cpp rand» можно найти довольно много материалов. Например обширную статью на русском https://en.cppreference.com/w/cpp/numeric/random/rand и чуть более сухую на английском https://en.cppreference.com/w/cpp/numeric/random/rand

Вкратце перескажу основные тезисы:

Функция rand возвращает случайное число от 0 до RAND_MAX. Значение RAND_MAX зависит от библиотеки, но должно быть не менее 32767.

Если просто вызывать функцию rand, то при разных запусках, последовательность случайных чисел будет одинаковой.

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

Я от себя добавлю, что аргумент функции srand еще называют зерном (или «сидом» от слова seed). Вы подобное видели во многих играх при создании мира.

Примеры начальных значений для ГПСЧ

Если зерно мира заполнять одним и тем же значением, то получатся одинаковые последовательности случайных значений. В играх получатся одинаковые миры. Вот пример использования ГСПЧ с постоянным значением зерна.

как сделать игру в командной строке windows. image loader. как сделать игру в командной строке windows фото. как сделать игру в командной строке windows-image loader. картинка как сделать игру в командной строке windows. картинка image loader. Я решил создать игру. Какой жанр? Может рпг, платформер или симулятор чего-нибудь? Или…может лучше что-нибудь старенькое и простое? Ооо да, это время текстовых игр.

При запуске у меня появляется фраза «first is 440 second is 19053». Независимо от количества запусков получаются одни и те же числа. У вас могут получиться другие числа, но от запуска к запуску они должны быть одинаковы.

Чтобы последовательность менялась от запуска к запуску нужно передавать такое зерно, которое будет всегда разное. Что у нас не повторяется при запуске одной и той же программы раз за разом?

как сделать игру в командной строке windows. bf06b9d6dcabdbceddcc236d12662954. как сделать игру в командной строке windows фото. как сделать игру в командной строке windows-bf06b9d6dcabdbceddcc236d12662954. картинка как сделать игру в командной строке windows. картинка bf06b9d6dcabdbceddcc236d12662954. Я решил создать игру. Какой жанр? Может рпг, платформер или симулятор чего-нибудь? Или…может лучше что-нибудь старенькое и простое? Ооо да, это время текстовых игр.Инициализация зерна случайности с помощью std::chrono

Я варварски сконвертировал uion64_t в int, но этого оказалось достаточно.

Следующая проблема в том, что числа получаются огромные. Повторюсь, что rand возвращает число от 0 до RAND_MAX, который у меня равен 0x7fff. В десятичной системе счисления диапазон случайных чисел получается от 0 до 32767.

Вы когда-нибудь задумывались, а почему остаток от деления нам действительно помогает? Не получится ли такого, что какое-то число будет встречаться чаще других?

Строгое математическое доказательство будет чрезмерным для этой статьи. Поэтому постараюсь объяснить «на пальцах».

Как вы можете заметить, диапазон от 0 до 14 разделился на несколько отрезков от 0 до 3. Числа в правом столбце возрастают так же равномерно, как и в левом. Точно такая же закономерность прослеживается и на диапазоне от 0 до 32767. Функция rand() может вернуть каждое число «из левого столбца» с равной вероятностью. После нахождения остатка от деления мы получим соответствующее число из правого столбца.

Обратите внимание, что последний диапазон «от 0 до 3» в правом столбце не успел закончиться. Если числа в левом диапазоне будут выбираться с равной вероятностью, то числа 0, 1, 2 из правого столбца будут появляться чаще, чем число 3. То же самое будет и на полном масштабе, если 32767 не будет делиться нацело на выбранное вами ограничение. Впрочем, исходный диапазон рандома достаточно велик, чтобы мы не заметили этот небольшой недостаток. В документации на cppreference в формуле выбора числа есть попытка компенсировать его делением на «((RAND_MAX + 1u)/6)».

Как уже говорилось выше, рандом возвращает числа от 0 до ограничения. Проблема в том, что при написании игры нужно использовать числа не от 0, а например от 10 до 20. В реализации std::rand приходится упражняться со сложением и вычитанием. К счастью, я нашел ссылку на статью с описанием синтаксиса, который был добавлен в стандарт C++ от 2011 года. https://en.cppreference.com/w/cpp/numeric/random/uniformintdistribution В нем есть красивый способ описать рандом в нужном диапазоне. Немножко оформления и можно просто получать случайные числа в нужном диапазоне.

Подробнее о Вихре Мерсенна можно почитать на Википедии https://ru.wikipedia.org/wiki/%D0%92%D0%B8%D1%85%D1%80%D1%8C_%D0%9C%D0%B5%D1%80%D1%81%D0%B5%D0%BD%D0%BD%D0%B0

Меня такая находка очень порадовала. Пора переходить в туториалах для новичков на стандарт C++ хотя бы десятилетней давности. Этот фрагмент кода я специально для вас оформил текстом, который можно скопировать.

Изменение размера консоли

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

Изменение размера окна консоли тоже оказалось задачкой с подвохом. Первый запрос был тривиальным «c++ change size of console window». Первый ответ на него подробно объяснял как сделать это с помощью настроек окна консоли на уровне операционной системы. То есть не из самой игры, а со стороны пользователя. Прикладывать эту инструкцию к игре я посчитал неправильным. Нужен способ сделать это из самой программы. Второй и последующие ответы описывали изменение размера окна консоли с помощью функции MoveWindow. Фактическое количество текста при этом не менялось. Если окно становилось слишком маленьким, то появлялись полосы прокрутки.

На тот момент я не знал точно, какие размеры стандартные и что я могу туда поставить. Поэтому указал 20 на 20. Для проверки размеров окна я также вывел прямоугольник из цифр от 0 до 9 шириной 20 на 20. Получился вот такой код:

как сделать игру в командной строке windows. 24aaf24cf0d6be91a9527e81b3fc1b86. как сделать игру в командной строке windows фото. как сделать игру в командной строке windows-24aaf24cf0d6be91a9527e81b3fc1b86. картинка как сделать игру в командной строке windows. картинка 24aaf24cf0d6be91a9527e81b3fc1b86. Я решил создать игру. Какой жанр? Может рпг, платформер или симулятор чего-нибудь? Или…может лучше что-нибудь старенькое и простое? Ооо да, это время текстовых игр.Заполнение экрана символами и изменение размера консоли

Вывод получился вот таким

как сделать игру в командной строке windows. 3983e432d496c36f3e71a5fe1ab69ab7. как сделать игру в командной строке windows фото. как сделать игру в командной строке windows-3983e432d496c36f3e71a5fe1ab69ab7. картинка как сделать игру в командной строке windows. картинка 3983e432d496c36f3e71a5fe1ab69ab7. Я решил создать игру. Какой жанр? Может рпг, платформер или симулятор чего-нибудь? Или…может лучше что-нибудь старенькое и простое? Ооо да, это время текстовых игр.

Поскольку это работа на уровне WinAPI, в результате получился код ошибки. Я в основном работаю с java стеком и обычно вижу стектрейсы и тексты исключений. Несмотря на это, принцип решения проблемы не изменился. Для расшифровки кода ошибки нужно воспользоваться официальной документацией. Она легко ищется запросом «getlasterror error codes». Кодов ошибок описано около девяти тысяч на нескольких страницах. Для моего случая подойдет первая страница https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes—0-499-

Ошибка гласит ERROR-INVALID-PARAMETER 87 (0x57) The parameter is incorrect.

Маловато объяснений. Тогда я проверил как другие пишут этот код. Запрос «SetConsoleScreenBufferSize incorrect argument» привел меня вот на этот вопрос на SO https://stackoverflow.com/questions/12900713/reducing-console-size

В ключевых аспектах код ответа был похож на мой. Но в нем содержалось важное дополнение «If you call SetConsoleScreenBufferSize with illegal value in COORDS (e.g. too little height/width) then you get an error, usually 87 ‘invalid argument’.»

Исходный код был видоизменен вот так:

как сделать игру в командной строке windows. f48ed71134f37a1898fe277621687c71. как сделать игру в командной строке windows фото. как сделать игру в командной строке windows-f48ed71134f37a1898fe277621687c71. картинка как сделать игру в командной строке windows. картинка f48ed71134f37a1898fe277621687c71. Я решил создать игру. Какой жанр? Может рпг, платформер или симулятор чего-нибудь? Или…может лучше что-нибудь старенькое и простое? Ооо да, это время текстовых игр.

Вывод при этом получился таким

как сделать игру в командной строке windows. 4e23e03e95cade0ae55eab0a7714fdd8. как сделать игру в командной строке windows фото. как сделать игру в командной строке windows-4e23e03e95cade0ae55eab0a7714fdd8. картинка как сделать игру в командной строке windows. картинка 4e23e03e95cade0ae55eab0a7714fdd8. Я решил создать игру. Какой жанр? Может рпг, платформер или симулятор чего-нибудь? Или…может лучше что-нибудь старенькое и простое? Ооо да, это время текстовых игр.

Ускорение вывода текста

Запустив программу для заполнения большого экрана символами, я обнаружил, что текст пишется в консоль очень медленно. Заполнение символами экрана 200 на 80 заметно человеческому глазу. За одну секунду получится обновить экран лишь 1-2 раза. Это вряд ли связано с производительностью компьютера. Интуиция подсказывает, что это искусственное ограничение. При решении этой проблемы у меня было два направления поиска:

Как быстро написать много текста?

Как обновить только тот фрагмент экрана, который действительно менялся?

Сначала я поискал «cpp console reduce delay between screen updates». Практически все ссылки вели на советы по добавлению паузы, что мне совершенно не интересно. Только один ответ в выдаче говорил что-либо об ускорении вывода https://stackoverflow.com/questions/26376094/c-writing-to-console-without-delays. В нем предлагается подготовить большой буфер в памяти и вывести его одной командой.

Затем я поискал «windows.h write lot of text without animation» и нашел вот такой вопрос с очень любопытным ответом. https://stackoverflow.com/questions/34842526/update-console-without-flickering-c

Автор вопроса и автор ответа разговаривают в контексте создания консольной игры. Вместо полной очистки и полного заполнения экрана в ответе предлагается:

Переставить курсор и писать новый текст поверх старого без предварительной очистки экрана.

Переписывать фрагменты, а не отдельные буквы.

Переписывать отдельные буквы на экране.

Использовать два буфера и писать на экран только разницу.

Обратите внимание, в ответе на вопрос есть еще пример настройки цвета текста в консоли. У меня, к сожалению, не хватило времени воспроизвести этот прием.

Перестановка курсора

Простой перенос курсора в верхний левый угол экрана значительно улучшил ситуацию на windows 7. Я все еще видел процесс заполнения экрана, но текст на экране не исчезал два раза в секунду. У меня пропадали некоторые линии. У моего студента была windows 10 и без дополнительных ухищрений было видно только мигание самого курсора в разных частях экрана. Пропадания линий замечено не было.

За основу был взят код из главы «Изменение размера консоли».

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

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

Для перестановки курсора достаточно вызвать функцию SetConsoleCursorPosition и передать ей в качестве аргументов поток вывода и структуру с координатами желаемой позиции курсора.

Вызов функции SetConsoleCursorPosition для перестановки курсора

Как я уже говорил, это улучшило ситуацию, но проблему полностью не решило. Поэтому я решил раскопать поглубже.

Вывод готовых фрагментов текста

Вывод текста фрагментами

В примерах еще часто использовался std::cout.flush(), который тащит за собой подключение iostream. Мне не хотелось использовать дополнительную библиотеку. Наверняка аналог есть и в stdio, который уже подключен. Мой запрос для поиска был «stdio flush output». Две ссылки на Stackoverflow указывают на fflush

Функция fflush вызывается с аргументом stdout. Я минут 10 искал как правильно заполнить переменную stdout, а оказалось она просто доступна из глобальной области видимости.

Настройка буферизации stdio.h

Настройка буфера вывода с помощью stdio.h

Измерение производительности

Тут я осознал, что не в состоянии сравнить производительность разных вариантов «на глаз». Поэтому вкрутил измерение времени до и после обновления экрана. Напишу тут порядок цифр, потому что значения с точностью до микросекунд не существенны для сравнения. Код практически полностью списал из ответа к этому вопросу https://stackoverflow.com/a/21856299

Функция получения времени с точностью до микросекунд

Измерил количество микросекунд без явного указания буфера. Три запуска, три числа: 8930000 8880000 9220000.

с размером буфера 1/16 от 740000 до 750000 микросекунд

с размером буфера 1/8 от 40000 до 39000 микросекунд

с размером буфера 1/4 от 18000 до 19000 микросекунд

с размером буфера 1/2 от 12000 до 13000 микросекунд

с размером буфера равным ширине консоли от 90000 до 10000.

Во всех этих случаях наблюдается мигание строк на экране размером с буфер. То есть при буфере 1/4, мигают фрагменты в четверть строки. С буфером равным ширине консоли получается очень большой разброс, причем без промежуточных значений. либо за 10000, либо за 90000. При этом мигает так, как будто буфер половина. redraw настройка буфера библиотеки.PNG

Велосипедный вариант с заполнением массива и выводом его на экран получился такой же, как с размером буфера равным ширине консоли. Были цифры 90000 и 10000 без промежуточных значений. При этом для создания буфера меньшего размера пришлось бы значительно усложнить реализацию. redraw велосипедный буфер.PNG

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

Заключение

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

Источник

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

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