Non-working solveable
This commit is contained in:
parent
cb5738cde6
commit
17e12141ae
286
Controller.cpp
286
Controller.cpp
@ -1,26 +1,27 @@
|
|||||||
#include "Controller.h"
|
#include "Controller.h"
|
||||||
|
|
||||||
Controller::Controller(Drawer& drawer) : drawer(drawer){};
|
#include <exception>
|
||||||
|
|
||||||
|
static const std::array<uint8_t, 42> 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) {
|
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,
|
gridSize.z,
|
||||||
vector<vector<CardT>>(gridSize.x, vector<CardT>(gridSize.y, EMPTY)));
|
vector<vector<CardT>>(gridSize.x, vector<CardT>(gridSize.y, EMPTY)));
|
||||||
|
|
||||||
layout.readLayout(table);
|
layout.readLayout(table); // считываем формат поля из файла
|
||||||
|
|
||||||
remaining = layout.getTilesNumber();
|
remaining = layout.getTilesNumber(); // получаем предполагаемое количество камней
|
||||||
|
|
||||||
if (remaining == 144) {
|
if (remaining == 144) // другие форматы не каноничны, поэтому мы их не поддерживаем
|
||||||
for (int i = 0; i < 34; i++)
|
// Заполняем массив-счётчик карт с различными id
|
||||||
cardsCounter[i] = 4;
|
cardsCounter = defaultCardsCounter;
|
||||||
for (int i = 34; i < TILE_IMAGES_N; i++)
|
|
||||||
cardsCounter[i] = 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TLVec& Controller::getTable() {
|
TLVec& Controller::getTable() {
|
||||||
@ -35,166 +36,236 @@ void Controller::fill(bool solveable) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Controller::fillSolveableTable() {
|
void Controller::fillSolveableTable() {
|
||||||
srand(time(NULL));
|
srand(time(NULL)); // инициализируем генератор случайных чисел
|
||||||
|
|
||||||
auto not_end = remaining;
|
auto not_end = remaining; // сохраняем в отдельную переменную количество оставшихся камней
|
||||||
|
|
||||||
std::set<ThreePoint> positions;
|
std::set<ThreePoint> positions; // инициализируем сет для хранения доступных для вставки позиций
|
||||||
|
|
||||||
positions.insert(getRandLowest());
|
positions.insert(getRandLowest()); // вставляем случайную начальную позицию
|
||||||
|
|
||||||
auto next_ptr = positions.begin();
|
auto next_ptr = positions.begin(); // инициализируем указатель на позицию, куда будет вставляться следующий камень
|
||||||
|
|
||||||
while (
|
while (!positions.empty()) {
|
||||||
!positions.empty() ||
|
|
||||||
(not_end && !(positions.insert(getRandLowest()), positions.empty()))) {
|
|
||||||
int id = genRandId();
|
int id = genRandId();
|
||||||
|
|
||||||
if (id < 34) {
|
emplace_table(id, *next_ptr, positions); // вставляем id в next_ptr
|
||||||
emplace_rand(id, positions, next_ptr, false);
|
not_end--; // уменьшаем счётчик оставшихся для вставки камней
|
||||||
emplace_rand(id, positions, next_ptr, true);
|
|
||||||
|
|
||||||
cardsCounter[id]--;
|
next_rand(positions, next_ptr, false, not_end); // Находим случайную новую позицию так, чтобы она не накрывала предыдущую
|
||||||
not_end -= 2;
|
|
||||||
} else {
|
if (id < 34) // если id парный
|
||||||
emplace_rand(id, positions, next_ptr, true);
|
cardsCounter[id]--; // уменьшаем счётчик карт этого id ещё на 1, так как вставим его ещё раз
|
||||||
not_end--;
|
else
|
||||||
}
|
id = getFreeSingularId(id);
|
||||||
|
|
||||||
|
emplace_table(id, *next_ptr, positions);
|
||||||
|
not_end--;
|
||||||
|
|
||||||
|
next_rand(positions, next_ptr, true, not_end);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
wxPoint Controller::getRandLowest() {
|
wxPoint Controller::getRandLowest() {
|
||||||
int overall = gridSize.x * gridSize.y;
|
int overall = gridSize.x * gridSize.y; // вычисляем количество позиций в горизонтальном "срезе" массива
|
||||||
int x, y;
|
int x, y; // объявляем координаты для возвращаемой позиции
|
||||||
|
|
||||||
do {
|
do {
|
||||||
int pos = rand() % overall;
|
int pos = rand() % overall; // получаем случайный номер позиции
|
||||||
x = pos / gridSize.y;
|
x = pos / gridSize.y; // вычисляем x
|
||||||
y = pos % gridSize.y;
|
y = pos % gridSize.y; // и y
|
||||||
} while (table[0][x][y] != FREE);
|
} 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<ThreePoint>& positions) {
|
||||||
|
table[pos.z][pos.x][pos.y] = id;
|
||||||
|
|
||||||
|
push_available(positions, pos); // записываем в сет новые позиции
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef WXDEBUG // Если компилируем в режиме дебага
|
||||||
|
|
||||||
#include <wx/file.h>
|
#include <wx/file.h>
|
||||||
|
|
||||||
void print_list(const std::set<ThreePoint>& positions) {
|
void print_list(const std::set<ThreePoint>& positions) {
|
||||||
wxFile f("tmp.txt", wxFile::write_append);
|
wxFile f("tmp.txt", wxFile::write_append); // Открываем файл для записи
|
||||||
|
|
||||||
for (const auto& el : positions)
|
for (const auto& el : positions) // Итерируемся по всем позициям
|
||||||
f.Write(itowxS(el.z) + " " + itowxS(el.x) + " " + itowxS(el.y) + "\n");
|
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
|
#endif
|
||||||
|
|
||||||
void Controller::emplace_rand(int id, std::set<ThreePoint>& positions,
|
void Controller::next_rand(std::set<ThreePoint>& positions,
|
||||||
std::set<ThreePoint>::iterator& next_ptr,
|
std::set<ThreePoint>::iterator& ptr,
|
||||||
bool canBeUp) {
|
bool canOverlap, uint8_t& not_end) {
|
||||||
#ifdef WXDEBUG
|
#ifdef WXDEBUG // Если компилируем в режиме дебага
|
||||||
print_list(positions);
|
print_list(positions); // выводим список позиций
|
||||||
#endif
|
#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;
|
if (not_end) {
|
||||||
ThreePoint prev = *next_ptr;
|
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
|
auto rand_ptr = ptr;
|
||||||
cyclic_shift(next_ptr, positions);
|
|
||||||
while (!canBeUp && !wouldBeUpFree(prev, *next_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) {
|
bool Controller::wouldOverlap(const ThreePoint& prev, const ThreePoint& next) {
|
||||||
table[next.z][next.x][next.y] = 1;
|
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<ThreePoint>& positions,
|
void Controller::push_available(std::set<ThreePoint>& positions,
|
||||||
const ThreePoint& pos) {
|
const ThreePoint& pos) {
|
||||||
|
auto& p = pos;
|
||||||
|
|
||||||
int z = pos.z, x = pos.x, y = pos.y;
|
int z = pos.z, x = pos.x, y = pos.y;
|
||||||
|
|
||||||
// clang-format off
|
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}))
|
||||||
if (x >= 2 && table[z][x-2][y] == FREE) // left
|
|
||||||
positions.emplace(z, x-2, y);
|
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);
|
positions.emplace(z, x+2, y);
|
||||||
|
|
||||||
if (y >= 1 && (y < 2 || table[z][x][y-2] != FREE)) { // half bottom
|
if (NFree(p, {0, 0, -2})) { // half top
|
||||||
if (x >= 2 && table[z][x-2][y-1] == FREE) // left
|
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);
|
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);
|
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 (NFree(p, {0, 0, 2})) { // half bottom
|
||||||
if (x >= 2 && table[z][x-2][y+1] == FREE) // left
|
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);
|
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);
|
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);
|
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);
|
positions.emplace(z, x, y+2);
|
||||||
|
|
||||||
if (z + 1 < gridSize.z) { // higher
|
/* Higher */
|
||||||
if (table[z+1][x][y] == FREE) // straight
|
|
||||||
positions.emplace(z+1, x, y);
|
|
||||||
|
|
||||||
if (y >= 1 && (y < 2 || table[z][x][y-2] != FREE) && table[z+1][x][y-1] == FREE) // half top
|
if (Free(p, {1, 0, 0})) // straight
|
||||||
positions.emplace(z+1, x, y-1);
|
positions.emplace(z+1, x, y);
|
||||||
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 (x >= 1 && (x < 2 || table[z][x-2][y] != FREE)) {// half left
|
if (NFree(p, {0, -1, -2}) && NFree(p, {0, 0, -2}) && NFree(p, {0, 1, -2}) && Free(p, {1, 0, -1})) // half top
|
||||||
if (table[z+1][x-1][y] == FREE) // straight
|
positions.emplace(z+1, x, y-1);
|
||||||
positions.emplace(z+1, x-1, y);
|
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 && (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 (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
|
if (NFree(p, {0, -2, 0})) {// half left
|
||||||
positions.emplace(z+1, x-1, y+1);
|
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 (NFree(p, {0, -2, 1}) && NFree(p, {0, -2, 2}) && NFree(p, {0, 0, 2}) && Free(p, {1, -1, 1})) // half bottom
|
||||||
if (table[z+1][x+1][y] == FREE) // straight
|
positions.emplace(z+1, x-1, y+1);
|
||||||
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
|
if (NFree(p, {0, 2, 0})) { // half right
|
||||||
positions.emplace(z+1, x+1, y-1);
|
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 (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 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() {
|
void Controller::free_table() {
|
||||||
|
// Итерируемся по массиву table размерности gridSize
|
||||||
for (int z = 0; z < gridSize.z; z++)
|
for (int z = 0; z < gridSize.z; z++)
|
||||||
for (int x = 0; x < gridSize.x; x++)
|
for (int x = 0; x < gridSize.x; x++)
|
||||||
for (int y = 0; y < gridSize.y; y++) {
|
for (int y = 0; y < gridSize.y; y++) {
|
||||||
CardT id = table[z][x][y];
|
CardT id = table[z][x][y]; // считываем id данной ячейки
|
||||||
|
|
||||||
if (id >= 0) {
|
if (id >= 0) { // если это валидный id камня
|
||||||
cardsCounter[id]++;
|
cardsCounter[id]++; // наращиваем счётчик камней
|
||||||
|
|
||||||
table[z][x][y] = FREE;
|
table[z][x][y] = FREE;
|
||||||
}
|
}
|
||||||
@ -234,6 +305,17 @@ CardT Controller::genRandId() {
|
|||||||
return id;
|
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
|
* It also changes point to top right coordinate of card
|
||||||
*/
|
*/
|
||||||
|
18
Controller.h
18
Controller.h
@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
class Controller {
|
class Controller {
|
||||||
public:
|
public:
|
||||||
Controller(Drawer& drawer);
|
Controller(Drawer& drawer) : drawer(drawer){};
|
||||||
|
|
||||||
int stopwatch = -1;
|
int stopwatch = -1;
|
||||||
|
|
||||||
@ -43,14 +43,24 @@ private:
|
|||||||
CardT* selected = nullptr;
|
CardT* selected = nullptr;
|
||||||
|
|
||||||
void fillSolveableTable();
|
void fillSolveableTable();
|
||||||
|
|
||||||
wxPoint getRandLowest();
|
wxPoint getRandLowest();
|
||||||
void emplace_rand(int id, std::set<ThreePoint>& positions,
|
|
||||||
std::set<ThreePoint>::iterator& next_ptr, bool canBeUp);
|
void emplace_table(CardT id, const ThreePoint& pos, std::set<ThreePoint>& positions);
|
||||||
bool wouldBeUpFree(const ThreePoint& prev, const ThreePoint& next);
|
void next_rand(std::set<ThreePoint>& positions,
|
||||||
|
std::set<ThreePoint>::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<ThreePoint>& positions, const ThreePoint& pos);
|
void push_available(std::set<ThreePoint>& positions, const ThreePoint& pos);
|
||||||
|
|
||||||
void fillRandom();
|
void fillRandom();
|
||||||
|
|
||||||
|
CardT getFreeSingularId(CardT prev);
|
||||||
CardT genRandId();
|
CardT genRandId();
|
||||||
|
|
||||||
CardT* getCardByPosition(ThreePoint& point);
|
CardT* getCardByPosition(ThreePoint& point);
|
||||||
|
71
Drawer.cpp
71
Drawer.cpp
@ -84,30 +84,30 @@ void Drawer::drawTile(wxDC& dc, int8_t index, const wxPoint& position,
|
|||||||
|
|
||||||
dc.SetBrush(back);
|
dc.SetBrush(back);
|
||||||
|
|
||||||
dc.DrawRoundedRectangle(position.x + (tilePixelSize.GetWidth() / 10 + 3) -
|
dc.DrawRoundedRectangle(position.x + (tilePixelSize.GetWidth() / TILE_WIDTH * TILE_PADDING_SCALE) -
|
||||||
(tilePixelSize.GetWidth() / 10 + 3) * zIndex,
|
(tilePixelSize.GetWidth() / TILE_WIDTH * TILE_PADDING_SCALE) * zIndex,
|
||||||
position.y + (tilePixelSize.GetHeight() / 10 + 3) -
|
position.y + (tilePixelSize.GetHeight() / TILE_WIDTH * TILE_PADDING_SCALE) -
|
||||||
(tilePixelSize.GetHeight() / 10 + 3) * zIndex,
|
(tilePixelSize.GetHeight() / TILE_WIDTH * TILE_PADDING_SCALE) * zIndex,
|
||||||
tilePixelSize.GetWidth() * 2,
|
tilePixelSize.GetWidth() * 2,
|
||||||
tilePixelSize.GetHeight() * 2, 10);
|
tilePixelSize.GetHeight() * 2, (tilePixelSize.GetHeight() / TILE_HEIGHT * TILE_PADDING_SCALE));
|
||||||
|
|
||||||
dc.SetBrush(front);
|
dc.SetBrush(front);
|
||||||
|
|
||||||
dc.DrawRoundedRectangle(
|
dc.DrawRoundedRectangle(
|
||||||
position.x - (tilePixelSize.GetWidth() / 10 + 3) * zIndex,
|
position.x - (tilePixelSize.GetWidth() / TILE_WIDTH * TILE_PADDING_SCALE) * zIndex,
|
||||||
position.y - (tilePixelSize.GetHeight() / 10 + 3) * zIndex,
|
position.y - (tilePixelSize.GetHeight() / TILE_WIDTH * TILE_PADDING_SCALE) * zIndex,
|
||||||
tilePixelSize.GetWidth() * 2, tilePixelSize.GetHeight() * 2, 10);
|
tilePixelSize.GetWidth() * 2, tilePixelSize.GetHeight() * 2, (tilePixelSize.GetHeight() / TILE_HEIGHT * TILE_PADDING_SCALE));
|
||||||
|
|
||||||
dc.SetBrush(_bgColor);
|
dc.SetBrush(_bgColor);
|
||||||
|
|
||||||
if (tileImages[index].IsOk()) {
|
if (tileImages[index].IsOk()) {
|
||||||
wxPoint pos;
|
wxPoint pos;
|
||||||
pos.x = position.x + 10 - (tilePixelSize.GetWidth() / 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 + 10 - (tilePixelSize.GetHeight() / 10 + 3) * 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)
|
if (tileImages[index].GetWidth() != tilePixelSize.x * 2 - tilePixelSize.GetWidth() / TILE_WIDTH * TILE_PADDING_SCALE * 2)
|
||||||
dc.DrawBitmap(tileImages[index].Scale(tilePixelSize.x * 2 - 20,
|
dc.DrawBitmap(tileImages[index].Scale(tilePixelSize.x * 2 - tilePixelSize.GetWidth() / TILE_WIDTH * TILE_PADDING_SCALE * 2,
|
||||||
tilePixelSize.y * 2 - 20),
|
tilePixelSize.y * 2 - tilePixelSize.GetHeight() / TILE_HEIGHT * TILE_PADDING_SCALE * 2),
|
||||||
pos);
|
pos);
|
||||||
else
|
else
|
||||||
dc.DrawBitmap(tileImages[index], pos);
|
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
|
* Resizes tile and whole board bitmap size to the resolution, set in this
|
||||||
* instance
|
* instance
|
||||||
*/
|
*/
|
||||||
bool Drawer::resizeBoard(const TLVec& layout, const Dimensions& gridSize) {
|
bool Drawer::resizeBoard(const TLVec& layout, const Dimensions& gridSize, bool force) {
|
||||||
bool res = false;
|
bool res = false;
|
||||||
|
|
||||||
const int gridPoint = mmin(resolution.x / (gridSize.x * TILE_WIDTH),
|
const int gridPoint = mmin(
|
||||||
resolution.y / (gridSize.y * TILE_HEIGHT));
|
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));
|
wxLogDebug(wxString::Format("Resize board: %i", gridPoint));
|
||||||
|
|
||||||
if (gridPoint != prevGridPoint) {
|
if (gridPoint != prevGridPoint || force) {
|
||||||
tablePixelRect.SetSize({gridPoint * TILE_WIDTH * gridSize.x,
|
|
||||||
gridPoint * TILE_HEIGHT * gridSize.y});
|
|
||||||
|
|
||||||
tilePixelSize.Set(gridPoint * TILE_WIDTH, gridPoint * TILE_HEIGHT);
|
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,
|
tablePixelRect.SetPosition({(resolution.x - tablePixelRect.width) / 2,
|
||||||
(resolution.y - tablePixelRect.height) / 2});
|
(resolution.y - tablePixelRect.height) / 2});
|
||||||
|
|
||||||
if (gridPoint != prevGridPoint) {
|
if (gridPoint != prevGridPoint || force) {
|
||||||
composeBoard(layout, gridSize);
|
composeBoard(layout, gridSize);
|
||||||
res = true;
|
res = true;
|
||||||
}
|
}
|
||||||
@ -153,9 +165,12 @@ bool Drawer::resizeBoard(const TLVec& layout, const Dimensions& gridSize) {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
wxPoint Drawer::toGrid(const wxPoint& point) const {
|
wxPoint Drawer::toGrid(wxPoint point) const {
|
||||||
wxPoint out(-1, -1);
|
wxPoint out(-1, -1);
|
||||||
|
|
||||||
|
point.x -= boardPadding.x;
|
||||||
|
point.y -= boardPadding.y;
|
||||||
|
|
||||||
if (point.x >= tablePixelRect.x &&
|
if (point.x >= tablePixelRect.x &&
|
||||||
point.x <= tablePixelRect.x + tablePixelRect.width &&
|
point.x <= tablePixelRect.x + tablePixelRect.width &&
|
||||||
point.y >= tablePixelRect.y &&
|
point.y >= tablePixelRect.y &&
|
||||||
@ -168,20 +183,22 @@ wxPoint Drawer::toGrid(const wxPoint& point) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
wxPoint Drawer::fromGrid(int x, int y) 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 {
|
wxPoint Drawer::fromGrid(const wxPoint& point) const {
|
||||||
return fromGrid(point.x, point.y);
|
return fromGrid(point.x, point.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
wxSize Drawer::composeMinSize(const wxSize& gridSize) {
|
wxSize Drawer::composeMinSize(const Dimensions& gridSize) {
|
||||||
wxSize ms;
|
wxSize ms;
|
||||||
|
|
||||||
ms.SetWidth(MIN_GRID_POINT * TILE_WIDTH * gridSize.x);
|
ms.SetWidth(TILE_WIDTH * gridSize.x + gridSize.z * TILE_PADDING_SCALE);
|
||||||
ms.SetHeight(MIN_GRID_POINT * TILE_HEIGHT * gridSize.y);
|
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));
|
wxLogDebug(wxString::Format("MinSize %i %i", ms.x, ms.y));
|
||||||
|
|
||||||
return ms;
|
return ms;
|
||||||
}
|
}
|
||||||
|
11
Drawer.h
11
Drawer.h
@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
#define TILE_IMAGES_N 42
|
#define TILE_IMAGES_N 42
|
||||||
|
|
||||||
#define MIN_GRID_POINT 3
|
#define TILE_PADDING_SCALE 1.25
|
||||||
|
|
||||||
class Drawer {
|
class Drawer {
|
||||||
public:
|
public:
|
||||||
@ -22,13 +22,13 @@ public:
|
|||||||
void composeBoard(const TLVec& layout, const Dimensions& gridSize);
|
void composeBoard(const TLVec& layout, const Dimensions& gridSize);
|
||||||
|
|
||||||
void resizeBg(const wxSize& tableSize);
|
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(int x, int y) const;
|
||||||
wxPoint fromGrid(const wxPoint& point) const;
|
wxPoint fromGrid(const wxPoint& point) const;
|
||||||
|
|
||||||
wxSize composeMinSize(const wxSize& gridSize);
|
wxSize composeMinSize(const Dimensions& gridSize);
|
||||||
|
|
||||||
wxSize tableSize;
|
wxSize tableSize;
|
||||||
|
|
||||||
@ -47,6 +47,9 @@ private:
|
|||||||
wxBitmap bgBitmap;
|
wxBitmap bgBitmap;
|
||||||
wxBitmap boardBitmap;
|
wxBitmap boardBitmap;
|
||||||
|
|
||||||
|
wxPoint boardPadding;
|
||||||
|
wxPoint tilePadding;
|
||||||
|
|
||||||
int prevGridPoint;
|
int prevGridPoint;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ void GamePanel::Start(const wxString& path, bool solvable,
|
|||||||
sb->SetStatusText(PRemaining(controller.remaining), 1); // во вторую процент оставшихся камней
|
sb->SetStatusText(PRemaining(controller.remaining), 1); // во вторую процент оставшихся камней
|
||||||
|
|
||||||
bool redrawn =
|
bool redrawn =
|
||||||
drawer.resizeBoard(controller.getTable(), controller.gridSize); // Изменяем размер доски
|
drawer.resizeBoard(controller.getTable(), controller.gridSize, true); // Изменяем размер доски
|
||||||
if (!redrawn) // и если при этом изменился размер камней,
|
if (!redrawn) // и если при этом изменился размер камней,
|
||||||
drawer.composeBoard(controller.getTable(), controller.gridSize); // перерисовываем доску
|
drawer.composeBoard(controller.getTable(), controller.gridSize); // перерисовываем доску
|
||||||
|
|
||||||
@ -80,37 +80,37 @@ void GamePanel::OnResize(wxSizeEvent& _) {
|
|||||||
drawer.resizeBg(resolution); // изменяем размер фона
|
drawer.resizeBg(resolution); // изменяем размер фона
|
||||||
|
|
||||||
if (controller.gameStarted()) // если уже начали игру
|
if (controller.gameStarted()) // если уже начали игру
|
||||||
drawer.resizeBoard(controller.getTable(), controller.gridSize); // перерисовываем доску
|
drawer.resizeBoard(controller.getTable(), controller.gridSize, false); // перерисовываем доску
|
||||||
}
|
}
|
||||||
|
|
||||||
Refresh();
|
Refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
wxDEFINE_EVENT(END_EVT, wxCommandEvent);
|
wxDEFINE_EVENT(END_EVT, wxCommandEvent); // Определяем событие об окончании игры
|
||||||
|
|
||||||
void GamePanel::OnTimer(wxTimerEvent& _) {
|
void GamePanel::OnTimer(wxTimerEvent& _) {
|
||||||
controller.stopwatch += 1;
|
controller.stopwatch += 1; // Наращиваем счётчик таймера
|
||||||
sb->SetStatusText(LTimeToStr(controller.stopwatch), 0);
|
sb->SetStatusText(LTimeToStr(controller.stopwatch), 0); // и выводим его новое значение
|
||||||
|
|
||||||
if (controller.remaining == 0) {
|
if (controller.remaining == 0) { // если убраны все карты,
|
||||||
wxCommandEvent event(END_EVT);
|
wxCommandEvent event(END_EVT); // создаём экземпляр события окончания игры
|
||||||
event.SetString(LTimeToStr(controller.stopwatch));
|
event.SetString(LTimeToStr(controller.stopwatch)); // сохраняем в нём время игры
|
||||||
wxPostEvent(GetParent(), event);
|
wxPostEvent(GetParent(), event); // посылаем событие в родительский класс
|
||||||
|
|
||||||
timer.Stop();
|
timer.Stop(); // останавливаем таймер
|
||||||
controller.stopwatch = 0;
|
controller.stopwatch = 0; // сбрасываем счётчик таймера
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GamePanel::OnClick(wxMouseEvent& _) {
|
void GamePanel::OnClick(wxMouseEvent& _) {
|
||||||
if (controller.gameStarted()) {
|
if (controller.gameStarted()) { // Если игра начата,
|
||||||
controller.handleClick(ScreenToClient(wxGetMousePosition()));
|
controller.handleClick(ScreenToClient(wxGetMousePosition())); // выполняем обработку клика в контроллере
|
||||||
sb->SetStatusText(PRemaining(controller.remaining), 1);
|
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));
|
wxLogDebug(wxString::Format(_("Remaining %i"), controller.remaining));
|
||||||
|
|
||||||
Refresh();
|
Refresh(); // вызываем перерисовку окна
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ MainFrame::MainFrame()
|
|||||||
|
|
||||||
void MainFrame::initMenu() {
|
void MainFrame::initMenu() {
|
||||||
wxMenu* menuGame = new wxMenu; // Создаём подменю
|
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->Append(IDM_Open, _("Открыть карту"));
|
||||||
menuGame->AppendCheckItem(IDM_Solveable, _("Генерировать решаемую карту"));
|
menuGame->AppendCheckItem(IDM_Solveable, _("Генерировать решаемую карту"));
|
||||||
menuGame->AppendSeparator(); // Добавляем горизонтальный разделитель в меню
|
menuGame->AppendSeparator(); // Добавляем горизонтальный разделитель в меню
|
||||||
|
@ -16,9 +16,14 @@ bool XmlLayout::openFile(const wxString& openPath) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Dimensions XmlLayout::getDimensions() {
|
Dimensions XmlLayout::getDimensions() {
|
||||||
return {wxAtoi(layoutDoc.GetRoot()->GetAttribute("layers")),
|
auto root = layoutDoc.GetRoot();
|
||||||
wxAtoi(layoutDoc.GetRoot()->GetAttribute("ux")) + 2,
|
|
||||||
wxAtoi(layoutDoc.GetRoot()->GetAttribute("uy")) + 2};
|
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) {
|
void XmlLayout::readLayout(TLVec& table) {
|
||||||
@ -28,8 +33,8 @@ void XmlLayout::readLayout(TLVec& table) {
|
|||||||
|
|
||||||
while (tilePtr) {
|
while (tilePtr) {
|
||||||
if (tilePtr->GetName().IsSameAs("tile")) {
|
if (tilePtr->GetName().IsSameAs("tile")) {
|
||||||
x = wxAtoi(tilePtr->GetAttribute("x"));
|
x = wxAtoi(tilePtr->GetAttribute("x")) - lx;
|
||||||
y = wxAtoi(tilePtr->GetAttribute("y"));
|
y = wxAtoi(tilePtr->GetAttribute("y")) - ly;
|
||||||
l = wxAtoi(tilePtr->GetAttribute("layer")) - 1;
|
l = wxAtoi(tilePtr->GetAttribute("layer")) - 1;
|
||||||
|
|
||||||
table[l][x][y] = FREE;
|
table[l][x][y] = FREE;
|
||||||
|
@ -19,6 +19,9 @@ public:
|
|||||||
private:
|
private:
|
||||||
wxString path;
|
wxString path;
|
||||||
|
|
||||||
|
int lx;
|
||||||
|
int ly;
|
||||||
|
|
||||||
wxXmlDocument layoutDoc;
|
wxXmlDocument layoutDoc;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user