diff --git a/.gitignore b/.gitignore index 8d8154e..98bde69 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ build/ class_diagram/ diagrams/ -.vscode/*.log \ No newline at end of file +.vscode/*.log +report/.~lock* \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 93df527..7260e29 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -27,6 +27,62 @@ "typeinfo": "cpp", "unordered_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" } \ No newline at end of file diff --git a/App.cpp b/App.cpp index 79ac0ca..3fb5f21 100644 --- a/App.cpp +++ b/App.cpp @@ -8,13 +8,13 @@ wxIMPLEMENT_APP(MyApp); bool MyApp::OnInit() { wxImage::AddHandler(new wxPNGHandler()); - MainFrame* frame = new MainFrame(); + MainFrame* frame = new MainFrame(); // Создаём, if (argc >= 2 && wxFileExists(argv[1])) frame->layoutPath = argv[1]; - frame->Show(true); - SetTopWindow(frame); + frame->Show(true); // показываем + SetTopWindow(frame); // и устанавливаем главным окном, а так же выносим вперёд основное окно игры return true; } diff --git a/Controller.cpp b/Controller.cpp index f24c845..1161347 100644 --- a/Controller.cpp +++ b/Controller.cpp @@ -51,8 +51,8 @@ void Controller::fillSolveableTable() { int id = genRandId(); if (id < 34) { - emplace_rand(id, positions, next_ptr, true); emplace_rand(id, positions, next_ptr, false); + emplace_rand(id, positions, next_ptr, true); cardsCounter[id]--; not_end -= 2; @@ -69,7 +69,7 @@ wxPoint Controller::getRandLowest() { do { int pos = rand() % overall; - x = (pos / gridSize.y) % gridSize.x; + x = pos / gridSize.y; y = pos % gridSize.y; } while (table[0][x][y] != FREE); @@ -103,16 +103,15 @@ void Controller::emplace_rand(int id, std::set& positions, push_available(positions, *next_ptr); auto prev_ptr = next_ptr; - auto prev = *next_ptr; + ThreePoint prev = *next_ptr; - do { - next_ptr++; - - if (next_ptr == positions.end()) - next_ptr = positions.begin(); - } while (!canBeUp && !wouldBeUpFree(prev, *next_ptr)); + cyclic_shift(next_ptr, positions); 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) { @@ -167,10 +166,10 @@ void Controller::push_available(std::set& positions, if (table[z+1][x-1][y] == FREE) // straight positions.emplace(z+1, x-1, y); - if (y >= 1 && (x < 2 || table[z][x-2][y-1] != FREE) && 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); - 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); } @@ -178,10 +177,10 @@ void Controller::push_available(std::set& positions, if (table[z+1][x+1][y] == FREE) // straight positions.emplace(z+1, x+1, y); - if (y >= 1 && (x + 2 >= gridSize.x || table[z][x+2][y-1] != FREE) && 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); - 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); } } diff --git a/GamePanel.cpp b/GamePanel.cpp index 93cfeb0..08fc860 100644 --- a/GamePanel.cpp +++ b/GamePanel.cpp @@ -5,6 +5,9 @@ #include "events.h" #include "utils.h" +/** + * Объявляем таблицу обработчиков событий + */ // clang-format off wxBEGIN_EVENT_TABLE(GamePanel, wxPanel) EVT_PAINT(GamePanel::OnPaint) @@ -15,32 +18,32 @@ wxEND_EVENT_TABLE(); // clang-format on GamePanel::GamePanel(wxFrame* parent) - : wxPanel(parent), controller(drawer), - sb(((wxFrame*)this->GetParent())->GetStatusBar()), timer(this, TIMER_ID) { - SetBackgroundStyle(wxBG_STYLE_PAINT); + : wxPanel(parent), controller(drawer), // вызываем родительский конструктор и передаём drawer в конструктор + sb(((wxFrame*)this->GetParent())->GetStatusBar()), timer(this, TIMER_ID) { // инициализируем указатель на статус бар окна, создаём таймер + SetBackgroundStyle(wxBG_STYLE_PAINT); // Устанавливаем wxBG_STYLE_PAINT для того, чтобы при вызове OnPaint не мерцало окно } void GamePanel::Start(const wxString& path, bool solvable, std::function setMinSize) { - wxLogDebug(_("Started game")); + wxLogDebug(_("Started game")); // здесь и далее - сообщения, которые выводятся в консоль в дебаг-версии - controller.stopwatch = 0; - controller.loadLayout(path); - controller.fill(solvable); + controller.stopwatch = 0; // сбрасываем таймер + controller.loadLayout(path); // загружаем в контроллер схему + 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(PRemaining(controller.remaining), 1); + sb->SetStatusText(LTimeToStr(controller.stopwatch), 0); // В первую колонку статус бара пишем время игры, + sb->SetStatusText(PRemaining(controller.remaining), 1); // во вторую процент оставшихся камней - bool redrawn = - drawer.resizeBoard(controller.getTable(), controller.gridSize); - if (!redrawn) - drawer.composeBoard(controller.getTable(), controller.gridSize); + bool redrawn = + drawer.resizeBoard(controller.getTable(), controller.gridSize); // Изменяем размер доски + if (!redrawn) // и если при этом изменился размер камней, + drawer.composeBoard(controller.getTable(), controller.gridSize); // перерисовываем доску - Refresh(); + Refresh(); // вызываем перерисовку окна } void GamePanel::undo() { @@ -52,32 +55,32 @@ void GamePanel::undo() { } void GamePanel::reshuffle(bool solvable) { - controller.free_table(); - controller.fill(solvable); + controller.free_table(); // очищаем стол в контроллере + controller.fill(solvable); // заполняем его заново - drawer.composeBoard(controller.getTable(), controller.gridSize); + drawer.composeBoard(controller.getTable(), controller.gridSize); // перерисовываем стол Refresh(); } void GamePanel::OnPaint(wxPaintEvent& _) { - wxAutoBufferedPaintDC dc(this); + wxAutoBufferedPaintDC dc(this); // создаём контекст с буфером для предотвращения мерцания wxLogDebug(_("OnPaint")); - drawer.drawTable(dc); + drawer.drawTable(dc); // отрисовываем в нём кадр } void GamePanel::OnResize(wxSizeEvent& _) { - const wxSize& resolution = GetClientSize(); + const wxSize& resolution = GetClientSize(); // получаем размер клиентской части окна (без рамок, тулбара и статусбара) wxLogDebug(wxString::Format("OnResize %i %i", resolution.x, resolution.y)); - if (isPositive(resolution)) { - drawer.resizeBg(resolution); + if (isPositive(resolution)) { // на некоторых платформах первоначальный размер экрана может установиться в 0, поэтому лучше проверять это + drawer.resizeBg(resolution); // изменяем размер фона - if (controller.gameStarted()) - drawer.resizeBoard(controller.getTable(), controller.gridSize); + if (controller.gameStarted()) // если уже начали игру + drawer.resizeBoard(controller.getTable(), controller.gridSize); // перерисовываем доску } Refresh(); diff --git a/MainFrame.cpp b/MainFrame.cpp index 66fb1b4..566791a 100644 --- a/MainFrame.cpp +++ b/MainFrame.cpp @@ -14,41 +14,35 @@ MainFrame::MainFrame() dataDirPath(wxStandardPaths::Get().GetUserDataDir()) { SetIcon(logo_icon); - initMenu(); - bindMenu(); + initMenu(); // Создание пунктов меню + bindMenu(); // Подключение обработчиков пунктов меню - Bind(wxEVT_SHOW, [this](wxShowEvent& _) -> void { - if (!layoutPath.IsEmpty() || openLayout()) - panel->Start(layoutPath, solveable, - [this](const wxSize& size) -> void { - this->SetMinClientSize(size); - }); + Bind(wxEVT_SHOW, [this](wxShowEvent& _) -> void { // обработчик события отображения окна + if (!layoutPath.IsEmpty() || openLayout()) // если пользователь выбрал схему + startGame(); }); - Bind(END_EVT, [this](wxCommandEvent& evt) -> void { + Bind(END_EVT, [this](wxCommandEvent& evt) -> void { // обработчик кастомного события окончания игры wxMessageDialog dlg(this, _("Хотите сыграть снова?"), - _("Игра окончена"), wxYES_NO); + _("Игра окончена"), wxYES_NO); // Создаём диалог с предложением начать игру заново dlg.SetExtendedMessage(_("Поздравляем, вы закончили игру за ") + - evt.GetString()); - if (dlg.ShowModal() == wxID_YES) { - panel->Start(layoutPath, solveable, - [this](const wxSize& size) -> void { - this->SetMinClientSize(size); - }); - } + evt.GetString()); // Устанавливаем время, затраченное на полное выполнение карты + if (dlg.ShowModal() == wxID_YES) // Если пользователь хочет, + startGame(); // начинаме игру заново }); - CreateStatusBar(2); + CreateStatusBar(2); // Создаём статусбар с двумя колонками - panel = new GamePanel(this); + panel = new GamePanel(this); // Создаём + panel->SetFocus(); // и показываем панель, где отрисовывается игровое поле } void MainFrame::initMenu() { - wxMenu* menuGame = new wxMenu; - menuGame->Append(IDM_New_Game, _("Начать сначала")); + wxMenu* menuGame = new wxMenu; // Создаём подменю + menuGame->Append(IDM_New_Game, _("Начать сначала")); // Создаем пункт меню с id обработчика IDM_New_Game, далее аналогично menuGame->Append(IDM_Open, _("Открыть карту")); menuGame->AppendCheckItem(IDM_Solveable, _("Генерировать решаемую карту")); - menuGame->AppendSeparator(); + menuGame->AppendSeparator(); // Добавляем горизонтальный разделитель в меню menuGame->Append(IDM_Undo, _("Отменить ход")); menuGame->Append(IDM_Reshuffle, _("Перемешать поле")); menuGame->AppendSeparator(); @@ -59,71 +53,64 @@ void MainFrame::initMenu() { menuHelp->Append(IDM_Rules, _("Правила игры")); menuHelp->Append(IDM_About, _("О программе")); - wxMenuBar* menuBar = new wxMenuBar; - menuBar->Append(menuGame, _("Игра")); + wxMenuBar* menuBar = new wxMenuBar; // Создаём меню бар, + menuBar->Append(menuGame, _("Игра")); // куда подключаем созданные выше подменю menuBar->Append(menuHelp, _("Помощь")); - SetMenuBar(menuBar); + SetMenuBar(menuBar); // И устанавливаем его как основной для этой панели } void MainFrame::bindMenu() { Bind( - wxEVT_MENU, [this](wxCommandEvent& _) -> void { Close(); }, IDM_Exit); + wxEVT_MENU, [this](wxCommandEvent& _) -> void { Close(); }, IDM_Exit); // Вешаем обработчик закртытия игры при выборе соответствующего пункта меню - Bind( + Bind( // Вешаем обработчик для открытия схемы и запуска соответствующей игры wxEVT_MENU, [this](wxCommandEvent& _) -> void { if (openLayout()) - panel->Start(layoutPath, solveable, - [this](const wxSize& size) -> void { - this->SetMinClientSize(size); - }); + startGame(); }, IDM_Open); - Bind( + Bind( // Вешаем обработчик для открытия диалога с "помощью" wxEVT_MENU, [this](wxCommandEvent& _) -> void { (new HelpDlg(this, wxID_ANY))->Show(); }, IDM_Help); - Bind( + Bind( // Вешаем обработчик для открытия диалога "о программе" wxEVT_MENU, [this](wxCommandEvent& _) -> void { (new AboutDlg(this, wxID_ANY))->Show(); }, IDM_About); - Bind( + Bind( // Вешаем обработчик для открытия диалога с "правилами" wxEVT_MENU, [this](wxCommandEvent& _) -> void { (new RulesDlg(this, wxID_ANY))->Show(); }, IDM_Rules); - Bind( + Bind( // Вешаем обработчик для запуска новой игры wxEVT_MENU, [this](wxCommandEvent& _) -> void { - if (!layoutPath.IsEmpty() || openLayout()) { - panel->Start(layoutPath, solveable, - [this](const wxSize& size) -> void { - this->SetMinClientSize(size); - }); - } + if (!layoutPath.IsEmpty() || openLayout()) + startGame(); }, IDM_New_Game); - Bind( + Bind( // Вешаем обработчик для установки режима генерации поля wxEVT_MENU, [this](wxCommandEvent& evt) -> void { solveable = evt.IsChecked(); }, IDM_Solveable); - Bind( + Bind( // Вешаем обработчик для отмены хода wxEVT_MENU, [this](wxCommandEvent& _) -> void { panel->undo(); }, IDM_Undo); - Bind( + Bind( // Вешаем обработчик для перемешивания поля wxEVT_MENU, [this](wxCommandEvent& _) -> void { panel->reshuffle(solveable); }, IDM_Reshuffle); @@ -135,10 +122,10 @@ void MainFrame::bindMenu() { * @return true if user have chosen a file, false if cancelled */ bool MainFrame::openLayout() { - wxFileDialog openFileDlg( + wxFileDialog openFileDlg( // Создаём диалог для запроса файла схемы this, _("Открыть карту"), - dataDirPath + wxFileName::GetPathSeparator() + _("layouts"), - _("Turtle.smlf"), _("Файлы Mahjong карт (*.smlf)|*.smlf"), + dataDirPath + wxFileName::GetPathSeparator() + _("layouts"), // Стандартный путь до файлов приложения + _("Turtle.smlf"), _("Файлы Mahjong карт (*.smlf)|*.smlf"), // Стандартный файл и поддерживаемые форматы файлов wxFD_OPEN | wxFD_FILE_MUST_EXIST); if (openFileDlg.ShowModal() == wxID_CANCEL) @@ -147,3 +134,9 @@ bool MainFrame::openLayout() { layoutPath = openFileDlg.GetPath(); return true; } + +void MainFrame::startGame() { + panel->Start(layoutPath, solveable, // запускаем игру с путём до схемы, выбранным режимом заполнения поля + setMinSize_fn // и проброшенной функцией установки минимального размера окна + ); +} diff --git a/MainFrame.h b/MainFrame.h index 3551236..d4853e0 100644 --- a/MainFrame.h +++ b/MainFrame.h @@ -21,6 +21,10 @@ private: GamePanel* panel; bool openLayout(); + void startGame(); + + const std::function setMinSize_fn = + [this](const wxSize& size) -> void { this->SetMinClientSize(size); }; const wxString dataDirPath; diff --git a/utils.cpp b/utils.cpp index b241429..7cd0ec3 100644 --- a/utils.cpp +++ b/utils.cpp @@ -19,4 +19,10 @@ wxString PRemaining(uint8_t remaining) { bool isPositive(const wxSize& size) { return size.x > 0 && size.y > 0; -} \ No newline at end of file +} + +void cyclic_shift(std::set::iterator& it, const std::set& cont) { + it++; + if (it == cont.end()) + it = cont.begin(); +} diff --git a/utils.h b/utils.h index cc5cb5e..a1c7c35 100644 --- a/utils.h +++ b/utils.h @@ -3,7 +3,9 @@ #include "wxw.h" +#include #include +#include using std::vector; @@ -55,4 +57,6 @@ enum Values { MATCHED = -3, EMPTY, FREE }; bool isPositive(const wxSize& size); +void cyclic_shift(std::set::iterator& it, const std::set& cont); + #endif