diff --git a/src/ChessboardGrid/ChessboardGrid.cpp b/src/ChessboardGrid/ChessboardGrid.cpp index c4d7376..8fed47e 100644 --- a/src/ChessboardGrid/ChessboardGrid.cpp +++ b/src/ChessboardGrid/ChessboardGrid.cpp @@ -82,10 +82,10 @@ void ChessboardGrid::updateDisposition(const GameUtils::Disposition &newDisposit case GameUtils::PC_DAME: chessboard[i]->SetBackgroundBitmap(pcIsFirstPlayer ? mFirstDame : mSecondDame); break; - case GameUtils::PL_PAWN: + case GameUtils::PLAYER_PAWN: chessboard[i]->SetBackgroundBitmap(pcIsFirstPlayer ? mSecondPawn : mFirstPawn); break; - case GameUtils::PL_DAME: + case GameUtils::PLAYER_DAME: chessboard[i]->SetBackgroundBitmap(pcIsFirstPlayer ? mSecondDame : mFirstDame); break; } diff --git a/src/GameAlgorithm/GameAlgorithm.cpp b/src/GameAlgorithm/GameAlgorithm.cpp index 9190e22..46983b5 100644 --- a/src/GameAlgorithm/GameAlgorithm.cpp +++ b/src/GameAlgorithm/GameAlgorithm.cpp @@ -7,6 +7,14 @@ #include #include "GameAlgorithm.h" +struct { + bool operator()(GameUtils::Move *a, GameUtils::Move *b) const { return a->score < b->score; } +} sortAscending; + +struct { + bool operator()(GameUtils::Move *a, GameUtils::Move *b) const { return a->score > b->score; } +} sortDiscending; + GameUtils::Move *GameAlgorithm::calculateBestMove(const GameUtils::Disposition &disposition, int depth) { if (depth < 0) return nullptr; @@ -16,16 +24,22 @@ GameUtils::Move *GameAlgorithm::calculateBestMove(const GameUtils::Disposition & std::vector moves = GameUtils::findMoves(disposition, false); std::shuffle(moves.begin(), moves.end(), std::random_device()); + std::sort(moves.begin(), moves.end(), sortAscending); for (GameUtils::Move *move: moves) { int score = minimax(move, move->score, false, depth, alpha, beta); + if (score == INT_MAX) { + bestScore = INT_MAX; + res_move = move; + break; + } + if (score > bestScore) { bestScore = score; res_move = move; } + if (score > alpha) { alpha = score; - if (beta <= alpha) - break; // ignore subtree } } @@ -33,7 +47,6 @@ GameUtils::Move *GameAlgorithm::calculateBestMove(const GameUtils::Disposition & return nullptr; } else if (res_move == nullptr) { // bestScore equals INT_MIN - //res_move = moves.at(0); // all moves have the minimum score res_move = moves.front(); } @@ -54,15 +67,17 @@ int GameAlgorithm::minimax(const GameUtils::Move *start_move, int oldScore, bool if (maximizing) { bestScore = INT_MIN; std::vector moves = GameUtils::findMoves(start_move->disposition, false); + std::sort(moves.begin(), moves.end(), sortAscending); for (GameUtils::Move *move: moves) { score = minimax(move, oldScore + move->score, false, depth - 1, alpha, beta); if (score > bestScore) { bestScore = score; - } - if (score > alpha) { - alpha = score; - if (beta <= alpha) - break; // ignore subtree + + if (score > alpha) { + alpha = score; + if (beta <= alpha) + break; // ignore other moves because parent won't choose this path + } } } @@ -74,15 +89,17 @@ int GameAlgorithm::minimax(const GameUtils::Move *start_move, int oldScore, bool bestScore = INT_MAX; std::vector moves = GameUtils::findMoves(start_move->disposition, true); + std::sort(moves.begin(), moves.end(), sortDiscending); for (GameUtils::Move *move: moves) { score = minimax(move, oldScore - move->score, true, depth - 1, alpha, beta); if (score < bestScore) { bestScore = score; - } - if (score < beta) { - beta = score; - if (beta <= alpha) - break; // ignore subtree + + if (score < beta) { + beta = score; + if (beta <= alpha) + break; // ignore other moves because parent won't choose this path + } } } diff --git a/src/GameUtils/GameUtils.cpp b/src/GameUtils/GameUtils.cpp index 4c237f8..5e68bd3 100644 --- a/src/GameUtils/GameUtils.cpp +++ b/src/GameUtils/GameUtils.cpp @@ -4,20 +4,20 @@ #include "GameUtils.h" #include "GameAlgorithm/GameAlgorithm.h" -GameUtils::AlgorithmThread::AlgorithmThread(wxEvtHandler *evtHandler, const Disposition &disposition, int gameDifficult, - int id) : wxThread(wxTHREAD_DETACHED), m_disposition(disposition) { +GameUtils::AlgorithmThread::AlgorithmThread(wxEvtHandler *evtHandler, const Disposition &disposition, int gameDifficulty, + int id) : wxThread(wxTHREAD_DETACHED), mDisposition(disposition) { m_evtHandler = evtHandler; - m_gameDifficult = gameDifficult; - m_id = id; + mGameDifficulty = gameDifficulty; + mThreadID = id; } void *GameUtils::AlgorithmThread::Entry() { - auto *data = GameAlgorithm::calculateBestMove(m_disposition, m_gameDifficult); + auto *data = GameAlgorithm::calculateBestMove(mDisposition, mGameDifficulty); if (TestDestroy()) { free(data); } else { - auto *evt = new wxCommandEvent(wxEVT_MENU, m_id); + auto *evt = new wxCommandEvent(wxEVT_MENU, mThreadID); evt->SetClientData(data); wxQueueEvent(m_evtHandler, evt); } @@ -29,45 +29,41 @@ GameUtils::MoveList GameUtils::findMoves(const Disposition &disposition, bool pl MoveList moves; if (player) { - for (int row = 0, col; row < 8; row++) { - for (col = 0; col < 8; col++) { - switch (disposition[row * 8 + col]) { - case PL_DAME: - addMoveStep(moves, disposition, row, col, true, false, 0); - addMoveStep(moves, disposition, row, col, true, true, 0); - addMoveStep(moves, disposition, row, col, false, false, 0); - addMoveStep(moves, disposition, row, col, false, true, 0); - break; - case PL_PAWN: - addMoveStep(moves, disposition, row, col, false, false, 0); - addMoveStep(moves, disposition, row, col, false, true, 0); - break; - case EMPTY: - case PC_PAWN: - case PC_DAME: - break; - } + for (int position = 0; position < 64; position++) { + switch (disposition[position]) { + case PLAYER_DAME: + addMoveStep(moves, disposition, position, true, false, 0); + addMoveStep(moves, disposition, position, true, true, 0); + addMoveStep(moves, disposition, position, false, false, 0); + addMoveStep(moves, disposition, position, false, true, 0); + break; + case PLAYER_PAWN: + addMoveStep(moves, disposition, position, false, false, 0); + addMoveStep(moves, disposition, position, false, true, 0); + break; + case EMPTY: + case PC_PAWN: + case PC_DAME: + break; } } } else { - for (int row = 0, col; row < 8; row++) { - for (col = 0; col < 8; col++) { - switch (disposition[row * 8 + col]) { - case PC_DAME: - addMoveStep(moves, disposition, row, col, false, false, 0); - addMoveStep(moves, disposition, row, col, false, true, 0); - addMoveStep(moves, disposition, row, col, true, false, 0); - addMoveStep(moves, disposition, row, col, true, true, 0); - break; - case PC_PAWN: - addMoveStep(moves, disposition, row, col, true, false, 0); - addMoveStep(moves, disposition, row, col, true, true, 0); - break; - case EMPTY: - case PL_PAWN: - case PL_DAME: - break; - } + for (int position = 0; position < 64; position++) { + switch (disposition[position]) { + case PC_DAME: + addMoveStep(moves, disposition, position, false, false, 0); + addMoveStep(moves, disposition, position, false, true, 0); + addMoveStep(moves, disposition, position, true, false, 0); + addMoveStep(moves, disposition, position, true, true, 0); + break; + case PC_PAWN: + addMoveStep(moves, disposition, position, true, false, 0); + addMoveStep(moves, disposition, position, true, true, 0); + break; + case EMPTY: + case PLAYER_PAWN: + case PLAYER_DAME: + break; } } } @@ -91,27 +87,27 @@ GameUtils::MoveList GameUtils::findMoves(const Disposition &disposition, bool pl return moves; } -bool GameUtils::addMoveStep(MoveList &moves, const Disposition &disposition, int s_row, int s_col, bool row_offset, +bool GameUtils::addMoveStep(MoveList &moves, const Disposition &disposition, int source_position, bool row_offset, bool col_offset, int score) { // invalid move (out of bounds) - if (s_row == (row_offset ? 7 : 0) || s_col == (col_offset ? 7 : 0)) + if (source_position / 8 == (row_offset ? 7 : 0) || source_position % 8 == (col_offset ? 7 : 0)) return false; - int row = s_row + (row_offset ? 1 : -1), col = s_col + (col_offset ? 1 : -1); - PieceType s_value = disposition[s_row * 8 + s_col]; + int position = source_position + (row_offset ? 8 : -8) + (col_offset ? 1 : -1); + PieceType source_value = disposition[source_position]; bool isValid = true; - int mid_value = disposition[row * 8 + col]; + int mid_value = disposition[position]; if (mid_value == EMPTY) { // move without jump, only if the number of jumps equals 0 (first and last step of the move) if (score == 0) { Disposition copy; std::copy(disposition.begin(), disposition.end(), copy.begin()); - copy[s_row * 8 + s_col] = EMPTY; + copy[source_position] = EMPTY; if (row_offset) { - copy[row * 8 + col] = (row == 7 && s_value == PC_PAWN) ? PC_DAME : s_value; + copy[position] = (position / 8 == 7 && source_value == PC_PAWN) ? PC_DAME : source_value; } else { - copy[row * 8 + col] = (row == 0 && s_value == PL_PAWN) ? PL_DAME : s_value; + copy[position] = (position / 8 == 0 && source_value == PLAYER_PAWN) ? PLAYER_DAME : source_value; } moves.push_back(new Move(copy, false, 0)); return true; @@ -119,38 +115,34 @@ bool GameUtils::addMoveStep(MoveList &moves, const Disposition &disposition, int return false; } - if (row == (row_offset ? 7 : 0) || col == (col_offset ? 7 : 0)) { + int jump_position = position + (row_offset ? 8 : -8) + (col_offset ? 1 : -1); + + // invalid move with jump (out of bounds) + if (position / 8 == (row_offset ? 7 : 0) || position % 8 == (col_offset ? 7 : 0) || disposition[jump_position] != EMPTY) { return false; } - if (s_value == (row_offset ? PC_PAWN : PL_PAWN)) { - if (mid_value != (row_offset ? PL_PAWN : PC_PAWN)) { + if (source_value == (row_offset ? PC_PAWN : PLAYER_PAWN)) { + if (mid_value != (row_offset ? PLAYER_PAWN : PC_PAWN)) { return false; // white man only eat black man and vice-versa } - // move with jump from pawn - row += row_offset ? 1 : -1; - col += col_offset ? 1 : -1; - if (disposition[row * 8 + col] != EMPTY) { - return false; - } - - // possible jump, check only in the same y direction + // move with jump from pawn, check only in the same y direction Disposition copy; std::copy(disposition.begin(), disposition.end(), copy.begin()); - copy[s_row * 8 + s_col] = EMPTY; - copy[row * 8 + col + (row_offset ? -8 : 8) + (col_offset ? -1 : 1)] = EMPTY; + copy[source_position] = EMPTY; + copy[position] = EMPTY; if (row_offset) { - copy[row * 8 + col] = (row == 7) ? PC_DAME : PC_PAWN; + copy[jump_position] = (jump_position / 8 == 7) ? PC_DAME : PC_PAWN; } else { - copy[row * 8 + col] = (row == 0) ? PL_DAME : PL_PAWN; + copy[jump_position] = (jump_position / 8 == 0) ? PLAYER_DAME : PLAYER_PAWN; } score += PAWN_SCORE; - if (addMoveStep(moves, copy, row, col, row_offset, false, score)) + if (addMoveStep(moves, copy, jump_position, row_offset, false, score)) isValid = false; - if (addMoveStep(moves, copy, row, col, row_offset, true, score)) + if (addMoveStep(moves, copy, jump_position, row_offset, true, score)) isValid = false; if (isValid) @@ -158,40 +150,34 @@ bool GameUtils::addMoveStep(MoveList &moves, const Disposition &disposition, int return true; } - if (s_value == PC_DAME) { + + if (source_value == PC_DAME) { if (mid_value == PC_DAME || mid_value == PC_PAWN) return false; } - if (s_value == PL_DAME) { - if (mid_value == PL_DAME || mid_value == PL_PAWN) + if (source_value == PLAYER_DAME) { + if (mid_value == PLAYER_DAME || mid_value == PLAYER_PAWN) return false; } // move with jump from dame - row += row_offset ? 1 : -1; - col += col_offset ? 1 : -1; - if (disposition[row * 8 + col] != EMPTY) { - return false; - } - - // possible jump Disposition copy; std::copy(disposition.begin(), disposition.end(), copy.begin()); - copy[s_row * 8 + s_col] = EMPTY; - copy[row * 8 + col + (row_offset ? -8 : 8) + (col_offset ? -1 : 1)] = EMPTY; - copy[row * 8 + col] = s_value; - score += DAME_SCORE; + copy[source_position] = EMPTY; + copy[position] = EMPTY; + copy[jump_position] = source_value; + score += (mid_value == PC_DAME || mid_value == PC_DAME ) ? DAME_SCORE : PAWN_SCORE; - if (addMoveStep(moves, copy, row, col, row_offset, false, score)) + if (addMoveStep(moves, copy, jump_position, row_offset, false, score)) isValid = false; - if (addMoveStep(moves, copy, row, col, row_offset, true, score)) + if (addMoveStep(moves, copy, jump_position, row_offset, true, score)) isValid = false; - if (addMoveStep(moves, copy, row, col, !row_offset, false, score)) + if (addMoveStep(moves, copy, jump_position, !row_offset, false, score)) isValid = false; - if (addMoveStep(moves, copy, row, col, !row_offset, true, score)) + if (addMoveStep(moves, copy, jump_position, !row_offset, true, score)) isValid = false; if (isValid) diff --git a/src/GameUtils/GameUtils.h b/src/GameUtils/GameUtils.h index ed07df0..ab64076 100644 --- a/src/GameUtils/GameUtils.h +++ b/src/GameUtils/GameUtils.h @@ -20,11 +20,11 @@ class GameUtils { public: enum PieceType { - EMPTY = 0, // no piece - PC_PAWN, // pc pawn - PC_DAME, // pc dame - PL_PAWN, // player pawn - PL_DAME // player dame + EMPTY = 0, + PC_PAWN, + PC_DAME, + PLAYER_PAWN, + PLAYER_DAME }; /** @@ -34,11 +34,11 @@ class GameUtils { class AlgorithmThread : public wxThread { public: - AlgorithmThread(wxEvtHandler *evtHandler, const Disposition &disposition, int gameDifficult, int id); + AlgorithmThread(wxEvtHandler *evtHandler, const Disposition &disposition, int gameDifficulty, int id); private: - const Disposition &m_disposition; - int m_gameDifficult, m_id; + const Disposition &mDisposition; + int mGameDifficulty, mThreadID; void *Entry() override; @@ -84,7 +84,7 @@ class GameUtils { GameUtils() = default; static bool addMoveStep(MoveList &moves, const Disposition &disposition, - int s_row, int s_col, bool row_offset, bool col_offset, int score); + int source_position, bool row_offset, bool col_offset, int score); }; diff --git a/src/MatchManager/MatchManager.cpp b/src/MatchManager/MatchManager.cpp index 57f7722..46531e4 100644 --- a/src/MatchManager/MatchManager.cpp +++ b/src/MatchManager/MatchManager.cpp @@ -89,7 +89,7 @@ void MatchManager::onChessboardSquareClick(wxMouseEvent &event) { int currentPos = event.GetId(); if (selectedPos == selectedNone) { - if ((m_disposition[currentPos] == GameUtils::PL_PAWN || m_disposition[currentPos] == GameUtils::PL_DAME)) { + if ((m_disposition[currentPos] == GameUtils::PLAYER_PAWN || m_disposition[currentPos] == GameUtils::PLAYER_DAME)) { if (highlightPossibleMoves(currentPos)) { chessboardGrid->SetSquareSelectedOverlay(currentPos); selectedPos = currentPos; @@ -101,7 +101,7 @@ void MatchManager::onChessboardSquareClick(wxMouseEvent &event) { } // change selection - if ((m_disposition[currentPos] == GameUtils::PL_PAWN || m_disposition[currentPos] == GameUtils::PL_DAME)) { + if ((m_disposition[currentPos] == GameUtils::PLAYER_PAWN || m_disposition[currentPos] == GameUtils::PLAYER_DAME)) { chessboardGrid->ClearSquareOverlay(); // it clears selectedPos and possible moves if (currentPos == selectedPos) { selectedPos = selectedNone; @@ -196,7 +196,7 @@ void MatchManager::setDefaultLayout() { if (i < (8 * 3)) { m_disposition[i] = GameUtils::PC_PAWN; } else if (i >= (8 * 5)) { - m_disposition[i] = GameUtils::PL_PAWN; + m_disposition[i] = GameUtils::PLAYER_PAWN; } else { m_disposition[i] = GameUtils::EMPTY; } @@ -217,7 +217,7 @@ GameUtils::Move *MatchManager::findPlayerMove(int oldIndex, int newIndex) { // here only if this move change the old position that becomes empty newValue = move->disposition[newIndex]; - if (newValue == GameUtils::PL_PAWN || newValue == GameUtils::PL_DAME) + if (newValue == GameUtils::PLAYER_PAWN || newValue == GameUtils::PLAYER_DAME) return move; // this is the correct move from oldIndex to newIndex } diff --git a/src/MatchManager/MatchManager.h b/src/MatchManager/MatchManager.h index 81c2da5..235d60c 100644 --- a/src/MatchManager/MatchManager.h +++ b/src/MatchManager/MatchManager.h @@ -7,8 +7,9 @@ #include "ChessboardGrid/ChessboardGrid.h" #include "GameUtils/GameUtils.h" -#define DEF_MIN_GD 1 -#define DEF_MAX_GD 10 +#define DEF_MIN_GD 0 +#define DEF_MAX_GD 12 +#define DEFAULT_DIFFICULTY 3 #define THREAD_ID 1 /** @@ -89,7 +90,7 @@ class MatchManager { ChessboardGrid *chessboardGrid; GameUtils::MoveList moves{}; bool mIsEnd, mIsPlaying, mIsPcFirstPlayer; - int gameDifficulty = minGD, selectedPos = selectedNone; + int gameDifficulty = DEFAULT_DIFFICULTY, selectedPos = selectedNone; UpdateCallback m_onUpdate;