Змейка на PDCurses С++

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

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

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

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

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

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

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

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

Источник: https://habr.com/ru/articles/733664/


Интересные статьи

Интересные статьи

Эта статья написана по приколу. В ней я за считанные минуты расскажу, как создать игру «Змейка» на Scala с использованием ScalaFX. Ранее я выложил эту игру в видеоформате. В этом видео я хотел пр...
Практическое решение задачи о матрице «змейкой» Только для алгоритмистов! Всего голосов 3: ↑1 и ↓2 -1 Просмотры 172 Добавить в закладки 1
После прохождения курса по Haskell решил закрепить знания первым проектом. Писать будем змейку для терминала. Чтобы придать игре уникальности, добавим бота, который сам ...
Добрый времени суток. Давайте научим компьютер играть в змейку. Собственно, это первая часть статьи о том, как можно попробовать решить эту задачку. Скажем так разминочная, перед основ...
Танели Арманто не любит рассказывать людям, что изменил мир. На самом деле я уверен, что если вы не являетесь другом его семьи, то ничего о нём не слышали. Обычно он никогда не рассказывает о сво...