Змейка — это классическая игра, в которой вы управляете змейкой. Она ползает по экрану и собирает еду, становясь длиннее. Цель игры - не попасться на свой собственный хвост и не удариться об стену. Чем длиннее змейка, тем сложнее управлять ей.
Игра очень проста в управлении. Вы используете клавиши со стрелками на клавиатуре, чтобы изменить направление движения змейки. Если змея съест еду, то становится длиннее и вы зарабатываете очки. Если змея столкнется со своим хвостом или ударится об стену, игра закончится.
Змейка всегда останется популярной игрой из-за своей простоты и увлекательности. Она является ретро - классикой, в которую играют на компьютере, телефоне или планшете.
Создаём проект С++ и подключаем библиотеку PDCurses
При создании проекта в Visual Studio 2022 выбираем шаблон С++ консольные приложения.
Даём название проекту, устанавливаем галочку, поместить решение и проект в одном каталоге и создаём проект.
Загружаем библиотеку PDCurses c github на любую операционную систему или под Windows Visual Studio с нашего телеграмм канала.
Копируем библиотеку в папку с проектом и подключаем.
Открываем папку в проводнике и копируем библиотеку.
Подключаем библиотеку PDCurses к проекту
В дополнительный каталог включаемых файлов указываем расположение заголовочных фалов библиотеки, в нашем варианте папку с проектом.
В дополнительных каталогах библиотек указываем расположения файла pdcurses.lib, в нашем случае корневая папка проекта.
В дополнительные зависимости записываем файл pdcurses.lib. Нажимаем кнопку применить, тем самым сохраняем внесённые изменения в свойства проекта. После того как мы удачно подключили библиотеку PDCurses, мы можем начинать писать код.
#include <chrono>
#include <random>
#include <vector>
#include <string>
#include <curses.h>
using namespace std;
using namespace std::chrono;
bool game_exit = false;
struct vector2di {
int x = 0;
int y = 0;
};
В начале программы подключаем необходимые заголовочные файлы:
сhrono, random (обеспечивают работу генератора случайных чисел), vector (динамический массив для сохранения координат хвоста змейки), string (с помощью функции to_string () конвертируем значение целочисленной переменной в строковое представление), curses (предназначенная для управления вводом-выводом на терминал, позволяет задавать экранные координаты и цвет выводимых символов).
Cообщаем компилятору, что мы хотим использовать всё, что находится в пространстве имен std и std::chrono.
Объявляем и инициализируем глобальную переменную game_exit, которая будет определять завершение программы.
Создаём структуру vector2di, для хранения координат по оси икс и игрек.
Функция main() :
int main()
{
// переключаем шрифт для отображения кириллицы
system("chcp 1251");
// инициализируем экран curses
initscr();
// прячем курсор
curs_set(0);
// запускаем цветной режим
start_color();
// режимы цветов 1,2,3,4,5
init_pair(1, COLOR_WHITE, COLOR_BLUE);
init_pair(2, COLOR_MAGENTA, COLOR_BLUE);
init_pair(3, COLOR_GREEN, COLOR_BLUE);
init_pair(4, COLOR_RED, COLOR_BLUE);
init_pair(5, COLOR_YELLOW, COLOR_BLACK);
//цвет фона, режим 1
bkgd(COLOR_PAIR(1));
// начальные координаты головы змейки
vector2di snake_head{ 10,10 };
// направление и шаг перемещения змейки
vector2di vector_step{ 1,0 };
// постоянные координаты яблока
vector2di apple{ 15,15 };
// случайные координаты яблока
vector2di rnd_apple;
// динамический массив координат хвоста змеи
vector<vector2di> snake_tail;
// частота смены кадров
int frame_rate = 100;
// количество собранных яблок
int eaten_apples = 0;
// **** начало работы генератора случайных чисел ****
// момент системного времени
long long seed = system_clock::now().time_since_epoch().count();
// запуск генератора случайных чисел
default_random_engine rnd(static_cast<unsigned int>(seed));
// установка диапазона случайных координат яблока
uniform_int_distribution<int> apple_x(10, 97);
uniform_int_distribution<int> apple_y(5, 22);
// **** конец работы генератора случайных чисел ****
// разрешаем использовать специальные клавиши в нашем случае стрелки для управления змейкой
keypad(stdscr, true);
// выключает отображение нажатых клавиш на экране
noecho();
// **** игровой цикл ****
while (!game_exit) {
// присваиваем случайные значения переменным случайных координат яблока
rnd_apple.x = apple_x(rnd);
rnd_apple.y = apple_y(rnd);
// рисуем игровое поле
show_map(apple, eaten_apples);
// перемещаем змейку по игровому полю
move_snake(snake_head, vector_step, snake_tail, apple, rnd_apple, eaten_apples);
// частота сменны игрового кадра
timeout(frame_rate);
// **** управление змейкой и игрой ****
// проверка нажатой клавиши
switch (getch()) {
case KEY_UP:
if (vector_step.y == 0) {
vector_step.y = -1;
vector_step.x = 0;
frame_rate = 170;
}
break;
case KEY_DOWN:
if (vector_step.y == 0) {
vector_step.y = 1;
vector_step.x = 0;
frame_rate = 170;
}
break;
case KEY_LEFT:
if (vector_step.x == 0) {
vector_step.x = -1;
vector_step.y = 0;
frame_rate = 100;
}
break;
case KEY_RIGHT:
if (vector_step.x == 0) {
vector_step.x = 1;
vector_step.y = 0;
frame_rate = 100;
}
break;
case 'q':
game_exit = true;
break;
default:
break;
}
// **** конец управления змейкой и игрой ****
}
// **** конец игрового цикла ****
endwin();
return 0;
}
В начале функции определяем и инициализируем переменные, включаем и настраиваем терминал curses.
Функция initscr() определяет тип терминала и инициализирует все структуры данных реализации, также вызывает операцию обновления для очистки экрана. Данная функция вызывается только один раз.
Функция curs_set () устанавливает внешний вид курсора на основе значения видимости :
В нашем коде устанавливаем значение ноль и делаем курсор невидимым.
Функция start_color() – инициализирует использование цветов на терминале.
Процедура init_pair() задаёт определение пары цветов. Она принимает три аргумента: номер пары, номер цвета переднего плана и номер цвета фона.
Константы цветов:
Функция bkgd () устанавливает цвета для текущего терминала.
При создании генератора случайных чисел обязательно должны быть подключены соответствующие заголовочные файлы. Создаём целочисленную переменную seed и записываем в неё момент времени. Передаём момент времени в генератор случайных чисел объект rnd. Генерируем случайные числа координат яблока в установленном диапазоне по иксу apple_x(10, 97) и по игреку apple_y(5, 22).
В параметрах функции keypad() разрешаем использовать специальные клавиши, в нашем случае стрелки, указав значение true.
Функция noecho() отключает отображение в терминале нажатых клавиш.
Игровой цикл while будет работать пока игра не завершится.
В цикле присваиваем случайные значения переменным rnd_apple.x, rnd_apple.y. Рисуем игровое поле используя функцию show_map() и перемещаем по игровому полю змейку через функцию move_snake(). В параметрах функции timeout() устанавливаем время задержки кадров в миллисекундах.
Создаём переключатель обработки нажатых клавиш через функцию getch(). При нажатии стрелок меняем вектор направления движения змейки и время обновления кадра. При нажатии клавиши < q >, выходим из игры.
Функция endwin() останавливает работу терминала curses.
Функция show_map():
void show_map(const vector2di& apple, const int& eaten_apples) {
// очистка экрана
clear();
// перемещение курсора
move(2, 55);
// устанавливаем атрибуты для текста яркость и номер цветовой пары
attrset(A_DIM | COLOR_PAIR(1));
// выводим текст в консоль
printw("Змейка\t\t");
attrset(A_BOLD | COLOR_PAIR(5));
printw(" количество собранных яблок < ");
// значение целочисленной переменной переводим в строковое
string s_eaten_apples = to_string(eaten_apples);
// выводим значение переменной
printw(s_eaten_apples.c_str());
printw(" > ");
attrset(A_DIM | COLOR_PAIR(1));
// рисуем игровое поле
for (int y = 4; y < 28; y++) {
for (int x = 5; x < 112; x++) {
if (y == 4 || y == 27 || x == 5 || x == 111) {
move(y, x);
printw("*");
}
}
}
// рисуем яблоко
move(apple.y, apple.x);
attrset(A_BOLD | COLOR_PAIR(2));
printw("@");
}
В параметрах функции передаём ссылку на координаты яблока apple и ссылку на количество подобранных яблок eaten_apples.
Функция clear() очищает экран, удаляя весь текст.
Функция move(), перемещает курсор в координаты заданные в параметрах по игреку 2, а по иксу 55.
Функция attrset(), задаём тип и цвет отображения символов.
Возможные параметры функции attrset():
Функция printw() выводит текст в консоль.
Функция to_string() приводит значение целочисленной переменной eaten_apples в строковой тип для вывода её значения в консоль.
С помощью циклов for() и символа « * » рисуем границы игрового поля, далее отображаем символ « @ » обозначающий яблоко.
Определяем функцию перемещения змейки move_snake():
void move_snake(vector2di& snake_head, vector2di& vector_step, vector<vector2di>& snake_tail,
vector2di& apple, vector2di rnd, int& eaten_apples) {
// устанавливаем цвет змейки
attrset(A_BOLD | COLOR_PAIR(3));
// Если змейка с хвостом, рисуем хвост
if (!snake_tail.empty()) {
for (auto const& mov : snake_tail) {
move(mov.y, mov.x);
printw("#");
}
}
// изменяем координаты головы змейки
snake_head.x += vector_step.x;
snake_head.y += vector_step.y;
// перемещаем курсов в координаты головы змейки
move(snake_head.y, snake_head.x);
// проверяем символ в установленных координатах курсора
auto s = static_cast<char>(winch(stdscr));
// *** Если змейка столкнулась с хвостом или границами игрового поля
// ### Выводим игровое меню ###
if (s == '*' || s == '#') {
attrset(A_BOLD | COLOR_PAIR(4));
move(13, 55);
printw("Конец игры");
move(14, 42);
printw("Выход - < q > Начать заново - < n >");
// *** Цикл выбора игрового меню ***
do {
if (getch() == 'q') {
// выход из игры
game_exit = true;
return;
}
if (getch() == 'n')
{
// рестарт задаём начальные координаты переменным
snake_head = { 10,10 };
vector_step = { 1,0 };
snake_tail.clear();
apple = { 15,15 };
eaten_apples = 0;
return;
}
} while (true);
// *** Конец цикла выбора игрового меню ***
}
// ### Конец игрового меню ###
// Если змейка съедает яблоко
if (s == '@') {
// увеличиваем количество съеденных яблок
eaten_apples++;
// добавляем хвост змейки
snake_tail.push_back({ snake_head.x, snake_head.y });
// рисуем голову змейки
printw("$");
do {
// задаём новые координаты яблока
apple.x = rnd.x; apple.y = rnd.y;
move(apple.y, apple.x);
auto s = static_cast<char>(winch(stdscr));
// повторяем цикл пока координаты яблока совпадают с хвостом змейки
} while (s == '#');
}
// Если змейка переместилась в свободное поле
else {
// рисуем голову змейки
printw("$");
// обновляем координаты хвоста змеи
if (!snake_tail.empty()) {
snake_tail.erase(snake_tail.begin());
snake_tail.push_back({ snake_head.x, snake_head.y });
}
}
};
В параметрах функции передаём ссылки на: координаты головы змейки snake_head, шаг и вектор перемещения змейки vector_step, динамический массив координат хвоста snake_tail, координаты яблока apple, случайные координаты яблока rnd, количество собранных яблок eaten_apples.
Более подробную инструкцию вы можете получить, посмотрев видео « Игра на С++ Змейка »