Mocno poprawiony algorytm minimax i wykrywanie ruchów

This commit is contained in:
Bartłomiej Pluta
2016-05-25 12:02:14 +02:00
parent ffde3025d2
commit aa65a0f7a9
10 changed files with 781 additions and 654 deletions

View File

@@ -28,7 +28,10 @@ struct Movement
bool operator==(const Movement& b) { return ((begin == b.begin) && (end == b.end)); } bool operator==(const Movement& b) { return ((begin == b.begin) && (end == b.end)); }
// Wypisywanie ruchu do terminala // 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ę // Klasa implementująca planszę
@@ -42,6 +45,9 @@ private:
// Zaznaczone kafelki na mapie(ułatwienie dla gracza) // Zaznaczone kafelki na mapie(ułatwienie dla gracza)
std::list<Vector> selected_tiles; std::list<Vector> selected_tiles;
// Zaznaczony ruch na mapie
bool is_selected_movement;
public: public:
// Konstruktor inicjujący planszę // Konstruktor inicjujący planszę
@@ -63,8 +69,14 @@ public:
// Przesuń pionek na określoną pozycję // Przesuń pionek na określoną pozycję
bool movePawn(Movement movement); bool movePawn(Movement movement);
// Czy ruch jest możliwy (czy dana pozycja jest osiągalna) // Pobierz ilość białych pionków
bool isMovementPossible(Movement movement); 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 // Czy bicie z danej pozycji jest możliwe
bool isPossibleBeating(Vector position); bool isPossibleBeating(Vector position);
@@ -96,8 +108,11 @@ public:
// Zaznacz wybrane kafelki na mapie // Zaznacz wybrane kafelki na mapie
void selectTiles(std::list<Vector> tiles) { selected_tiles = tiles; } void selectTiles(std::list<Vector> tiles) { selected_tiles = tiles; }
// Zaznacz ruch na mapie
void selectMovement(Movement movement);
// Odznacz kafelki na mapie // 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 // Wyświetl zawartość planszy na standardowym wyjściu
void display(); void display();

View File

@@ -10,7 +10,7 @@
const std::string GAME_TITLE = "Warcaby"; const std::string GAME_TITLE = "Warcaby";
// Ilość pól w każdym wymiarze // 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) // Długość boku pojedynczego kafelka(który jest kwadratem)
const int TILE_SIZE = 64; const int TILE_SIZE = 64;

View File

@@ -25,6 +25,14 @@ enum Player
PL_AI PL_AI
}; };
// Typ wyliczeniowy implementujący stany gry
enum GameState
{
GS_PAUSED,
GS_RUNNING,
GS_WIN,
GS_LOSS
};
// Klasa gry // Klasa gry
class Game class Game
@@ -35,6 +43,9 @@ class Game
// Plansza do gry w warcaby // Plansza do gry w warcaby
Board board; Board board;
// Stan gry
GameState game_state;
// Zaznaczony pionek // Zaznaczony pionek
Vector selected; Vector selected;
@@ -44,9 +55,6 @@ class Game
// Tura(czy gracz, czy AI) // Tura(czy gracz, czy AI)
Color round; Color round;
// Flaga oznaczająca sekwencję ruchów(aby gracz nie mógł zmienić pionka w trakcie gry)
bool movements_sequence;
// Punkty gracza // Punkty gracza
int player_score; int player_score;
@@ -84,13 +92,21 @@ private:
// Rysuje HUD (czyli ilość punktów etc.) // Rysuje HUD (czyli ilość punktów etc.)
void drawHUD(); void drawHUD();
// Aktualizuj stan gry
void gameUpdate();
// Wyświetl ekran końcowy
void displayTheEnd();
// Główna pętla gry // Główna pętla gry
void loop(); void loop();
public: public:
// Konstruktor inicjalizujący parametry gry i wyzwalający pętlę główną gry // 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);
}; };

View File

@@ -21,7 +21,7 @@ private:
// Funkcja zwraca kolor kolejnego gracza z drzewa minimaks // Funkcja zwraca kolor kolejnego gracza z drzewa minimaks
Color getColorFromDepth(int depth); Color getColorFromDepth(int depth);
public:
// Funkcja heurystyczna // Funkcja heurystyczna
int evaluate(Board& board, const Color& color); int evaluate(Board& board, const Color& color);
public: public:
@@ -35,7 +35,7 @@ public:
int alphabeta(Board board, int depth, int alpha, int beta, Movement& best_movement); int alphabeta(Board board, int depth, int alpha, int beta, Movement& best_movement);
// Funkcja startowa algorytmu minimax z cięciem alfa-beta // Funkcja startowa algorytmu minimax z cięciem alfa-beta
Movement minimax(Board board); Movement getBestMovement(Board board);
}; };

View File

@@ -1,6 +1,7 @@
#ifndef MISC_HH #ifndef MISC_HH
#define MISC_HH #define MISC_HH
#include <iostream>
#include <cmath> #include <cmath>
#include <SFML/Window.hpp> #include <SFML/Window.hpp>
@@ -10,13 +11,13 @@
struct Vector struct Vector
{ {
public: public:
unsigned int x; int x;
unsigned int y; int y;
public: public:
// Konstruktor tworzący parę (_x, _y) // 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 // 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) {} Vector(const RealVector& real_position) : x(real_position.x/TILE_SIZE), y(real_position.y/TILE_SIZE) {}
@@ -41,12 +42,21 @@ public:
Vector operator*=(int a) { return *this = *this*a; } Vector operator*=(int a) { return *this = *this*a; }
Vector operator*=(float 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 // Porównanie wektorów
bool operator==(Vector v) { return ((x == v.x) && (y == v.y)); } bool operator==(Vector v) { return ((x == v.x) && (y == v.y)); }
// Norma w przestrzeni Manhattan // Norma w przestrzeni Manhattan
int manhattanNorm() const { return abs(x) + abs(y); } int manhattanNorm() const { return abs(x) + abs(y); }
// Wyświetl wektor
void display() const { std::cout << "(" << x << ", " << y << ")"; }
}; };

View File

@@ -16,6 +16,13 @@ enum Color
CL_BLACK 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 // Klasa reprezentująca pionek
class Pawn : public Object class Pawn : public Object
{ {

View File

@@ -1,6 +1,6 @@
#include "../inc/board.hh" #include "../inc/board.hh"
Board::Board() Board::Board() : is_selected_movement(false)
{ {
// Czyścimy całą tablicę // Czyścimy całą tablicę
for(int i=0; i<TILES_COUNT; ++i) for(int j=0; j<TILES_COUNT; ++j) board[i][j] = NULL; for(int i=0; i<TILES_COUNT; ++i) for(int j=0; j<TILES_COUNT; ++j) board[i][j] = NULL;
@@ -8,23 +8,15 @@ Board::Board()
void Board::initBoard() void Board::initBoard()
{ {
// 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 // Tworzymy czarne pionki
for(int i=1; i<TILES_COUNT; i=i+2) createPawn(Vector(i, 0), CL_BLACK); for(int i=!(n%2); i<TILES_COUNT; i=i+2) createPawn(Vector(i, n), CL_BLACK);
for(int i=0; i<TILES_COUNT; i=i+2) createPawn(Vector(i, 1), CL_BLACK);
for(int i=1; i<TILES_COUNT; i=i+2) createPawn(Vector(i, 2), CL_BLACK);
// Tworzymy białe pionki // Tworzymy białe pionki
for(int i=0; i<TILES_COUNT; i=i+2) createPawn(Vector(i, TILES_COUNT-1), CL_WHITE); for(int i=(n%2); i<TILES_COUNT; i=i+2) createPawn(Vector(i, TILES_COUNT-n-1), CL_WHITE);
for(int i=1; i<TILES_COUNT; i=i+2) createPawn(Vector(i, TILES_COUNT-2), CL_WHITE); }
for(int i=0; i<TILES_COUNT; i=i+2) createPawn(Vector(i, TILES_COUNT-3), CL_WHITE);
createPawn(Vector(5, 4), CL_WHITE);
// createPawn(Vector(4, 4), CL_BLACK);
// createPawn(Vector(5, 3), CL_BLACK);
// createPawn(Vector(6, 4), CL_BLACK);
// createPawn(Vector(5, 5), CL_BLACK);
board[5][4]->upgrade();
} }
Pawn* Board::createPawn(Vector position, Color color) Pawn* Board::createPawn(Vector position, Color color)
@@ -65,12 +57,6 @@ bool Board::movePawn(Movement movement)
return true; return true;
} }
bool Board::isMovementPossible(Movement movement)
{
/** TODO **/
return true;
}
Pawn Board::deletePawn(Vector position) Pawn Board::deletePawn(Vector position)
{ {
Pawn deleted_pawn = *board[position.x][position.y]; Pawn deleted_pawn = *board[position.x][position.y];
@@ -118,8 +104,11 @@ void Board::draw(sf::RenderWindow& window)
// Ustawiamy pozycję na i*TILE_SIZE, j*TILE_SIZE -- czyli np. dla TILE_SIZE = 64: (0,0), (0,64), (0,128), ... // 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); tile.setPosition(x*TILE_SIZE, y*TILE_SIZE);
// Ustawiamy kolor wypełnienia co drugiego kafelka na jasny // 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)); 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 // Rysujemy kafelki
window.draw(tile); window.draw(tile);
@@ -255,7 +244,6 @@ std::list<Movement> Board::getPossibleMovements(Vector position)
// Jeżeli pionek jest damką // Jeżeli pionek jest damką
if(getPawn(position)->isQueen()) if(getPawn(position)->isQueen())
{ {
// Jeżeli ruch w górę-lewo jest możliwy (jest puste miejsce) dorzuć ruch 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(position.x >= 1 && position.y >= 1)
if(!getPawn(position+Vector(-1, -1))) movements.push_back(Movement(position, position+Vector(-1, -1))); if(!getPawn(position+Vector(-1, -1))) movements.push_back(Movement(position, position+Vector(-1, -1)));
@@ -272,23 +260,24 @@ std::list<Movement> Board::getPossibleMovements(Vector position)
if(position.x < TILES_COUNT-1 && position.y < TILES_COUNT-1) 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))); 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 // Jeżeli ruch w górę jest możliwy (jest puste miejsce) dorzuć ruch do listy
if(position.y >= 1) if(position.y >= 1)
if(!getPawn(position+Vector(0, -1))) movements.push_back(Movement(position, position+Vector(0, -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 // Jeżeli ruch w dół jest możliwy (jest puste miejsce) dorzuć ruch do listy
if(position.y < TILES_COUNT-1) if(position.y < TILES_COUNT-1)
if(!getPawn(position+Vector(0, 1))) movements.push_back(Movement(position, position+Vector(0, 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 // Jeżeli ruch w lewo jest możliwy (jest puste miejsce) dorzuć ruch do listy
if(position.x >= 1) if(position.x >= 1)
if(!getPawn(position+Vector(-1, 0))) movements.push_back(Movement(position, position+Vector(-1, 0))); ;//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 // Jeżeli ruch w prawo jest możliwy (jest puste miejsce) dorzuć ruch do listy
if(position.x < TILES_COUNT-1) if(position.x < TILES_COUNT-1)
if(!getPawn(position+Vector(1, 0))) movements.push_back(Movement(position, position+Vector(1, 0))); ;//if(!getPawn(position+Vector(1, 0))) movements.push_back(Movement(position, position+Vector(1, 0)));
} }
// Jeżeli jest to zwykły pionek // Jeżeli jest to zwykły pionek
@@ -403,11 +392,67 @@ Board::Board(const Board& original)
// Kopiujemy wszystko... // Kopiujemy wszystko...
for(int x = 0; x<TILES_COUNT; ++x) for(int x = 0; x<TILES_COUNT; ++x)
for(int y = 0; y<TILES_COUNT; ++y) for(int y = 0; y<TILES_COUNT; ++y)
{
// Jeżeli istnieje pionek na tym polu... // Jeżeli istnieje pionek na tym polu...
if(original.board[x][y]) if(original.board[x][y])
{
// ... to w nowotworzonej planszy tworzymy identyczny pionek // ... to w nowotworzonej planszy tworzymy identyczny pionek
createPawn(Vector(x, y), original.board[x][y]->getColor()); createPawn(Vector(x, y), original.board[x][y]->getColor());
// Jeżeli ten pionek jest damką ...
if(original.board[x][y]->isQueen())
// ... to promujemy nowoutworzonego pionka na damkę
board[x][y]->upgrade();
}
}
}
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);
} }

View File

@@ -1,14 +1,15 @@
#include "../inc/game.hh" #include "../inc/game.hh"
Game::Game() Game::Game(Color player, int ai)
: :
selected(TILES_COUNT+1, TILES_COUNT+1), // Brak zaznaczenia game_state(GS_RUNNING), // Stan gry
player_color(CL_WHITE), // Domyślny kolor gracza selected(TILES_COUNT+1, TILES_COUNT+1), // Brak zaznaczenia
round(CL_WHITE), // Pierwszy ruch mają białe pionki player_color(player), // Kolor gracza
movements_sequence(false), // Ciąg kolejnych ruchów round(CL_WHITE), // Pierwszy ruch mają białe pionki
player_score(0), // Wyzerowana ilość punktów gracza player_score(0), // Wyzerowana ilość punktów gracza
ai_score(0), // Wyzerowana ilość punktów AI ai_score(0), // Wyzerowana ilość punktów AI
minimax(7, getAIColor()) // Poziom AI oraz kolor AI minimax(ai, getAIColor()) // Poziom AI oraz kolor AI
{ {
// Ustawienia antyaliasingu // Ustawienia antyaliasingu
sf::ContextSettings settings; sf::ContextSettings settings;
@@ -42,83 +43,25 @@ Player Game::getPlayer(Vector position)
bool Game::movePawn(Movement movement) bool Game::movePawn(Movement movement)
{ {
// Pomocnicze wektory // Lista możliwych ruchów
Vector& position = movement.begin; std::list<Movement> possible_movements = board.getPossibleGlobalMovements(round);
Vector& target = movement.end;
// Jeżeli pionek startowy nie istnieje // Jeżeli ruch jest niemożliwy, zwróć false
if(!board.getPawn(position)) return false; if(find(possible_movements.begin(), possible_movements.end(), movement) == possible_movements.end()) return false;
// Jeżeli ruch należy do drugiego gracza // Jeżeli jest to bicie
if(board.getPawn(position)->getColor() != round) return false; 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
// Jeżeli kafelek jest zajęty // Wykonaj ruch i zwróć sukces, jeśli się powiodło
if(board.getPawn(target)) return false; return board.movePawn(movement);
// 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) void Game::executePlayerRound(sf::Vector2f mouse_position)
@@ -132,19 +75,35 @@ void Game::executePlayerRound(sf::Vector2f mouse_position)
// Jeżeli tura należy do gracza, pionek istnieje i należy do gracza // Jeżeli tura należy do gracza, pionek istnieje i należy do gracza
if((round == getPlayerColor()) && ptr && (ptr->getColor() == getPlayerColor())) if((round == getPlayerColor()) && ptr && (ptr->getColor() == getPlayerColor()))
{ {
// Odznaczamy wszystkie zaznaczenia
board.deselectTiles();
// Zaznaczamy pionek i pobieramy do niego wskaźnik // Zaznaczamy pionek i pobieramy do niego wskaźnik
ptr = board.selectPawn(Vector(position)); ptr = board.selectPawn(Vector(position));
// Pobieramy jego pozycję i zapisujemy do zmiennej przechowującej zaznaczony pionek // Pobieramy jego pozycję i zapisujemy do zmiennej przechowującej zaznaczony pionek
selected = ptr->getPosition(); selected = ptr->getPosition();
// Zaznacz dozwolone kafelki na mapie // Jeżeli nie ma możliwości bicia, zaznacz dozwolone kafelki na mapie
if(!board.arePossibleGlobalBeatings(getPlayerColor()))
{
std::list<Vector> possible_tiles; // Lista dozwolonych kafelków std::list<Vector> possible_tiles; // Lista dozwolonych kafelków
for(const auto& m: board.getPossibleMovements(selected)) // Pobieramy dozwolone kafelki docelowe z dozwolonych ruchów for(const auto& m: board.getPossibleMovements(selected)) // Pobieramy dozwolone kafelki docelowe z dozwolonych ruchów
possible_tiles.push_back(m.end); possible_tiles.push_back(m.end);
board.selectTiles(possible_tiles); // Zaznaczamy je na planszy board.selectTiles(possible_tiles); // Zaznaczamy je na planszy
} }
// W przeciwnym wypadku zaznacz tylko bicia
else
{
std::list<Vector> 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), // 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ąć): // to znaczy, że jest to ruch docelowy (czyli należy pionek przesunąć):
if(!ptr) if(!ptr)
@@ -152,59 +111,21 @@ void Game::executePlayerRound(sf::Vector2f mouse_position)
// Jeżeli pionek jest poprawnie zaznaczony // Jeżeli pionek jest poprawnie zaznaczony
if(board.getPawn(selected)) if(board.getPawn(selected))
{ {
// Pomocnicza flaga // Przesuwamy pionek o ile to możliwe (kliknięte miejsce = position jest naszym docelowym kafelkiem)
bool move_is_possible = false; if(movePawn(Movement(selected, position)))
// Jeżeli możliwe jest bicie
if(board.isPossibleBeating(selected))
{ {
// Pobieramy listę możliwych bić
std::list<Movement> 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 // Jeżeli się udało, to aktualizujemy pozycję zaznaczonego pionka
selected = position; 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 // Odznaczamy pionek
board.getPawn(selected)->deselect(); board.getPawn(selected)->deselect();
// Odznaczamy kafelki (EXPERIMENTAL) // Odznaczamy kafelki
board.deselectTiles(); board.deselectTiles();
// i kończymy turę // i kończymy turę
round = getAIColor(); round = getAIColor();
} }
// W przeciwnym razie zaznaczamy na mapie dozwolone kafelki
else
{
board.deselectTiles();
std::list<Vector> 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
}
}
} }
} }
} }
@@ -225,9 +146,15 @@ void Game::eventHandler()
{ {
// Lewy klawisz // Lewy klawisz
if(event.mouseButton.button == sf::Mouse::Left) 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));
// Wykonaj turę gracza // W przeciwnym wypadku zamknij okno
executePlayerRound(sf::Vector2f(event.mouseButton.x, event.mouseButton.y)); else window.close();
}
} }
} }
@@ -238,6 +165,29 @@ void Game::loop()
// Nieskończona pętla główna gry // Nieskończona pętla główna gry
while(window.isOpen()) while(window.isOpen())
{ {
// 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ń // Przechwytywanie zdarzeń
eventHandler(); eventHandler();
@@ -250,23 +200,18 @@ void Game::loop()
// Rysuj HUD // Rysuj HUD
drawHUD(); 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 // Wyświetlenie ekranu
window.display(); 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() void Game::drawHUD()
{ {
// Ilość punktów gracza i AI
sf::Font font; font.loadFromFile("res/arial.ttf"); sf::Font font; font.loadFromFile("res/arial.ttf");
std::stringstream player_score_ss; player_score_ss << "Gracz: " << player_score; std::stringstream player_score_ss; player_score_ss << "Gracz: " << player_score;
@@ -287,3 +232,38 @@ void Game::drawHUD()
window.draw(ai_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);
}

View File

@@ -9,9 +9,46 @@
#include "../inc/game.hh" #include "../inc/game.hh"
#include "../inc/minimax.hh" #include "../inc/minimax.hh"
int main()
// Punkt startowy programu
int main(int argc, char *argv[])
{ {
Game game; // 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; return 0;
} }

View File

@@ -22,36 +22,40 @@ int MiniMax::evaluate(Board& board, const Color& color)
if(ptr->isQueen()) pawn_value = 10; if(ptr->isQueen()) pawn_value = 10;
else pawn_value = 1; else pawn_value = 1;
/*** SPRAWDZAMY MOŻLIWE BICIA ***/
if(board.isPossibleBeating(Vector(x, y))) pawn_value *= 5;
/*** ZASADA TRZECH OBSZARÓW ***/ /*** ZASADA TRZECH OBSZARÓW ***/
{ {
// Jeżeli pionek należy do obszaru III -- waga 1 // Jeżeli pionek należy do obszaru III (środkowego) -- waga 1
if(((ptr->getPosition().x >= 3) && (ptr->getPosition().y >= 3)) && if(((ptr->getPosition().x >= 3) && (ptr->getPosition().y >= 3)) &&
((ptr->getPosition().x < (TILES_COUNT-3)) && (ptr->getPosition().y < (TILES_COUNT-3)))) ((ptr->getPosition().x < (TILES_COUNT-3)) && (ptr->getPosition().y < (TILES_COUNT-3))))
; // więc nie ruszamy pawn_value *= 1; // więc nie ruszamy
// Jeżeli pionek należy do obszaru II -- waga 2 // Jeżeli pionek należy do obszaru II -- waga 2
else if(((ptr->getPosition().x >= 2) && (ptr->getPosition().y >= 2)) && else if(((ptr->getPosition().x >= 2) && (ptr->getPosition().y >= 2)) &&
((ptr->getPosition().x < (TILES_COUNT-2)) && (ptr->getPosition().y < (TILES_COUNT-2)))) ((ptr->getPosition().x < (TILES_COUNT-2)) && (ptr->getPosition().y < (TILES_COUNT-2))))
pawn_value *= 2; pawn_value *= 2;
// W przeciwnym wypadku, zostaje waga 1 // Jeżeli pionek należy do obszaru I (skrajnego) -- waga 3
else else
pawn_value *= 3; pawn_value *= 3;
} }
/*** MOŻLIWOŚĆ BICIA ***/
if(board.isPossibleBeating(ptr->getPosition())) pawn_value += 5;
// Jeżeli NIE jest to pionek należący do gracza zdefiniowanego kolorem color // Jeżeli NIE jest to pionek należący do gracza zdefiniowanego kolorem color
// przemnażamy przez -1 // przemnażamy przez -1
if(ptr->getColor() != color) pawn_value *= -1; if(ptr->getColor() != color) pawn_value *= -1;
// Dodajemy pawn_value do wyceny // Dodajemy pawn_value do wyceny
value += pawn_value; value += pawn_value;
} }
} }
// TODO wykrywanie bicia w heurystyce
// Zwracamy wycenę // Zwracamy wycenę
return value; return value;
} }
@@ -77,7 +81,6 @@ int MiniMax::alphabeta(Board board, int depth, int alpha, int beta, Movement& be
// Jeżeli jest to liść lub korzeń // Jeżeli jest to liść lub korzeń
if((depth == DEPTH_MAX) || (depth == 0)) return evaluate(board, color); if((depth == DEPTH_MAX) || (depth == 0)) return evaluate(board, color);
// Jeżeli jest teraz ruch przeciwnika // Jeżeli jest teraz ruch przeciwnika
if(color != AI_COLOR) if(color != AI_COLOR)
{ {
@@ -93,14 +96,17 @@ int MiniMax::alphabeta(Board board, int depth, int alpha, int beta, Movement& be
// Pobieramy wartość MIN (czyli gracz minimalizuje zysk AI) // Pobieramy wartość MIN (czyli gracz minimalizuje zysk AI)
beta = std::min(beta, alphabeta(new_board, depth+1, alpha, beta, best_movement)); beta = std::min(beta, alphabeta(new_board, depth+1, alpha, beta, best_movement));
// Jeżeli alfa >= beta odcinamy gałąź alpha // Jeżeli alfa >= beta odcinamy gałąź alfa
if(alpha >= beta) break; if(alpha >= beta) break;
} }
// Zwracamy wartość najlepszego ruchu
return beta;
} }
// Jeśli jest teraz ruch AI // Jeśli jest teraz ruch AI
else else
{
// Dla każdego potomka // Dla każdego potomka
for(auto& m: board.getPossibleGlobalMovements(color)) for(auto& m: board.getPossibleGlobalMovements(color))
{ {
@@ -110,22 +116,32 @@ int MiniMax::alphabeta(Board board, int depth, int alpha, int beta, Movement& be
// Wykonujemy ruch // Wykonujemy ruch
new_board.movePawn(m); new_board.movePawn(m);
// Pobieramy wartość MAX (czyli AI maksymalizuje własny zysk) // Pobieramy wartość stanu gry dla potomka
alpha = std::max(alpha, alphabeta(new_board, depth+1, alpha, beta, best_movement)); int temp = 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ć // Liczymy MAX (czyli AI maksymalizuje własny zysk)
// ruch, za pomocą którego doszliśmy do najlepszego rozwiązania według strategii minimax) // oraz jednocześnie warunek najlepszego ruchu
best_movement = m; 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 // Jeżeli alfa >= beta odcinamy gałąź beta
if(alpha >= beta) break; if(alpha >= beta) break;
} }
// Zwracamy wartość najlepszego ruchu // Zwracamy wartość najlepszego ruchu
return alpha; return alpha;
}
} }
Movement MiniMax::minimax(Board board) Movement MiniMax::getBestMovement(Board board)
{ {
// Tworzymy bufor na ruch // Tworzymy bufor na ruch
Movement best_movement; Movement best_movement;
@@ -136,3 +152,4 @@ Movement MiniMax::minimax(Board board)
// Zwracamy najlepszy ruch // Zwracamy najlepszy ruch
return best_movement; return best_movement;
} }