diff --git a/inc/board.hh b/inc/board.hh index 765c253..40f713e 100644 --- a/inc/board.hh +++ b/inc/board.hh @@ -69,6 +69,9 @@ public: // Czy bicie z danej pozycji jest możliwe bool isPossibleBeating(Vector position); + // Czy istnieje możliwe bicie dla danego koloru + bool arePossibleGlobalBeatings(Color color); + // Kontener możliwych bić std::list getPossibleBeatings(Vector position); @@ -76,7 +79,10 @@ public: std::list getPossibleMovements(Vector position); // Pobierz możliwe ruchy wszystkich pionków danego koloru - std::list getPossibleMovements(Color color); + std::list getPossibleGlobalMovements(Color color); + + // Pobierz możliwe bicia wszystkich pionków danego koloru + std::list getPossibleGlobalBeatings(Color color); // Usuń pionek z określonej pozycji Pawn deletePawn(Vector position); diff --git a/inc/minimax.hh b/inc/minimax.hh index a3a42bb..a2358f3 100644 --- a/inc/minimax.hh +++ b/inc/minimax.hh @@ -22,16 +22,13 @@ private: // Funkcja zwraca kolor kolejnego gracza z drzewa minimaks Color getColorFromDepth(int depth); - // Funkcja wyceniająca na zasadzie trzech obszarów - int f1(Board& board, const Color& color); + // Funkcja heurystyczna + int evaluate(Board& board, const Color& color); public: // Konstruktor przyjmuje parametry: maksymalna głębokość algorytmu MiniMaks oraz kolor pionków // sztucznej inteligencji MiniMax(int depth_max, Color ai_color) : DEPTH_MAX(depth_max), AI_COLOR(ai_color) {} - - // Funkcja heurystyczna - int evaluate(Board& board, const Color& color) { return f1(board, color); } // Algorytm MiniMax z cięciem alfa-beta (funkcja zwraca wartość najlepszego ruchu, natomiast // poprzez referencję zwraca najlepszy ruch best_movement diff --git a/src/board.cpp b/src/board.cpp index ab021f2..9c3e22a 100644 --- a/src/board.cpp +++ b/src/board.cpp @@ -17,6 +17,14 @@ void Board::initBoard() for(int i=0; iupgrade(); + } Pawn* Board::createPawn(Vector position, Color color) @@ -145,56 +153,15 @@ Pawn* Board::selectPawn(Vector position) bool Board::isPossibleBeating(Vector position) { - // Pobieramy wskaźnik do pionka znajdującego się na zadanej pozycji - Pawn* position_pawn = getPawn(position); - - // Jeżeli go tam nie ma, zwracamy false - if(!position_pawn) return false; - - // Zabezpieczenie przed przekroczeniem indeksów - if((getPawn(position)->getPosition().x > 1 && - getPawn(position)->getPosition().y > 1) && - (getPawn(position)->getPosition().x < TILES_COUNT-1 && - getPawn(position)->getPosition().y < TILES_COUNT-1)) - - // Jeżeli na polu przesuniętym o wektor [1,1] jest pionek, i jest on koloru przeciwnego - // a za pole zanim jest puste, zwracamy true - if(getPawn(position+Vector(1, 1)) && - (getPawn(position+Vector(1, 1))->getColor() != position_pawn->getColor()) && - (!getPawn(position+Vector(2, 2)))) return true; - - // ... analogicznie w pozostałych kierunkach - - // Zabezpieczenie przed przekroczeniem indeksów - if((getPawn(position)->getPosition().x > 1 && - getPawn(position)->getPosition().y > 1) && - (getPawn(position)->getPosition().x < TILES_COUNT-1 && - getPawn(position)->getPosition().y < TILES_COUNT-1)) - - if(getPawn(position+Vector(1, -1)) && - (getPawn(position+Vector(1, -1))->getColor() != position_pawn->getColor()) && - (!getPawn(position+Vector(2, -2)))) return true; - - // Zabezpieczenie przed przekroczeniem indeksów - if(getPawn(position)->getPosition().x > 1 && - getPawn(position)->getPosition().y > 1) - - if(getPawn(position+Vector(-1, 1)) && - (getPawn(position+Vector(-1, 1))->getColor() != position_pawn->getColor()) && - (!getPawn(position+Vector(-2, 2)))) return true; - - // Zabezpieczenie przed przekroczeniem indeksów - if(getPawn(position)->getPosition().x > 1 && - getPawn(position)->getPosition().y > 1) - - if(getPawn(position+Vector(-1, -1)) && - (getPawn(position+Vector(-1, -1))->getColor() != position_pawn->getColor()) && - (!getPawn(position+Vector(-2, -2)))) return true; - - return false; - + return getPossibleBeatings(position).size(); } +bool Board::arePossibleGlobalBeatings(Color color) +{ + return getPossibleGlobalBeatings(color).size(); +} + + std::list Board::getPossibleBeatings(Vector position) { // Tworzymy nowy kontener na pozycje @@ -206,28 +173,69 @@ std::list Board::getPossibleBeatings(Vector position) // Jeżeli go tam nie ma, zwracamy pusty kontener if(!position_pawn) return beatings; - // Jeżeli na polu przesuniętym o wektor [1,1] jest pionek, i jest on koloru przeciwnego - // a za pole zanim jest puste, dorzucamy tą pozycję do kontenera - if(position.x < TILES_COUNT-1 && position.y < TILES_COUNT-1) - if(getPawn(position+Vector(1, 1)) && - (getPawn(position+Vector(1, 1))->getColor() != position_pawn->getColor()) && - (!getPawn(position+Vector(2, 2)))) beatings.push_back(Movement(position, position+Vector(2, 2))); + //// Bicie normalnymi pionkami - // ... analogicznie w pozostałych kierunkach - if(position.x < TILES_COUNT-1 && position.y > 1) - if(getPawn(position+Vector(1, -1)) && - (getPawn(position+Vector(1, -1))->getColor() != position_pawn->getColor()) && - (!getPawn(position+Vector(2, -2)))) beatings.push_back(Movement(position, position+Vector(2, -2))); + // Bicie o wektor [-2, -2] + if((position.x >= 2 && position.y >= 2) && // Jeżeli bicie nie wyjdzie poza planszę + (getPawn(position+Vector(-1, -1))) && // Jeżeli istnieje bity pionek + (getPawn(position+Vector(-1, -1))->getColor() != position_pawn->getColor()) && // Jeżeli jest to pionek przeciwnego koloru + (!getPawn(position+Vector(-2, -2)))) // Jeżeli pole docelowe jest puste + beatings.push_back(Movement(position, position+Vector(-2, -2))); // Dorzuć ruch do listy - if(position.x > 1 && position.y < TILES_COUNT-1) - if(getPawn(position+Vector(-1, 1)) && - (getPawn(position+Vector(-1, 1))->getColor() != position_pawn->getColor()) && - (!getPawn(position+Vector(-2, 2)))) beatings.push_back(Movement(position, position+Vector(-2, 2))); + // Bicie o wektor [-2, 2] + if((position.x >= 2 && position.y < TILES_COUNT-2) && // Jeżeli bicie nie wyjdzie poza planszę + (getPawn(position+Vector(-1, 1))) && // Jeżeli istnieje bity pionek + (getPawn(position+Vector(-1, 1))->getColor() != position_pawn->getColor()) && // Jeżeli jest to pionek przeciwnego koloru + (!getPawn(position+Vector(-2, 2)))) // Jeżeli pole docelowe jest puste + beatings.push_back(Movement(position, position+Vector(-2, 2))); // Dorzuć ruch do listy - if(position.x > 1 && position.y > 1) - if(getPawn(position+Vector(-1, -1)) && - (getPawn(position+Vector(-1, -1))->getColor() != position_pawn->getColor()) && - (!getPawn(position+Vector(-2, -2)))) beatings.push_back(Movement(position, position+Vector(-2, -2))); + // Bicie o wektor [2, 2] + if((position.x < TILES_COUNT-2 && position.y < TILES_COUNT-2) && // Jeżeli bicie nie wyjdzie poza planszę + (getPawn(position+Vector(1, 1))) && // Jeżeli istnieje bity pionek + (getPawn(position+Vector(1, 1))->getColor() != position_pawn->getColor()) && // Jeżeli jest to pionek przeciwnego koloru + (!getPawn(position+Vector(2, 2)))) // Jeżeli pole docelowe jest puste + beatings.push_back(Movement(position, position+Vector(2, 2))); // Dorzuć ruch do listy + + // Bicie o wektor [2, -2] + if((position.x < TILES_COUNT-2 && position.y >= 2) && // Jeżeli bicie nie wyjdzie poza planszę + (getPawn(position+Vector(1, -1))) && // Jeżeli istnieje bity pionek + (getPawn(position+Vector(1, -1))->getColor() != position_pawn->getColor()) && // Jeżeli jest to pionek przeciwnego koloru + (!getPawn(position+Vector(2, -2)))) // Jeżeli pole docelowe jest puste + beatings.push_back(Movement(position, position+Vector(2, -2))); // Dorzuć ruch do listy + + //// Bicie damkami + + // Jeżeli pionek jest damką posiada dodatkowe możliwości bicia + if(getPawn(position)->isQueen()) + { + // Bicie o wektor [-2, 0] + if((position.x >= 2) && // Jeżeli bicie nie wyjdzie poza planszę + (getPawn(position+Vector(-1, 0))) && // Jeżeli istnieje bity pionek + (getPawn(position+Vector(-1, 0))->getColor() != position_pawn->getColor()) && // Jeżeli jest to pionek przeciwnego koloru + (!getPawn(position+Vector(-2, 0)))) // Jeżeli pole docelowe jest puste + beatings.push_back(Movement(position, position+Vector(-2, 0))); // Dorzuć ruch do listy + + // Bicie o wektor [0, 2] + if((position.y < TILES_COUNT-2) && // Jeżeli bicie nie wyjdzie poza planszę + (getPawn(position+Vector(0, 1))) && // Jeżeli istnieje bity pionek + (getPawn(position+Vector(0, 1))->getColor() != position_pawn->getColor()) && // Jeżeli jest to pionek przeciwnego koloru + (!getPawn(position+Vector(0, 2)))) // Jeżeli pole docelowe jest puste + beatings.push_back(Movement(position, position+Vector(0, 2))); // Dorzuć ruch do listy + + // Bicie o wektor [2, 0] + if((position.x < TILES_COUNT-2) && // Jeżeli bicie nie wyjdzie poza planszę + (getPawn(position+Vector(1, 0))) && // Jeżeli istnieje bity pionek + (getPawn(position+Vector(1, 0))->getColor() != position_pawn->getColor()) && // Jeżeli jest to pionek przeciwnego koloru + (!getPawn(position+Vector(2, 0)))) // Jeżeli pole docelowe jest puste + beatings.push_back(Movement(position, position+Vector(2, 0))); // Dorzuć ruch do listy + + // Bicie o wektor [0, -2] + if((position.y < TILES_COUNT-2) && // Jeżeli bicie nie wyjdzie poza planszę + (getPawn(position+Vector(0, -1))) && // Jeżeli istnieje bity pionek + (getPawn(position+Vector(0, -1))->getColor() != position_pawn->getColor()) && // Jeżeli jest to pionek przeciwnego koloru + (!getPawn(position+Vector(0, -2)))) // Jeżeli pole docelowe jest puste + beatings.push_back(Movement(position, position+Vector(0, -2))); // Dorzuć ruch do listy + } // Zwracamy kontener return beatings; @@ -243,46 +251,105 @@ std::list Board::getPossibleMovements(Vector position) // Jeżeli pionek nie istnieje, zwróć pustą listę if(!getPawn(position)) return movements; - - // Jeżeli to ruch czarnego pionka - if(getPawn(position)->getColor() == CL_BLACK) + + // Jeżeli pionek jest damką + if(getPawn(position)->isQueen()) { - // Zabezpieczenie przed przekroczeniem indeksów - if(getPawn(position)->getPosition().x > 0 && - getPawn(position)->getPosition().y > 0) - - // Jeżeli miejsce 1 jest wolne, wrzuć do listy + + // Jeżeli ruch w górę-lewo jest możliwy (jest puste miejsce) dorzuć ruch do listy + if(position.x >= 1 && position.y >= 1) + if(!getPawn(position+Vector(-1, -1))) movements.push_back(Movement(position, position+Vector(-1, -1))); + + // Jeżeli ruch w górę-prawo jest możliwy (jest puste miejsce) dorzuć ruch do listy + if(position.x < TILES_COUNT-1 && position.y >= 1) + if(!getPawn(position+Vector(1, -1))) movements.push_back(Movement(position, position+Vector(1, -1))); + + // Jeżeli ruch w dół-lewo jest możliwy (jest puste miejsce) dorzuć ruch do listy + if(position.x >= 1 && position.y < TILES_COUNT-1) + if(!getPawn(position+Vector(-1, 1))) movements.push_back(Movement(position, position+Vector(-1, 1))); + + // Jeżeli ruch w dół-prawo jest możliwy (jest puste miejsce) dorzuć ruch do listy + if(position.x < TILES_COUNT-1 && position.y < TILES_COUNT-1) if(!getPawn(position+Vector(1, 1))) movements.push_back(Movement(position, position+Vector(1, 1))); - // Zabezpieczenie przed przekroczeniem indeksów - if(getPawn(position)->getPosition().x > 0 && - getPawn(position)->getPosition().y > 0) - - // Jeżeli miejsce 2 jest wolne, wrzuć do listy - if(!getPawn(position+Vector(-1, 1))) movements.push_back(Movement(position, position+Vector(-1, 1))); + // Jeżeli ruch w górę jest możliwy (jest puste miejsce) dorzuć ruch do listy + if(position.y >= 1) + if(!getPawn(position+Vector(0, -1))) movements.push_back(Movement(position, position+Vector(0, -1))); + + // Jeżeli ruch w dół jest możliwy (jest puste miejsce) dorzuć ruch do listy + if(position.y < TILES_COUNT-1) + if(!getPawn(position+Vector(0, 1))) movements.push_back(Movement(position, position+Vector(0, 1))); + + // Jeżeli ruch w lewo jest możliwy (jest puste miejsce) dorzuć ruch do listy + if(position.x >= 1) + if(!getPawn(position+Vector(-1, 0))) movements.push_back(Movement(position, position+Vector(-1, 0))); + + // Jeżeli ruch w prawo jest możliwy (jest puste miejsce) dorzuć ruch do listy + if(position.x < TILES_COUNT-1) + if(!getPawn(position+Vector(1, 0))) movements.push_back(Movement(position, position+Vector(1, 0))); + + } - // Jeżeli to ruch białego pionka - if(getPawn(position)->getColor() == CL_WHITE) + // Jeżeli jest to zwykły pionek + else { - // Zabezpieczenie przed przekroczeniem indeksów - if(getPawn(position)->getPosition().x > 1 && - getPawn(position)->getPosition().y > 1) - - // Jeżeli miejsce 1 jest wolne, wrzuć do listy - if(!getPawn(position+Vector(-1, -1))) movements.push_back(Movement(position, position+Vector(-1, -1))); + // Jeżeli to ruch białego pionka + if(getPawn(position)->getColor() == CL_WHITE) + { + // Jeżeli ruch w górę-lewo jest możliwy (jest puste miejsce) dorzuć ruch do listy + if(position.x >= 1 && position.y >= 1) + if(!getPawn(position+Vector(-1, -1))) movements.push_back(Movement(position, position+Vector(-1, -1))); + + // Jeżeli ruch w górę-prawo jest możliwy (jest puste miejsce) dorzuć ruch do listy + if(position.x < TILES_COUNT-1 && position.y >= 1) + if(!getPawn(position+Vector(1, -1))) movements.push_back(Movement(position, position+Vector(1, -1))); + } - // Jeżeli miejsce 2 jest wolne, wrzuć do listy - if(position.y > 1) - if(!getPawn(position+Vector(1, -1))) movements.push_back(Movement(position, position+Vector(1, -1))); + // Jeżeli to ruch czarnego pionka + else + { + // Jeżeli ruch w dół-lewo jest możliwy (jest puste miejsce) dorzuć ruch do listy + if(position.x >= 1 && position.y < TILES_COUNT-1) + if(!getPawn(position+Vector(-1, 1))) movements.push_back(Movement(position, position+Vector(-1, 1))); + + // Jeżeli ruch w dół-prawo jest możliwy (jest puste miejsce) dorzuć ruch do listy + if(position.x < TILES_COUNT-1 && position.y < TILES_COUNT-1) + if(!getPawn(position+Vector(1, 1))) movements.push_back(Movement(position, position+Vector(1, 1))); + } } return movements; } -std::list Board::getPossibleMovements(Color color) +std::list Board::getPossibleGlobalBeatings(Color color) { std::list movements; + // Dla każdych pionków + for(int x = 0; x < TILES_COUNT; ++x) + for(int y = 0; y < TILES_COUNT; ++y) + { + // Jeżeli to jest pionek + if(board[x][y]) + { + // Jeżeli jest to pionek o który nam chodzi + if(board[x][y]->getColor() == color) + + // Dodaj wszystkie jego możliwe ruchy do listy ruchów + for(auto m: getPossibleBeatings(Vector(x, y))) movements.push_back(m); + } + } + + return movements; +} + +std::list Board::getPossibleGlobalMovements(Color color) +{ + std::list movements; + + // Jeżeli istnieją bicia zwracamy tylko je + if(arePossibleGlobalBeatings(color)) return getPossibleGlobalBeatings(color); + // Dla każdych pionków for(int x = 0; x < TILES_COUNT; ++x) for(int y = 0; y < TILES_COUNT; ++y) diff --git a/src/game.cpp b/src/game.cpp index 29365ea..aaaef0c 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -129,8 +129,8 @@ void Game::executePlayerRound(sf::Vector2f mouse_position) // Pobieramy informacje o pionku(lub jego braku) z interesującego nas pola Pawn* ptr = board.getPawn(position); - // Jeżeli tura należy do gracza, pionek istnieje i należy do gracza oraz ruch nie został jeszcze rozpoczęty: - if((round == getPlayerColor()) && ptr && (ptr->getColor() == getPlayerColor()) && (!movements_sequence)) + // Jeżeli tura należy do gracza, pionek istnieje i należy do gracza + if((round == getPlayerColor()) && ptr && (ptr->getColor() == getPlayerColor())) { // Zaznaczamy pionek i pobieramy do niego wskaźnik ptr = board.selectPawn(Vector(position)); @@ -228,7 +228,7 @@ void Game::eventHandler() // Wykonaj turę gracza executePlayerRound(sf::Vector2f(event.mouseButton.x, event.mouseButton.y)); - + } } } diff --git a/src/minimax.cpp b/src/minimax.cpp index e519436..a1c554e 100644 --- a/src/minimax.cpp +++ b/src/minimax.cpp @@ -1,10 +1,10 @@ #include "../inc/minimax.hh" -int MiniMax::f1(Board& board, const Color& color) +int MiniMax::evaluate(Board& board, const Color& color) { // Wycena - int value = 0, tmp_value; + int value = 0, pawn_value; // Wskaźnik na aktualnie oceniany pionek Pawn* ptr = NULL; @@ -19,27 +19,36 @@ int MiniMax::f1(Board& board, const Color& color) if(ptr) { // Jeżeli pionek jest damką: - if(ptr->isQueen()) tmp_value = 10; - else tmp_value = 1; + if(ptr->isQueen()) pawn_value = 10; + else pawn_value = 1; - // Jeżeli pionek należy do obszaru III -- waga 3 - if(((ptr->getPosition().x >= 3) && (ptr->getPosition().y >= 3)) && - ((ptr->getPosition().x < (TILES_COUNT-3)) && (ptr->getPosition().y < (TILES_COUNT-3)))) - tmp_value *= 3; + /*** ZASADA TRZECH OBSZARÓW ***/ + { + // Jeżeli pionek należy do obszaru III -- waga 1 + if(((ptr->getPosition().x >= 3) && (ptr->getPosition().y >= 3)) && + ((ptr->getPosition().x < (TILES_COUNT-3)) && (ptr->getPosition().y < (TILES_COUNT-3)))) + ; // więc nie ruszamy + + // Jeżeli pionek należy do obszaru II -- waga 2 + else if(((ptr->getPosition().x >= 2) && (ptr->getPosition().y >= 2)) && + ((ptr->getPosition().x < (TILES_COUNT-2)) && (ptr->getPosition().y < (TILES_COUNT-2)))) + pawn_value *= 2; + + // W przeciwnym wypadku, zostaje waga 1 + else + pawn_value *= 3; - // Jeżeli pionek należy do obszaru II -- waga 2 - else if(((ptr->getPosition().x >= 2) && (ptr->getPosition().y >= 2)) && - ((ptr->getPosition().x < (TILES_COUNT-2)) && (ptr->getPosition().y < (TILES_COUNT-2)))) - tmp_value *= 2; - - // W przeciwnym wypadku, zostaje waga 1 + } + /*** MOŻLIWOŚĆ BICIA ***/ + if(board.isPossibleBeating(ptr->getPosition())) pawn_value += 5; + // Jeżeli NIE jest to pionek należący do gracza zdefiniowanego kolorem color // przemnażamy przez -1 - if(ptr->getColor() != color) tmp_value *= -1; + if(ptr->getColor() != color) pawn_value *= -1; - // Dodajemy tmp_value do wyceny - value += tmp_value; + // Dodajemy pawn_value do wyceny + value += pawn_value; } } @@ -73,7 +82,7 @@ int MiniMax::alphabeta(Board board, int depth, int alpha, int beta, Movement& be if(color != AI_COLOR) { // Dla każdego potomka - for(auto& m: board.getPossibleMovements(color)) + for(auto& m: board.getPossibleGlobalMovements(color)) { // Tworzymy nowy stan gry Board new_board(board); @@ -93,7 +102,7 @@ int MiniMax::alphabeta(Board board, int depth, int alpha, int beta, Movement& be else // Dla każdego potomka - for(auto& m: board.getPossibleMovements(color)) + for(auto& m: board.getPossibleGlobalMovements(color)) { // Tworzymy nowy stan gry Board new_board(board);