Sprawozdanie
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
BIN
sprawozdanie/sprawozdanie.pdf
Normal file
BIN
sprawozdanie/sprawozdanie.pdf
Normal file
Binary file not shown.
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
43
src/game.cpp
43
src/game.cpp
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user