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

View File

@@ -33,6 +33,9 @@ const int WINDOW_HEIGHT = TILES_COUNT * TILE_SIZE + 30;
// Wartość nieskończoności
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
typedef sf::Vector2f RealVector;

View File

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

View File

@@ -19,21 +19,19 @@ private:
private:
// Funkcja zwraca kolor kolejnego gracza z drzewa minimaks
Color getColorFromDepth(int depth);
public:
// Algorytm MiniMax z cięciem alfa-beta (funkcja zwraca wartość najlepszego ruchu, natomiast
// poprzez referencję zwraca najlepszy ruch best_movement, ruch wykonuje player
int alphabeta(Board board, int depth, int alpha, int beta, const Color& player, Movement& best_movement);
// Funkcja heurystyczna
int evaluate(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) {}
// 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 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
if(position.x < TILES_COUNT-1 && position.y < TILES_COUNT-1)
if(!getPawn(position+Vector(1, 1))) movements.push_back(Movement(position, position+Vector(1, 1)));
// TODO zmodyfikowane ruchy
// TODO - zrobić, aby damka mogła robić długie ruchy (to samo dla bicia!)
// Jeżeli ruch w górę jest możliwy (jest puste miejsce) dorzuć ruch do listy
if(position.y >= 1)
;//if(!getPawn(position+Vector(0, -1))) movements.push_back(Movement(position, position+Vector(0, -1)));
// Jeżeli ruch w dół jest możliwy (jest puste miejsce) dorzuć ruch do listy
if(position.y < TILES_COUNT-1)
;//if(!getPawn(position+Vector(0, 1))) movements.push_back(Movement(position, position+Vector(0, 1)));
// Jeżeli ruch w lewo jest możliwy (jest puste miejsce) dorzuć ruch do listy
if(position.x >= 1)
;//if(!getPawn(position+Vector(-1, 0))) movements.push_back(Movement(position, position+Vector(-1, 0)));
// Jeżeli ruch w prawo jest możliwy (jest puste miejsce) dorzuć ruch do listy
if(position.x < TILES_COUNT-1)
;//if(!getPawn(position+Vector(1, 0))) movements.push_back(Movement(position, position+Vector(1, 0)));
}
// Jeżeli 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.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
(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)))
(movement.getVector() == Vector(2, 2)))
killPawn(movement.begin + movement.getVector()/2); // zbij zabijanego pionka
// 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ąć):
if(!ptr)
{
// Tworzymy ruch z zaznaczonych pozycji
Movement m(selected, position);
// Jeżeli pionek jest poprawnie zaznaczony
if(board.getPawn(selected))
{
// Przesuwamy pionek o ile to możliwe (kliknięte miejsce = position jest naszym docelowym kafelkiem)
if(movePawn(Movement(selected, position)))
if(movePawn(m))
{
// Jeżeli się udało, to aktualizujemy pozycję zaznaczonego pionka
selected = position;
@@ -123,6 +122,9 @@ void Game::executePlayerRound(sf::Vector2f mouse_position)
// Odznaczamy kafelki
board.deselectTiles();
// Dorzucamy ruch na stos
movements_list.push_back(m);
// i kończymy turę
round = getAIColor();
}
@@ -152,10 +154,7 @@ void Game::eventHandler()
// W przeciwnym wypadku zamknij okno
else window.close();
}
}
}
}
@@ -182,6 +181,7 @@ void Game::loop()
// Ustaw turę na gracza
round = getPlayerColor();
}
// Aktualizuj stan gry
@@ -248,6 +248,16 @@ void Game::gameUpdate()
if(!board.getNumberOfBlackPawns()) game_state = GS_LOSS;
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()
@@ -267,3 +277,18 @@ void Game::displayTheEnd()
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;
color = CL_WHITE;
ai = 6;
ai = 8;
}
else
{

View File

@@ -1,14 +1,18 @@
#include "../inc/minimax.hh"
int MiniMax::evaluate(Board& board, const Color& color)
{
// Wycena
int value = 0, pawn_value;
int value = 0, pawn_value = 0;
// Wskaźnik na aktualnie oceniany pionek
Pawn* ptr = NULL;
// Ilość pionków gracza
int player_pawns = 0;
// Ilość pionków przeciwnika
int opponent_pawns = 0;
// Przeglądamy całą planszę
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));
if(ptr)
{
// Jeżeli pionek jest damką:
if(ptr->isQueen()) pawn_value = 10;
else pawn_value = 1;
// Zerujemy wartość tymczasową
pawn_value = 0;
/*** DAMKA / PIONEK ***/
if(ptr->isQueen()) pawn_value += 50;
else pawn_value += 1;
/*** 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 ***/
{
// Jeżeli pionek należy do obszaru III (środkowego) -- waga 1
if(((ptr->getPosition().x >= 3) && (ptr->getPosition().y >= 3)) &&
((ptr->getPosition().x < (TILES_COUNT-3)) && (ptr->getPosition().y < (TILES_COUNT-3))))
pawn_value *= 1; // więc nie ruszamy
pawn_value += 0; // więc nie ruszamy
// Jeżeli pionek należy do obszaru II -- waga 2
else if(((ptr->getPosition().x >= 2) && (ptr->getPosition().y >= 2)) &&
((ptr->getPosition().x < (TILES_COUNT-2)) && (ptr->getPosition().y < (TILES_COUNT-2))))
pawn_value *= 2;
pawn_value += 100;
// Jeżeli pionek należy do obszaru I (skrajnego) -- waga 3
else
pawn_value *= 3;
pawn_value += 500;
}
// Jeżeli NIE jest to pionek należący do gracza zdefiniowanego kolorem color
// 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
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ę
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
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 to liść, zwróć wartość funkcji heurystycznej
if(depth == DEPTH_MAX) return evaluate(board, player);
// Jeżeli jest teraz ruch przeciwnika
if(color != AI_COLOR)
if(player != AI_COLOR)
{
// Dla każdego potomka
for(auto& m: board.getPossibleGlobalMovements(color))
for(auto& m: board.getPossibleGlobalMovements(player))
{
// Tworzymy nowy stan gry
Board new_board(board);
@@ -93,8 +94,8 @@ int MiniMax::alphabeta(Board board, int depth, int alpha, int beta, Movement& be
// 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));
// Pobieramy wartość MIN (czyli gracz minimalizuje zysk AI) -- ruch AI
beta = std::min(beta, alphabeta(new_board, depth+1, alpha, beta, AI_COLOR, best_movement));
// Jeżeli alfa >= beta odcinamy gałąź alfa
if(alpha >= beta) break;
@@ -108,7 +109,7 @@ int MiniMax::alphabeta(Board board, int depth, int alpha, int beta, Movement& be
else
{
// Dla każdego potomka
for(auto& m: board.getPossibleGlobalMovements(color))
for(auto& m: board.getPossibleGlobalMovements(player))
{
// Tworzymy nowy stan gry
Board new_board(board);
@@ -116,8 +117,8 @@ int MiniMax::alphabeta(Board board, int depth, int alpha, int beta, Movement& be
// Wykonujemy ruch
new_board.movePawn(m);
// Pobieramy wartość stanu gry dla potomka
int temp = alphabeta(new_board, depth+1, alpha, beta, best_movement);
// Pobieramy wartość stanu gry dla potomka (ruch przeciwnika)
int temp = alphabeta(new_board, depth+1, alpha, beta, getOpposedColor(AI_COLOR), best_movement);
// Liczymy MAX (czyli AI maksymalizuje własny zysk)
// 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
// 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
if(alpha >= beta) break;
@@ -147,7 +151,7 @@ Movement MiniMax::getBestMovement(Board board)
Movement best_movement;
// 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
return best_movement;