From 258eaf9e3b56bc0f383daa5bd503cc04e4fa8de7 Mon Sep 17 00:00:00 2001 From: dm1sh Date: Thu, 9 Jun 2022 21:13:24 +0300 Subject: [PATCH] Did some refactoring in Drawer and Controller, added comments --- Controller.cpp | 179 +++++++++++++++++++-------------------- Controller.h | 17 ++-- Drawer.cpp | 225 ++++++++++++++++++++++++++----------------------- Drawer.h | 12 +-- utils.h | 52 ++++++------ 5 files changed, 252 insertions(+), 233 deletions(-) diff --git a/Controller.cpp b/Controller.cpp index f1669bc..6ae12cd 100644 --- a/Controller.cpp +++ b/Controller.cpp @@ -35,12 +35,13 @@ void Controller::fill(bool solveable) { fillRandom(); } + void Controller::fillSolveableTable() { srand(time(NULL)); // инициализируем генератор случайных чисел auto not_end = remaining; // сохраняем в отдельную переменную количество оставшихся камней - std::set 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& 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 -void print_list(const std::set& 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& positions) { #endif -void Controller::next_rand(std::set& positions, - std::set::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& 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& 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; // если счётчик таймера наращивается, игра началась } \ No newline at end of file diff --git a/Controller.h b/Controller.h index f0ad71d..666bff8 100644 --- a/Controller.h +++ b/Controller.h @@ -2,7 +2,6 @@ #define CONTROLLER_H #include -#include #include #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& positions); - void next_rand(std::set& positions, - std::set::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& positions, const ThreePoint& pos); + void push_available(PosSet& positions, const ThreePoint& pos) const; void fillRandom(); diff --git a/Drawer.cpp b/Drawer.cpp index bfb5054..81c3c97 100644 --- a/Drawer.cpp +++ b/Drawer.cpp @@ -1,5 +1,8 @@ #include "Drawer.h" +#include +#include + 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)); diff --git a/Drawer.h b/Drawer.h index 0ad38d9..9df2856 100644 --- a/Drawer.h +++ b/Drawer.h @@ -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; diff --git a/utils.h b/utils.h index a1c7c35..07e6799 100644 --- a/utils.h +++ b/utils.h @@ -3,60 +3,64 @@ #include "wxw.h" -#include +#include #include #include 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 { + size_t operator()(const ThreePoint& p) const { + return std::hash()(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; // алиас для типа + +struct CardEntry { // используется как элемент стека, хранящего историю ходов ThreePoint pos; CardT id; }; using TLVec = vector>>; -enum Values { MATCHED = -3, EMPTY, FREE }; +enum Values { // перечисление псевдо-id в таблице для не занятых реальными id камней позиций + MATCHED = -3, // уже убран + EMPTY, // не должно быть камня + FREE // доступен для вставки камня +}; bool isPositive(const wxSize& size); -void cyclic_shift(std::set::iterator& it, const std::set& cont); - #endif