Ниже приводится не полноценная игра, а очень маленькая и примитивная заготовка в духе “рогаликов”. Использование консольного вывода в качестве графического инструмента привлекательно своей исключительной простотой.
Пусть дан лабиринт (48 столбцов, 16 строк), представленный в виде двумерного массива символов:
char maze[16][49]
{
"################################################",
"# # # #",
"###### ####### ########## ####### #",
"# # # # #",
"# # # # ######## # ####",
"# ########### # # # # #",
"# # ####### # # # #",
"# # # ######### #### #########",
"# # # #",
"############### ######## ############# #",
"# # # # #",
"# ########### # ###### # #######",
"# # # # #",
"# # ############## ############### #",
"# # # # #",
"################################################"
};
Позиция игрока в нём задаётся двумя целочисленными координатами:
int player_x, player_y;
Игрок начинает движение из верхнего левого угла:
void new_game()
{
player_x = player_y = 1;
}
Процесс вывода изображения лабиринта и игрока на консоль осуществляется с помощью следующих функций:
void draw_maze()
{
at(0, 0);
ink(color::grey);
for (auto &row : maze)
cout << row << '\n';
}
void draw_player()
{
at(player_x, player_y);
ink(color::yellow);
cout.put('@');
}
О функциях at, ink и других см. ниже.
Чтение пользовательского ввода, вызывающего движение игрока осуществляется функцией move_player:
void move_player()
{
player_x += is_pressed(key::right) - is_pressed(key::left);
player_y += is_pressed(key::down) - is_pressed(key::up);
}
Процесс “игры” реализован в виде цикла в функции main, который вызывает по очереди функции вывода новой “картинки” и чтения ввода:
int main()
{
new_game();
while (!is_pressed(key::escape))
{
if (is_pressed('r'))
{
cls();
new_game();
}
draw_maze();
move_player();
draw_player();
delay();
}
return 0;
}
“Запретить” игроку ходить сквозь стены (символы '#'
). Перед изменением координат игрока надо проверять, может ли игрок сходить в клетку.
На выбор: реализовать что-то из нижеперечисленных пунктов (достаточно чего-то одного):
'#'
(заменяет на ' '
), если это не внешняя стена лабиринта.
// 2d_walk.cpp
#include "con1.hpp"
using namespace con1;
////////////////////////////////////////////////////////////////////////////////
// Global state.
////////////////////////////////////////////////////////////////////////////////
char maze[16][49]
{
"################################################",
"# # # #",
"###### ####### ########## ####### #",
"# # # # #",
"# # # # ######## # ####",
"# ########### # # # # #",
"# # ####### # # # #",
"# # # ######### #### #########",
"# # # #",
"############### ######## ############# #",
"# # # # #",
"# ########### # ###### # #######",
"# # # # #",
"# # ############## ############### #",
"# # # # #",
"################################################"
};
int player_x, player_y;
////////////////////////////////////////////////////////////////////////////////
// Procedures.
////////////////////////////////////////////////////////////////////////////////
void new_game()
{
player_x = player_y = 1;
}
void draw_maze()
{
at(0, 0);
ink(color::grey);
for (auto &row : maze)
cout << row << '\n';
}
void draw_player()
{
at(player_x, player_y);
ink(color::yellow);
cout.put('@');
}
void move_player()
{
player_x += is_pressed(key::right) - is_pressed(key::left);
player_y += is_pressed(key::down) - is_pressed(key::up);
}
////////////////////////////////////////////////////////////////////////////////
// Game loop.
////////////////////////////////////////////////////////////////////////////////
int main()
{
new_game();
while (!is_pressed(key::escape))
{
if (is_pressed('r'))
{
cls();
new_game();
}
draw_maze();
move_player();
draw_player();
sleep(50);
}
return 0;
}
Библиотека con1 предоставляет ряд простых функций для управления вводом-выводом в консоли Windows и состоит из двух файлов: con1.hpp и con1.cpp (ссылки даны на ревизии, доступные на момент написания данного текста). По умолчанию библиотека сконфигурирована для использования в режиме header-only (достаточно включить файл con1.hpp в свою программу и положить con1.cpp “рядом” с ним).
Так как библиотека con1 написана поверх Win API, её невозможно использовать на других ОС. В случае POSIX-систем можно воспользоваться библиотекой ncurses для замены функционала con1. Вариант кода для ncurses приведён ниже.
Библиотека con1 реэкспортирует следующие элементы Стандартной библиотеки C++: cin, cout, cerr, clog, endl, string, getline.
Библиотека предоставляет следующие функции (пространство имён con1):
void delay() — пауза до нажатия любой клавиши. Может использоваться для “задержки экрана”;
void at(x, y) — установить курсор в позицию с координатами (x, y);
void ink_paper(i, p) — эквивалентно паре вызовов: ink(i); paper(p);
void reset_all_keys() — сбросить состояние всех клавиш (т.е. все клавиши сразу после этого вызова будут считаться ненажатыми).
Пространство имён con1::color:
Пространство имён con1::key:
Перед попыткой скомпилировать данный код рекомендуется убедиться, что библиотека ncurses (включая -dev
вариант для разработчиков) установлена. Для компоновки с данной библиотекой следует указать ключ -lncurses
.
// 2d_walk_nc.cpp
#include <ncurses.h>
////////////////////////////////////////////////////////////////////////////////
// Global state.
////////////////////////////////////////////////////////////////////////////////
char maze[16][49]
{
"################################################",
"# # # #",
"###### ####### ########## ####### #",
"# # # # #",
"# # # # ######## # ####",
"# ########### # # # # #",
"# # ####### # # # #",
"# # # ######### #### #########",
"# # # #",
"############### ######## ############# #",
"# # # # #",
"# ########### # ###### # #######",
"# # # # #",
"# # ############## ############### #",
"# # # # #",
"################################################"
};
int player_x, player_y;
int last_key;
////////////////////////////////////////////////////////////////////////////////
// Procedures.
////////////////////////////////////////////////////////////////////////////////
void new_game()
{
player_x = player_y = 1;
}
void draw_maze()
{
int y = 0;
attron(COLOR_PAIR(1));
for (auto &row : maze)
mvaddstr(y++, 0, row);
}
void draw_player()
{
attron(COLOR_PAIR(2));
mvaddch(player_y, player_x, '@' | A_BOLD);
}
void move_player()
{
player_x += (last_key == KEY_RIGHT) - (last_key == KEY_LEFT);
player_y += (last_key == KEY_DOWN) - (last_key == KEY_UP);
}
////////////////////////////////////////////////////////////////////////////////
// Game loop.
////////////////////////////////////////////////////////////////////////////////
int main()
{
initscr();
start_color();
init_pair(1, COLOR_WHITE, COLOR_BLACK); // maze colors
init_pair(2, COLOR_YELLOW, COLOR_BLACK); // player colors
raw();
keypad(stdscr, TRUE);
noecho();
curs_set(0);
new_game();
do
{
if (last_key == 'r')
{
clear();
new_game();
}
draw_maze();
move_player();
draw_player();
refresh();
} while (last_key = getch(), last_key != '\x1b'); // escape ASCII code
endwin();
return 0;
}
Кувшинов Д.Р. © 2016