diff --git a/inc/board.hh b/inc/board.hh index b50d948..765c253 100644 --- a/inc/board.hh +++ b/inc/board.hh @@ -3,22 +3,57 @@ /* Plik zawiera implementację planszy(board) do gry w warcaby. */ +#include +#include #include #include #include "pawn.hh" +// Struktura przechowująca informacje o ruchu +struct Movement +{ + // Początek ruchu + Vector begin; + + // Koniec ruchu + Vector end; + + Movement() : begin(0, 0), end(0, 0) {} + + // Prosty konstruktor + Movement(Vector b, Vector e) : begin(b), end(e) {} + + // Operator porównania + 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; } +}; + +// Klasa implementująca planszę class Board { private: // Plansza (czyli dwuwymiarowa tablica wskaźników na pionki) Pawn* board[TILES_COUNT][TILES_COUNT]; + + // Zaznaczone kafelki na mapie(ułatwienie dla gracza) + std::list selected_tiles; + public: // Konstruktor inicjujący planszę Board(); + // Konstruktor kopiujący + Board(const Board& original); + + // Tworzy pionki i układa je w początkowym ułożeniu + // według zasad gry w warcaby + void initBoard(); + // Utwórz nowy pionek na zadanej pozycji Pawn* createPawn(Vector position, Color color); @@ -26,10 +61,22 @@ public: Pawn* getPawn(Vector position); // Przesuń pionek na określoną pozycję - bool movePawn(Vector position, Vector target); + bool movePawn(Movement movement); // Czy ruch jest możliwy (czy dana pozycja jest osiągalna) - bool isMovementPossible(Vector position, Vector target); + bool isMovementPossible(Movement movement); + + // Czy bicie z danej pozycji jest możliwe + bool isPossibleBeating(Vector position); + + // Kontener możliwych bić + std::list getPossibleBeatings(Vector position); + + // Pobierz możliwe ruchy pionka + std::list getPossibleMovements(Vector position); + + // Pobierz możliwe ruchy wszystkich pionków danego koloru + std::list getPossibleMovements(Color color); // Usuń pionek z określonej pozycji Pawn deletePawn(Vector position); @@ -38,8 +85,17 @@ public: void draw(sf::RenderWindow& window); // Zaznacz pionek o zadanej pozycji - Pawn* selectPawn(Vector position); + Pawn* selectPawn(Vector position); + // Zaznacz wybrane kafelki na mapie + void selectTiles(std::list tiles) { selected_tiles = tiles; } + + // Odznacz kafelki na mapie + void deselectTiles() { selected_tiles.clear(); } + + // Wyświetl zawartość planszy na standardowym wyjściu + void display(); + // Destruktor ~Board() {} diff --git a/inc/def.hh b/inc/def.hh index 27bc682..3c6a240 100644 --- a/inc/def.hh +++ b/inc/def.hh @@ -2,11 +2,12 @@ #define DEF_HH #include +#include /* Plik zawiera definicje stałych globalnych oraz przedefiniowanie istniejących typów. */ // Tytuł gry (widoczny m.in. na pasku z tytułem okna) -//const char* GAME_TITLE = "Warcaby"; +const std::string GAME_TITLE = "Warcaby"; // Ilość pól w każdym wymiarze const int TILES_COUNT = 10; @@ -27,7 +28,10 @@ const int QUEEN_PAWN_OUTLINE_THICKNESS = 4; const int WINDOW_WIDTH = TILES_COUNT * TILE_SIZE; // Wysokość okna -const int WINDOW_HEIGHT = TILES_COUNT * TILE_SIZE; +const int WINDOW_HEIGHT = TILES_COUNT * TILE_SIZE + 30; + +// Wartość nieskończoności +const int INF = 9999; // Przedefiniowanie sf::Vector2f na RealVector typedef sf::Vector2f RealVector; diff --git a/inc/game.hh b/inc/game.hh index 145b2a3..43dfe73 100644 --- a/inc/game.hh +++ b/inc/game.hh @@ -3,6 +3,11 @@ /* Plik implementuje klasę gry, w której znajduje się m.in. główna pętla gry oraz która determinuje całą rozgrywkę. */ +#include +#include +#include +#include +#include #include #include @@ -11,6 +16,7 @@ #include "object.hh" #include "pawn.hh" #include "board.hh" +#include "minimax.hh" // Typ wyliczeniowy rozróżniający gracza od sztucznej inteligencji enum Player @@ -19,10 +25,10 @@ enum Player PL_AI }; + // Klasa gry class Game { -private: // Okno naszego programu sf::RenderWindow window; @@ -30,21 +36,27 @@ private: Board board; // Zaznaczony pionek - Pawn* selected; // może lepiej Vector??? + Vector selected; // Kolor gracza Color player_color; + + // 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; - // Tura(czy gracz, czy komputer) + // Punkty gracza + int player_score; - // punkty gracza i komputera + // Punkty AI + int ai_score; - // stan gry - -public: + // AI + MiniMax minimax; - // Konstruktor inicjalizujący parametry gry i wyzwalający pętlę główną gry - Game(); +private: // Zabij pionka (doliczając do tego punkty) void killPawn(Vector position); @@ -57,14 +69,29 @@ public: // Zwróć kolor przeciwnika Color getAIColor() const { return (player_color==CL_WHITE)?CL_BLACK:CL_WHITE; } - + // Przesuń pionek na określoną pozycję uwzględniając już // przy tym zasady gry w warcaby - bool movePawn(Vector position, Vector target); + bool movePawn(Movement movement); + + // Wykonaj turę gracza (argumentem jest jedyny możliwy sposób interakcji gracza z programem + // czyli pozycja myszy w momencie kliknięcia lewego przycisku) + void executePlayerRound(sf::Vector2f mouse_position); + + // Przechwytywanie zdarzeń + void eventHandler(); + // Rysuje HUD (czyli ilość punktów etc.) + void drawHUD(); + // Główna pętla gry void loop(); +public: + + // Konstruktor inicjalizujący parametry gry i wyzwalający pętlę główną gry + Game(); + }; #endif diff --git a/inc/minimax.hh b/inc/minimax.hh new file mode 100644 index 0000000..a3a42bb --- /dev/null +++ b/inc/minimax.hh @@ -0,0 +1,45 @@ +#ifndef MINIMAX_HH +#define MINIMAX_HH + +/* Plik implementuje klasę reprezentującą strategię Minimaks */ + +#include +#include "def.hh" +#include "board.hh" + +class MiniMax +{ +private: + + // Maksymalny poziom zagłębienia algorytmu MiniMax + const int DEPTH_MAX; + + // Kolor komputera + const Color AI_COLOR; + +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); +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 + 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); + +}; + +#endif diff --git a/inc/misc.hh b/inc/misc.hh index c105785..b87b40a 100644 --- a/inc/misc.hh +++ b/inc/misc.hh @@ -1,6 +1,7 @@ #ifndef MISC_HH #define MISC_HH +#include #include #include "def.hh" @@ -8,9 +9,12 @@ /* Struktura reprezentująca wektor w układzie odniesienia planszy do gier (a nie według rzeczywistych współrzędnych - pikseli). */ struct Vector { +public: unsigned int x; unsigned int y; +public: + // Konstruktor tworzący parę (_x, _y) Vector(unsigned int _x, unsigned int _y) : x(_x), y(_y) {} @@ -36,7 +40,15 @@ struct Vector Vector operator*(float a) { return Vector(a*x, a*y); } 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); } }; + + #endif diff --git a/res/arial.ttf b/res/arial.ttf new file mode 100644 index 0000000..12cc15c Binary files /dev/null and b/res/arial.ttf differ diff --git a/src/board.cpp b/src/board.cpp index 4808add..ab021f2 100644 --- a/src/board.cpp +++ b/src/board.cpp @@ -4,7 +4,10 @@ Board::Board() { // Czyścimy całą tablicę for(int i=0; idraw(window); + } } Pawn* Board::selectPawn(Vector position) @@ -111,3 +141,206 @@ Pawn* Board::selectPawn(Vector position) // Zwracamy NULL, bo pionek nie istnieje return NULL; } + + +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; + +} + +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); + + // 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))); + + // ... 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))); + + 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))); + + 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))); + + // Zwracamy kontener + return beatings; + +} + +std::list Board::getPossibleMovements(Vector 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 to ruch czarnego pionka + if(getPawn(position)->getColor() == CL_BLACK) + { + // 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 + 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 to ruch białego pionka + if(getPawn(position)->getColor() == CL_WHITE) + { + // 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 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))); + } + return movements; +} + +std::list Board::getPossibleMovements(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: 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; + + // Pozioma linia + for(int i=0; igetColor()); + +} diff --git a/src/game.cpp b/src/game.cpp index e555b9e..29365ea 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -2,31 +2,288 @@ Game::Game() : - window(sf::VideoMode(WINDOW_WIDTH, WINDOW_HEIGHT, 32), "Warcaby", sf::Style::Close), - selected(NULL), - player_color(CL_WHITE) + 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 { - // Kod inicjalizujący + // 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; + + // Usuń pionek + board.deletePawn(position); } Player Game::getPlayer(Vector position) { - // - return PL_HUMAN; + if(board.getPawn(position)->getColor() == getPlayerColor()) return PL_HUMAN; + else return PL_AI; } -bool Game::movePawn(Vector position, Vector target) +bool Game::movePawn(Movement movement) { - // - return true; + // 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; + + // 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 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)); + + // 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; +} + +void Game::executePlayerRound(sf::Vector2f 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); + + // 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)) + { + // 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)) + { + // 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); + + // 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; + } + + // 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)) + { + // 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(); + } + + // W przeciwnym razie zaznaczamy na mapie dozwolone kafelki + 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 + } + + } + } + } +} + +void Game::eventHandler() +{ + // 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) + { + // Lewy klawisz + if(event.mouseButton.button == sf::Mouse::Left) + + // Wykonaj turę gracza + executePlayerRound(sf::Vector2f(event.mouseButton.x, event.mouseButton.y)); + + } + } } 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()) + { + // Ruch AI + movePawn(minimax.minimax(board)); + + // Ustaw turę na gracza + round = getPlayerColor(); + } + } } + +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)); + + 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); +} + diff --git a/src/main.cpp b/src/main.cpp index f4decdc..0402dcd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,33 +7,11 @@ #include "../inc/pawn.hh" #include "../inc/board.hh" #include "../inc/game.hh" +#include "../inc/minimax.hh" int main() { - /* sf::ContextSettings settings; - settings.antialiasingLevel = 8; - sf::RenderWindow window(sf::VideoMode(WINDOW_WIDTH, WINDOW_HEIGHT, 32), "Warcaby", sf::Style::Close, settings); - Board board; - Pawn* selected; - while(window.isOpen()) - { - sf::Event event; - while(window.pollEvent(event)) - { - if(event.type == sf::Event::Closed) window.close(); - if(event.type == sf::Event::MouseButtonPressed) - { - if(event.mouseButton.button == sf::Mouse::Left) - { - Pawn* tmp = board.selectPawn(Vector(sf::Vector2f(event.mouseButton.x, event.mouseButton.y))); - if(tmp) selected = tmp; - else board.movePawn(selected->getPosition(), Vector(sf::Vector2f(event.mouseButton.x, event.mouseButton.y))); - } - } - } - window.clear(sf::Color(255, 255, 255)); - board.draw(window); - window.display(); - }*/ + Game game; + return 0; } diff --git a/src/minimax.cpp b/src/minimax.cpp new file mode 100644 index 0000000..e519436 --- /dev/null +++ b/src/minimax.cpp @@ -0,0 +1,129 @@ +#include "../inc/minimax.hh" + + +int MiniMax::f1(Board& board, const Color& color) +{ + // Wycena + int value = 0, tmp_value; + + // Wskaźnik na aktualnie oceniany pionek + Pawn* ptr = NULL; + + + // Przeglądamy całą planszę + for(int x = 0; xisQueen()) tmp_value = 10; + else tmp_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; + + // 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 + + // Jeżeli NIE jest to pionek należący do gracza zdefiniowanego kolorem color + // przemnażamy przez -1 + if(ptr->getColor() != color) tmp_value *= -1; + + // Dodajemy tmp_value do wyceny + value += tmp_value; + } + } + + // 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 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.getPossibleMovements(color)) + { + // Tworzymy nowy stan gry + Board new_board(board); + + // 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; + } + } + + // Jeśli jest teraz ruch AI + else + + // Dla każdego potomka + for(auto& m: board.getPossibleMovements(color)) + { + // Tworzymy nowy stan gry + Board new_board(board); + + // Wykonujemy ruch + new_board.movePawn(m); + + // Pobieramy wartość MAX (czyli AI maksymalizuje własny zysk) + alpha = std::max(alpha, alphabeta(new_board, depth+1, alpha, beta, best_movement)); + + // 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; + } + + // Zwracamy wartość najlepszego ruchu + return alpha; +} + +Movement MiniMax::minimax(Board board) +{ + // Tworzymy bufor na ruch + Movement 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; +}