From 17e12141ae0291261f1664622d01b1c50264bdd4 Mon Sep 17 00:00:00 2001 From: dm1sh Date: Wed, 8 Jun 2022 21:42:25 +0300 Subject: [PATCH] Non-working solveable --- Controller.cpp | 286 +++++++++++++++++++++++++++++++------------------ Controller.h | 18 +++- Drawer.cpp | 71 +++++++----- Drawer.h | 11 +- GamePanel.cpp | 32 +++--- MainFrame.cpp | 2 +- XmlLayout.cpp | 15 ++- XmlLayout.h | 3 + 8 files changed, 279 insertions(+), 159 deletions(-) diff --git a/Controller.cpp b/Controller.cpp index 1161347..f1669bc 100644 --- a/Controller.cpp +++ b/Controller.cpp @@ -1,26 +1,27 @@ #include "Controller.h" -Controller::Controller(Drawer& drawer) : drawer(drawer){}; +#include + +static const std::array defaultCardsCounter{ + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1}; void Controller::loadLayout(const wxString& path) { - layout.openFile(path); + layout.openFile(path); // открываем файл карты - gridSize = layout.getDimensions(); + gridSize = layout.getDimensions(); // получаем размеры карты - table = TLVec( + table = TLVec( // создаём трёхмерный вектор из id карт gridSize.z, vector>(gridSize.x, vector(gridSize.y, EMPTY))); - layout.readLayout(table); + layout.readLayout(table); // считываем формат поля из файла - remaining = layout.getTilesNumber(); + remaining = layout.getTilesNumber(); // получаем предполагаемое количество камней - if (remaining == 144) { - for (int i = 0; i < 34; i++) - cardsCounter[i] = 4; - for (int i = 34; i < TILE_IMAGES_N; i++) - cardsCounter[i] = 1; - } + if (remaining == 144) // другие форматы не каноничны, поэтому мы их не поддерживаем + // Заполняем массив-счётчик карт с различными id + cardsCounter = defaultCardsCounter; } TLVec& Controller::getTable() { @@ -35,166 +36,236 @@ void Controller::fill(bool solveable) { } void Controller::fillSolveableTable() { - srand(time(NULL)); + srand(time(NULL)); // инициализируем генератор случайных чисел - auto not_end = remaining; + auto not_end = remaining; // сохраняем в отдельную переменную количество оставшихся камней - std::set positions; + std::set positions; // инициализируем сет для хранения доступных для вставки позиций - positions.insert(getRandLowest()); + positions.insert(getRandLowest()); // вставляем случайную начальную позицию - auto next_ptr = positions.begin(); + auto next_ptr = positions.begin(); // инициализируем указатель на позицию, куда будет вставляться следующий камень - while ( - !positions.empty() || - (not_end && !(positions.insert(getRandLowest()), positions.empty()))) { + while (!positions.empty()) { int id = genRandId(); - if (id < 34) { - emplace_rand(id, positions, next_ptr, false); - emplace_rand(id, positions, next_ptr, true); + emplace_table(id, *next_ptr, positions); // вставляем id в next_ptr + not_end--; // уменьшаем счётчик оставшихся для вставки камней - cardsCounter[id]--; - not_end -= 2; - } else { - emplace_rand(id, positions, next_ptr, true); - not_end--; - } + next_rand(positions, next_ptr, false, not_end); // Находим случайную новую позицию так, чтобы она не накрывала предыдущую + + if (id < 34) // если id парный + cardsCounter[id]--; // уменьшаем счётчик карт этого id ещё на 1, так как вставим его ещё раз + else + id = getFreeSingularId(id); + + emplace_table(id, *next_ptr, positions); + not_end--; + + next_rand(positions, next_ptr, true, not_end); } } wxPoint Controller::getRandLowest() { - int overall = gridSize.x * gridSize.y; - int x, y; + int overall = gridSize.x * gridSize.y; // вычисляем количество позиций в горизонтальном "срезе" массива + int x, y; // объявляем координаты для возвращаемой позиции do { - int pos = rand() % overall; - x = pos / gridSize.y; - y = pos % gridSize.y; - } while (table[0][x][y] != FREE); + int pos = rand() % overall; // получаем случайный номер позиции + x = pos / gridSize.y; // вычисляем x + y = pos % gridSize.y; // и y + } while (table[0][x][y] != FREE); // повторяем цикл, если эта позиция недоступна для вставки - return {x, y}; + return wxPoint(x, y); // возвращаем wxPoint } -#ifdef WXDEBUG +void Controller::emplace_table(CardT id, const ThreePoint& pos, std::set& positions) { + table[pos.z][pos.x][pos.y] = id; + + push_available(positions, pos); // записываем в сет новые позиции +} + +#ifdef WXDEBUG // Если компилируем в режиме дебага #include void print_list(const std::set& positions) { - wxFile f("tmp.txt", wxFile::write_append); + wxFile f("tmp.txt", wxFile::write_append); // Открываем файл для записи - for (const auto& el : positions) - f.Write(itowxS(el.z) + " " + itowxS(el.x) + " " + itowxS(el.y) + "\n"); + for (const auto& el : positions) // Итерируемся по всем позициям + f.Write(itowxS(el.z) + " " + itowxS(el.x) + " " + itowxS(el.y) + "\n"); // Выводим координаты в файл - f.Write("_ size: " + itowxS(positions.size()) + "\n"); + f.Write("_ size: " + itowxS(positions.size()) + "\n"); // В конце выводим количество элементов } #endif -void Controller::emplace_rand(int id, std::set& positions, - std::set::iterator& next_ptr, - bool canBeUp) { -#ifdef WXDEBUG - print_list(positions); +void Controller::next_rand(std::set& positions, + std::set::iterator& ptr, + bool canOverlap, uint8_t& not_end) { +#ifdef WXDEBUG // Если компилируем в режиме дебага + print_list(positions); // выводим список позиций #endif - table[next_ptr->z][next_ptr->x][next_ptr->y] = id; + ThreePoint prev = *ptr; // сохраняем предыдущее значение итератора - push_available(positions, *next_ptr); + positions.erase(ptr); // удаляем только что вставленный итератор - auto prev_ptr = next_ptr; - ThreePoint prev = *next_ptr; + if (not_end) { + if (positions.empty()) + ptr = positions.insert(getRandLowest()).first; + else { + ptr = positions.begin(); - cyclic_shift(next_ptr, positions); + int rand_d = rand() % positions.size(); - positions.erase(prev_ptr); + for (int i = 0; i < rand_d; i++) + ptr++; - do - cyclic_shift(next_ptr, positions); - while (!canBeUp && !wouldBeUpFree(prev, *next_ptr)); + auto rand_ptr = ptr; + + while (!canOverlap && ptr != positions.end() && wouldOverlap(prev, *ptr)) // Пока не найдём тот, что не будет закрывать только что вставленную позицию, если не canBeUp + ptr++; + + if (ptr == positions.end()) + ptr = positions.begin(); + + while (!canOverlap && ptr != rand_ptr && wouldOverlap(prev, *ptr)) + ptr++; + + 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 (!res.second) + res = positions.insert(getRandLowest()); + + if (!res.second) + throw std::runtime_error("Wrong map layout or internal error"); + + ptr = res.first; + } + } + } + } } -bool Controller::wouldBeUpFree(const ThreePoint& prev, const ThreePoint& next) { - table[next.z][next.x][next.y] = 1; +bool Controller::wouldOverlap(const ThreePoint& prev, const ThreePoint& next) { + table[next.z][next.x][next.y] = 1; // вставляем в позицию next временный камень - bool res = upFree(prev); + bool res = !upFree(prev); // проверяем, будет ли свободен сверху камень в позиции prev - table[next.z][next.x][next.y] = FREE; + table[next.z][next.x][next.y] = FREE; // удаляем временный камень - return res; + return res; // возвращаем результат проверки } +/** + * 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; // более короткий алиса для переменной + + 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)) && + ((d.y == 0) || (d.y < 0 && p.y >= -d.y) || (d.y > 0 && p.y + d.y < gS.y)); +} + +/** + * 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) { + 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) { + 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) { + const ThreePoint& pos) { + auto& p = pos; + int z = pos.z, x = pos.x, y = pos.y; - // clang-format off - if (x >= 2 && table[z][x-2][y] == FREE) // left + 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})) positions.emplace(z, x-2, y); - if (x + 2 < gridSize.x && table[z][x+2][y] == FREE) // right + 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); - if (y >= 1 && (y < 2 || table[z][x][y-2] != FREE)) { // half bottom - if (x >= 2 && table[z][x-2][y-1] == FREE) // left + if (NFree(p, {0, 0, -2})) { // half top + if (NFree(p, {-1, -2, -1}) && NFree(p, {-1, -3, -1}) && NFree(p, {-1, -2, 0}) && NFree(p, {-1, -3, 0}) && NFree(p, {-1, -2, -2}) && NFree(p, {-1, -3, -2}) && NFree(p, {-1, -1, -2}) && Free(p, {0, -2, -1})) // left positions.emplace(z, x-2, y-1); - if (x + 2 < gridSize.x && table[z][x+2][y-1] == FREE) // right + if (NFree(p, {-1, 2, -1}) && NFree(p, {-1, 3, -1}) && NFree(p, {-1, 2, 0}) && NFree(p, {-1, 3, 0}) && NFree(p, {-1, 2, -2}) && NFree(p, {-1, 3, -2}) && NFree(p, {-1, 1, -2}) && Free(p, {0, 2, -1})) // right positions.emplace(z, x+2, y-1); } - if (y + 1 < gridSize.y && (y + 2 >= gridSize.y || table[z][x][y+2] != FREE)) { // half bottom - if (x >= 2 && table[z][x-2][y+1] == FREE) // left + if (NFree(p, {0, 0, 2})) { // half bottom + if (NFree(p, {-1, -2, 1}) && NFree(p, {-1, -3, 1}) && NFree(p, {-1, -2, 0}) && NFree(p, {-1, -3, 0}) && NFree(p, {-1, -2, 2}) && NFree(p, {-1, -3, 2}) && NFree(p, {-1, -1, 2}) && Free(p, {0, -2, 1})) // left positions.emplace(z, x-2, y+1); - if (x + 2 < gridSize.x && table[z][x+2][y+1] == FREE) // right + if (NFree(p, {-1, 2, 1}) && NFree(p, {-1, 3, 1}) && NFree(p, {-1, 2, 0}) && NFree(p, {-1, 3, 0}) && NFree(p, {-1, 2, 2}) && NFree(p, {-1, 3, 2}) && NFree(p, {-1, 1, 2}) && Free(p, {0, 2, 1})) // right positions.emplace(z, x+2, y+1); } - if (y >= 2 && table[z][x][y-2] == FREE) // up + if (NFree(p, {-1, 0, -2}) && NFree(p, {-1, 0, -3}) && NFree(p, {-1, -1, -2}) && NFree(p, {-1, -1, -3}) && NFree(p, {-1, 1, -2}) && NFree(p, {-1, 1, -3}) && Free(p, {0, 0, -2})) // top positions.emplace(z, x, y-2); - if (y + 2 < gridSize.y && table[z][x][y+2] == FREE) // bottom + if (NFree(p, {-1, 0, 2}) && NFree(p, {-1, 0, 3}) && NFree(p, {-1, -1, 2}) && NFree(p, {-1, -1, 3}) && NFree(p, {-1, 1, 2}) && NFree(p, {-1, 1, 3}) && Free(p, {0, 0, 2})) // bottom positions.emplace(z, x, y+2); - if (z + 1 < gridSize.z) { // higher - if (table[z+1][x][y] == FREE) // straight - positions.emplace(z+1, x, y); + /* Higher */ - if (y >= 1 && (y < 2 || table[z][x][y-2] != FREE) && table[z+1][x][y-1] == FREE) // half top - positions.emplace(z+1, x, y-1); - if (y + 1 < gridSize.y && (y + 2 >= gridSize.y || table[z][x][y+2] != FREE) && table[z+1][x][y+1] == FREE) // half bottom - positions.emplace(z+1, x, y+1); + if (Free(p, {1, 0, 0})) // straight + positions.emplace(z+1, x, y); - if (x >= 1 && (x < 2 || table[z][x-2][y] != FREE)) {// half left - if (table[z+1][x-1][y] == FREE) // straight - positions.emplace(z+1, x-1, y); - - if (y >= 1 && (x < 2 || table[z][x-2][y-1] != FREE || (y < 2 || table[z][x-2][y-2] != FREE)) && (y < 2 || table[z][x][y-2] != FREE) && table[z+1][x-1][y-1] == FREE) // half top - positions.emplace(z+1, x-1, y-1); + if (NFree(p, {0, -1, -2}) && NFree(p, {0, 0, -2}) && NFree(p, {0, 1, -2}) && Free(p, {1, 0, -1})) // half top + positions.emplace(z+1, x, y-1); + if (NFree(p, {0, -1, 2}) && NFree(p, {0, 0, 2}) && NFree(p, {0, 1, 2}) && Free(p, {1, 0, 1})) // half bottom + positions.emplace(z+1, x, y+1); - if (y + 1 < gridSize.y && (x < 2 || table[z][x-2][y+1] != FREE || (y + 2 < gridSize.y || table[z][x-2][y+2] != FREE)) && (y + 2 >= gridSize.y || table[z][x][y+2] != FREE) && table[z+1][x-1][y+1] == FREE) // half bottom - positions.emplace(z+1, x-1, y+1); - } + if (NFree(p, {0, -2, 0})) {// half left + if (NFree(p, {0, -2, -1}) && NFree(p, {0, -2, 1}) && Free(p, {1, -1, 0})) // straight + positions.emplace(z+1, x-1, y); + + if (NFree(p, {0, -2, -1}) && NFree(p, {0, -2, -2}) && NFree(p, {0, 0, -2}) && Free(p, {1, -1, -1})) // half top + positions.emplace(z+1, x-1, y-1); - if (x + 1 < gridSize.x && (x + 2 >= gridSize.x || table[z][x+2][y] != FREE)) { // half right - if (table[z+1][x+1][y] == FREE) // straight - positions.emplace(z+1, x+1, y); - - if (y >= 1 && (x + 2 >= gridSize.x || table[z][x+2][y-1] != FREE || (y < 2 || table[z][x+2][y-2] != FREE)) && (y < 2 || table[z][x][y-2] != FREE) && table[z+1][x+1][y-1] == FREE) // half top - positions.emplace(z+1, x+1, y-1); - - if (y + 1 < gridSize.y && (x + 2 >= gridSize.x || table[z][x+2][y+1] != FREE || (y + 2 < gridSize.y || table[z][x+2][y+2] != FREE)) && (y + 2 >= gridSize.y || table[z][x][y+2] != FREE) && table[z+1][x+1][y+1] == FREE) // half bottom - positions.emplace(z+1, x+1, y+1); - } + if (NFree(p, {0, -2, 1}) && NFree(p, {0, -2, 2}) && NFree(p, {0, 0, 2}) && Free(p, {1, -1, 1})) // half bottom + positions.emplace(z+1, x-1, y+1); + } + + if (NFree(p, {0, 2, 0})) { // half right + if (NFree(p, {0, 2, -1}) && NFree(p, {0, 2, 1}) && Free(p, {1, 1, 0})) // straight + positions.emplace(z+1, x+1, y); + + if (NFree(p, {0, 2, -1}) && NFree(p, {0, 2, -2}) && NFree(p, {0, 0, -2}) && Free(p, {1, 1, -1})) // half top + positions.emplace(z+1, x+1, y-1); + + if (NFree(p, {0, 2, 1}) && NFree(p, {0, 2, 2}) && NFree(p, {0, 0, 2}) && Free(p, {1, 1, 1})) // half bottom + positions.emplace(z+1, x+1, y+1); } - // clang-format on } +/** + * Removes all set stones and makes their positions free again + */ void Controller::free_table() { + // Итерируемся по массиву table размерности gridSize 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 id = table[z][x][y]; + CardT id = table[z][x][y]; // считываем id данной ячейки - if (id >= 0) { - cardsCounter[id]++; + if (id >= 0) { // если это валидный id камня + cardsCounter[id]++; // наращиваем счётчик камней table[z][x][y] = FREE; } @@ -234,6 +305,17 @@ CardT Controller::genRandId() { return id; } +CardT Controller::getFreeSingularId(CardT prev) { + CardT id = (prev < 38) ? 34 : 38; + + while (id < TILE_IMAGES_N && cardsCounter[id] == 0) + id++; + + cardsCounter[id]--; + + return id; +} + /** * It also changes point to top right coordinate of card */ diff --git a/Controller.h b/Controller.h index 9fa05aa..f0ad71d 100644 --- a/Controller.h +++ b/Controller.h @@ -12,7 +12,7 @@ class Controller { public: - Controller(Drawer& drawer); + Controller(Drawer& drawer) : drawer(drawer){}; int stopwatch = -1; @@ -43,14 +43,24 @@ private: CardT* selected = nullptr; void fillSolveableTable(); + wxPoint getRandLowest(); - void emplace_rand(int id, std::set& positions, - std::set::iterator& next_ptr, bool canBeUp); - bool wouldBeUpFree(const ThreePoint& prev, const ThreePoint& next); + + 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); + + 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); + void push_available(std::set& positions, const ThreePoint& pos); void fillRandom(); + CardT getFreeSingularId(CardT prev); CardT genRandId(); CardT* getCardByPosition(ThreePoint& point); diff --git a/Drawer.cpp b/Drawer.cpp index 17c490a..bfb5054 100644 --- a/Drawer.cpp +++ b/Drawer.cpp @@ -84,30 +84,30 @@ void Drawer::drawTile(wxDC& dc, int8_t index, const wxPoint& position, dc.SetBrush(back); - dc.DrawRoundedRectangle(position.x + (tilePixelSize.GetWidth() / 10 + 3) - - (tilePixelSize.GetWidth() / 10 + 3) * zIndex, - position.y + (tilePixelSize.GetHeight() / 10 + 3) - - (tilePixelSize.GetHeight() / 10 + 3) * zIndex, + 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, 10); + tilePixelSize.GetHeight() * 2, (tilePixelSize.GetHeight() / TILE_HEIGHT * TILE_PADDING_SCALE)); dc.SetBrush(front); dc.DrawRoundedRectangle( - position.x - (tilePixelSize.GetWidth() / 10 + 3) * zIndex, - position.y - (tilePixelSize.GetHeight() / 10 + 3) * zIndex, - tilePixelSize.GetWidth() * 2, tilePixelSize.GetHeight() * 2, 10); + 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.SetBrush(_bgColor); if (tileImages[index].IsOk()) { wxPoint pos; - pos.x = position.x + 10 - (tilePixelSize.GetWidth() / 10 + 3) * zIndex; - pos.y = position.y + 10 - (tilePixelSize.GetHeight() / 10 + 3) * zIndex; + 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].GetWidth() != tilePixelSize.x * 2) - dc.DrawBitmap(tileImages[index].Scale(tilePixelSize.x * 2 - 20, - tilePixelSize.y * 2 - 20), + 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); @@ -125,25 +125,37 @@ 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 Drawer::resizeBoard(const TLVec& layout, const Dimensions& gridSize, bool force) { bool res = false; - const int gridPoint = mmin(resolution.x / (gridSize.x * TILE_WIDTH), - resolution.y / (gridSize.y * TILE_HEIGHT)); + const int gridPoint = mmin( + 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) { - tablePixelRect.SetSize({gridPoint * TILE_WIDTH * gridSize.x, - gridPoint * TILE_HEIGHT * gridSize.y}); - + if (gridPoint != prevGridPoint || force) { 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) картами на верхних позициях (их может и не быть, но проверять это дорого) + + 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) + ) + ); } tablePixelRect.SetPosition({(resolution.x - tablePixelRect.width) / 2, (resolution.y - tablePixelRect.height) / 2}); - if (gridPoint != prevGridPoint) { + if (gridPoint != prevGridPoint || force) { composeBoard(layout, gridSize); res = true; } @@ -153,9 +165,12 @@ bool Drawer::resizeBoard(const TLVec& layout, const Dimensions& gridSize) { return res; } -wxPoint Drawer::toGrid(const wxPoint& point) const { +wxPoint Drawer::toGrid(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 && @@ -168,20 +183,22 @@ wxPoint Drawer::toGrid(const wxPoint& point) const { } wxPoint Drawer::fromGrid(int x, int y) const { - return {x * tilePixelSize.x, y * tilePixelSize.y}; + return {x * tilePixelSize.x + boardPadding.x, y * tilePixelSize.y + boardPadding.y}; } wxPoint Drawer::fromGrid(const wxPoint& point) const { return fromGrid(point.x, point.y); } -wxSize Drawer::composeMinSize(const wxSize& gridSize) { +wxSize Drawer::composeMinSize(const Dimensions& gridSize) { wxSize ms; - ms.SetWidth(MIN_GRID_POINT * TILE_WIDTH * gridSize.x); - ms.SetHeight(MIN_GRID_POINT * TILE_HEIGHT * gridSize.y); + 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 += {1, 1}; wxLogDebug(wxString::Format("MinSize %i %i", ms.x, ms.y)); return ms; -} \ No newline at end of file +} diff --git a/Drawer.h b/Drawer.h index b780260..0ad38d9 100644 --- a/Drawer.h +++ b/Drawer.h @@ -10,7 +10,7 @@ #define TILE_IMAGES_N 42 -#define MIN_GRID_POINT 3 +#define TILE_PADDING_SCALE 1.25 class Drawer { public: @@ -22,13 +22,13 @@ public: void composeBoard(const TLVec& layout, const Dimensions& gridSize); void resizeBg(const wxSize& tableSize); - bool resizeBoard(const TLVec& layout, const Dimensions& gridSize); + bool resizeBoard(const TLVec& layout, const Dimensions& gridSize, bool force); - wxPoint toGrid(const wxPoint& point) const; + wxPoint toGrid(wxPoint point) const; wxPoint fromGrid(int x, int y) const; wxPoint fromGrid(const wxPoint& point) const; - wxSize composeMinSize(const wxSize& gridSize); + wxSize composeMinSize(const Dimensions& gridSize); wxSize tableSize; @@ -47,6 +47,9 @@ private: wxBitmap bgBitmap; wxBitmap boardBitmap; + wxPoint boardPadding; + wxPoint tilePadding; + int prevGridPoint; }; diff --git a/GamePanel.cpp b/GamePanel.cpp index 08fc860..fe0f60e 100644 --- a/GamePanel.cpp +++ b/GamePanel.cpp @@ -39,7 +39,7 @@ void GamePanel::Start(const wxString& path, bool solvable, sb->SetStatusText(PRemaining(controller.remaining), 1); // во вторую процент оставшихся камней bool redrawn = - drawer.resizeBoard(controller.getTable(), controller.gridSize); // Изменяем размер доски + drawer.resizeBoard(controller.getTable(), controller.gridSize, true); // Изменяем размер доски if (!redrawn) // и если при этом изменился размер камней, drawer.composeBoard(controller.getTable(), controller.gridSize); // перерисовываем доску @@ -80,37 +80,37 @@ void GamePanel::OnResize(wxSizeEvent& _) { drawer.resizeBg(resolution); // изменяем размер фона if (controller.gameStarted()) // если уже начали игру - drawer.resizeBoard(controller.getTable(), controller.gridSize); // перерисовываем доску + drawer.resizeBoard(controller.getTable(), controller.gridSize, false); // перерисовываем доску } Refresh(); } -wxDEFINE_EVENT(END_EVT, wxCommandEvent); +wxDEFINE_EVENT(END_EVT, wxCommandEvent); // Определяем событие об окончании игры void GamePanel::OnTimer(wxTimerEvent& _) { - controller.stopwatch += 1; - sb->SetStatusText(LTimeToStr(controller.stopwatch), 0); + controller.stopwatch += 1; // Наращиваем счётчик таймера + sb->SetStatusText(LTimeToStr(controller.stopwatch), 0); // и выводим его новое значение - if (controller.remaining == 0) { - wxCommandEvent event(END_EVT); - event.SetString(LTimeToStr(controller.stopwatch)); - wxPostEvent(GetParent(), event); + if (controller.remaining == 0) { // если убраны все карты, + wxCommandEvent event(END_EVT); // создаём экземпляр события окончания игры + event.SetString(LTimeToStr(controller.stopwatch)); // сохраняем в нём время игры + wxPostEvent(GetParent(), event); // посылаем событие в родительский класс - timer.Stop(); - controller.stopwatch = 0; + timer.Stop(); // останавливаем таймер + controller.stopwatch = 0; // сбрасываем счётчик таймера } } void GamePanel::OnClick(wxMouseEvent& _) { - if (controller.gameStarted()) { - controller.handleClick(ScreenToClient(wxGetMousePosition())); - sb->SetStatusText(PRemaining(controller.remaining), 1); + if (controller.gameStarted()) { // Если игра начата, + controller.handleClick(ScreenToClient(wxGetMousePosition())); // выполняем обработку клика в контроллере + sb->SetStatusText(PRemaining(controller.remaining), 1); // устанавливаем процент оставшихся камней в статусбар - drawer.composeBoard(controller.getTable(), controller.gridSize); + drawer.composeBoard(controller.getTable(), controller.gridSize); // отрисовываем новое поле wxLogDebug(wxString::Format(_("Remaining %i"), controller.remaining)); - Refresh(); + Refresh(); // вызываем перерисовку окна } } diff --git a/MainFrame.cpp b/MainFrame.cpp index 566791a..7de7af1 100644 --- a/MainFrame.cpp +++ b/MainFrame.cpp @@ -39,7 +39,7 @@ MainFrame::MainFrame() void MainFrame::initMenu() { wxMenu* menuGame = new wxMenu; // Создаём подменю - menuGame->Append(IDM_New_Game, _("Начать сначала")); // Создаем пункт меню с id обработчика IDM_New_Game, далее аналогично + menuGame->Append(IDM_New_Game, _("Начать сначала\tCTRL+N")); // Создаем пункт меню с id обработчика IDM_New_Game, далее аналогично menuGame->Append(IDM_Open, _("Открыть карту")); menuGame->AppendCheckItem(IDM_Solveable, _("Генерировать решаемую карту")); menuGame->AppendSeparator(); // Добавляем горизонтальный разделитель в меню diff --git a/XmlLayout.cpp b/XmlLayout.cpp index 96c2f72..63a4746 100644 --- a/XmlLayout.cpp +++ b/XmlLayout.cpp @@ -16,9 +16,14 @@ bool XmlLayout::openFile(const wxString& openPath) { } Dimensions XmlLayout::getDimensions() { - return {wxAtoi(layoutDoc.GetRoot()->GetAttribute("layers")), - wxAtoi(layoutDoc.GetRoot()->GetAttribute("ux")) + 2, - wxAtoi(layoutDoc.GetRoot()->GetAttribute("uy")) + 2}; + auto root = layoutDoc.GetRoot(); + + lx = wxAtoi(root->GetAttribute("lx")); + ly = wxAtoi(root->GetAttribute("ly")); + + return {wxAtoi(root->GetAttribute("layers")), + wxAtoi(root->GetAttribute("ux")) + 2 - lx, + wxAtoi(root->GetAttribute("uy")) + 2 - ly}; } void XmlLayout::readLayout(TLVec& table) { @@ -28,8 +33,8 @@ void XmlLayout::readLayout(TLVec& table) { while (tilePtr) { if (tilePtr->GetName().IsSameAs("tile")) { - x = wxAtoi(tilePtr->GetAttribute("x")); - y = wxAtoi(tilePtr->GetAttribute("y")); + x = wxAtoi(tilePtr->GetAttribute("x")) - lx; + y = wxAtoi(tilePtr->GetAttribute("y")) - ly; l = wxAtoi(tilePtr->GetAttribute("layer")) - 1; table[l][x][y] = FREE; diff --git a/XmlLayout.h b/XmlLayout.h index 1d2e7f4..7eaec54 100644 --- a/XmlLayout.h +++ b/XmlLayout.h @@ -19,6 +19,9 @@ public: private: wxString path; + int lx; + int ly; + wxXmlDocument layoutDoc; };