From aa65a0f7a939193d2d96995509dffad541a4b033 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Pluta?= Date: Wed, 25 May 2016 12:02:14 +0200 Subject: [PATCH] =?UTF-8?q?Mocno=20poprawiony=20algorytm=20minimax=20i=20w?= =?UTF-8?q?ykrywanie=20ruch=C3=B3w?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- inc/board.hh | 23 +- inc/def.hh | 2 +- inc/game.hh | 24 +- inc/minimax.hh | 4 +- inc/misc.hh | 16 +- inc/pawn.hh | 7 + src/board.cpp | 643 ++++++++++++++++++++++++++---------------------- src/game.cpp | 446 ++++++++++++++++----------------- src/main.cpp | 45 +++- src/minimax.cpp | 225 +++++++++-------- 10 files changed, 781 insertions(+), 654 deletions(-) diff --git a/inc/board.hh b/inc/board.hh index 40f713e..b709da3 100644 --- a/inc/board.hh +++ b/inc/board.hh @@ -28,7 +28,10 @@ struct Movement bool operator==(const Movement& b) { return ((begin == b.begin) && (end == b.end)); } // Wypisywanie ruchu do terminala - void display() { std::cout << begin.x << ", " << begin.y << "\t->\t" << end.x << ", " << end.y << std::endl; } + void display() { begin.display(); std::cout<< "\t->\t"; end.display(); std::cout << std::endl; } + + // Zwraca wektor ruchu + Vector getVector() { return end-begin; } }; // Klasa implementująca planszę @@ -42,6 +45,9 @@ private: // Zaznaczone kafelki na mapie(ułatwienie dla gracza) std::list selected_tiles; + // Zaznaczony ruch na mapie + bool is_selected_movement; + public: // Konstruktor inicjujący planszę @@ -63,9 +69,15 @@ public: // Przesuń pionek na określoną pozycję bool movePawn(Movement movement); - // Czy ruch jest możliwy (czy dana pozycja jest osiągalna) - bool isMovementPossible(Movement movement); + // Pobierz ilość białych pionków + unsigned int getNumberOfWhitePawns() const; + // Pobierz ilość czarnych pionków + unsigned int getNumberOfBlackPawns() const; + + // Promuj na damki te pionki, które doszły na linię promocji + void upgrade(); + // Czy bicie z danej pozycji jest możliwe bool isPossibleBeating(Vector position); @@ -96,8 +108,11 @@ public: // Zaznacz wybrane kafelki na mapie void selectTiles(std::list tiles) { selected_tiles = tiles; } + // Zaznacz ruch na mapie + void selectMovement(Movement movement); + // Odznacz kafelki na mapie - void deselectTiles() { selected_tiles.clear(); } + void deselectTiles() { is_selected_movement = false; selected_tiles.clear(); } // Wyświetl zawartość planszy na standardowym wyjściu void display(); diff --git a/inc/def.hh b/inc/def.hh index 3c6a240..6f112ce 100644 --- a/inc/def.hh +++ b/inc/def.hh @@ -10,7 +10,7 @@ const std::string GAME_TITLE = "Warcaby"; // Ilość pól w każdym wymiarze -const int TILES_COUNT = 10; +const int TILES_COUNT = 8; // Długość boku pojedynczego kafelka(który jest kwadratem) const int TILE_SIZE = 64; diff --git a/inc/game.hh b/inc/game.hh index 43dfe73..4d1d526 100644 --- a/inc/game.hh +++ b/inc/game.hh @@ -25,6 +25,14 @@ enum Player PL_AI }; +// Typ wyliczeniowy implementujący stany gry +enum GameState + { + GS_PAUSED, + GS_RUNNING, + GS_WIN, + GS_LOSS + }; // Klasa gry class Game @@ -35,6 +43,9 @@ class Game // Plansza do gry w warcaby Board board; + // Stan gry + GameState game_state; + // Zaznaczony pionek Vector selected; @@ -43,9 +54,6 @@ class Game // Tura(czy gracz, czy AI) Color round; - - // Flaga oznaczająca sekwencję ruchów(aby gracz nie mógł zmienić pionka w trakcie gry) - bool movements_sequence; // Punkty gracza int player_score; @@ -83,6 +91,12 @@ private: // Rysuje HUD (czyli ilość punktów etc.) void drawHUD(); + + // Aktualizuj stan gry + void gameUpdate(); + + // Wyświetl ekran końcowy + void displayTheEnd(); // Główna pętla gry void loop(); @@ -90,7 +104,9 @@ private: public: // Konstruktor inicjalizujący parametry gry i wyzwalający pętlę główną gry - Game(); + // player - kolor gracza (zawsze biały kolor zaczyna) + // ai - poziom AI (głębokość przeszukiwania drzewa gry przez algorytm minimax) + Game(Color player, int ai); }; diff --git a/inc/minimax.hh b/inc/minimax.hh index a2358f3..095f833 100644 --- a/inc/minimax.hh +++ b/inc/minimax.hh @@ -21,7 +21,7 @@ private: // Funkcja zwraca kolor kolejnego gracza z drzewa minimaks Color getColorFromDepth(int depth); - +public: // Funkcja heurystyczna int evaluate(Board& board, const Color& color); public: @@ -35,7 +35,7 @@ public: int alphabeta(Board board, int depth, int alpha, int beta, Movement& best_movement); // Funkcja startowa algorytmu minimax z cięciem alfa-beta - Movement minimax(Board board); + Movement getBestMovement(Board board); }; diff --git a/inc/misc.hh b/inc/misc.hh index b87b40a..4130326 100644 --- a/inc/misc.hh +++ b/inc/misc.hh @@ -1,6 +1,7 @@ #ifndef MISC_HH #define MISC_HH +#include #include #include @@ -10,13 +11,13 @@ struct Vector { public: - unsigned int x; - unsigned int y; + int x; + int y; public: // Konstruktor tworzący parę (_x, _y) - Vector(unsigned int _x, unsigned int _y) : x(_x), y(_y) {} + Vector(int _x, int _y) : x(_x), y(_y) {} // Konstruktor tworzący parę (x, y) na podstawie rzeczywistych współrzędnych Vector(const RealVector& real_position) : x(real_position.x/TILE_SIZE), y(real_position.y/TILE_SIZE) {} @@ -41,11 +42,20 @@ public: Vector operator*=(int a) { return *this = *this*a; } Vector operator*=(float a) { return *this = *this*a; } + // Dzielenie wektora przez liczbę + Vector operator/(int a) { return Vector(x/a, y/a); } + Vector operator/(float a) { return Vector(x/a, y/a); } + Vector operator/=(int a) { return *this = *this/a; } + Vector operator/=(float a) { return *this = *this/a; } + // Porównanie wektorów bool operator==(Vector v) { return ((x == v.x) && (y == v.y)); } // Norma w przestrzeni Manhattan int manhattanNorm() const { return abs(x) + abs(y); } + + // Wyświetl wektor + void display() const { std::cout << "(" << x << ", " << y << ")"; } }; diff --git a/inc/pawn.hh b/inc/pawn.hh index e21d69a..2acad57 100644 --- a/inc/pawn.hh +++ b/inc/pawn.hh @@ -16,6 +16,13 @@ enum Color CL_BLACK }; +// Zwróć kolor przeciwny do argumentu +inline Color getOpposedColor(Color color) +{ + if(color == CL_WHITE) return CL_BLACK; + else return CL_WHITE; +} + // Klasa reprezentująca pionek class Pawn : public Object { diff --git a/src/board.cpp b/src/board.cpp index 9c3e22a..c2f1a45 100644 --- a/src/board.cpp +++ b/src/board.cpp @@ -1,413 +1,458 @@ #include "../inc/board.hh" -Board::Board() +Board::Board() : is_selected_movement(false) { - // Czyścimy całą tablicę - for(int i=0; iupgrade(); + // Wyliczamy ilość rzędów w zależności od rozmiaru planszy + for(int n=0; n<(TILES_COUNT/2 - 1); ++n) + { + // Tworzymy czarne pionki + for(int i=!(n%2); isetPosition(target); + // Przesuwamy pionek na planszy + board[target.x][target.y] = board[position.x][position.y]; + board[position.x][position.y] = NULL; - // Zwracamy prawdę, że się udało - return true; -} + // Aktualizujemy informację o pozycji w pionku + board[target.x][target.y]->setPosition(target); -bool Board::isMovementPossible(Movement movement) -{ - /** TODO **/ - return true; + // Zwracamy prawdę, że się udało + return true; } Pawn Board::deletePawn(Vector position) { - Pawn deleted_pawn = *board[position.x][position.y]; - delete board[position.x][position.y]; - board[position.x][position.y] = NULL; - return deleted_pawn; + Pawn deleted_pawn = *board[position.x][position.y]; + delete board[position.x][position.y]; + board[position.x][position.y] = NULL; + return deleted_pawn; } void Board::draw(sf::RenderWindow& window) { - // Wymiar X - for(int x=0; xdraw(window); - + // Jeżeli istnieją na danym polu pionki, to je rysujemy + if(board[x][y]) board[x][y]->draw(window); + + } } - } - - // Zaznacz kafelki przeznaczone do zaznaczenia - for(auto selected_tile: selected_tiles) - { - // Pobieramy współrzędne - int x = selected_tile.x; - int y = selected_tile.y; - - // Tworzymy pojedynczy kafelek o wymiarach TILE_SIZE x TILE_SIZE - sf::RectangleShape tile(sf::Vector2f(TILE_SIZE, TILE_SIZE)); - // Ustawiamy pozycję na i*TILE_SIZE, j*TILE_SIZE -- czyli np. dla TILE_SIZE = 64: (0,0), (0,64), (0,128), ... - tile.setPosition(x*TILE_SIZE, y*TILE_SIZE); + // Zaznacz kafelki przeznaczone do zaznaczenia + for(auto selected_tile: selected_tiles) + { + // Pobieramy współrzędne + int x = selected_tile.x; + int y = selected_tile.y; - // Ustawiamy kolor wypełnienia co drugiego kafelka na jasny - tile.setFillColor(((x+y)%2)?sf::Color(200, 124, 50):sf::Color(255, 255, 220)); + // Tworzymy pojedynczy kafelek o wymiarach TILE_SIZE x TILE_SIZE + sf::RectangleShape tile(sf::Vector2f(TILE_SIZE, TILE_SIZE)); - // Rysujemy kafelki - window.draw(tile); + // Ustawiamy pozycję na i*TILE_SIZE, j*TILE_SIZE -- czyli np. dla TILE_SIZE = 64: (0,0), (0,64), (0,128), ... + tile.setPosition(x*TILE_SIZE, y*TILE_SIZE); - // Jeżeli istnieją na danym polu pionki, to je rysujemy - if(board[x][y]) board[x][y]->draw(window); - } + // Jeżeli to zwykłe zaznaczenie, ustawiamy kolor wypełnienia co drugiego kafelka na jasny + if(!is_selected_movement) + tile.setFillColor(((x+y)%2)?sf::Color(200, 124, 50):sf::Color(255, 255, 220)); + else + tile.setFillColor(((x+y)%2)?sf::Color(167, 200, 50):sf::Color(178, 255, 174)); + + // Rysujemy kafelki + window.draw(tile); + + // Jeżeli istnieją na danym polu pionki, to je rysujemy + if(board[x][y]) board[x][y]->draw(window); + } } Pawn* Board::selectPawn(Vector position) { - // Jeżeli pionek na tym polu istnieje - if(board[position.x][position.y]) - { - // Odznaczamy wszystkie pionki - for(int x = 0; xdeselect(); + // Jeżeli pionek na tym polu istnieje + if(board[position.x][position.y]) + { + // Odznaczamy wszystkie pionki + for(int x = 0; xdeselect(); - // Zaznaczamy właściwy - board[position.x][position.y]->select(); + // Zaznaczamy właściwy + board[position.x][position.y]->select(); - // Zwracamy zaznaczony - return board[position.x][position.y]; - } + // Zwracamy zaznaczony + return board[position.x][position.y]; + } - // Zwracamy NULL, bo pionek nie istnieje - return NULL; + // Zwracamy NULL, bo pionek nie istnieje + return NULL; } bool Board::isPossibleBeating(Vector position) { - return getPossibleBeatings(position).size(); + return getPossibleBeatings(position).size(); } bool Board::arePossibleGlobalBeatings(Color color) { - return getPossibleGlobalBeatings(color).size(); + return getPossibleGlobalBeatings(color).size(); } std::list Board::getPossibleBeatings(Vector position) { - // Tworzymy nowy kontener na pozycje - std::list beatings; - - // Pobieramy wskaźnik do pionka znajdującego się na zadanej pozycji - Pawn* position_pawn = getPawn(position); + // Tworzymy nowy kontener na pozycje + std::list beatings; - // Jeżeli go tam nie ma, zwracamy pusty kontener - if(!position_pawn) return beatings; + // Pobieramy wskaźnik do pionka znajdującego się na zadanej pozycji + Pawn* position_pawn = getPawn(position); - //// Bicie normalnymi pionkami + // Jeżeli go tam nie ma, zwracamy pusty kontener + if(!position_pawn) return beatings; - // 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 + //// Bicie normalnymi pionkami - // 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 + // 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 - // 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 >= 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 [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 [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, -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 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 damkami - // 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 - } + // 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; - // Zwracamy kontener - return beatings; - } std::list Board::getPossibleMovements(Vector position) { - // Jeżeli są możliwe bicia, zwróć je - if(isPossibleBeating(position)) return getPossibleBeatings(position); + // Jeżeli są możliwe bicia, zwróć je + if(isPossibleBeating(position)) return getPossibleBeatings(position); - std::list movements; - - // Jeżeli pionek nie istnieje, zwróć pustą listę - if(!getPawn(position)) return movements; - - // Jeżeli pionek jest damką - if(getPawn(position)->isQueen()) - { + std::list movements; - // 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 pionek nie istnieje, zwróć pustą listę + if(!getPawn(position)) return movements; - // 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))); - - // 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 jest to zwykły pionek - else - { - // Jeżeli to ruch białego pionka - if(getPawn(position)->getColor() == CL_WHITE) + // Jeżeli pionek jest damką + if(getPawn(position)->isQueen()) { - // 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 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))); + + // TODO zmodyfikowane ruchy + // TODO - zrobić, aby damka mogła robić długie ruchy (to samo dla bicia!) + + // 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 czarnego pionka - else + // Jeżeli jest to zwykły pionek + 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))); + // 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 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; + return movements; } std::list Board::getPossibleGlobalBeatings(Color color) { - std::list movements; + 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) + // 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; + // 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; + 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) - { - // 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) + // Jeżeli istnieją bicia zwracamy tylko je + if(arePossibleGlobalBeatings(color)) return getPossibleGlobalBeatings(color); - // Dodaj wszystkie jego możliwe ruchy do listy ruchów - for(auto m: getPossibleMovements(Vector(x, y))) movements.push_back(m); - } - } - - return 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: getPossibleMovements(Vector(x, y))) movements.push_back(m); + } + } + + return movements; } void Board::display() { - // Wyświetl poziomą linię - for(int i=0; igetColor() == CL_WHITE) std::cout << " O "; - else std::cout << " @ "; - } else std::cout << " "; - } - std::cout << "|" << std::endl; + // Pojedynczy wiersz + for(int x = 0; xgetColor() == CL_WHITE) std::cout << " O "; + else std::cout << " @ "; + } else std::cout << " "; + } + std::cout << "|" << std::endl; - // Pozioma linia - for(int i=0; igetColor()); - // Jeżeli istnieje pionek na tym polu... - if(original.board[x][y]) + // Jeżeli ten pionek jest damką ... + if(original.board[x][y]->isQueen()) + + // ... to promujemy nowoutworzonego pionka na damkę + board[x][y]->upgrade(); + } + } - // ... to w nowotworzonej planszy tworzymy identyczny pionek - createPawn(Vector(x, y), original.board[x][y]->getColor()); - +} + +unsigned int Board::getNumberOfWhitePawns() const +{ + // Inicjujemy licznik + unsigned int count = 0; + + // Przeglądamy calą planszę + for(int x = 0; x < TILES_COUNT; ++x) + for(int y = 0; y < TILES_COUNT; ++y) + + // Jeśli znajdziemy biały pionek, zwiększamy licznik o 1 + if(board[x][y] && board[x][y]->getColor() == CL_WHITE) ++count; + return count; +} + +unsigned int Board::getNumberOfBlackPawns() const +{ + // Inicjujemy licznik + unsigned int count = 0; + + // Przeglądamy calą planszę + for(int x = 0; x < TILES_COUNT; ++x) + for(int y = 0; y < TILES_COUNT; ++y) + + // Jeśli znajdziemy czarny pionek, zwiększamy licznik o 1 + if(board[x][y] && board[x][y]->getColor() == CL_BLACK) ++count; + return count; +} + +void Board::upgrade() +{ + // Dla całej szerokości planszy (czyli całej linii) + for(int x = 0; x < TILES_COUNT; ++x) + { + // Jeśli na górnej linii promocji znajduje się biały pionek, promuj go + if(board[x][0] && board[x][0]->getColor() == CL_WHITE) board[x][0]->upgrade(); + + // Jeśli na dolnej linii promocji znajduje się czarny pionek, promuj go + if(board[x][TILES_COUNT-1] && board[x][TILES_COUNT-1]->getColor() == CL_BLACK) board[x][TILES_COUNT-1]->upgrade(); + } +} + +void Board::selectMovement(Movement movement) +{ + is_selected_movement = true; + selected_tiles.push_back(movement.begin); + selected_tiles.push_back(movement.end); } diff --git a/src/game.cpp b/src/game.cpp index aaaef0c..9b78fcc 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1,289 +1,269 @@ #include "../inc/game.hh" -Game::Game() - : - selected(TILES_COUNT+1, TILES_COUNT+1), // Brak zaznaczenia - player_color(CL_WHITE), // Domyślny kolor gracza - round(CL_WHITE), // Pierwszy ruch mają białe pionki - movements_sequence(false), // Ciąg kolejnych ruchów - player_score(0), // Wyzerowana ilość punktów gracza - ai_score(0), // Wyzerowana ilość punktów AI - minimax(7, getAIColor()) // Poziom AI oraz kolor AI -{ - // Ustawienia antyaliasingu - sf::ContextSettings settings; - settings.antialiasingLevel = 8; - - // Utworzenie okna - window.create(sf::VideoMode(WINDOW_WIDTH, WINDOW_HEIGHT, 32), GAME_TITLE, sf::Style::Close, settings); +Game::Game(Color player, int ai) +: +game_state(GS_RUNNING), // Stan gry +selected(TILES_COUNT+1, TILES_COUNT+1), // Brak zaznaczenia +player_color(player), // Kolor gracza +round(CL_WHITE), // Pierwszy ruch mają białe pionki +player_score(0), // Wyzerowana ilość punktów gracza +ai_score(0), // Wyzerowana ilość punktów AI +minimax(ai, getAIColor()) // Poziom AI oraz kolor AI - // Wygenerowanie planszy startowej - board.initBoard(); - - // Pętla główna - loop(); +{ + // Ustawienia antyaliasingu + sf::ContextSettings settings; + settings.antialiasingLevel = 8; + + // Utworzenie okna + window.create(sf::VideoMode(WINDOW_WIDTH, WINDOW_HEIGHT, 32), GAME_TITLE, sf::Style::Close, settings); + + // Wygenerowanie planszy startowej + board.initBoard(); + + // Pętla główna + loop(); } void Game::killPawn(Vector position) { - // Dodaj odpowiednio punkty - if(board.getPawn(position)->getColor() == getPlayerColor()) ++ai_score; - else ++player_score; + // Dodaj odpowiednio punkty + if(board.getPawn(position)->getColor() == getPlayerColor()) ++ai_score; + else ++player_score; - // Usuń pionek - board.deletePawn(position); + // Usuń pionek + board.deletePawn(position); } Player Game::getPlayer(Vector position) { - if(board.getPawn(position)->getColor() == getPlayerColor()) return PL_HUMAN; - else return PL_AI; + if(board.getPawn(position)->getColor() == getPlayerColor()) return PL_HUMAN; + else return PL_AI; } bool Game::movePawn(Movement movement) { - // Pomocnicze wektory - Vector& position = movement.begin; - Vector& target = movement.end; - - // Jeżeli pionek startowy nie istnieje - if(!board.getPawn(position)) return false; - - // Jeżeli ruch należy do drugiego gracza - if(board.getPawn(position)->getColor() != round) return false; - - // Jeżeli kafelek jest zajęty - if(board.getPawn(target)) return false; + // Lista możliwych ruchów + std::list possible_movements = board.getPossibleGlobalMovements(round); - // Jeżeli to ruch czarnego pionka - if(board.getPawn(position)->getColor() == CL_BLACK) - { - // Jeżeli jest to zwykły ruch na ukos - if((target-position == Vector(1, 1)) || // Jeżeli jest to ruch o wektor [1, 1] - (target-position == Vector(-1, 1))) // --//-- - return board.movePawn(Movement(position, target)); // Rusz pionek - } + // Jeżeli ruch jest niemożliwy, zwróć false + if(find(possible_movements.begin(), possible_movements.end(), movement) == possible_movements.end()) return false; - // Jeżeli to ruch białego pionka - if(board.getPawn(position)->getColor() == CL_WHITE) - { - // Jeżeli jest to zwykły ruch na ukos - if((target-position == Vector(-1, -1)) || // Jeżeli jest to ruch o wektor [1, 1] - (target-position == Vector(1, -1))) // --//-- - return board.movePawn(Movement(position, target)); // Rusz pionek - } - - // Jeżeli jest to przeskok nad drugim graczem - if((target-position == Vector(-2, -2)) && // Jeżeli jest to ruch o wektor [-2, -2] - (board.getPawn(position+Vector(-1, -1))) && // Jeżeli istnieje pionek na polu przesuniętym o [-1, -1] - (board.getPawn(position+Vector(-1, -1))->getColor() != board.getPawn(position)->getColor())) // Jeżeli jest to gracz o innym kolorze - { - // Zabij zbijanego pionka - killPawn(position+Vector(-1, -1)); + // Jeżeli jest to bicie + if((movement.getVector() == Vector(-2, -2)) || // Możliwości bicia zwykłego pionka + (movement.getVector() == Vector(-2, 2)) || + (movement.getVector() == Vector(2, -2)) || + (movement.getVector() == Vector(2, 2)) || + (movement.getVector() == Vector(-2, 0)) || // + możliwości bicia damką + (movement.getVector() == Vector(0, 2)) || + (movement.getVector() == Vector(2, 0)) || + (movement.getVector() == Vector(0, -2))) + killPawn(movement.begin + movement.getVector()/2); // zbij zabijanego pionka - // Przesuń pionek - return board.movePawn(Movement(position, target)); - } - - if((target-position == Vector(-2, 2)) && // Jeżeli jest to ruch o wektor [-2, -2] - (board.getPawn(position+Vector(-1, 1))) && // Jeżeli istnieje pionek na polu przesuniętym o [-1, -1] - (board.getPawn(position+Vector(-1, 1))->getColor() != board.getPawn(position)->getColor())) // Jeżeli jest to gracz o innym kolorze - { - // Zabij zbijanego pionka - killPawn(position+Vector(-1, 1)); - - // Przesuń pionek - return board.movePawn(Movement(position, target)); - } - - if((target-position == Vector(2, -2)) && // Jeżeli jest to ruch o wektor [-2, -2] - (board.getPawn(position+Vector(1, -1))) && // Jeżeli istnieje pionek na polu przesuniętym o [-1, -1] - (board.getPawn(position+Vector(1, -1))->getColor() != board.getPawn(position)->getColor())) // Jeżeli jest to gracz o innym kolorze - { - // Zabij zbijanego pionka - killPawn(position+Vector(1, -1)); - - // Przesuń pionek - return board.movePawn(Movement(position, target)); - } - - if((target-position == Vector(2, 2)) && // Jeżeli jest to ruch o wektor [-2, -2] - (board.getPawn(position+Vector(1, 1))) && // Jeżeli istnieje pionek na polu przesuniętym o [-1, -1] - (board.getPawn(position+Vector(1, 1))->getColor() != board.getPawn(position)->getColor())) // Jeżeli jest to gracz o innym kolorze - { - // Zabij zbijanego pionka - killPawn(position+Vector(1, 1)); - - // Przesuń pionek - return board.movePawn(Movement(position, target)); - } - - return false; + // Wykonaj ruch i zwróć sukces, jeśli się powiodło + return board.movePawn(movement); } void Game::executePlayerRound(sf::Vector2f mouse_position) { - // Konwersja pozycji myszy na współrzędne kafelka - Vector position(mouse_position); + // Konwersja pozycji myszy na współrzędne kafelka + Vector position(mouse_position); - // Pobieramy informacje o pionku(lub jego braku) z interesującego nas pola - Pawn* ptr = board.getPawn(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 - if((round == getPlayerColor()) && ptr && (ptr->getColor() == getPlayerColor())) - { - // Zaznaczamy pionek i pobieramy do niego wskaźnik - ptr = board.selectPawn(Vector(position)); - - // Pobieramy jego pozycję i zapisujemy do zmiennej przechowującej zaznaczony pionek - selected = ptr->getPosition(); - - // Zaznacz dozwolone kafelki na mapie - std::list possible_tiles; // Lista dozwolonych kafelków - for(const auto& m: board.getPossibleMovements(selected)) // Pobieramy dozwolone kafelki docelowe z dozwolonych ruchów - possible_tiles.push_back(m.end); - board.selectTiles(possible_tiles); // Zaznaczamy je na planszy - } - - // Jeżeli pionek nie istnieje w danym miejscu (czyli kliknięto na puste pole), - // to znaczy, że jest to ruch docelowy (czyli należy pionek przesunąć): - if(!ptr) - { - // Jeżeli pionek jest poprawnie zaznaczony - if(board.getPawn(selected)) + // Jeżeli tura należy do gracza, pionek istnieje i należy do gracza + if((round == getPlayerColor()) && ptr && (ptr->getColor() == getPlayerColor())) { - // Pomocnicza flaga - bool move_is_possible = false; - - // Jeżeli możliwe jest bicie - if(board.isPossibleBeating(selected)) - { - // Pobieramy listę możliwych bić - std::list beatings = board.getPossibleBeatings(selected); + // Odznaczamy wszystkie zaznaczenia + board.deselectTiles(); - // Jeżeli gracz próbuje bić, zezwól na ruch (czyli bicie jest obowiązkowe) - if(find(beatings.begin(), beatings.end(), Movement(selected, position)) != beatings.end()) move_is_possible = true; - } + // Zaznaczamy pionek i pobieramy do niego wskaźnik + ptr = board.selectPawn(Vector(position)); - // Jeżeli nie jest możliwe bicie, to zezwól na ruch - else move_is_possible = true; - - // Jeżeli zezwolono na ruch - // przesuwamy pionek o ile to możliwe (kliknięte miejsce = position jest naszym docelowym kafelkiem) - if(move_is_possible && movePawn(Movement(selected, position))) - { - // Ustaw sekwencję ruchów - movements_sequence = true; - - // Jeżeli się udało, to aktualizujemy pozycję zaznaczonego pionka - selected = position; - - // Jeżeli już nie ma możliwości bicia (bicie obowiązkowe), kończymy turę - if(!board.isPossibleBeating(selected)) + // Pobieramy jego pozycję i zapisujemy do zmiennej przechowującej zaznaczony pionek + selected = ptr->getPosition(); + + // Jeżeli nie ma możliwości bicia, zaznacz dozwolone kafelki na mapie + if(!board.arePossibleGlobalBeatings(getPlayerColor())) { - // Kończymy sekwencję ruchów - movements_sequence = false; - - // Odznaczamy pionek - board.getPawn(selected)->deselect(); - - // Odznaczamy kafelki (EXPERIMENTAL) - board.deselectTiles(); - - // i kończymy turę - round = getAIColor(); + std::list possible_tiles; // Lista dozwolonych kafelków + for(const auto& m: board.getPossibleMovements(selected)) // Pobieramy dozwolone kafelki docelowe z dozwolonych ruchów + possible_tiles.push_back(m.end); + board.selectTiles(possible_tiles); // Zaznaczamy je na planszy } - // W przeciwnym razie zaznaczamy na mapie dozwolone kafelki - else + // W przeciwnym wypadku zaznacz tylko bicia + else { - board.deselectTiles(); - std::list possible_tiles; // Lista dozwolonych kafelków - for(const auto& m: board.getPossibleMovements(selected)) // Pobieramy dozwolone kafelki docelowe z dozwolonych ruchów - possible_tiles.push_back(m.end); - board.selectTiles(possible_tiles); // Zaznaczamy je na planszy + std::list possible_tiles; // Lista dozwolonych kafelków + for(const auto& m: board.getPossibleGlobalBeatings(getPlayerColor())) // Pobieramy dozwolone kafelki docelowe z dozwolonych bić + possible_tiles.push_back(m.end); + board.selectTiles(possible_tiles); // Zaznaczamy je na planszy + } + + } + + // Jeżeli pionek nie istnieje w danym miejscu (czyli kliknięto na puste pole), + // to znaczy, że jest to ruch docelowy (czyli należy pionek przesunąć): + if(!ptr) + { + // Jeżeli pionek jest poprawnie zaznaczony + if(board.getPawn(selected)) + { + // Przesuwamy pionek o ile to możliwe (kliknięte miejsce = position jest naszym docelowym kafelkiem) + if(movePawn(Movement(selected, position))) + { + // Jeżeli się udało, to aktualizujemy pozycję zaznaczonego pionka + selected = position; + + // Odznaczamy pionek + board.getPawn(selected)->deselect(); + + // Odznaczamy kafelki + board.deselectTiles(); + + // i kończymy turę + round = getAIColor(); + } } - - } } - } } void Game::eventHandler() { - // Kontener na zdarzenia - sf::Event event; + // Kontener na zdarzenia + sf::Event event; - // Kolejka zdarzeń - while(window.pollEvent(event)) - { - // Obsługa zamykania okna gry - if(event.type == sf::Event::Closed) window.close(); - - // Obsługa myszy - if(event.type == sf::Event::MouseButtonPressed) + // Kolejka zdarzeń + while(window.pollEvent(event)) { - // Lewy klawisz - if(event.mouseButton.button == sf::Mouse::Left) + // Obsługa zamykania okna gry + if(event.type == sf::Event::Closed) window.close(); - // Wykonaj turę gracza - executePlayerRound(sf::Vector2f(event.mouseButton.x, event.mouseButton.y)); + // Obsługa myszy + if(event.type == sf::Event::MouseButtonPressed) + { + // Lewy klawisz + if(event.mouseButton.button == sf::Mouse::Left) + { + // Jeżeli gra jest "w trakcie gry", wykonaj turę gracza + if(game_state == GS_RUNNING) executePlayerRound(sf::Vector2f(event.mouseButton.x, event.mouseButton.y)); + // W przeciwnym wypadku zamknij okno + else window.close(); + + + } + + } } - } } void Game::loop() { - // Nieskończona pętla główna gry - while(window.isOpen()) - { - // Przechwytywanie zdarzeń - eventHandler(); - - // Czyszczenie okna - window.clear(sf::Color::Black); - - // Rysowanie planszy - board.draw(window); - - // Rysuj HUD - drawHUD(); - - // Wyświetlenie ekranu - window.display(); - - // Jeżeli teraz tura należy do AI - if(round == getAIColor()) + // Nieskończona pętla główna gry + while(window.isOpen()) { - // Ruch AI - movePawn(minimax.minimax(board)); - - // Ustaw turę na gracza - round = getPlayerColor(); + // Jeżeli gra jest "w trakcie gry" + if(game_state == GS_RUNNING) + { + // Jeżeli teraz tura należy do AI + if(round == getAIColor()) + { + // Pobieramy najlepszy ruch AI + Movement ai_movement = minimax.getBestMovement(board); + + // Wykonujemy go + movePawn(ai_movement); + + // I zaznaczamy na mapie (ułatwienie dla gracza) + board.selectMovement(ai_movement); + + // Ustaw turę na gracza + round = getPlayerColor(); + } + + // Aktualizuj stan gry + gameUpdate(); + } + + // Przechwytywanie zdarzeń + eventHandler(); + + // Czyszczenie okna + window.clear(sf::Color::Black); + + // Rysowanie planszy + board.draw(window); + + // Rysuj HUD + drawHUD(); + + // Jeżeli gra się zakończyła ... + if(game_state == GS_WIN || game_state == GS_LOSS) displayTheEnd(); // ... wyświetlamy ekran końcowy + + // Wyświetlenie ekranu + window.display(); } - } } void Game::drawHUD() { - sf::Font font; font.loadFromFile("res/arial.ttf"); - - std::stringstream player_score_ss; player_score_ss << "Gracz: " << player_score; - std::stringstream ai_score_ss; ai_score_ss << "AI: " << ai_score; - - sf::Text player_score_text(player_score_ss.str(), font); - sf::Text ai_score_text(ai_score_ss.str(), font); - - player_score_text.setCharacterSize(24); - player_score_text.setColor(sf::Color::White); - player_score_text.setPosition(sf::Vector2f(10, TILES_COUNT*TILE_SIZE)); + // Ilość punktów gracza i AI - ai_score_text.setCharacterSize(24); - ai_score_text.setColor(sf::Color::White); - ai_score_text.setPosition(sf::Vector2f(TILES_COUNT*TILE_SIZE - 70, TILES_COUNT*TILE_SIZE)); - - window.draw(player_score_text); - window.draw(ai_score_text); + sf::Font font; font.loadFromFile("res/arial.ttf"); + + std::stringstream player_score_ss; player_score_ss << "Gracz: " << player_score; + std::stringstream ai_score_ss; ai_score_ss << "AI: " << ai_score; + + sf::Text player_score_text(player_score_ss.str(), font); + sf::Text ai_score_text(ai_score_ss.str(), font); + + player_score_text.setCharacterSize(24); + player_score_text.setColor(sf::Color::White); + player_score_text.setPosition(sf::Vector2f(10, TILES_COUNT*TILE_SIZE)); + + ai_score_text.setCharacterSize(24); + ai_score_text.setColor(sf::Color::White); + ai_score_text.setPosition(sf::Vector2f(TILES_COUNT*TILE_SIZE - 70, TILES_COUNT*TILE_SIZE)); + + window.draw(player_score_text); + window.draw(ai_score_text); } +void Game::gameUpdate() +{ + // Promuj odpowiednie pionki na damki + board.upgrade(); + + // Sprawdź stan gry + if(getPlayerColor() == CL_WHITE) + { + if(!board.getNumberOfBlackPawns()) game_state = GS_WIN; + if(!board.getNumberOfWhitePawns()) game_state = GS_LOSS; + } + else + { + if(!board.getNumberOfBlackPawns()) game_state = GS_LOSS; + if(!board.getNumberOfWhitePawns()) game_state = GS_WIN; + } +} + +void Game::displayTheEnd() +{ + // Przyciemniony ekran + sf::RectangleShape shadow(sf::Vector2f(TILES_COUNT*TILE_SIZE, TILES_COUNT*TILE_SIZE)); + shadow.setFillColor(sf::Color(0, 0, 0, 140)); + shadow.setPosition(0, 0); + window.draw(shadow); + + // Napis "Zwycięstwo"/"Porażka" + sf::Font font; font.loadFromFile("res/arial.ttf"); + sf::Text text = (game_state == GS_WIN)?sf::Text(L"Zwycięstwo", font):sf::Text(L"Porażka", font); + text.setCharacterSize(60); + text.setColor(sf::Color::White); + text.setPosition(WINDOW_WIDTH/2-140, WINDOW_HEIGHT/2-60); + window.draw(text); + +} diff --git a/src/main.cpp b/src/main.cpp index 0402dcd..f06353d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,9 +9,46 @@ #include "../inc/game.hh" #include "../inc/minimax.hh" -int main() -{ - Game game; - return 0; +// Punkt startowy programu +int main(int argc, char *argv[]) +{ + // Kolor gracza + Color color; + + // Poziom AI + int ai; + + // Obsługa błędów i wartości domyślne + if(argc != 3) + { + std::cout << "Użycie: checkers COLOR AI\n\tCOLOR - kolor gracza\n\tAI - poziom AI" << std::endl; + color = CL_WHITE; + ai = 6; + } + else + { + // Obsługa wejścia + // Konwersja std::string na Color + color = (std::string(argv[1]) == "black")?CL_BLACK:CL_WHITE; + + // Konwersja std::string na int + std::stringstream ai_ss; ai_ss << argv[2]; ai_ss >> ai; + + } + + // Zabezpieczenie przed zbyt małym lub dużym poziomem AI + if(ai < 2 || ai > 8) + { + std::cout << "Poziom AI powinien być z przedziału [2, 8]." << std::endl; + return 1; + } + + // Informacja o grze + std::cout << "Grasz jako " << ((color==CL_WHITE)?"białe":"czarne") << " z poziomem AI = " << ai << std::endl; + + // Start gry + Game game(color, ai); + + return 0; } diff --git a/src/minimax.cpp b/src/minimax.cpp index a1c554e..6bbacba 100644 --- a/src/minimax.cpp +++ b/src/minimax.cpp @@ -3,136 +3,153 @@ int MiniMax::evaluate(Board& board, const Color& color) { - // Wycena - int value = 0, pawn_value; + // Wycena + int value = 0, pawn_value; - // Wskaźnik na aktualnie oceniany pionek - Pawn* ptr = NULL; - + // Wskaźnik na aktualnie oceniany pionek + Pawn* ptr = NULL; - // Przeglądamy całą planszę - for(int x = 0; xisQueen()) pawn_value = 10; - else pawn_value = 1; - /*** 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; + // Przeglądamy całą planszę + for(int x = 0; xisQueen()) pawn_value = 10; + else pawn_value = 1; - } + /*** SPRAWDZAMY MOŻLIWE BICIA ***/ + if(board.isPossibleBeating(Vector(x, y))) pawn_value *= 5; - /*** 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) pawn_value *= -1; - - // Dodajemy pawn_value do wyceny - value += pawn_value; - } - } + /*** ZASADA TRZECH OBSZARÓW ***/ + { + // Jeżeli pionek należy do obszaru III (środkowego) -- waga 1 + if(((ptr->getPosition().x >= 3) && (ptr->getPosition().y >= 3)) && + ((ptr->getPosition().x < (TILES_COUNT-3)) && (ptr->getPosition().y < (TILES_COUNT-3)))) + pawn_value *= 1; // więc nie ruszamy - // Zwracamy wycenę - return value; + // 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; + + // Jeżeli pionek należy do obszaru I (skrajnego) -- waga 3 + else + pawn_value *= 3; + + } + + // Jeżeli NIE jest to pionek należący do gracza zdefiniowanego kolorem color + // przemnażamy przez -1 + if(ptr->getColor() != color) pawn_value *= -1; + + // Dodajemy pawn_value do wyceny + value += pawn_value; + + + } + } + + // TODO wykrywanie bicia w heurystyce + + // Zwracamy wycenę + return value; } Color MiniMax::getColorFromDepth(int depth) { - // Dla parzystych głębokości ruch należy do AI - if(depth%2) return AI_COLOR; + // Dla parzystych głębokości ruch należy do AI + if(depth%2) return AI_COLOR; - // Dla nieparzystych -- do gracza - else - { - if(AI_COLOR == CL_WHITE) return CL_BLACK; - else return CL_WHITE; - } + // Dla nieparzystych -- do gracza + else + { + if(AI_COLOR == CL_WHITE) return CL_BLACK; + else return CL_WHITE; + } } int MiniMax::alphabeta(Board board, int depth, int alpha, int beta, Movement& best_movement) { - // Kolor aktualnego gracza - Color color = getColorFromDepth(depth); - - // Jeżeli jest to liść lub korzeń - if((depth == DEPTH_MAX) || (depth == 0)) return evaluate(board, color); - - - // Jeżeli jest teraz ruch przeciwnika - if(color != AI_COLOR) - { - // Dla każdego potomka - for(auto& m: board.getPossibleGlobalMovements(color)) + // Kolor aktualnego gracza + Color color = getColorFromDepth(depth); + + // Jeżeli jest to liść lub korzeń + if((depth == DEPTH_MAX) || (depth == 0)) return evaluate(board, color); + + // Jeżeli jest teraz ruch przeciwnika + if(color != AI_COLOR) { - // Tworzymy nowy stan gry - Board new_board(board); + // Dla każdego potomka + for(auto& m: board.getPossibleGlobalMovements(color)) + { + // Tworzymy nowy stan gry + Board new_board(board); - // Wykonujemy ruch - new_board.movePawn(m); + // Wykonujemy ruch + new_board.movePawn(m); - // Pobieramy wartość MIN (czyli gracz minimalizuje zysk AI) - beta = std::min(beta, alphabeta(new_board, depth+1, alpha, beta, best_movement)); - - // Jeżeli alfa >= beta odcinamy gałąź alpha - if(alpha >= beta) break; + // Pobieramy wartość MIN (czyli gracz minimalizuje zysk AI) + beta = std::min(beta, alphabeta(new_board, depth+1, alpha, beta, best_movement)); + + // Jeżeli alfa >= beta odcinamy gałąź alfa + if(alpha >= beta) break; + } + + // Zwracamy wartość najlepszego ruchu + return beta; } - } - // Jeśli jest teraz ruch AI - else - - // Dla każdego potomka - for(auto& m: board.getPossibleGlobalMovements(color)) - { - // Tworzymy nowy stan gry - Board new_board(board); - - // Wykonujemy ruch - new_board.movePawn(m); + // Jeśli jest teraz ruch AI + else + { + // Dla każdego potomka + for(auto& m: board.getPossibleGlobalMovements(color)) + { + // Tworzymy nowy stan gry + Board new_board(board); - // Pobieramy wartość MAX (czyli AI maksymalizuje własny zysk) - alpha = std::max(alpha, alphabeta(new_board, depth+1, alpha, beta, best_movement)); + // Wykonujemy ruch + new_board.movePawn(m); - // Zwracamy w referencji ruch (dążymy do tego, aby na poziomie głębokości = 1 mieć - // ruch, za pomocą którego doszliśmy do najlepszego rozwiązania według strategii minimax) - best_movement = m; - - // Jeżeli alfa >= beta odcinamy gałąź beta - if(alpha >= beta) break; - } + // Pobieramy wartość stanu gry dla potomka + int temp = alphabeta(new_board, depth+1, alpha, beta, best_movement); - // Zwracamy wartość najlepszego ruchu - return alpha; + // Liczymy MAX (czyli AI maksymalizuje własny zysk) + // oraz jednocześnie warunek najlepszego ruchu + if(temp > alpha) + { + // Jeśli temp > alpha, przypisujemy alpha a= temp (z definicji MAX(a, b)) + alpha = temp; + + // Na poziomie rekurencji = 1 zwracamy w referencji + // ruch, za pomocą którego doszliśmy do najlepszego rozwiązania według strategii minimax + if(depth==1) best_movement = m; + } + + // Jeżeli alfa >= beta odcinamy gałąź beta + if(alpha >= beta) break; + + } + + // Zwracamy wartość najlepszego ruchu + return alpha; + } } -Movement MiniMax::minimax(Board board) +Movement MiniMax::getBestMovement(Board board) { - // Tworzymy bufor na ruch - Movement best_movement; + // Tworzymy bufor na ruch + Movement best_movement; - // Pobieramy algorytmem minimax z cięciem alfa-beta najlepszy ruch - alphabeta(board, 1, -INF, INF, best_movement); + // Pobieramy algorytmem minimax z cięciem alfa-beta najlepszy ruch + alphabeta(board, 1, -INF, INF, best_movement); - // Zwracamy najlepszy ruch - return best_movement; + // Zwracamy najlepszy ruch + return best_movement; } +