Did some refactoring in Drawer and Controller, added comments
This commit is contained in:
parent
1a5b905464
commit
258eaf9e3b
179
Controller.cpp
179
Controller.cpp
@ -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; // если счётчик таймера наращивается, игра началась
|
||||
}
|
17
Controller.h
17
Controller.h
@ -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();
|
||||
|
||||
|
225
Drawer.cpp
225
Drawer.cpp
@ -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));
|
||||
|
||||
|
12
Drawer.h
12
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;
|
||||
|
||||
|
52
utils.h
52
utils.h
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user