Did some refactoring in Drawer and Controller, added comments

This commit is contained in:
Dmitriy Shishkov 2022-06-09 21:13:24 +03:00
parent 1a5b905464
commit 258eaf9e3b
No known key found for this signature in database
GPG Key ID: 26720CB2A9608C97
5 changed files with 252 additions and 233 deletions

View File

@ -35,12 +35,13 @@ void Controller::fill(bool solveable) {
fillRandom();
}
void Controller::fillSolveableTable() {
srand(time(NULL)); // инициализируем генератор случайных чисел
auto not_end = remaining; // сохраняем в отдельную переменную количество оставшихся камней
std::set<ThreePoint> positions; // инициализируем сет для хранения доступных для вставки позиций
PosSet positions; // инициализируем сет для хранения доступных для вставки позиций
positions.insert(getRandLowest()); // вставляем случайную начальную позицию
@ -66,7 +67,7 @@ void Controller::fillSolveableTable() {
}
}
wxPoint Controller::getRandLowest() {
wxPoint Controller::getRandLowest() const {
int overall = gridSize.x * gridSize.y; // вычисляем количество позиций в горизонтальном "срезе" массива
int x, y; // объявляем координаты для возвращаемой позиции
@ -79,7 +80,7 @@ wxPoint Controller::getRandLowest() {
return wxPoint(x, y); // возвращаем wxPoint
}
void Controller::emplace_table(CardT id, const ThreePoint& pos, std::set<ThreePoint>& positions) {
void Controller::emplace_table(CardT id, const ThreePoint& pos, PosSet& positions) {
table[pos.z][pos.x][pos.y] = id;
push_available(positions, pos); // записываем в сет новые позиции
@ -89,7 +90,7 @@ void Controller::emplace_table(CardT id, const ThreePoint& pos, std::set<ThreePo
#include <wx/file.h>
void print_list(const std::set<ThreePoint>& positions) {
void print_list(const PosSet& positions) {
wxFile f("tmp.txt", wxFile::write_append); // Открываем файл для записи
for (const auto& el : positions) // Итерируемся по всем позициям
@ -100,8 +101,8 @@ void print_list(const std::set<ThreePoint>& positions) {
#endif
void Controller::next_rand(std::set<ThreePoint>& positions,
std::set<ThreePoint>::iterator& ptr,
void Controller::next_rand(PosSet& positions,
PosSet::iterator& ptr,
bool canOverlap, uint8_t& not_end) {
#ifdef WXDEBUG // Если компилируем в режиме дебага
print_list(positions); // выводим список позиций
@ -111,42 +112,38 @@ void Controller::next_rand(std::set<ThreePoint>& positions,
positions.erase(ptr); // удаляем только что вставленный итератор
if (not_end) {
if (positions.empty())
ptr = positions.insert(getRandLowest()).first;
else {
ptr = positions.begin();
if (not_end) { // если ещё есть камни для вставки
if (positions.empty()) // если не осталось позиций,
ptr = positions.insert(getRandLowest()).first; // вставляем любую позицию из нижней плоскости и используем её в следующий раз
else { // иначе
ptr = positions.begin(); // устанавливаем итератор в первую позицию
int rand_d = rand() % positions.size();
int rand_d = rand() % positions.size(); // получаем случайное смещение внутри набора возможных позиций
for (int i = 0; i < rand_d; i++)
ptr++;
std::advance(ptr, rand_d); // смещаем итератор
auto rand_ptr = ptr;
const auto rand_ptr = ptr; // сохраняем предыдущее положение итератора
while (!canOverlap && ptr != positions.end() && wouldOverlap(prev, *ptr)) // Пока не найдём тот, что не будет закрывать только что вставленную позицию, если не canBeUp
ptr++;
if (ptr == positions.end())
ptr = positions.begin();
while (!canOverlap && ptr != positions.end() && wouldOverlap(prev, *ptr)) // Пока не найдём тот, что не будет закрывать только что вставленную позицию, если не canBeUp или дошли до конца набора
ptr++; // наращиваем итератор
while (!canOverlap && ptr != rand_ptr && wouldOverlap(prev, *ptr))
ptr++;
if (ptr == positions.end()) { // если ни одна из позиций начиная с rand_ptr не подошла (нельзя выбирать накрывающую предыдущий камень и все позиции накрывают)
ptr = positions.begin(); // начинаем с начала
if (ptr == rand_ptr && wouldOverlap(prev, *ptr)) {
auto _ = wouldOverlap(prev, *ptr);
if (not_end == positions.size())
ptr = positions.begin();
else {
auto res = positions.insert(getRandLowest());
while (!canOverlap && ptr != rand_ptr && wouldOverlap(prev, *ptr)) // Пока не найдём тот, что не будет закрывать только что вставленную позицию, если не canBeUp и не дошли до rand_ptr
ptr++; // наращиваем итератор
}
while (!res.second)
res = positions.insert(getRandLowest());
if (ptr == rand_ptr && !canOverlap && wouldOverlap(prev, *ptr)) { // если итератор совпадает с rand_ptr и при этом ptr перекрывает prev,
if (not_end == positions.size()) // если уже все позиции добавлены в набор
ptr = positions.begin(); // просто выбираем первую из них
else { // иначе
auto res = positions.insert(getRandLowest()); // пытаемся вставить вставляем случайную позицию в нижней плоскости
if (!res.second)
throw std::runtime_error("Wrong map layout or internal error");
while (!res.second) // пока не произошла вставка позиции в набор
res = positions.insert(getRandLowest()); // пытаемся вставить случайную позицию в нижней плоскости
ptr = res.first;
ptr = res.first; // получаем итератор на только что вставленную позицию
}
}
}
@ -166,8 +163,8 @@ bool Controller::wouldOverlap(const ThreePoint& prev, const ThreePoint& next) {
/**
* Checks if position `p`, shifted by `d`, is not out of bounds of array `table` with dimensions `gridSize`
*/
bool Controller::corrInd(const ThreePoint& p, const ThreePoint& d) {
auto& gS = gridSize; // более короткий алиса для переменной
bool Controller::corrInd(const ThreePoint& p, const ThreePoint& d) const {
auto& gS = gridSize; // более короткий алиас для переменной
return ((d.z == 0) || (d.z < 0 && p.z >= -d.z) || (d.z > 0 && p.z + d.z < gS.z)) &&
((d.x == 0) || (d.x < 0 && p.x >= -d.x) || (d.x > 0 && p.x + d.x < gS.x)) &&
@ -177,27 +174,29 @@ bool Controller::corrInd(const ThreePoint& p, const ThreePoint& d) {
/**
* Checks if position `p`, shifted by `d`, is not out of bounds and available for insert (FREE)
*/
bool Controller::Free(const ThreePoint& p, const ThreePoint& d) {
bool Controller::Free(const ThreePoint& p, const ThreePoint& d) const {
return corrInd(p, d) && (table[p.z + d.z][p.x + d.x][p.y + d.y] == FREE);
}
/**
* Checks if position `p`, shifted by `d`, is out of bounds or unavailable for insert (FREE)
*/
bool Controller::NFree(const ThreePoint& p, const ThreePoint& d) {
bool Controller::NFree(const ThreePoint& p, const ThreePoint& d) const {
return !corrInd(p, d) || (table[p.z + d.z][p.x + d.x][p.y + d.y] != FREE);
}
/**
* Pushes all positions, that are close to `pos`, available for insert and don't overlap other available positions
*/
void Controller::push_available(std::set<ThreePoint>& positions,
const ThreePoint& pos) {
auto& p = pos;
void Controller::push_available(PosSet& positions,
const ThreePoint& pos) const {
auto& p = pos; // короткий алиас для переменной
int z = pos.z, x = pos.x, y = pos.y;
int z = pos.z, x = pos.x, y = pos.y; // "разбираем" объект pos на координаты
if (NFree(p, {-1, -2, 0}) && NFree(p, {-1, -3, 0}) && NFree(p, {-1, -2, -1}) && NFree(p, {-1, -3, -1}) && NFree(p, {-1, -2, 1}) && NFree(p, {-1, -3, 1}) && Free(p, {0, -2, 0}))
// дальше идёт миллион условий, которые проще просто прочитать, нежели описывать, что они проверяют. В комментариях возле условий указано, в какую сторону относительно pos смещается вставляемая позиция
if (NFree(p, {-1, -2, 0}) && NFree(p, {-1, -3, 0}) && NFree(p, {-1, -2, -1}) && NFree(p, {-1, -3, -1}) && NFree(p, {-1, -2, 1}) && NFree(p, {-1, -3, 1}) && Free(p, {0, -2, 0})) // left
positions.emplace(z, x-2, y);
if (NFree(p, {-1, 2, 0}) && NFree(p, {-1, 3, 0}) && NFree(p, {-1, 2, -1}) && NFree(p, {-1, 3, -1}) && NFree(p, {-1, 2, 1}) && NFree(p, {-1, 3, 1}) && Free(p, {0, 2, 0})) // right
positions.emplace(z, x+2, y);
@ -271,60 +270,60 @@ void Controller::free_table() {
}
}
steps = decltype(steps)();
steps = decltype(steps)(); // сбрасываем стек steps (decltype удобен, ибо тогда не зависим от класса, просто вызываем стандартный конструктор)
}
void Controller::fillRandom() {
srand(time(NULL));
srand(time(NULL)); // инициализируем генератор случайных чисел
wxLogDebug(itowxS(remaining));
auto not_end = remaining;
auto not_end = remaining; // сохраняем количество оставшихся для вставки камней
// итерируемся по всему массиву поля, пока не вставим все камни
for (int z = 0; z < gridSize.z && not_end; z++)
for (int x = 0; x < gridSize.x && not_end; x++)
for (int y = 0; y < gridSize.y && not_end; y++)
if (table[z][x][y] == FREE) {
table[z][x][y] = genRandId();
not_end--;
if (table[z][x][y] == FREE) { // если в эту позицию можно вставить камень
table[z][x][y] = genRandId(); // получаем случайный id и вставляем его туда
not_end--; // уменьшаем счётчик оставшихся камней
}
}
CardT Controller::genRandId() {
CardT id;
int w = 0;
do {
id = rand() % TILE_IMAGES_N;
w++;
} while (cardsCounter[id] == 0);
id = rand() % TILE_IMAGES_N; // получаем случайное число-индекс в массиве имён камней
} while (cardsCounter[id] == 0); // повторяем тело цикла, если эти id уже закончились
cardsCounter[id]--;
cardsCounter[id]--; // уменьшаем счётчик оставшихся для вставки камней этого типа
return id;
return id; // возвращаем полученный id
}
CardT Controller::getFreeSingularId(CardT prev) {
CardT id = (prev < 38) ? 34 : 38;
CardT id = (prev < 38) ? 34 : 38; // устанавливаем первый из id, которые считаются одинаковыми с prev
while (id < TILE_IMAGES_N && cardsCounter[id] == 0)
while (id < TILE_IMAGES_N && cardsCounter[id] == 0) // ищем в массиве оставшихся камней свободный id (если начинаем с 34, так как id выбираются парами, обязательно останется хотя бы один id, пренадлежащий этой группе (до 48), поэтому границу можно оставить одинаковой)
id++;
cardsCounter[id]--;
cardsCounter[id]--; // уменьшаем счётчик оставшихся id
return id;
return id; // возвращаем его
}
/**
* Gets pointer to top card by grid position
* It also changes point to top right coordinate of card
*/
CardT* Controller::getCardByPosition(ThreePoint& point) {
int8_t topIndex = -1;
CardT* res = nullptr;
int8_t topIndex = -1; // начинаем с -1, чтобы если не нашёлся ни один камень, получить невалидную позицию
CardT* res = nullptr; // указатель на элемент массива
ThreePoint realPos = point;
ThreePoint realPos(point); // сохраняем копию позиции, чтобы при смещении не ломать позицию, для которой ищем
// ищем верхнюю карту с верхним левым углом в данной позиции (нажатие в левую верхнюю четверть камня)
for (int z = table.size() - 1; z >= 0; z--)
if (table[z][point.x][point.y] >= 0) {
if (z > topIndex) {
@ -334,6 +333,7 @@ CardT* Controller::getCardByPosition(ThreePoint& point) {
break;
}
// ищем верхнюю карту с верхним левым углом в данной позиции, смещённой на единицу влево (нажатие в правую верхнюю четверть камня)
if (point.x > 0)
for (int z = table.size() - 1; z >= 0; z--)
if (table[z][point.x - 1][point.y] >= 0) {
@ -347,6 +347,7 @@ CardT* Controller::getCardByPosition(ThreePoint& point) {
break;
}
// ищем верхнюю карту с верхним левым углом в данной позиции, смещённой на единицу вверх (нажатие в левую нижнюю четверть камня)
if (point.y > 0)
for (int z = table.size() - 1; z >= 0; z--)
if (table[z][point.x][point.y - 1] >= 0) {
@ -360,6 +361,7 @@ CardT* Controller::getCardByPosition(ThreePoint& point) {
break;
}
// ищем верхнюю карту с верхним левым углом в данной позиции, одновременно смещённой вверх и влево (нажатие в правую нижнюю четверть камня)
if (point.x > 0 && point.y > 0)
for (int z = table.size() - 1; z >= 0; z--)
if (table[z][point.x - 1][point.y - 1] >= 0) {
@ -372,7 +374,8 @@ CardT* Controller::getCardByPosition(ThreePoint& point) {
}
break;
}
// обновляем переданную позицию так, чтобы она указывала на левый верхний угол карты
point.x = realPos.x;
point.y = realPos.y;
point.z = topIndex;
@ -386,7 +389,7 @@ bool Controller::available(const ThreePoint& point) const {
bool Controller::upFree(const ThreePoint& point) const {
if (point.z == table.size() - 1)
if (point.z == table.size() - 1) // если находимся на самом верхнем уровне по оси z
return true;
return !((table[point.z + 1][point.x][point.y] >= 0) ||
@ -429,58 +432,54 @@ bool Controller::sideFree(const ThreePoint& point) const {
}
void Controller::handleClick(const wxPoint& point) {
wxPoint posPlain = drawer.toGrid(point);
ThreePoint pos(drawer.toGrid(point)); // переводим позицию в координатах окна в координаты сетки
ThreePoint pos = {-1, posPlain.x, posPlain.y};
if (pos.x > -1) { // если попали по полю
CardT* card = getCardByPosition(pos); // получаем карту, в которую попали и смещаем позицию в её левый верхний угол
if (pos.x > -1) {
CardT* card = getCardByPosition(pos);
if (pos.z >= 0 && available(pos)) {
if (selected != nullptr && sameValues(*card, *selected) &&
selected != card) {
steps.push({CardEntry{drawer.marked, *selected},
if (pos.z >= 0 && available(pos)) { // если действительно получили карту и она доступна для убирания
if (selected != nullptr && sameValues(*card, *selected) && // если уже есть выбранная карта и она такая же, как эта по значению,
selected != card) { // но при этом не является тем же указателем
steps.push({CardEntry{drawer.marked, *selected}, // сохраняем эту пару в истории
CardEntry{pos, *card}});
*selected = MATCHED;
*selected = MATCHED; // записываем в доску то, что эти карты убраны
*card = MATCHED;
selected = nullptr;
selected = nullptr; // сбрасываем убранную карту
remaining -= 2;
remaining -= 2; // уменьшаем счётчик оставшихся для убирания карт на 1
drawer.marked = {-1, -1, -1};
drawer.marked = {-1, -1, -1}; // сбрасываем координаты выбранной карты для "художника"
} else {
selected = card;
drawer.marked = pos;
selected = card; // устанавливаем указатель на выбранную сейчас карту
drawer.marked = pos; // устанавливаем координаты выбранной карты для "художника"
}
}
}
}
bool Controller::sameValues(CardT a, CardT b) const {
if (a == b)
if (a == b) // если id карт равны
return true;
else if (a >= 38 && b >= 38)
else if (a >= 38 && b >= 38) // или они входят в одну
return true;
else if (a >= 34 && a <= 37 && b >= 34 && b <= 37)
else if (a >= 34 && a <= 37 && b >= 34 && b <= 37) // из групп, где каждой карты по одной,но при этом все они считаются одинаковыми
return true;
return false;
}
void Controller::undo() {
if (steps.size()) {
for (const CardEntry& entry : steps.top()) {
table[entry.pos.z][entry.pos.x][entry.pos.y] = entry.id;
cardsCounter[entry.id]++;
}
if (steps.size()) { // если есть шаги для отмены
for (const CardEntry& entry : steps.top()) // в цикле по каждому из пары камней
table[entry.pos.z][entry.pos.x][entry.pos.y] = entry.id; // возвращаем его на доску
remaining += 2;
steps.pop();
remaining += 2; // наращиваем счётчик оставшихся для уборки камней
steps.pop(); // удаляем только что восстановленные камни из истории
}
}
bool Controller::gameStarted() const {
return stopwatch > 0;
return stopwatch > 0; // если счётчик таймера наращивается, игра началась
}

View File

@ -2,7 +2,6 @@
#define CONTROLLER_H
#include <array>
#include <set>
#include <stack>
#include "wxw.h"
@ -44,19 +43,19 @@ private:
void fillSolveableTable();
wxPoint getRandLowest();
wxPoint getRandLowest() const;
void emplace_table(CardT id, const ThreePoint& pos, std::set<ThreePoint>& positions);
void next_rand(std::set<ThreePoint>& positions,
std::set<ThreePoint>::iterator& ptr, bool canOverlap, uint8_t& not_end);
void emplace_table(CardT id, const ThreePoint& pos, PosSet& positions);
void next_rand(PosSet& positions,
PosSet::iterator& ptr, bool canOverlap, uint8_t& not_end);
bool wouldOverlap(const ThreePoint& prev, const ThreePoint& next);
bool corrInd(const ThreePoint& p, const ThreePoint& d);
bool Free(const ThreePoint& p, const ThreePoint& d);
bool NFree(const ThreePoint& p, const ThreePoint& d);
bool corrInd(const ThreePoint& p, const ThreePoint& d) const;
bool Free(const ThreePoint& p, const ThreePoint& d) const;
bool NFree(const ThreePoint& p, const ThreePoint& d) const;
void push_available(std::set<ThreePoint>& positions, const ThreePoint& pos);
void push_available(PosSet& positions, const ThreePoint& pos) const;
void fillRandom();

View File

@ -1,5 +1,8 @@
#include "Drawer.h"
#include <wx/filename.h>
#include <wx/stdpaths.h>
static const char* tileImageNames[TILE_IMAGES_N] = {
// clang-format off
"Pin1", "Pin2", "Pin3", "Pin4", "Pin5", "Pin6", "Pin7", "Pin8", "Pin9",
@ -12,112 +15,117 @@ static const char* tileImageNames[TILE_IMAGES_N] = {
// clang-format on
};
Drawer::Drawer() : marked{-1, -1, -1} {
wxString path = wxStandardPaths::Get().GetUserDataDir() + wxFileName::GetPathSeparator() + _("tiles") + wxFileName::GetPathSeparator();
for (int i = 0; i < TILE_IMAGES_N; i++) { // В цикле по именам изображений
bool succeed = tileImages[i].LoadFile( // загружаем их
path + _(tileImageNames[i]) + _(".png"), // из стандартной папки для ресурсов приложения
wxBITMAP_TYPE_PNG); // в формате png
if (!succeed) // В случае ошибки заргрузки выводим сообщение об этом
wxLogDebug(_("failed to load tile ./resources/tiles/") + // с путём,
_(tileImageNames[i]) + _(".png with index") + // именем файла
wxString::Format("%i", i)); // и индексом в массиве
}
}
void Drawer::drawTable(wxDC& dc) const {
dc.DrawBitmap(bgBitmap, 0, 0, false); // отрисовываем в dc битмап с фоном, начиная из левого верхнего угла без маски
if (boardBitmap.IsOk()) { // Если изображение доски построено
wxLogDebug("Drawing board");
dc.DrawBitmap(boardBitmap, boardPixelRect.GetPosition(), true); // отрисовываем битмап с ней в установленном при рейсайзе положении, используя маску
}
}
static const wxColor lGreen{0x07, 0x55, 0x2b};
static const wxColor dGreen{0x01, 0x2d, 0x16};
Drawer::Drawer() : marked{-1, -1, -1} {
for (int i = 0; i < TILE_IMAGES_N; i++) {
bool succeed = tileImages[i].LoadFile(
_("./resources/tiles/") + _(tileImageNames[i]) + _(".png"),
wxBITMAP_TYPE_PNG);
if (!succeed)
wxLogDebug(_("failed to load tile ./resources/tiles/") +
_(tileImageNames[i]) + _(".png with index") +
wxString::Format("%i", i));
}
}
void Drawer::drawTable(wxDC& dc) {
dc.DrawBitmap(bgBitmap, 0, 0, false);
if (boardBitmap.IsOk()) {
wxLogDebug("Drawing board");
dc.DrawBitmap(boardBitmap, tablePixelRect.GetPosition(), true);
}
}
void Drawer::composeBG() {
bgBitmap = wxBitmap(resolution);
bgBitmap = wxBitmap(resolution); // создаём битмап размером со всю панейль
wxLogDebug(
wxString::Format("Rebuild bg %i %i", resolution.x, resolution.y));
wxMemoryDC dc;
dc.SelectObject(bgBitmap);
wxMemoryDC dc; // создаём dc в памяти
dc.SelectObject(bgBitmap); // выбираем свежесозданный битмап как холст для рисования
dc.GradientFillConcentric(wxRect(wxPoint(0, 0), resolution), lGreen,
dGreen);
dc.GradientFillConcentric(wxRect(wxPoint(0, 0), resolution), lGreen, // рисуем радиальный градиент на весь битмап, переходящий от светлозелёного в центре
dGreen); // к тёмнозелёному по краям
}
void Drawer::composeBoard(const TLVec& layout, const Dimensions& gridSize) {
boardBitmap = wxBitmap(tablePixelRect.GetSize());
void Drawer::composeBoard(const TLVec& table, const Dimensions& gridSize) {
boardBitmap = wxBitmap(boardPixelRect.GetSize()); // Создаём битмап согласно размерам, полученным при последнем ресайзе окна
wxLogDebug(_("Rebuild board"));
wxMemoryDC dc;
dc.SelectObject(boardBitmap);
wxMemoryDC dc; // создаём dc в памяти
dc.SelectObject(boardBitmap); // выбираем свежесозданный битмап как холст для рисования
// итерируемся по всем индексам массива table
for (int z = 0; z < gridSize.z; z++)
for (int x = 0; x < gridSize.x; x++)
for (int y = 0; y < gridSize.y; y++) {
CardT c = layout[z][x][y];
if (c >= 0)
drawTile(dc, c, fromGrid(x, y), z);
CardT c = table[z][x][y]; // получаем id в данной позиции
if (c >= 0) // если тут стоит камень
drawTile(dc, c, fromGrid(x, y), z); // отрисовываем его
}
dc.SelectObject(wxNullBitmap);
dc.SelectObject(wxNullBitmap); // отцепляем dc (обычно это происходит при выходе объекта за поле видимости, но здесь это нужно сделать вручную, чтобы создать маску)
wxMask* mask = new wxMask(boardBitmap, wxColor(0x00, 0x00, 0x00));
boardBitmap.SetMask(mask);
wxMask* mask = new wxMask(boardBitmap, wxColor(0x00, 0x00, 0x00)); // создаём маску по полю, чтобы при отрисовке там, где нет камней не было чёрного фона
boardBitmap.SetMask(mask); // устанавливаем маску для битмапа
}
void Drawer::drawTile(wxDC& dc, int8_t index, const wxPoint& position,
uint8_t zIndex) const {
wxBrush _bgColor = dc.GetBrush();
wxBrush _bgColor = dc.GetBrush(); // сохраняем цвет кисти, которая была в dc по умолчанию
wxBrush front = wxColor(0xff, 0xff, 0xff);
wxBrush back = wxColor(0xc8, 0xc8, 0xc8);
wxBrush front = wxColor(0xff, 0xff, 0xff); // создаём кисти для лицевой части камня
wxBrush back = wxColor(0xc8, 0xc8, 0xc8); // и подложки
if (position == fromGrid({marked.x, marked.y}) && marked.z == zIndex) {
front = wxColor(0xc8, 0xff, 0xc8);
// wxLogDebug(wxString::Format("%i %i %i - %i %i %i (%i %i %i)", zIndex, position.x, position.y, marked.z, fromGrid(marked).x, fromGrid(marked).y, marked.z, marked.x, marked.y));
if (position == fromGrid(marked) && marked.z == zIndex) { // если данный камень выбран,
front = wxColor(0xc8, 0xff, 0xc8); // заменяем цвета на аналогичные с зелёным оттенком
back = wxColor(0xbe, 0xdc, 0xbe);
}
dc.SetBrush(back);
dc.SetBrush(back); // устанавливаем кисть для рисования подложки камня
dc.DrawRoundedRectangle(position.x + (tilePixelSize.GetWidth() / TILE_WIDTH * TILE_PADDING_SCALE) -
(tilePixelSize.GetWidth() / TILE_WIDTH * TILE_PADDING_SCALE) * zIndex,
position.y + (tilePixelSize.GetHeight() / TILE_WIDTH * TILE_PADDING_SCALE) -
(tilePixelSize.GetHeight() / TILE_WIDTH * TILE_PADDING_SCALE) * zIndex,
tilePixelSize.GetWidth() * 2,
tilePixelSize.GetHeight() * 2, (tilePixelSize.GetHeight() / TILE_HEIGHT * TILE_PADDING_SCALE));
dc.DrawRoundedRectangle( // рисуем подложку,
position.x - tilePadding.x * (zIndex - 1), // смещённую относительно плоской позиции на оду единицу смещения вправо и на zIndex единиц влево,
position.y - tilePadding.y * (zIndex - 1), // на единицу смещения вниз и на zIndex единиц вверх
tilePixelSize.x * 2, tilePixelSize.y * 2, // размер каждой из сторон в два раза больше, чем размеры на сетке
tilePadding.y // радиус закругления равен одной единице смещения
);
dc.SetBrush(front);
dc.DrawRoundedRectangle(
position.x - (tilePixelSize.GetWidth() / TILE_WIDTH * TILE_PADDING_SCALE) * zIndex,
position.y - (tilePixelSize.GetHeight() / TILE_WIDTH * TILE_PADDING_SCALE) * zIndex,
tilePixelSize.GetWidth() * 2, tilePixelSize.GetHeight() * 2, (tilePixelSize.GetHeight() / TILE_HEIGHT * TILE_PADDING_SCALE));
dc.DrawRoundedRectangle( // рисуем лицевую часть,
position.x - tilePadding.x * zIndex, // смещённую относительно плоской позиции на zIndex единиц смещения влево
position.y - tilePadding.y * zIndex, // и на zIndex единиц вверх
tilePixelSize.x * 2, tilePixelSize.y * 2, // размер каждой из сторон в два раза больше, чем размеры на сетке
tilePadding.y // радиус закругления равен одной единице смещения
);
dc.SetBrush(_bgColor);
dc.SetBrush(_bgColor); // возвращаем изначальный цвет кисти dc
if (tileImages[index].IsOk()) {
wxPoint pos;
pos.x = position.x + (tilePixelSize.GetWidth() / TILE_WIDTH * TILE_PADDING_SCALE) - (tilePixelSize.GetWidth() / TILE_WIDTH * TILE_PADDING_SCALE) * zIndex;
pos.y = position.y + (tilePixelSize.GetHeight() / TILE_HEIGHT * TILE_PADDING_SCALE) - (tilePixelSize.GetHeight() / TILE_WIDTH * TILE_PADDING_SCALE) * zIndex;
if (tileImages[index].IsOk()) { // если при загрузке картинки не возникло проблем
wxPoint pos; // верхний левый угол картинки (так же как у подложки) смещён относительно лицевой части на одну единицу смещения
pos.x = position.x - tilePadding.x * (zIndex - 1);
pos.y = position.y - tilePadding.y * (zIndex - 1);
if (tileImages[index].GetWidth() != tilePixelSize.x * 2 - tilePixelSize.GetWidth() / TILE_WIDTH * TILE_PADDING_SCALE * 2)
dc.DrawBitmap(tileImages[index].Scale(tilePixelSize.x * 2 - tilePixelSize.GetWidth() / TILE_WIDTH * TILE_PADDING_SCALE * 2,
tilePixelSize.y * 2 - tilePixelSize.GetHeight() / TILE_HEIGHT * TILE_PADDING_SCALE * 2),
pos);
else
dc.DrawBitmap(tileImages[index], pos);
dc.DrawBitmap( // отрисовываем картинку, масштабируя её под размер камня - две единицы смещения для отступов
tileImages[index].Scale(tilePixelSize.x * 2 - tilePadding.x * 2,
tilePixelSize.y * 2 - tilePadding.y * 2),
pos);
}
}
void Drawer::resizeBg(const wxSize& resolution) {
if (this->resolution != resolution) {
this->resolution = resolution;
composeBG();
if (this->resolution != resolution) { // если размер окна действительно изменился
this->resolution = resolution; // устанавливаем новое разрешение и
composeBG(); // перерисовываем фон
}
}
@ -125,78 +133,87 @@ void Drawer::resizeBg(const wxSize& resolution) {
* Resizes tile and whole board bitmap size to the resolution, set in this
* instance
*/
bool Drawer::resizeBoard(const TLVec& layout, const Dimensions& gridSize, bool force) {
bool res = false;
bool Drawer::resizeBoard(const TLVec& table, const Dimensions& gridSize, bool force) {
bool res = false; // произошла ли полная перерисовка поля, или только был о рассчитано новое положение битмапа
const int gridPoint = mmin(
const int gridPoint = mmin( // минимум из двух осей (по x и по y), подробнее о формулах в (4)
resolution.x / (gridSize.x * TILE_WIDTH + gridSize.z * TILE_PADDING_SCALE),
resolution.y * TILE_WIDTH /
(gridSize.y * TILE_HEIGHT * TILE_WIDTH + TILE_HEIGHT * gridSize.z * TILE_PADDING_SCALE));
wxLogDebug(wxString::Format("Resize board: %i", gridPoint));
if (gridPoint != prevGridPoint || force) {
tilePixelSize.Set(gridPoint * TILE_WIDTH, gridPoint * TILE_HEIGHT);
if (gridPoint != prevGridPoint || force) { // если gridPoint изменился, или перерасчёт принудительный
tilePixelSize.Set(gridPoint * TILE_WIDTH, gridPoint * TILE_HEIGHT); // устанавливаем новый размер половины стороны камня (по сетке)
boardPadding.x = (tilePixelSize.x / TILE_WIDTH * TILE_PADDING_SCALE) * (gridSize.z - 1); // Смещение, создаваемое самыми левыми картами на верхних позициях (их может и не быть, но проверять это дорого)
boardPadding.y = (tilePixelSize.y / TILE_WIDTH * TILE_PADDING_SCALE) * (gridSize.z - 1); // Смещение, создаваемое самыми верхними (в плоскости xy) картами на верхних позициях (их может и не быть, но проверять это дорого)
tilePadding.x = tilePixelSize.x / TILE_WIDTH * TILE_PADDING_SCALE; // Смещение, даваемое подложками карт вдоль оси x
tilePadding.y = tilePixelSize.y / TILE_WIDTH * TILE_PADDING_SCALE; // Смещение, даваемое подложками карт вдоль оси y
tablePixelRect.SetSize(
wxSize((tilePixelSize.x * gridSize.x) + // Размер только плоских карт
boardPadding.x + // см. выше
(tilePixelSize.x / TILE_WIDTH * TILE_PADDING_SCALE), // Смещение, даваемое подложками самых правых
(tilePixelSize.y * gridSize.y) + // Размер только плоских карт
boardPadding.y + // см. выше
(tilePixelSize.y / TILE_WIDTH * TILE_PADDING_SCALE) // Смещение, даваемое подложками самых нижних (в плоскости xy)
)
boardPadding.x = tilePadding.x * (gridSize.z - 1); // Смещение, создаваемое самыми левыми картами на верхних позициях (их может и не быть, но проверять это дорого)
boardPadding.y = tilePadding.y * (gridSize.z - 1); // Смещение, создаваемое самыми верхними (в плоскости xy) картами на верхних позициях (их может и не быть, но проверять это дорого)
boardPixelRect.SetWidth(
(tilePixelSize.x * gridSize.x) + // Размер только плоских карт
boardPadding.x + // см. выше
tilePadding.x // Смещение, даваемое подложками самых правых
);
boardPixelRect.SetHeight(
(tilePixelSize.y * gridSize.y) + // Размер только плоских карт
boardPadding.y + // см. выше
tilePadding.y // Смещение, даваемое подложками самых нижних (в плоскости xy)
);
}
tablePixelRect.SetPosition({(resolution.x - tablePixelRect.width) / 2,
(resolution.y - tablePixelRect.height) / 2});
boardPixelRect.SetPosition( // выравниваем по центру окна
wxPoint((resolution.x - boardPixelRect.width) / 2,
(resolution.y - boardPixelRect.height) / 2)
);
if (gridPoint != prevGridPoint || force) {
composeBoard(layout, gridSize);
res = true;
if (gridPoint != prevGridPoint || force) { // если gridPoint изменился, или перерасчёт принудительный
composeBoard(table, gridSize); // перерисовываем стол
res = true; // и сообщаем об этом
}
prevGridPoint = gridPoint;
prevGridPoint = gridPoint; // сохраняем новое значение gridPoint для "кеширования" стола
return res;
}
wxPoint Drawer::toGrid(wxPoint point) const {
wxPoint out(-1, -1);
wxPoint Drawer::toGrid(const wxPoint& point) const {
wxPoint out(-1, -1); // инициализируем координату не валидными значениями
point.x -= boardPadding.x;
point.y -= boardPadding.y;
if (point.x >= tablePixelRect.x &&
point.x <= tablePixelRect.x + tablePixelRect.width &&
point.y >= tablePixelRect.y &&
point.y <= tablePixelRect.y + tablePixelRect.height) {
out.x = (point.x - tablePixelRect.x) / tilePixelSize.x;
out.y = (point.y - tablePixelRect.y) / tilePixelSize.y;
if (point.x >= boardPixelRect.x + boardPadding.x && // если попали внутрь стола (за исключением декоративных отступов)
point.x <= boardPixelRect.x + boardPixelRect.width - tilePadding.x &&
point.y >= boardPixelRect.y + boardPadding.y &&
point.y <= boardPixelRect.y + boardPixelRect.height - tilePadding.y) {
out.x = (point.x - boardPixelRect.x - boardPadding.x) / tilePixelSize.x; // перводим курсор в координаты стола (поэтому вычитаем положение битмапа и декоративные отступы)
out.y = (point.y - boardPixelRect.y - boardPadding.y) / tilePixelSize.y;
}
return out;
return out; // возвращаем либо заглушку, либо, если попали, валидные координаты
}
/**
* Converts from grid position to board bitmap coordinates
*/
wxPoint Drawer::fromGrid(int x, int y) const {
return {x * tilePixelSize.x + boardPadding.x, y * tilePixelSize.y + boardPadding.y};
return {x * tilePixelSize.x + boardPadding.x, y * tilePixelSize.y + boardPadding.y}; // переводим из координат сетки в позицию внутри битмапа
}
/**
* Converts from grid position to board bitmap coordinates
*/
wxPoint Drawer::fromGrid(const wxPoint& point) const {
return fromGrid(point.x, point.y);
return fromGrid(point.x, point.y); // просто проксируем класс wxPoint в функцию с двумя аргументами
}
wxSize Drawer::composeMinSize(const Dimensions& gridSize) {
wxSize Drawer::composeMinSize(const Dimensions& gridSize) const {
wxSize ms;
ms.SetWidth(TILE_WIDTH * gridSize.x + gridSize.z * TILE_PADDING_SCALE);
ms.SetHeight(TILE_HEIGHT * gridSize.y + gridSize.z * TILE_PADDING_SCALE * TILE_HEIGHT / TILE_WIDTH);
ms.SetWidth(TILE_WIDTH * gridSize.x + gridSize.z * TILE_PADDING_SCALE); // минимальная ширина экрана при gridPoint = 1
ms.SetHeight(TILE_HEIGHT * gridSize.y + gridSize.z * TILE_PADDING_SCALE * TILE_HEIGHT / TILE_WIDTH); // минимальная высота экрана при gridPoint = 1
ms += {1, 1};
ms += {1, 1}; // добавляем по единице к каждой из сторон, чтобы при округлении не получить число меньше, чем нужно
wxLogDebug(wxString::Format("MinSize %i %i", ms.x, ms.y));

View File

@ -16,25 +16,25 @@ class Drawer {
public:
Drawer();
void drawTable(wxDC& dc);
void drawTable(wxDC& dc) const;
void composeBG();
void composeBoard(const TLVec& layout, const Dimensions& gridSize);
void composeBoard(const TLVec& table, const Dimensions& gridSize);
void resizeBg(const wxSize& tableSize);
bool resizeBoard(const TLVec& layout, const Dimensions& gridSize, bool force);
bool resizeBoard(const TLVec& table, const Dimensions& gridSize, bool force);
wxPoint toGrid(wxPoint point) const;
wxPoint toGrid(const wxPoint& point) const;
wxPoint fromGrid(int x, int y) const;
wxPoint fromGrid(const wxPoint& point) const;
wxSize composeMinSize(const Dimensions& gridSize);
wxSize composeMinSize(const Dimensions& gridSize) const;
wxSize tableSize;
wxSize tilePixelSize; // кратно 3x4, по умолчанию 600x800
wxSize resolution;
wxRect tablePixelRect;
wxRect boardPixelRect;
ThreePoint marked;

52
utils.h
View File

@ -3,60 +3,64 @@
#include "wxw.h"
#include <set>
#include <unordered_set>
#include <vector>
#include <iterator>
using std::vector;
wxString LTimeToStr(int time);
int upDiv(int a, int b);
wxString itowxS(int a);
wxString PRemaining(uint8_t remaining);
#define mmin(a, b) (a + b - abs(a - b)) / 2
#define mmax(a, b) (a + b + abs(a - b)) / 2
#define mmin(a, b) (a + b - abs(a - b)) / 2 // среднее арифметическое минус половина разницы
#define mmax(a, b) (a + b + abs(a - b)) / 2 // среднее арифметическое плюс половина разницы
using CardT = int16_t;
class Dimensions : public wxSize {
public:
Dimensions(int _z, int _x, int _y) : wxSize(_x, _y), z(_z){};
struct Dimensions : wxSize { // используется там, где необходимо задать размеры в трёх координатах
Dimensions(int _z, int _x, int _y) : z(_z), wxSize(_x, _y){};
Dimensions() : wxSize(), z(0){};
int z;
};
class ThreePoint {
public:
constexpr ThreePoint(int _z, int _x, int _y) : x(_x), y(_y), z(_z){};
ThreePoint(const wxPoint& a) : x(a.x), y(a.y), z(0){};
ThreePoint() : x(0), y(0), z(0){};
struct ThreePoint : wxPoint { // используется там, где необходимо задать положение в трёх координатах. Так же засчёт наследования от wxPoint, может быть передан в функцию, принимающую wxPoint
ThreePoint(int _z, int _x, int _y) : z(_z), wxPoint(_x, _y){};
ThreePoint(const wxPoint& a) : z(0), wxPoint(a.x, a.y){};
ThreePoint() : z(0), wxPoint(0, 0){};
bool operator<(const ThreePoint& b) const {
return z * 144 * 144 + x * 144 + y < b.z * 144 * 144 + b.x * 144 + b.y;
}
bool operator==(const ThreePoint& b) {
bool operator==(const ThreePoint& b) const { // требуется для того, чтобы использовать в std::unordered_set (так как это множетсво)
return z == b.z && x == b.x && y == b.y;
}
int x;
int y;
int z;
};
class CardEntry {
public:
// доопределяем функтор std::hash для параметра шаблона ThreePoint, чтобы использовать в std::unordered_set
namespace std {
template<> struct hash<ThreePoint> {
size_t operator()(const ThreePoint& p) const {
return std::hash<uint32_t>()(p.z * 288 * 288 + p.x * 288 + p.y); // координаты точки можно представить как число в 288-ичной системе счисления, так как в крайнем случае (если индексируется прямая линия из всех камней), максимальная координата будет 144*2-1. хэш для этого числа может иметь максимальное значение 287*288*288+287*288+287, что больше, чем 2^16, но меньше, чем 2^32, поэтому используем uint32_t (на самом деле это преобразуется в простое статическое преобразование типа к size_t, но документация совертует делать так)
}
};
}
using PosSet = std::unordered_set<ThreePoint>; // алиас для типа
struct CardEntry { // используется как элемент стека, хранящего историю ходов
ThreePoint pos;
CardT id;
};
using TLVec = vector<vector<vector<CardT>>>;
enum Values { MATCHED = -3, EMPTY, FREE };
enum Values { // перечисление псевдо-id в таблице для не занятых реальными id камней позиций
MATCHED = -3, // уже убран
EMPTY, // не должно быть камня
FREE // доступен для вставки камня
};
bool isPositive(const wxSize& size);
void cyclic_shift(std::set<ThreePoint>::iterator& it, const std::set<ThreePoint>& cont);
#endif