From 8a205242a29549a03c72a223141168869df3cdd2 Mon Sep 17 00:00:00 2001 From: Nicola Revelant Date: Thu, 15 Jun 2023 14:40:38 +0200 Subject: [PATCH] Add toggle first player button --- images/{pcDame.png => firstDame.png} | Bin images/{pcPawn.png => firstPawn.png} | Bin images/{plDame.png => secondDame.png} | Bin images/{plPawn.png => secondPawn.png} | Bin src/ChessboardGrid/ChessboardGrid.cpp | 22 +++--- src/ChessboardGrid/ChessboardGrid.h | 25 ++++--- src/Frame/Frame.cpp | 35 +++++---- src/Frame/Frame.h | 9 ++- src/GameUtils/GameUtils.cpp | 12 +++- src/MatchManager/MatchManager.cpp | 100 +++++++++++++++----------- src/MatchManager/MatchManager.h | 28 ++++++-- 11 files changed, 144 insertions(+), 87 deletions(-) rename images/{pcDame.png => firstDame.png} (100%) rename images/{pcPawn.png => firstPawn.png} (100%) rename images/{plDame.png => secondDame.png} (100%) rename images/{plPawn.png => secondPawn.png} (100%) diff --git a/images/pcDame.png b/images/firstDame.png similarity index 100% rename from images/pcDame.png rename to images/firstDame.png diff --git a/images/pcPawn.png b/images/firstPawn.png similarity index 100% rename from images/pcPawn.png rename to images/firstPawn.png diff --git a/images/plDame.png b/images/secondDame.png similarity index 100% rename from images/plDame.png rename to images/secondDame.png diff --git a/images/plPawn.png b/images/secondPawn.png similarity index 100% rename from images/plPawn.png rename to images/secondPawn.png diff --git a/src/ChessboardGrid/ChessboardGrid.cpp b/src/ChessboardGrid/ChessboardGrid.cpp index f2c32e7..3c4b20d 100644 --- a/src/ChessboardGrid/ChessboardGrid.cpp +++ b/src/ChessboardGrid/ChessboardGrid.cpp @@ -46,19 +46,19 @@ bool ChessboardGrid::Create(const wxColour &darkColor, const wxColour &lightColo return true; } -void ChessboardGrid::updateIcons(const wxBitmap &pcPawn, const wxBitmap &pcDame, - const wxBitmap &plPawn, const wxBitmap &plDame) { - m_pcPawn = pcPawn; - m_pcDame = pcDame; - m_plPawn = plPawn; - m_plDame = plDame; +void ChessboardGrid::updateIcons(const wxBitmap &firstPawn, const wxBitmap &firstDame, + const wxBitmap &secondPawn, const wxBitmap &secondDame) { + mFirstPawn = firstPawn; + mFirstDame = firstDame; + mSecondPawn = secondPawn; + mSecondDame = secondDame; } void ChessboardGrid::OnItemMouseClicked(wxMouseEvent &evt) { wxPostEvent(this, evt); } -void ChessboardGrid::updateDisposition(const GameUtils::Disposition &newDisposition) { +void ChessboardGrid::updateDisposition(const GameUtils::Disposition &newDisposition, bool pcIsFirstPlayer) { for (int i = 0; i < 64; i++) { chessboard[i]->SetBorder(); switch (newDisposition[i]) { @@ -66,16 +66,16 @@ void ChessboardGrid::updateDisposition(const GameUtils::Disposition &newDisposit chessboard[i]->SetBitmap(wxNullBitmap); break; case GameUtils::PC_PAWN: - chessboard[i]->SetBitmap(m_pcPawn); + chessboard[i]->SetBitmap(pcIsFirstPlayer ? mFirstPawn : mSecondPawn); break; case GameUtils::PC_DAME: - chessboard[i]->SetBitmap(m_pcDame); + chessboard[i]->SetBitmap(pcIsFirstPlayer ? mFirstDame : mSecondDame); break; case GameUtils::PL_PAWN: - chessboard[i]->SetBitmap(m_plPawn); + chessboard[i]->SetBitmap(pcIsFirstPlayer ? mSecondPawn : mFirstPawn); break; case GameUtils::PL_DAME: - chessboard[i]->SetBitmap(m_plDame); + chessboard[i]->SetBitmap(pcIsFirstPlayer ? mSecondDame : mFirstDame); break; } } diff --git a/src/ChessboardGrid/ChessboardGrid.h b/src/ChessboardGrid/ChessboardGrid.h index c514542..f25d8ae 100644 --- a/src/ChessboardGrid/ChessboardGrid.h +++ b/src/ChessboardGrid/ChessboardGrid.h @@ -26,31 +26,30 @@ class ChessboardGrid : public wxPanel { * @param winId Window ID, or wxID_ANY * @param pos Position relative to the parent */ - ChessboardGrid(const wxColour &darkColor, const wxColour &lightColor, int squareSize, - wxWindow *parent, wxWindowID winId = wxID_ANY, - const wxPoint &pos = wxDefaultPosition); + ChessboardGrid(const wxColour &darkColor, const wxColour &lightColor, int squareSize, wxWindow *parent, + wxWindowID winId = wxID_ANY, const wxPoint &pos = wxDefaultPosition); /** * Creates a new ChessboardGrid using two-step construction */ - bool Create(const wxColour &darkColor, const wxColour &lightColor, int squareSize, - wxWindow *parent, wxWindowID winId = wxID_ANY, - const wxPoint &pos = wxDefaultPosition); + bool Create(const wxColour &darkColor, const wxColour &lightColor, int squareSize, wxWindow *parent, + wxWindowID winId = wxID_ANY, const wxPoint &pos = wxDefaultPosition); /** * Updates the icons - * @param pcPawn PC pawn - * @param pcDame PC dame - * @param plPawn PL pawn - * @param plDame PL dame + * @param firstPawn First player's pawn + * @param firstDame First player's dame + * @param secondPawn Second player's pawn + * @param secondDame Second player's dame */ - void updateIcons(const wxBitmap &pcPawn, const wxBitmap &pcDame, const wxBitmap &plPawn, const wxBitmap &plDame); + void updateIcons(const wxBitmap &firstPawn, const wxBitmap &firstDame, const wxBitmap &secondPawn, const wxBitmap &secondDame); /** * Updates the disposition of the pieces in the chessboard and clears every border * @param newDisposition The new disposition to apply + * @param pcIsFirstPlayer True if PC is the first player */ - void updateDisposition(const GameUtils::Disposition &newDisposition); + void updateDisposition(const GameUtils::Disposition &newDisposition, bool pcIsFirstPlayer); /** * Set a new border for the square located at an specific location @@ -68,7 +67,7 @@ class ChessboardGrid : public wxPanel { ChessboardGrid(const ChessboardGrid &); // prevents copy-constructor std::array chessboard{}; - wxBitmap m_pcPawn, m_pcDame, m_plPawn, m_plDame; + wxBitmap mFirstPawn = wxNullBitmap, mFirstDame = wxNullBitmap, mSecondPawn = wxNullBitmap, mSecondDame = wxNullBitmap; GameUtils::MoveList moves; // list of moves the player can do int m_squareSize{}; diff --git a/src/Frame/Frame.cpp b/src/Frame/Frame.cpp index 3d56fdf..439f054 100644 --- a/src/Frame/Frame.cpp +++ b/src/Frame/Frame.cpp @@ -66,7 +66,8 @@ wxMenuBar *Frame::createMenuBar() { menuFile->Append(wxID_EXIT, _("&Exit"), _("Leave the game")); auto *menuSettings = new wxMenu; - menuSettings->Append(CHANGE_GD, _("&Change difficulty"), _("Change difficulty")); + menuSettings->Append(CHANGE_GD, _("Change &difficulty"), _("Change difficulty")); + menuSettings->Append(TOGGLE_FIRST_PLAYER, _("&Toggle first player"), _("Toggle first player")); auto *menuHelp = new wxMenu; menuHelp->Append(wxID_ABOUT, _("About " PROJECT_PRETTY_NAME), _("Open about dialog")); @@ -79,6 +80,7 @@ wxMenuBar *Frame::createMenuBar() { menuBar->Bind(wxEVT_MENU, &Frame::newMatchClicked, this, NEW_MATCH); menuBar->Bind(wxEVT_MENU, &Frame::closeFrame, this, wxID_EXIT); menuBar->Bind(wxEVT_MENU, &Frame::changeDifficultyClicked, this, CHANGE_GD); + menuBar->Bind(wxEVT_MENU, &Frame::flipFirstPlayer, this, TOGGLE_FIRST_PLAYER); menuBar->Bind(wxEVT_MENU, &Frame::aboutClicked, this, wxID_ABOUT); return menuBar; @@ -86,16 +88,16 @@ wxMenuBar *Frame::createMenuBar() { wxPanel *Frame::createChessboard(wxWindow *parent, const std::string &path) { // load images - wxBitmap pcPawn{path + "/images/pcPawn.png"}; - if (!pcPawn.IsOk()) return nullptr; - wxBitmap pcDame(path + "/images/pcDame.png"); - if (!pcDame.IsOk()) return nullptr; - wxBitmap plPawn(path + "/images/plPawn.png"); - if (!plPawn.IsOk()) return nullptr; - wxBitmap plDame(path + "/images/plDame.png"); - if (!plDame.IsOk()) return nullptr; - wxSize imageSize = pcPawn.GetSize(); - if (pcDame.GetSize() != imageSize || plPawn.GetSize() != imageSize || plDame.GetSize() != imageSize) return nullptr; + wxBitmap firstPawn{path + "/images/firstPawn.png"}; + if (!firstPawn.IsOk()) return nullptr; + wxBitmap firstDame(path + "/images/firstDame.png"); + if (!firstDame.IsOk()) return nullptr; + wxBitmap secondPawn(path + "/images/secondPawn.png"); + if (!secondPawn.IsOk()) return nullptr; + wxBitmap secondDame(path + "/images/secondDame.png"); + if (!secondDame.IsOk()) return nullptr; + wxSize imageSize = firstPawn.GetSize(); + if (firstDame.GetSize() != imageSize || secondPawn.GetSize() != imageSize || secondDame.GetSize() != imageSize) return nullptr; if (imageSize.GetWidth() != imageSize.GetHeight()) return nullptr; auto *chessboardPanel = new wxPanel(parent, wxID_ANY); @@ -105,7 +107,7 @@ wxPanel *Frame::createChessboard(wxWindow *parent, const std::string &path) { resources.getColor("light", DEF_LIGHT_COLOR), imageSize.GetWidth(), chessboardPanel, wxID_ANY, wxPoint(CHESSBOARD_BORDER_H, CHESSBOARD_BORDER_V)); - grid->updateIcons(pcPawn, pcDame, plPawn, plDame); + grid->updateIcons(firstPawn, firstDame, secondPawn, secondDame); int width, height; grid->GetSize(&width, &height); chessboardPanel->SetMinSize(wxSize(width + CHESSBOARD_BORDER_H * 2, height + CHESSBOARD_BORDER_V * 2)); @@ -175,6 +177,15 @@ void Frame::changeDifficultyClicked(wxCommandEvent &) { } } +void Frame::flipFirstPlayer(wxCommandEvent &) { + if (chessboardManager->isPlaying()) { + wxMessageDialog dialog(this, _("Are you sure you want to leave the game?"), _("New match"), wxYES_NO); + if (dialog.ShowModal() != wxID_YES) return; + } + + chessboardManager->flipFirstPlayer(); +} + void Frame::aboutClicked(wxCommandEvent &) { wxAboutDialogInfo dialog; dialog.SetName(wxFrame::GetTitle()); diff --git a/src/Frame/Frame.h b/src/Frame/Frame.h index 3b77b39..f867605 100644 --- a/src/Frame/Frame.h +++ b/src/Frame/Frame.h @@ -9,7 +9,6 @@ #include "../ChessboardGrid/ChessboardGrid.h" #include "wx/wx.h" -#define DEF_BORDER_COLOR wxColour() #define DEF_DARK_COLOR wxColour(32, 32, 32) #define DEF_LIGHT_COLOR wxColour(140, 140, 140) @@ -38,7 +37,8 @@ class Frame : public wxFrame { enum MenuItems { NEW_MATCH = 1, - CHANGE_GD + CHANGE_GD, + TOGGLE_FIRST_PLAYER, }; Resources resources{DATA_PATH}; @@ -82,6 +82,11 @@ class Frame : public wxFrame { */ void changeDifficultyClicked(wxCommandEvent &); + /** + * Change first player + */ + void flipFirstPlayer(wxCommandEvent &); + /** * Shows about dialog */ diff --git a/src/GameUtils/GameUtils.cpp b/src/GameUtils/GameUtils.cpp index eb0e39d..4c237f8 100644 --- a/src/GameUtils/GameUtils.cpp +++ b/src/GameUtils/GameUtils.cpp @@ -12,9 +12,15 @@ GameUtils::AlgorithmThread::AlgorithmThread(wxEvtHandler *evtHandler, const Disp } void *GameUtils::AlgorithmThread::Entry() { - wxCommandEvent evt(wxEVT_MENU, m_id); - evt.SetClientData(GameAlgorithm::calculateBestMove(m_disposition, m_gameDifficult)); - m_evtHandler->AddPendingEvent(evt); + auto *data = GameAlgorithm::calculateBestMove(m_disposition, m_gameDifficult); + + if (TestDestroy()) { + free(data); + } else { + auto *evt = new wxCommandEvent(wxEVT_MENU, m_id); + evt->SetClientData(data); + wxQueueEvent(m_evtHandler, evt); + } return nullptr; } diff --git a/src/MatchManager/MatchManager.cpp b/src/MatchManager/MatchManager.cpp index ed0f1c6..137c8ac 100644 --- a/src/MatchManager/MatchManager.cpp +++ b/src/MatchManager/MatchManager.cpp @@ -18,9 +18,10 @@ MatchManager::MatchManager(ChessboardGrid *chessboard, const wxColour &focusColo chessboard->Bind(wxEVT_MENU, &MatchManager::onThreadFinish, this, THREAD_ID); chessboardGrid = chessboard; - m_threadRunning = false; - m_isPlaying = false; - m_isEnd = false; + algorithmThread = nullptr; + mIsPlaying = false; + mIsEnd = false; + mIsPcFirstPlayer = false; } MatchManager::~MatchManager() { @@ -29,52 +30,64 @@ MatchManager::~MatchManager() { } } +bool MatchManager::newMatch() { + if (algorithmThread) { + algorithmThread->Delete(); + algorithmThread = nullptr; + } + + setDefaultLayout(); + chessboardGrid->updateDisposition(m_disposition, mIsPcFirstPlayer); + + // deletes all moves before re-assignment + for (GameUtils::Move *move: moves) + delete move; + + mIsEnd = false; + if (mIsPcFirstPlayer) { + moves.clear(); + makePCMove(); + } else { + mIsPlaying = false; + moves = GameUtils::findMoves(m_disposition, true); + notifyUpdate(TURN_PLAYER); + } + + return true; +} + void MatchManager::setOnUpdateListener(const std::function &listener) { m_onUpdate = listener; // if the game is over it doesn't call the listener - if (!m_isEnd) notifyUpdate(m_threadRunning ? TURN_PC : TURN_PLAYER); + if (!mIsEnd) notifyUpdate(algorithmThread ? TURN_PC : TURN_PLAYER); } bool MatchManager::changeDifficulty(int newDifficulty) { - if (m_threadRunning) return false; - if (newDifficulty < minGD || newDifficulty > maxGD) return false; gameDifficulty = newDifficulty; - if (isPlaying() || m_isEnd) newMatch(); - else notifyUpdate(TURN_PLAYER); + newMatch(); return true; } -int MatchManager::getDifficulty() const { - return gameDifficulty; +bool MatchManager::flipFirstPlayer() { + mIsPcFirstPlayer = !mIsPcFirstPlayer; + newMatch(); + return true; } -bool MatchManager::newMatch() { - if (m_threadRunning) return false; - - setDefaultLayout(); - chessboardGrid->updateDisposition(m_disposition); - m_isEnd = false; - m_isPlaying = false; - - // deletes all moves before re-assignment - for (GameUtils::Move *move: moves) - delete move; - - moves = GameUtils::findMoves(m_disposition, true); - notifyUpdate(TURN_PLAYER); - return true; +int MatchManager::getDifficulty() const { + return gameDifficulty; } bool MatchManager::isPlaying() const { - return m_isPlaying; + return mIsPlaying; } void MatchManager::onChessboardSquareClick(wxMouseEvent &event) { - if (m_threadRunning || m_isEnd) return; + if (algorithmThread || mIsEnd) return; int currentPos = event.GetId(); @@ -126,32 +139,39 @@ void MatchManager::onChessboardSquareClick(wxMouseEvent &event) { } // legal move - chessboardGrid->updateDisposition(m_disposition = move->disposition); + chessboardGrid->updateDisposition(m_disposition = move->disposition, mIsPcFirstPlayer); selectedPos = selectedNone; - auto *thread = new GameUtils::AlgorithmThread(chessboardGrid, m_disposition, gameDifficulty, THREAD_ID); - if (thread->Create() != wxTHREAD_NO_ERROR || thread->Run() != wxTHREAD_NO_ERROR) { - exit(1); - } + makePCMove(); +} - m_isPlaying = true; - m_threadRunning = true; +void MatchManager::makePCMove() { + mIsPlaying = true; notifyUpdate(TURN_PC); + algorithmThread = new GameUtils::AlgorithmThread(chessboardGrid, m_disposition, gameDifficulty, THREAD_ID); + if (algorithmThread->Create() != wxTHREAD_NO_ERROR || algorithmThread->Run() != wxTHREAD_NO_ERROR) { + std::cerr << "Cannot execute thread" << std::endl; + exit(1); + } } void MatchManager::onThreadFinish(wxCommandEvent &evt) { - m_threadRunning = false; + if (!algorithmThread) { + std::cerr << "Unwanted thread finished" << std::endl; + delete static_cast(evt.GetClientData()); + } + algorithmThread = nullptr; auto *pcMove = static_cast(evt.GetClientData()); if (pcMove == nullptr) { // PC cannot do anything, player won - m_isEnd = true; - m_isPlaying = false; + mIsEnd = true; + mIsPlaying = false; notifyUpdate(PLAYER_WON); return; } - chessboardGrid->updateDisposition(m_disposition = pcMove->disposition); + chessboardGrid->updateDisposition(m_disposition = pcMove->disposition, mIsPcFirstPlayer); delete pcMove; // deletes all moves before re-assignment @@ -161,8 +181,8 @@ void MatchManager::onThreadFinish(wxCommandEvent &evt) { moves = GameUtils::findMoves(m_disposition, true); if (moves.empty()) { // Player cannot do anything, PC won - m_isEnd = true; - m_isPlaying = false; + mIsEnd = true; + mIsPlaying = false; notifyUpdate(PC_WON); return; } diff --git a/src/MatchManager/MatchManager.h b/src/MatchManager/MatchManager.h index 80b0301..d1a1339 100644 --- a/src/MatchManager/MatchManager.h +++ b/src/MatchManager/MatchManager.h @@ -42,6 +42,12 @@ class MatchManager { virtual ~MatchManager(); + /** + * Reset the current match + * @return False when the algorithm thread is running + */ + bool newMatch(); + /** * Set a update listener that will be called when: *
    @@ -61,15 +67,15 @@ class MatchManager { bool changeDifficulty(int newDifficulty); /** - * @return Current difficulty + * Flip first player between PC and player + * @return */ - int getDifficulty() const; + bool flipFirstPlayer(); /** - * Reset the current match - * @return False when the algorithm thread is running + * @return Current difficulty */ - bool newMatch(); + int getDifficulty() const; /** * @return True if the player have moved at least 1 piece, and the game's not over @@ -79,16 +85,26 @@ class MatchManager { private: MatchManager(const MatchManager &); // prevents copy-constructor GameUtils::Disposition m_disposition{}; + GameUtils::AlgorithmThread *algorithmThread; ChessboardGrid *chessboardGrid; wxPen focusBorder, possibleMoveBorder; GameUtils::MoveList moves{}; - bool m_isEnd, m_isPlaying, m_threadRunning; + bool mIsEnd, mIsPlaying, mIsPcFirstPlayer; int gameDifficulty = minGD, selectedPos = selectedNone; std::function m_onUpdate; void onChessboardSquareClick(wxMouseEvent &evt); + /** + * Start the game algorithm to make a move + */ + void makePCMove(); + + /** + * Event triggered when the algorithm thread finished + * @param evt Event containing the PC's move + */ void onThreadFinish(wxCommandEvent &evt); /**