Skip to content

Shvandre/GameProject

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

42 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Docs for program design

Basics

Наша программа состоит из нескольких составных частей:

  1. Manager - самая главная штука, она может запускать игру, в теории когда нибудь научится загружать сохранения
  2. Game - это второй главный модуль, но вещей она делает гораздо больше, практически вся игровая логика завязана на этом модуле.
  3. EventListener - глобальная сушность, которая хранит в себе обрабатываемые ивенты, по факту является практически независимой копией игры в игре
  4. Message - штука, благодаря которой события общаются с игрой, передают все необходимые данные для дальнейшей обработки
  5. NewEventListenerInfo - данные о создании нового события
  6. GameData - по факту все данные нашей игры
  7. Binder - объект, умеющий привязывать события к кнопкам, также может привязывать их независимо от кнопок, чтобы они крутились бесконечно
  8. KeyboardListener - обработчик клавиатуры
  9. Display - рисовалка

Manager (Manager)

Штука, которая хранит в себе сам класс игры и умеет вызывать его методы. Т.е. отвечает за запуск игры.

Game (Basic/Game)

Game крутит в себе бесконечный цикл, который:

  1. Принимает нажатую кнопку из KeyboardListener
  2. Запрашивает у биндера функцию, которая привязана к этой кнопке, а также все функции, которые проходят независимо. Каждая из запрашиваемых функций привязана к экзмепляру какого-то из EventListener
  3. Последовательно прогоняет все эти функции и получает от каждой из них Message
  4. Обрабатывает каждый из этих Message

Также Game хранит в себе мастер-копию игровых данных и передает информацию о всех существующих EventListener'ах.

Game - это крайне фундаментальная штука в программе. Лезть с осторожностью.

EventListener (Events/EventListener)

EventListener - основная структура (класс), с которой мы работали. EventListener - это глобальный класс, который обрабатывает некое глобальное событие в игре, например: ходьбу по полю, бой, обработчик инвентаря, любое событие на карте и т.д. На самом деле, EventListener является абстрактным классом, от которого должны наследоваться все остальные, например, FieldEventListener, ReviveEventListener и т.д. Все EventListener'ы представляют собой своеобразное дерево, например, ходьба по полю породила бой, бой породил открытие инвентаря и т.д. Внутри EventListener'а происходят небольшие события, завязанные на функции, напримем функция move внутри FieldEventListenerа, которая подвинет персонажа на один шаг на карте. Сам EventListener хранит в себе не так много вещей:

  1. Свой Id
  2. Id родителя(0, если его нет)
  3. Констатная ссылка на GameData родителя
  4. Своя собственная копия GameData
  5. Дополнительные параметры в зависимости от цели (Например Hero или Price)

Конструктор EventListener'а принимает в себя эти параметры, в наследуемом от него классе вызывается конструктор EventListener'а, а потом идет уже конструктор наследуемого класса, в котором он получает из родительских данных то, что нужно (НЕ ВСЁ!!!), а также биндит необхоимые клавиши, рисует то, что нужно нарисовать функцией redraw (она не везде используется, но в некоторых случаях нужна кое-где ещё в base классе EventListener, что скрыто от глаз разработчика EventListenerа) или непосредственно, а также, вероятно, создает какие-то свои собственные поля.

FieldEventListener::FieldEventListener(const int newId, const int parent, const GameData *newData, Binder *binder) : EventListener(newId, parent, newData, binder){
    data.set_field(parentData->get_field());
    bind('w', &FieldEventListener::move, this, "move up", 1);
    bind('a', &FieldEventListener::move, this, "move left", 2);
    bind('s', &FieldEventListener::move, this, "move right", 3);
    bind('d', &FieldEventListener::move, this, "move down", 4);
    redraw();
}

Вот приблизительно, как это должно выглядеть. В этом примере мы в локальную data записали всё игровое поле родителя, забиндили кнопки, а также вызвали функцию "перерисовать".

Далее в EventListener прописаны какие-то функции, которые могут что-то делать. Если необходимо привязывать свои данные к биндеру, то эта функция должна:

  1. Возвращать объект типа Message(так как тогда эта функция будет напрямую общаться с Game), о котором позже
  2. В качестве параметров принимать только хэшируемые типы(из-за технических ограничений), такие как int, double, char, bool, std::string.

Эти функции могут работать с локальными данными, могут биндить другие функции данного класса, могут кидать запросы отрисовщику, могут(в случаее, если эта функция была забинжена) вернуть Message

Таким образом, создаётся глобальная сущность, которая умеет биндить функции, умеет что-то рисовать, имеет в себе свою собственную копию данных, может являться потомком в графе EventListener'ов, может быть для кого-то предком.

Message (Basic/Message)

Message - объект, с помощью которого события могут общаться с головным центром (Game). Внутри себя Message хранит несколько параметров:

  1. Дельта(в переводе на русский - разница) - копия игровых данных, которая должна быть применена к игровым данным предка, довольно легковесная и умная, так как хранит в себе только то, чем пользовался дочерний EventListener, а также применяет на данные только то, что есть в этой дельте.
  2. NewEventListenerInfo - штука, которая говорит, хочу ли я породить новый EventListener и все прилагающиеся к нему данные, о нём позже более подробно
  3. kill - хочу ли я убить текущий EventListener, например если у меня закончился бой
  4. Id - айдишник EventListenerа, который отправил сообщение
  5. Также есть специальный флаг empty, он говорит, вернули ли вы пустое собщение, или нет, например, если вы ходили по полю и с вами ничего критичного не случилось, можете отправить дефолтный конструктор, в котором empty==true, если вы создаёте Message руками, через конструктор с параметрами, то empty==false, на этот параметр вы никак сами повлиять не можете

NewEventListenerInfo (Events/NewEventListenerInfo/NewEventListenerInfo)

NewEventListenerInfo - абстрактный класс, чьи наследники хранят в себе всю необходимую информацию о генерации нового EventListener. Обязательными полями являются:

  1. parent - айдишник родительского EventListener, или 0, если его нет
  2. freeze - булева переменная, которая показывает, хотите ли вы заморозить выполнение родительского EventListener

В наследуемых от NewEventListenerInfo могут храниться любые поля, которые будут необходимы для порождения нового EventListener'а

GameData (GameData/GameData)

Gamedata - игровые данные. Внутри лежат различные поля класса, также все они обёрнуты в std::shared_ptr, чтобы, если локальная копия EventListenerа не хочет хранить какие то данные, оно занимало не очень много места. С каждым полем вы можете работать с помощью трёх методов;

  1. set - записать данные
  2. get - получить данные
  3. get_ptr - получить по указателю, чтобы, если мы хотим спуститься вглубь, например, GameData->Field->Cells[i][j]->type, мы не плодили лишнюю память. Получаете вы такой же std::shared_ptr

Теперь более конкретно про поля GameData:

  1. isGameOver - флаг, который показывает, завершена ли игра
  2. Field field - игровое поле
  3. std::vector<Hero> heroes - бойцы
  4. Hero dead - последний умерший боец
  5. Inventory inventory - инвентарь

Field

  1. std::pair<int, int> dimensions - размеры поля
  2. std::vector<std::vector<Cell>> cells - двумерный вектор с клетками
  3. std::pair<int, int> current - текущая позиция на поле
  4. int depth - глубина(номер уровня)

Cell

  1. std::string roomtype - тип комнаты(нет комнаты, коридор, комната, граница комнаты(надо для удобства))
  2. NewEventListenerInfo newInfo - информация о событии, которое должно тут произойти, удобно хранить в таком формате, так как можно сразу кинуть в Message

Hero

  1. std::string name - имя персонажа
  2. int maxHp - максимальное здоровье персонажа
  3. int hp - текущее здоровье персонажа
  4. int dmg - базовый урон персонажа
  5. int initiative - инициатива
  6. int attention - внимательность, используется на ловушках
  7. std::vector<std::vector<unsigned>> sprite - спрайт персонажа
  8. std::vector<Ability> - 3 способности персонажа

Ability

  1. std::string name - название способности
  2. std::string hint - подсказка, которая отображается на экране, при выборе способности
  3. bool team - команда, на которую применяется способность. true - свои, false - чужие
  4. bool multitarget - флаг, означающий применение по всем целям сразу или одной
  5. double damage - доля от базового урона, которая нанесётся при атаке
  6. double heal - доля от максимального здоровья, которое будет восполнено
  7. bool healStatus - флаг, означающий восстановление от статус-эффектов
  8. int burn - число ходов, на которое применяется горение
  9. int stun - число ходов, на которое применяется стан

Binder (Basix/Binder/Binder)

Binder - одна из самых фундаментальных вещей, позволяющая вам вешать функции на кнопки или повесить функцию без кнопки. Функции биндера могут вызываться через обёртки на эти функции в EventListener. Сами методы:

  1. bind - передаёте кнопку(-1, если без кнопки), указатель на функцию, а затем аргументы
  2. rebind - передаёте кнопку(-1, если без кнопки), указатель на функцию, а затем аргументы
  3. unbind - указатель на функцию, а затем аргументы Обратите внимание, что unbind можно вызвать без кнопки, он сам понимает, откуда конкретно ему отвязать

Также bind не сработает, если эта функция уже забинжена, тогда используется rebind, а также если на эту кнопку(кроме -1) уже что то забинжено

Биндер не умеет вешать одну функцию на разные кнопки одновременно, а также на одну кнопку много функций.

KeyboardListener (Basic/KeyboardListener)

KeyboardListener - менеджер работы с кнопками. Умеет возвращать кнопку, а также вернуть название кнопки по idшнику.

Mini (micro) docs for Display module

Display

Usage

Сначала нужно поставить себе ncurses чтобы всё точно работало. На маке - через brew. Ubuntu/Debian/Kali etc...

sudo apt-get install libncursesw5-dev
Display display; //Можно сделать любое количество объектов типа Display.
Display display2; //Считайте, что это бесплатно. Запросы можно посылать в любой объект дисплея.
Display display3; //На протяжении всего рантайма должен существовать хотя бы один объект дисплея, 
//когда уничтожается последний объект всё стирается и работа завершается. Держите хотя бы один объект созданным (хотя мы скорее всего просто сделаем один дисплей в мейне)

НЕ СТОИТ СОЗДАВАТЬ static объекты дисплея. Это приводит к ошибкам. Лучше каждый раз создать новый объект. Это бесплатно.

WindowEvent event(WindowEvent::INFO, std::to_string(key)); //В данном случае WindowEvent - это то, что будет 
//отправлено, напечатано в окно с текстом.  
display.SendEvent(event);
struct WindowEvent{
    enum EventType{
        NONE, //Send without any color and [announcment] IDK when should you use, but you can debug with it)
        INFO, //Important info, Blue color
        ACTION, //Very important actions (e.x. in figth), Red color
        REPLY //For something that someone says, Orange color
    } type;
    std::string WindowEventString;
    std::string Author; //For REPLY only
    explicit WindowEvent(EventType type, std::string WindowEventString, std::string Author = "");
    //Тут код сам по себе описывает всё, что можно отправить в окно с текстом, если чего-то не хватает - смело пишите мне - добавим.
};

Использование спрайтов и создание цветов.

Почти все спрайты предвариательно отрисованы (пока нет но будут). И лежат в виде функций в namespace под названием Drawer. Однако, если есть желание, то спрайты можно рисовать самостоятельно.

Usage

NB! объект ColorManager должен быть создан строго ПОСЛЕ того, как инициализирован дисплей. Иначе будет брошено исключение std::runtime_error

//Создаем объект с помощью которого можно создавать пары цветов и сами цвета
ColorManager manager;
unsigned colorPair = manager.CreateColorPair(ColorManager::getColor(0, 255, 153), -1); //Создаем салатовые буквы на стандартном фоне
//Каждая пара цветов имеет тип unsigned. Каждый цвет - short и значение 0-255.
//CreateColorPair создаёт пару из двух цветов. Первый параметр - цвет буквы. Второй - цвет фона.
//Значение цвета -1 означает стандартный цвет. Для цвета текста - белый. Для фона чёрный.
//С помощью static метода getColor(int R, int G, int B) можно получить цвет, максимально близкий к заданному цвету в формате rgb.
//Всего доступно 256 цветов, поэтому выбран будет максимально ПОХОЖИЙ.

Так же существует 8 стандартных цветов.

#define COLOR_BLACK	0
#define COLOR_RED	1
#define COLOR_GREEN	2
#define COLOR_YELLOW	3
#define COLOR_BLUE	4
#define COLOR_MAGENTA	5
#define COLOR_CYAN	6
#define COLOR_WHITE	7

Создаем спрайт Спрайт, это двумерный вектор из символов. Каждый символ - это char | ATTRIBUTES | colorPair. Где | это оператор побитового ИЛИ. colorPair описан выше. ATTRIBUTES - это набор из атрибутов из следующего списка, соединенный с помощью побитового ИЛИ:

A_ITALIC - italic шрифт
A_BLINK - мерцающий
A_UNDERLINE - подчеркнутый
A_ALTCHARSET - другой набор символов (погуглите)
A_BOLD - жирный
A_DIM - половинная яркость
A_REVERSE - развернутый символ
A_INVIS - невидимый симовол 

Существуют и другие возможные аттрибуты. тык P.S. Не все атрибуты работают корректно. Некоторые устарели, некоторые могут не работать с конкретным терминалом. Основные (жирный, мерцание, подчёркивание точно работают, за остальные не ручаюсь)

Теперь действительно создаем спрайт:

std::vector<std::vector<unsigned>> sprite = {{176u | colorPair, 0, 'X' | colorPair, 'X' | colorPair, 'X' | colorPair}};

Важное замечание. Тут 176u - это unsigned со значением 176. 0 !БЕЗ АТРИБУТОВ это "прозрачный пиксель". Чтобы спрайты можно было накладывать друг на друга.

display.DrawSprite(sprite, 10, 10); //координаты в виде x, y считая от левого верхнего угла
display.ClearGraphixWindow(); //Полностью очистит экран

Обратите внимание, что если спрайт не поместится на экран - то будет брошено исключение std::runtime_error.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • C++ 98.0%
  • C 1.8%
  • CMake 0.2%