Sprawozdanie

This commit is contained in:
Bartłomiej Pluta
2016-06-03 16:32:58 +02:00
parent aa65a0f7a9
commit 163d8a1672
9 changed files with 140 additions and 75 deletions

View File

@@ -60,6 +60,9 @@ public:
// według zasad gry w warcaby // według zasad gry w warcaby
void initBoard(); void initBoard();
// Usuwa wszystkie pionki z planszy
void clearBoard();
// Utwórz nowy pionek na zadanej pozycji // Utwórz nowy pionek na zadanej pozycji
Pawn* createPawn(Vector position, Color color); Pawn* createPawn(Vector position, Color color);
@@ -78,6 +81,9 @@ public:
// Promuj na damki te pionki, które doszły na linię promocji // Promuj na damki te pionki, które doszły na linię promocji
void upgrade(); void upgrade();
// Czy pionek jest zagrożony biciem
bool isEndangered(Vector position);
// Czy bicie z danej pozycji jest możliwe // Czy bicie z danej pozycji jest możliwe
bool isPossibleBeating(Vector position); bool isPossibleBeating(Vector position);

View File

@@ -33,6 +33,9 @@ const int WINDOW_HEIGHT = TILES_COUNT * TILE_SIZE + 30;
// Wartość nieskończoności // Wartość nieskończoności
const int INF = 9999; const int INF = 9999;
// Domyślny plik zapisu i odczytu stanu gry
const std::string DEFAULT_STATE_FILE = "movements.txt";
// Przedefiniowanie sf::Vector2f na RealVector // Przedefiniowanie sf::Vector2f na RealVector
typedef sf::Vector2f RealVector; typedef sf::Vector2f RealVector;

View File

@@ -6,6 +6,7 @@
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <fstream>
#include <list> #include <list>
#include <algorithm> #include <algorithm>
#include <SFML/Window.hpp> #include <SFML/Window.hpp>
@@ -64,6 +65,9 @@ class Game
// AI // AI
MiniMax minimax; MiniMax minimax;
// Lista ruchów
std::list<Movement> movements_list;
private: private:
// Zabij pionka (doliczając do tego punkty) // Zabij pionka (doliczając do tego punkty)
@@ -98,6 +102,9 @@ private:
// Wyświetl ekran końcowy // Wyświetl ekran końcowy
void displayTheEnd(); void displayTheEnd();
// Zapisz stan gry do pliku
bool saveState(std::string filename);
// Główna pętla gry // Główna pętla gry
void loop(); void loop();

View File

@@ -19,21 +19,19 @@ private:
private: private:
// Funkcja zwraca kolor kolejnego gracza z drzewa minimaks // Algorytm MiniMax z cięciem alfa-beta (funkcja zwraca wartość najlepszego ruchu, natomiast
Color getColorFromDepth(int depth); // poprzez referencję zwraca najlepszy ruch best_movement, ruch wykonuje player
public: int alphabeta(Board board, int depth, int alpha, int beta, const Color& player, Movement& best_movement);
// Funkcja heurystyczna // Funkcja heurystyczna
int evaluate(Board& board, const Color& color); int evaluate(Board& board, const Color& color);
public: public:
// Konstruktor przyjmuje parametry: maksymalna głębokość algorytmu MiniMaks oraz kolor pionków // Konstruktor przyjmuje parametry: maksymalna głębokość algorytmu MiniMaks oraz kolor pionków
// sztucznej inteligencji // sztucznej inteligencji
MiniMax(int depth_max, Color ai_color) : DEPTH_MAX(depth_max), AI_COLOR(ai_color) {} MiniMax(int depth_max, Color ai_color) : DEPTH_MAX(depth_max), AI_COLOR(ai_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 // Funkcja startowa algorytmu minimax z cięciem alfa-beta
Movement getBestMovement(Board board); Movement getBestMovement(Board board);

Binary file not shown.

View File

@@ -259,25 +259,6 @@ std::list<Movement> Board::getPossibleMovements(Vector position)
// Jeżeli ruch w dół-prawo jest możliwy (jest puste miejsce) dorzuć ruch do listy // Jeżeli ruch w dół-prawo jest możliwy (jest puste miejsce) dorzuć ruch do listy
if(position.x < TILES_COUNT-1 && position.y < TILES_COUNT-1) if(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
if(position.y >= 1)
;//if(!getPawn(position+Vector(0, -1))) movements.push_back(Movement(position, position+Vector(0, -1)));
// Jeżeli ruch w dół jest możliwy (jest puste miejsce) dorzuć ruch do listy
if(position.y < TILES_COUNT-1)
;//if(!getPawn(position+Vector(0, 1))) movements.push_back(Movement(position, position+Vector(0, 1)));
// Jeżeli ruch w lewo jest możliwy (jest puste miejsce) dorzuć ruch do listy
if(position.x >= 1)
;//if(!getPawn(position+Vector(-1, 0))) movements.push_back(Movement(position, position+Vector(-1, 0)));
// Jeżeli ruch w prawo jest możliwy (jest puste miejsce) dorzuć ruch do listy
if(position.x < TILES_COUNT-1)
;//if(!getPawn(position+Vector(1, 0))) movements.push_back(Movement(position, position+Vector(1, 0)));
} }
// Jeżeli jest to zwykły pionek // Jeżeli jest to zwykły pionek
@@ -456,3 +437,44 @@ void Board::selectMovement(Movement movement)
selected_tiles.push_back(movement.begin); selected_tiles.push_back(movement.begin);
selected_tiles.push_back(movement.end); selected_tiles.push_back(movement.end);
} }
void Board::clearBoard()
{
for(int x = 0; x < TILES_COUNT; ++x)
for(int y = 0; y < TILES_COUNT; ++y)
if(board[x][y]) deletePawn(Vector(x, y));
}
bool Board::isEndangered(Vector position)
{
// Na krawędziach pionek napewno nie jest zagrożony
if((position.x == 0) || (position.x == TILES_COUNT-1)) return false;
if((position.y == 0) || (position.y == TILES_COUNT-1)) return false;
Color player_color = getPawn(position)->getColor();
if(getPawn(position - Vector(1, 1)) && // Jeżeli pole na północny zachód od pionka jest zajęte
getPawn(position - Vector(1, 1))->getColor() != player_color && // Należy do gracza przeciwnego
!getPawn(position + Vector(1, 1))) // a pole na południowy wschód jest wolne
return true; // To pionek jest zagrożony biciem
// Analogicznie w pozostałych kierunkach
if(getPawn(position - Vector(-1, 1)) &&
getPawn(position - Vector(-1, 1))->getColor() != player_color &&
!getPawn(position + Vector(-1, 1)))
return true;
if(getPawn(position - Vector(1, -1)) &&
getPawn(position - Vector(1, -1))->getColor() != player_color &&
!getPawn(position + Vector(1, -1)))
return true;
if(getPawn(position - Vector(-1, -1)) &&
getPawn(position - Vector(-1, -1))->getColor() != player_color &&
!getPawn(position + Vector(-1, -1)))
return true;
return false;
}

View File

@@ -53,11 +53,7 @@ bool Game::movePawn(Movement movement)
if((movement.getVector() == Vector(-2, -2)) || // Możliwości bicia zwykłego pionka 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, -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 killPawn(movement.begin + movement.getVector()/2); // zbij zabijanego pionka
// Wykonaj ruch i zwróć sukces, jeśli się powiodło // Wykonaj ruch i zwróć sukces, jeśli się powiodło
@@ -108,11 +104,14 @@ void Game::executePlayerRound(sf::Vector2f mouse_position)
// 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)
{ {
// Tworzymy ruch z zaznaczonych pozycji
Movement m(selected, position);
// Jeżeli pionek jest poprawnie zaznaczony // Jeżeli pionek jest poprawnie zaznaczony
if(board.getPawn(selected)) if(board.getPawn(selected))
{ {
// Przesuwamy pionek o ile to możliwe (kliknięte miejsce = position jest naszym docelowym kafelkiem) // Przesuwamy pionek o ile to możliwe (kliknięte miejsce = position jest naszym docelowym kafelkiem)
if(movePawn(Movement(selected, position))) if(movePawn(m))
{ {
// Jeżeli się udało, to aktualizujemy pozycję zaznaczonego pionka // Jeżeli się udało, to aktualizujemy pozycję zaznaczonego pionka
selected = position; selected = position;
@@ -123,6 +122,9 @@ void Game::executePlayerRound(sf::Vector2f mouse_position)
// Odznaczamy kafelki // Odznaczamy kafelki
board.deselectTiles(); board.deselectTiles();
// Dorzucamy ruch na stos
movements_list.push_back(m);
// i kończymy turę // i kończymy turę
round = getAIColor(); round = getAIColor();
} }
@@ -152,10 +154,7 @@ void Game::eventHandler()
// W przeciwnym wypadku zamknij okno // W przeciwnym wypadku zamknij okno
else window.close(); else window.close();
} }
} }
} }
} }
@@ -182,6 +181,7 @@ void Game::loop()
// Ustaw turę na gracza // Ustaw turę na gracza
round = getPlayerColor(); round = getPlayerColor();
} }
// Aktualizuj stan gry // Aktualizuj stan gry
@@ -248,6 +248,16 @@ void Game::gameUpdate()
if(!board.getNumberOfBlackPawns()) game_state = GS_LOSS; if(!board.getNumberOfBlackPawns()) game_state = GS_LOSS;
if(!board.getNumberOfWhitePawns()) game_state = GS_WIN; if(!board.getNumberOfWhitePawns()) game_state = GS_WIN;
} }
// Jeżeli jest to już koniec gry
if(game_state == GS_WIN || game_state == GS_LOSS)
{
// to odznacz wszystkie pola
board.deselectTiles();
// i zapisz stan gry do pliku
saveState(DEFAULT_STATE_FILE);
}
} }
void Game::displayTheEnd() void Game::displayTheEnd()
@@ -267,3 +277,18 @@ void Game::displayTheEnd()
window.draw(text); window.draw(text);
} }
bool Game::saveState(std::string filename)
{
// Otwieramy plik
std::fstream file(filename, std::ios::out);
// Jeżeli się nie udało - false;
if(!file.good()) return false;
// Zapisujemy całą liste dotychczasowych ruchów
int i = 0;
for(auto m: movements_list)
file << ++i << "# " << (!(i%2)?"Biały":"Czarny") << ":\t(" << m.begin.x << ", " << m.begin.y << ")\t->\t(" << m.end.x << ", " << m.end.y << ")" << std::endl;
return true;
}

View File

@@ -24,7 +24,7 @@ int main(int argc, char *argv[])
{ {
std::cout << "Użycie: checkers COLOR AI\n\tCOLOR - kolor gracza\n\tAI - poziom AI" << std::endl; std::cout << "Użycie: checkers COLOR AI\n\tCOLOR - kolor gracza\n\tAI - poziom AI" << std::endl;
color = CL_WHITE; color = CL_WHITE;
ai = 6; ai = 8;
} }
else else
{ {

View File

@@ -1,14 +1,18 @@
#include "../inc/minimax.hh" #include "../inc/minimax.hh"
int MiniMax::evaluate(Board& board, const Color& color) int MiniMax::evaluate(Board& board, const Color& color)
{ {
// Wycena // Wycena
int value = 0, pawn_value; int value = 0, pawn_value = 0;
// Wskaźnik na aktualnie oceniany pionek // Wskaźnik na aktualnie oceniany pionek
Pawn* ptr = NULL; Pawn* ptr = NULL;
// Ilość pionków gracza
int player_pawns = 0;
// Ilość pionków przeciwnika
int opponent_pawns = 0;
// Przeglądamy całą planszę // Przeglądamy całą planszę
for(int x = 0; x<TILES_COUNT; ++x) for(int x = 0; x<TILES_COUNT; ++x)
@@ -18,34 +22,42 @@ int MiniMax::evaluate(Board& board, const Color& color)
ptr = board.getPawn(Vector(x, y)); ptr = board.getPawn(Vector(x, y));
if(ptr) if(ptr)
{ {
// Jeżeli pionek jest damką: // Zerujemy wartość tymczasową
if(ptr->isQueen()) pawn_value = 10; pawn_value = 0;
else pawn_value = 1;
/*** DAMKA / PIONEK ***/
if(ptr->isQueen()) pawn_value += 50;
else pawn_value += 1;
/*** SPRAWDZAMY MOŻLIWE BICIA ***/ /*** SPRAWDZAMY MOŻLIWE BICIA ***/
if(board.isPossibleBeating(Vector(x, y))) pawn_value *= 5; if(board.isPossibleBeating(Vector(x, y))) pawn_value += 1000;
///*** SPRAWDZAMY, CZY PIONEK MOŻE BYĆ ZBITY ***/
//if(board.isEndangered(Vector(x, y))) pawn_value -= 10;
/*** ZASADA TRZECH OBSZARÓW ***/ /*** ZASADA TRZECH OBSZARÓW ***/
{ {
// Jeżeli pionek należy do obszaru III (środkowego) -- 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))))
pawn_value *= 1; // więc nie ruszamy pawn_value += 0; // 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 += 100;
// Jeżeli pionek należy do obszaru I (skrajnego) -- waga 3 // Jeżeli pionek należy do obszaru I (skrajnego) -- waga 3
else else
pawn_value *= 3; pawn_value += 500;
} }
// 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; // Do tego zliczamy ilość pionków
if(ptr->getColor() != color) { pawn_value *= -1; ++opponent_pawns; }
else ++player_pawns;
// Dodajemy pawn_value do wyceny // Dodajemy pawn_value do wyceny
value += pawn_value; value += pawn_value;
@@ -54,38 +66,27 @@ int MiniMax::evaluate(Board& board, const Color& color)
} }
} }
// TODO wykrywanie bicia w heurystyce
// Jeżeli nie ma już pionków przeciwnika na planszy, zwracamy INF (gdyż jest to wygrana)
if(!opponent_pawns) return INF;
// Jeżeli nie ma już pionków gracza na planszy, zwracamy -INF (przegrana)
if(!player_pawns) return -INF;
// Zwracamy wycenę // Zwracamy wycenę
return value; return value;
} }
Color MiniMax::getColorFromDepth(int depth) int MiniMax::alphabeta(Board board, int depth, int alpha, int beta, const Color& player, Movement& best_movement)
{ {
// Dla parzystych głębokości ruch należy do AI // Jeżeli jest to liść, zwróć wartość funkcji heurystycznej
if(depth%2) return AI_COLOR; if(depth == DEPTH_MAX) return evaluate(board, player);
// 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 // Jeżeli jest teraz ruch przeciwnika
if(color != AI_COLOR) if(player != AI_COLOR)
{ {
// Dla każdego potomka // Dla każdego potomka
for(auto& m: board.getPossibleGlobalMovements(color)) for(auto& m: board.getPossibleGlobalMovements(player))
{ {
// Tworzymy nowy stan gry // Tworzymy nowy stan gry
Board new_board(board); Board new_board(board);
@@ -93,8 +94,8 @@ 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ść MIN (czyli gracz minimalizuje zysk AI) // Pobieramy wartość MIN (czyli gracz minimalizuje zysk AI) -- ruch 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, AI_COLOR, best_movement));
// Jeżeli alfa >= beta odcinamy gałąź alfa // Jeżeli alfa >= beta odcinamy gałąź alfa
if(alpha >= beta) break; if(alpha >= beta) break;
@@ -108,7 +109,7 @@ int MiniMax::alphabeta(Board board, int depth, int alpha, int beta, Movement& be
else else
{ {
// Dla każdego potomka // Dla każdego potomka
for(auto& m: board.getPossibleGlobalMovements(color)) for(auto& m: board.getPossibleGlobalMovements(player))
{ {
// Tworzymy nowy stan gry // Tworzymy nowy stan gry
Board new_board(board); Board new_board(board);
@@ -116,8 +117,8 @@ 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ść stanu gry dla potomka // Pobieramy wartość stanu gry dla potomka (ruch przeciwnika)
int temp = alphabeta(new_board, depth+1, alpha, beta, best_movement); int temp = alphabeta(new_board, depth+1, alpha, beta, getOpposedColor(AI_COLOR), best_movement);
// Liczymy MAX (czyli AI maksymalizuje własny zysk) // Liczymy MAX (czyli AI maksymalizuje własny zysk)
// oraz jednocześnie warunek najlepszego ruchu // oraz jednocześnie warunek najlepszego ruchu
@@ -128,9 +129,12 @@ int MiniMax::alphabeta(Board board, int depth, int alpha, int beta, Movement& be
// Na poziomie rekurencji = 1 zwracamy w referencji // Na poziomie rekurencji = 1 zwracamy w referencji
// ruch, za pomocą którego doszliśmy do najlepszego rozwiązania według strategii minimax // ruch, za pomocą którego doszliśmy do najlepszego rozwiązania według strategii minimax
if(depth==1) best_movement = m; if(depth == 0) best_movement = m;
} }
// Jeżeli ruch najlepszy nie został wybrany, to wybieramy ruch, który nie jest optymalny, ale jest...
//else if((depth == 0) && (best_movement == Movement())) 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;
@@ -147,7 +151,7 @@ Movement MiniMax::getBestMovement(Board board)
Movement best_movement; Movement best_movement;
// Pobieramy algorytmem minimax z cięciem alfa-beta najlepszy ruch // Pobieramy algorytmem minimax z cięciem alfa-beta najlepszy ruch
alphabeta(board, 1, -INF, INF, best_movement); alphabeta(board, 0, -INF, INF, AI_COLOR, best_movement);
// Zwracamy najlepszy ruch // Zwracamy najlepszy ruch
return best_movement; return best_movement;