Змейка — это классическая игра, в которой вы управляете змейкой. Она ползает по экрану и собирает еду, становясь длиннее. Цель игры - не попасться на свой собственный хвост и не удариться об стену. Чем длиннее змейка, тем сложнее управлять ей.

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

Змейка всегда останется популярной игрой из-за своей простоты и увлекательности. Она является ретро - классикой, в которую играют на компьютере, телефоне или планшете.

Создаём проект С++ и подключаем библиотеку 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.

Более подробную инструкцию вы можете получить, посмотрев видео « Игра на С++ Змейка »

Клонировать репозиторий Змейка на С++

Телеграмм канал "Программирование игр С++/С#

Комментарии (8)