working before mass controller and drawer rewrite

This commit is contained in:
Dmitriy Shishkov 2022-06-08 21:41:44 +03:00
parent a2cf3d1c30
commit cb5738cde6
No known key found for this signature in database
GPG Key ID: 26720CB2A9608C97
9 changed files with 159 additions and 93 deletions

3
.gitignore vendored
View File

@ -1,4 +1,5 @@
build/ build/
class_diagram/ class_diagram/
diagrams/ diagrams/
.vscode/*.log .vscode/*.log
report/.~lock*

60
.vscode/settings.json vendored
View File

@ -27,6 +27,62 @@
"typeinfo": "cpp", "typeinfo": "cpp",
"unordered_set": "cpp", "unordered_set": "cpp",
"set": "cpp", "set": "cpp",
"tuple": "cpp" "tuple": "cpp",
} "hash_map": "cpp",
"hash_set": "cpp",
"string": "cpp",
"unordered_map": "cpp",
"string_view": "cpp",
"cctype": "cpp",
"clocale": "cpp",
"csignal": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"any": "cpp",
"atomic": "cpp",
"bit": "cpp",
"cfenv": "cpp",
"charconv": "cpp",
"chrono": "cpp",
"cinttypes": "cpp",
"complex": "cpp",
"condition_variable": "cpp",
"map": "cpp",
"algorithm": "cpp",
"functional": "cpp",
"iterator": "cpp",
"memory": "cpp",
"memory_resource": "cpp",
"numeric": "cpp",
"optional": "cpp",
"random": "cpp",
"ratio": "cpp",
"system_error": "cpp",
"utility": "cpp",
"fstream": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"mutex": "cpp",
"numbers": "cpp",
"ostream": "cpp",
"semaphore": "cpp",
"shared_mutex": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"stop_token": "cpp",
"streambuf": "cpp",
"thread": "cpp",
"valarray": "cpp",
"variant": "cpp"
},
"lldb.displayFormat": "auto",
"lldb.showDisassembly": "auto",
"lldb.dereferencePointers": true,
"lldb.consoleMode": "commands"
} }

View File

@ -8,13 +8,13 @@ wxIMPLEMENT_APP(MyApp);
bool MyApp::OnInit() { bool MyApp::OnInit() {
wxImage::AddHandler(new wxPNGHandler()); wxImage::AddHandler(new wxPNGHandler());
MainFrame* frame = new MainFrame(); MainFrame* frame = new MainFrame(); // Создаём,
if (argc >= 2 && wxFileExists(argv[1])) if (argc >= 2 && wxFileExists(argv[1]))
frame->layoutPath = argv[1]; frame->layoutPath = argv[1];
frame->Show(true); frame->Show(true); // показываем
SetTopWindow(frame); SetTopWindow(frame); // и устанавливаем главным окном, а так же выносим вперёд основное окно игры
return true; return true;
} }

View File

@ -51,8 +51,8 @@ void Controller::fillSolveableTable() {
int id = genRandId(); int id = genRandId();
if (id < 34) { if (id < 34) {
emplace_rand(id, positions, next_ptr, true);
emplace_rand(id, positions, next_ptr, false); emplace_rand(id, positions, next_ptr, false);
emplace_rand(id, positions, next_ptr, true);
cardsCounter[id]--; cardsCounter[id]--;
not_end -= 2; not_end -= 2;
@ -69,7 +69,7 @@ wxPoint Controller::getRandLowest() {
do { do {
int pos = rand() % overall; int pos = rand() % overall;
x = (pos / gridSize.y) % gridSize.x; x = pos / gridSize.y;
y = pos % gridSize.y; y = pos % gridSize.y;
} while (table[0][x][y] != FREE); } while (table[0][x][y] != FREE);
@ -103,16 +103,15 @@ void Controller::emplace_rand(int id, std::set<ThreePoint>& positions,
push_available(positions, *next_ptr); push_available(positions, *next_ptr);
auto prev_ptr = next_ptr; auto prev_ptr = next_ptr;
auto prev = *next_ptr; ThreePoint prev = *next_ptr;
do { cyclic_shift(next_ptr, positions);
next_ptr++;
if (next_ptr == positions.end())
next_ptr = positions.begin();
} while (!canBeUp && !wouldBeUpFree(prev, *next_ptr));
positions.erase(prev_ptr); positions.erase(prev_ptr);
do
cyclic_shift(next_ptr, positions);
while (!canBeUp && !wouldBeUpFree(prev, *next_ptr));
} }
bool Controller::wouldBeUpFree(const ThreePoint& prev, const ThreePoint& next) { bool Controller::wouldBeUpFree(const ThreePoint& prev, const ThreePoint& next) {
@ -167,10 +166,10 @@ void Controller::push_available(std::set<ThreePoint>& positions,
if (table[z+1][x-1][y] == FREE) // straight if (table[z+1][x-1][y] == FREE) // straight
positions.emplace(z+1, x-1, y); positions.emplace(z+1, x-1, y);
if (y >= 1 && (x < 2 || table[z][x-2][y-1] != FREE) && table[z+1][x-1][y-1] == FREE) // half top 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); positions.emplace(z+1, x-1, y-1);
if (y + 1 < gridSize.y && (x < 2 || table[z][x-2][y+1] != FREE) && table[z+1][x-1][y+1] == FREE) //half bottom 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); positions.emplace(z+1, x-1, y+1);
} }
@ -178,10 +177,10 @@ void Controller::push_available(std::set<ThreePoint>& positions,
if (table[z+1][x+1][y] == FREE) // straight if (table[z+1][x+1][y] == FREE) // straight
positions.emplace(z+1, x+1, y); positions.emplace(z+1, x+1, y);
if (y >= 1 && (x + 2 >= gridSize.x || table[z][x+2][y-1] != FREE) && table[z+1][x+1][y-1] == FREE) // half top 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); positions.emplace(z+1, x+1, y-1);
if (y + 1 < gridSize.y && (x + 2 >= gridSize.x || table[z][x+2][y+1] != FREE) && table[z+1][x+1][y+1] == FREE) //half bottom 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); positions.emplace(z+1, x+1, y+1);
} }
} }

View File

@ -5,6 +5,9 @@
#include "events.h" #include "events.h"
#include "utils.h" #include "utils.h"
/**
* Объявляем таблицу обработчиков событий
*/
// clang-format off // clang-format off
wxBEGIN_EVENT_TABLE(GamePanel, wxPanel) wxBEGIN_EVENT_TABLE(GamePanel, wxPanel)
EVT_PAINT(GamePanel::OnPaint) EVT_PAINT(GamePanel::OnPaint)
@ -15,32 +18,32 @@ wxEND_EVENT_TABLE();
// clang-format on // clang-format on
GamePanel::GamePanel(wxFrame* parent) GamePanel::GamePanel(wxFrame* parent)
: wxPanel(parent), controller(drawer), : wxPanel(parent), controller(drawer), // вызываем родительский конструктор и передаём drawer в конструктор
sb(((wxFrame*)this->GetParent())->GetStatusBar()), timer(this, TIMER_ID) { sb(((wxFrame*)this->GetParent())->GetStatusBar()), timer(this, TIMER_ID) { // инициализируем указатель на статус бар окна, создаём таймер
SetBackgroundStyle(wxBG_STYLE_PAINT); SetBackgroundStyle(wxBG_STYLE_PAINT); // Устанавливаем wxBG_STYLE_PAINT для того, чтобы при вызове OnPaint не мерцало окно
} }
void GamePanel::Start(const wxString& path, bool solvable, void GamePanel::Start(const wxString& path, bool solvable,
std::function<void(const wxSize& size)> setMinSize) { std::function<void(const wxSize& size)> setMinSize) {
wxLogDebug(_("Started game")); wxLogDebug(_("Started game")); // здесь и далее - сообщения, которые выводятся в консоль в дебаг-версии
controller.stopwatch = 0; controller.stopwatch = 0; // сбрасываем таймер
controller.loadLayout(path); controller.loadLayout(path); // загружаем в контроллер схему
controller.fill(solvable); controller.fill(solvable); // заполняем игровое поле
setMinSize(drawer.composeMinSize(controller.gridSize)); setMinSize(drawer.composeMinSize(controller.gridSize)); // устанавливаем минимальный размер окна
timer.Start(1000, wxTIMER_CONTINUOUS); timer.Start(1000, wxTIMER_CONTINUOUS); // Запускаем таймер для вызова события каждую секунду
sb->SetStatusText(LTimeToStr(controller.stopwatch), 0); sb->SetStatusText(LTimeToStr(controller.stopwatch), 0); // В первую колонку статус бара пишем время игры,
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); // Изменяем размер доски
if (!redrawn) if (!redrawn) // и если при этом изменился размер камней,
drawer.composeBoard(controller.getTable(), controller.gridSize); drawer.composeBoard(controller.getTable(), controller.gridSize); // перерисовываем доску
Refresh(); Refresh(); // вызываем перерисовку окна
} }
void GamePanel::undo() { void GamePanel::undo() {
@ -52,32 +55,32 @@ void GamePanel::undo() {
} }
void GamePanel::reshuffle(bool solvable) { void GamePanel::reshuffle(bool solvable) {
controller.free_table(); controller.free_table(); // очищаем стол в контроллере
controller.fill(solvable); controller.fill(solvable); // заполняем его заново
drawer.composeBoard(controller.getTable(), controller.gridSize); drawer.composeBoard(controller.getTable(), controller.gridSize); // перерисовываем стол
Refresh(); Refresh();
} }
void GamePanel::OnPaint(wxPaintEvent& _) { void GamePanel::OnPaint(wxPaintEvent& _) {
wxAutoBufferedPaintDC dc(this); wxAutoBufferedPaintDC dc(this); // создаём контекст с буфером для предотвращения мерцания
wxLogDebug(_("OnPaint")); wxLogDebug(_("OnPaint"));
drawer.drawTable(dc); drawer.drawTable(dc); // отрисовываем в нём кадр
} }
void GamePanel::OnResize(wxSizeEvent& _) { void GamePanel::OnResize(wxSizeEvent& _) {
const wxSize& resolution = GetClientSize(); const wxSize& resolution = GetClientSize(); // получаем размер клиентской части окна (без рамок, тулбара и статусбара)
wxLogDebug(wxString::Format("OnResize %i %i", resolution.x, resolution.y)); wxLogDebug(wxString::Format("OnResize %i %i", resolution.x, resolution.y));
if (isPositive(resolution)) { if (isPositive(resolution)) { // на некоторых платформах первоначальный размер экрана может установиться в 0, поэтому лучше проверять это
drawer.resizeBg(resolution); drawer.resizeBg(resolution); // изменяем размер фона
if (controller.gameStarted()) if (controller.gameStarted()) // если уже начали игру
drawer.resizeBoard(controller.getTable(), controller.gridSize); drawer.resizeBoard(controller.getTable(), controller.gridSize); // перерисовываем доску
} }
Refresh(); Refresh();

View File

@ -14,41 +14,35 @@ MainFrame::MainFrame()
dataDirPath(wxStandardPaths::Get().GetUserDataDir()) { dataDirPath(wxStandardPaths::Get().GetUserDataDir()) {
SetIcon(logo_icon); SetIcon(logo_icon);
initMenu(); initMenu(); // Создание пунктов меню
bindMenu(); bindMenu(); // Подключение обработчиков пунктов меню
Bind(wxEVT_SHOW, [this](wxShowEvent& _) -> void { Bind(wxEVT_SHOW, [this](wxShowEvent& _) -> void { // обработчик события отображения окна
if (!layoutPath.IsEmpty() || openLayout()) if (!layoutPath.IsEmpty() || openLayout()) // если пользователь выбрал схему
panel->Start(layoutPath, solveable, startGame();
[this](const wxSize& size) -> void {
this->SetMinClientSize(size);
});
}); });
Bind(END_EVT, [this](wxCommandEvent& evt) -> void { Bind(END_EVT, [this](wxCommandEvent& evt) -> void { // обработчик кастомного события окончания игры
wxMessageDialog dlg(this, _("Хотите сыграть снова?"), wxMessageDialog dlg(this, _("Хотите сыграть снова?"),
_("Игра окончена"), wxYES_NO); _("Игра окончена"), wxYES_NO); // Создаём диалог с предложением начать игру заново
dlg.SetExtendedMessage(_("Поздравляем, вы закончили игру за ") + dlg.SetExtendedMessage(_("Поздравляем, вы закончили игру за ") +
evt.GetString()); evt.GetString()); // Устанавливаем время, затраченное на полное выполнение карты
if (dlg.ShowModal() == wxID_YES) { if (dlg.ShowModal() == wxID_YES) // Если пользователь хочет,
panel->Start(layoutPath, solveable, startGame(); // начинаме игру заново
[this](const wxSize& size) -> void {
this->SetMinClientSize(size);
});
}
}); });
CreateStatusBar(2); CreateStatusBar(2); // Создаём статусбар с двумя колонками
panel = new GamePanel(this); panel = new GamePanel(this); // Создаём
panel->SetFocus(); // и показываем панель, где отрисовывается игровое поле
} }
void MainFrame::initMenu() { void MainFrame::initMenu() {
wxMenu* menuGame = new wxMenu; wxMenu* menuGame = new wxMenu; // Создаём подменю
menuGame->Append(IDM_New_Game, _("Начать сначала")); menuGame->Append(IDM_New_Game, _("Начать сначала")); // Создаем пункт меню с id обработчика IDM_New_Game, далее аналогично
menuGame->Append(IDM_Open, _("Открыть карту")); menuGame->Append(IDM_Open, _("Открыть карту"));
menuGame->AppendCheckItem(IDM_Solveable, _("Генерировать решаемую карту")); menuGame->AppendCheckItem(IDM_Solveable, _("Генерировать решаемую карту"));
menuGame->AppendSeparator(); menuGame->AppendSeparator(); // Добавляем горизонтальный разделитель в меню
menuGame->Append(IDM_Undo, _("Отменить ход")); menuGame->Append(IDM_Undo, _("Отменить ход"));
menuGame->Append(IDM_Reshuffle, _("Перемешать поле")); menuGame->Append(IDM_Reshuffle, _("Перемешать поле"));
menuGame->AppendSeparator(); menuGame->AppendSeparator();
@ -59,71 +53,64 @@ void MainFrame::initMenu() {
menuHelp->Append(IDM_Rules, _("Правила игры")); menuHelp->Append(IDM_Rules, _("Правила игры"));
menuHelp->Append(IDM_About, _("О программе")); menuHelp->Append(IDM_About, _("О программе"));
wxMenuBar* menuBar = new wxMenuBar; wxMenuBar* menuBar = new wxMenuBar; // Создаём меню бар,
menuBar->Append(menuGame, _("Игра")); menuBar->Append(menuGame, _("Игра")); // куда подключаем созданные выше подменю
menuBar->Append(menuHelp, _("Помощь")); menuBar->Append(menuHelp, _("Помощь"));
SetMenuBar(menuBar); SetMenuBar(menuBar); // И устанавливаем его как основной для этой панели
} }
void MainFrame::bindMenu() { void MainFrame::bindMenu() {
Bind( Bind(
wxEVT_MENU, [this](wxCommandEvent& _) -> void { Close(); }, IDM_Exit); wxEVT_MENU, [this](wxCommandEvent& _) -> void { Close(); }, IDM_Exit); // Вешаем обработчик закртытия игры при выборе соответствующего пункта меню
Bind( Bind( // Вешаем обработчик для открытия схемы и запуска соответствующей игры
wxEVT_MENU, wxEVT_MENU,
[this](wxCommandEvent& _) -> void { [this](wxCommandEvent& _) -> void {
if (openLayout()) if (openLayout())
panel->Start(layoutPath, solveable, startGame();
[this](const wxSize& size) -> void {
this->SetMinClientSize(size);
});
}, },
IDM_Open); IDM_Open);
Bind( Bind( // Вешаем обработчик для открытия диалога с "помощью"
wxEVT_MENU, wxEVT_MENU,
[this](wxCommandEvent& _) -> void { [this](wxCommandEvent& _) -> void {
(new HelpDlg(this, wxID_ANY))->Show(); (new HelpDlg(this, wxID_ANY))->Show();
}, },
IDM_Help); IDM_Help);
Bind( Bind( // Вешаем обработчик для открытия диалога "о программе"
wxEVT_MENU, wxEVT_MENU,
[this](wxCommandEvent& _) -> void { [this](wxCommandEvent& _) -> void {
(new AboutDlg(this, wxID_ANY))->Show(); (new AboutDlg(this, wxID_ANY))->Show();
}, },
IDM_About); IDM_About);
Bind( Bind( // Вешаем обработчик для открытия диалога с "правилами"
wxEVT_MENU, wxEVT_MENU,
[this](wxCommandEvent& _) -> void { [this](wxCommandEvent& _) -> void {
(new RulesDlg(this, wxID_ANY))->Show(); (new RulesDlg(this, wxID_ANY))->Show();
}, },
IDM_Rules); IDM_Rules);
Bind( Bind( // Вешаем обработчик для запуска новой игры
wxEVT_MENU, wxEVT_MENU,
[this](wxCommandEvent& _) -> void { [this](wxCommandEvent& _) -> void {
if (!layoutPath.IsEmpty() || openLayout()) { if (!layoutPath.IsEmpty() || openLayout())
panel->Start(layoutPath, solveable, startGame();
[this](const wxSize& size) -> void {
this->SetMinClientSize(size);
});
}
}, },
IDM_New_Game); IDM_New_Game);
Bind( Bind( // Вешаем обработчик для установки режима генерации поля
wxEVT_MENU, wxEVT_MENU,
[this](wxCommandEvent& evt) -> void { solveable = evt.IsChecked(); }, [this](wxCommandEvent& evt) -> void { solveable = evt.IsChecked(); },
IDM_Solveable); IDM_Solveable);
Bind( Bind( // Вешаем обработчик для отмены хода
wxEVT_MENU, [this](wxCommandEvent& _) -> void { panel->undo(); }, wxEVT_MENU, [this](wxCommandEvent& _) -> void { panel->undo(); },
IDM_Undo); IDM_Undo);
Bind( Bind( // Вешаем обработчик для перемешивания поля
wxEVT_MENU, wxEVT_MENU,
[this](wxCommandEvent& _) -> void { panel->reshuffle(solveable); }, [this](wxCommandEvent& _) -> void { panel->reshuffle(solveable); },
IDM_Reshuffle); IDM_Reshuffle);
@ -135,10 +122,10 @@ void MainFrame::bindMenu() {
* @return true if user have chosen a file, false if cancelled * @return true if user have chosen a file, false if cancelled
*/ */
bool MainFrame::openLayout() { bool MainFrame::openLayout() {
wxFileDialog openFileDlg( wxFileDialog openFileDlg( // Создаём диалог для запроса файла схемы
this, _("Открыть карту"), this, _("Открыть карту"),
dataDirPath + wxFileName::GetPathSeparator() + _("layouts"), dataDirPath + wxFileName::GetPathSeparator() + _("layouts"), // Стандартный путь до файлов приложения
_("Turtle.smlf"), _("Файлы Mahjong карт (*.smlf)|*.smlf"), _("Turtle.smlf"), _("Файлы Mahjong карт (*.smlf)|*.smlf"), // Стандартный файл и поддерживаемые форматы файлов
wxFD_OPEN | wxFD_FILE_MUST_EXIST); wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (openFileDlg.ShowModal() == wxID_CANCEL) if (openFileDlg.ShowModal() == wxID_CANCEL)
@ -147,3 +134,9 @@ bool MainFrame::openLayout() {
layoutPath = openFileDlg.GetPath(); layoutPath = openFileDlg.GetPath();
return true; return true;
} }
void MainFrame::startGame() {
panel->Start(layoutPath, solveable, // запускаем игру с путём до схемы, выбранным режимом заполнения поля
setMinSize_fn // и проброшенной функцией установки минимального размера окна
);
}

View File

@ -21,6 +21,10 @@ private:
GamePanel* panel; GamePanel* panel;
bool openLayout(); bool openLayout();
void startGame();
const std::function<void(const wxSize&)> setMinSize_fn =
[this](const wxSize& size) -> void { this->SetMinClientSize(size); };
const wxString dataDirPath; const wxString dataDirPath;

View File

@ -19,4 +19,10 @@ wxString PRemaining(uint8_t remaining) {
bool isPositive(const wxSize& size) { bool isPositive(const wxSize& size) {
return size.x > 0 && size.y > 0; return size.x > 0 && size.y > 0;
} }
void cyclic_shift(std::set<ThreePoint>::iterator& it, const std::set<ThreePoint>& cont) {
it++;
if (it == cont.end())
it = cont.begin();
}

View File

@ -3,7 +3,9 @@
#include "wxw.h" #include "wxw.h"
#include <set>
#include <vector> #include <vector>
#include <iterator>
using std::vector; using std::vector;
@ -55,4 +57,6 @@ enum Values { MATCHED = -3, EMPTY, FREE };
bool isPositive(const wxSize& size); bool isPositive(const wxSize& size);
void cyclic_shift(std::set<ThreePoint>::iterator& it, const std::set<ThreePoint>& cont);
#endif #endif