Лабораторная работа 5: двумерные массивы I

Кувшинов Д.Р.

2016


Общее оглавление


“Игра”

Введение

Экран
Экран

Ниже приводится не полноценная игра, а очень маленькая и примитивная заготовка в духе “рогаликов”. Использование консольного вывода в качестве графического инструмента привлекательно своей исключительной простотой.

Пусть дан лабиринт (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;
}

Код целиком.


Задание 1

“Запретить” игроку ходить сквозь стены (символы '#'). Перед изменением координат игрока надо проверять, может ли игрок сходить в клетку.


Задание 2

На выбор: реализовать что-то из нижеперечисленных пунктов (достаточно чего-то одного):

  1. Добавить телепорты: если игрок входит в телепорт, то он перемещается на ближайший другой телепорт в лабиринте. Телепорты должны быть видимы на экране.
  2. Добавить преследователя: на каждом ходу игрока преследователь ходит в сторону игрока на одну клетку (4 или 8 возможных направлений, по желанию). Если преследователь настигает игрока, игра сбрасывается. Преследователь не может проходить сквозь стены.
  3. Добавить убегающего: на каждом ходу игрока убегающий пытается увеличить расстояние между собой и игроком, переходя в соседнюю клетку (4 или 8 направлений, по желанию). Если игрок настигает убегающего, генерируется новый убегающий в некоторой свободной клетке. Убегающий не может проходить сквозь стены.
  4. Наделить игрока способностью стрелять (4 или 8 направлений, по желанию). Выстрел движется до попадания в стену и разрушает одну клетку '#' (заменяет на ' '), если это не внешняя стена лабиринта.


Приложение

Код программы

// 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

Библиотека 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):


Пространство имён con1::color:



Пространство имён con1::key:


Код программы (NCurses)

Перед попыткой скомпилировать данный код рекомендуется убедиться, что библиотека 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