Działający algorytm minimax z cięciami alfa-beta.
This commit is contained in:
62
inc/board.hh
62
inc/board.hh
@@ -3,22 +3,57 @@
|
||||
|
||||
/* Plik zawiera implementację planszy(board) do gry w warcaby. */
|
||||
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
#include <SFML/Window.hpp>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
#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<Vector> 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<Movement> getPossibleBeatings(Vector position);
|
||||
|
||||
// Pobierz możliwe ruchy pionka
|
||||
std::list<Movement> getPossibleMovements(Vector position);
|
||||
|
||||
// Pobierz możliwe ruchy wszystkich pionków danego koloru
|
||||
std::list<Movement> 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<Vector> 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() {}
|
||||
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
#define DEF_HH
|
||||
|
||||
#include <SFML/Window.hpp>
|
||||
#include <string>
|
||||
|
||||
/* 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;
|
||||
|
||||
49
inc/game.hh
49
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 <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <list>
|
||||
#include <algorithm>
|
||||
#include <SFML/Window.hpp>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
@@ -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
|
||||
|
||||
45
inc/minimax.hh
Normal file
45
inc/minimax.hh
Normal file
@@ -0,0 +1,45 @@
|
||||
#ifndef MINIMAX_HH
|
||||
#define MINIMAX_HH
|
||||
|
||||
/* Plik implementuje klasę reprezentującą strategię Minimaks */
|
||||
|
||||
#include <algorithm>
|
||||
#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
|
||||
12
inc/misc.hh
12
inc/misc.hh
@@ -1,6 +1,7 @@
|
||||
#ifndef MISC_HH
|
||||
#define MISC_HH
|
||||
|
||||
#include <cmath>
|
||||
#include <SFML/Window.hpp>
|
||||
|
||||
#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
|
||||
|
||||
BIN
res/arial.ttf
Normal file
BIN
res/arial.ttf
Normal file
Binary file not shown.
239
src/board.cpp
239
src/board.cpp
@@ -4,7 +4,10 @@ Board::Board()
|
||||
{
|
||||
// Czyścimy całą tablicę
|
||||
for(int i=0; i<TILES_COUNT; ++i) for(int j=0; j<TILES_COUNT; ++j) board[i][j] = NULL;
|
||||
}
|
||||
|
||||
void Board::initBoard()
|
||||
{
|
||||
// Tworzymy czarne pionki
|
||||
for(int i=1; i<TILES_COUNT; i=i+2) createPawn(Vector(i, 0), CL_BLACK);
|
||||
for(int i=0; i<TILES_COUNT; i=i+2) createPawn(Vector(i, 1), CL_BLACK);
|
||||
@@ -31,8 +34,12 @@ Pawn* Board::getPawn(Vector position)
|
||||
return board[position.x][position.y];
|
||||
}
|
||||
|
||||
bool Board::movePawn(Vector position, Vector target)
|
||||
bool Board::movePawn(Movement movement)
|
||||
{
|
||||
// Pomocnicze wektory
|
||||
Vector& position = movement.begin;
|
||||
Vector& target = movement.end;
|
||||
|
||||
// Jeżeli na pozycji startowej nie ma pionka
|
||||
if(!getPawn(position)) return false;
|
||||
|
||||
@@ -50,9 +57,9 @@ bool Board::movePawn(Vector position, Vector target)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Board::isMovementPossible(Vector position, Vector target)
|
||||
bool Board::isMovementPossible(Movement movement)
|
||||
{
|
||||
// TODO
|
||||
/** TODO **/
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -89,6 +96,29 @@ void Board::draw(sf::RenderWindow& 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);
|
||||
|
||||
// Ustawiamy kolor wypełnienia co drugiego kafelka na jasny
|
||||
tile.setFillColor(((x+y)%2)?sf::Color(200, 124, 50):sf::Color(255, 255, 220));
|
||||
|
||||
// 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)
|
||||
@@ -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<Movement> Board::getPossibleBeatings(Vector position)
|
||||
{
|
||||
// Tworzymy nowy kontener na pozycje
|
||||
std::list<Movement> 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<Movement> Board::getPossibleMovements(Vector position)
|
||||
{
|
||||
// Jeżeli są możliwe bicia, zwróć je
|
||||
if(isPossibleBeating(position)) return getPossibleBeatings(position);
|
||||
|
||||
std::list<Movement> 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<Movement> Board::getPossibleMovements(Color color)
|
||||
{
|
||||
std::list<Movement> 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; i<TILES_COUNT; ++i) std::cout << "+---";
|
||||
std::cout << "+" << std::endl;
|
||||
|
||||
// Wyświetl całą planszę
|
||||
for(int y = 0; y<TILES_COUNT; ++y)
|
||||
{
|
||||
// Pojedynczy wiersz
|
||||
for(int x = 0; x<TILES_COUNT; ++x)
|
||||
{
|
||||
std::cout << "|";
|
||||
if(board[x][y])
|
||||
{
|
||||
if(board[x][y]->getColor() == CL_WHITE) std::cout << " O ";
|
||||
else std::cout << " @ ";
|
||||
} else std::cout << " ";
|
||||
}
|
||||
std::cout << "|" << std::endl;
|
||||
|
||||
// Pozioma linia
|
||||
for(int i=0; i<TILES_COUNT; ++i) std::cout << "+---";
|
||||
std::cout << "+" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
Board::Board(const Board& original)
|
||||
{
|
||||
// Czyścimy całą tablicę
|
||||
for(int i=0; i<TILES_COUNT; ++i) for(int j=0; j<TILES_COUNT; ++j) board[i][j] = NULL;
|
||||
|
||||
// Kopiujemy wszystko...
|
||||
for(int x = 0; x<TILES_COUNT; ++x)
|
||||
for(int y = 0; y<TILES_COUNT; ++y)
|
||||
|
||||
// Jeżeli istnieje pionek na tym polu...
|
||||
if(original.board[x][y])
|
||||
|
||||
// ... to w nowotworzonej planszy tworzymy identyczny pionek
|
||||
createPawn(Vector(x, y), original.board[x][y]->getColor());
|
||||
|
||||
}
|
||||
|
||||
275
src/game.cpp
275
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<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
|
||||
}
|
||||
|
||||
// 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<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
|
||||
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<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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
28
src/main.cpp
28
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;
|
||||
}
|
||||
|
||||
129
src/minimax.cpp
Normal file
129
src/minimax.cpp
Normal file
@@ -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; x<TILES_COUNT; ++x)
|
||||
for(int y = 0; y<TILES_COUNT; ++y)
|
||||
{
|
||||
// Jeżeli pionek na tym polu istnieje
|
||||
ptr = board.getPawn(Vector(x, y));
|
||||
if(ptr)
|
||||
{
|
||||
// Jeżeli pionek jest damką:
|
||||
if(ptr->isQueen()) 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;
|
||||
}
|
||||
Reference in New Issue
Block a user