Działający algorytm minimax z cięciami alfa-beta.

This commit is contained in:
Bartłomiej Pluta
2016-05-20 23:13:52 +02:00
parent 6e055d6ffd
commit 8f1dc27b81
10 changed files with 794 additions and 53 deletions

View File

@@ -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());
}

View File

@@ -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);
}

View File

@@ -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
View 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;
}