2023-08-19 12:56:40 -05:00
|
|
|
#include "botinterface.hpp"
|
|
|
|
#include "common.hpp"
|
2024-08-03 01:26:10 -05:00
|
|
|
#include "gamestate.hpp"
|
2023-10-13 19:07:08 -05:00
|
|
|
#include <array>
|
|
|
|
#include <cstdlib>
|
2024-02-01 12:50:10 -06:00
|
|
|
#include <iostream>
|
2023-10-13 19:07:08 -05:00
|
|
|
#include <queue>
|
|
|
|
#include <stdexcept>
|
2023-08-19 12:56:40 -05:00
|
|
|
#include <SFML/System/Vector2.hpp>
|
|
|
|
|
2024-08-02 19:45:07 -05:00
|
|
|
PlayerDirection lastKnownDirection = kNone;
|
2023-10-13 19:07:08 -05:00
|
|
|
|
2024-08-02 19:45:07 -05:00
|
|
|
AISnake::AISnake() {
|
|
|
|
;
|
|
|
|
}
|
2023-10-13 19:07:08 -05:00
|
|
|
|
2024-08-02 19:45:07 -05:00
|
|
|
PlayerDirection AISnake::GetInput(const sf::Vector2f* source)
|
|
|
|
{
|
|
|
|
sf::Vector2f directionDelta;
|
2024-08-03 21:18:57 -05:00
|
|
|
if (!source)
|
2024-08-03 01:26:10 -05:00
|
|
|
return kUp;
|
|
|
|
while (*source == path.top() && !path.empty()) { path.pop(); }
|
|
|
|
if (path.empty()) { path.push(GetAnyOpenPath(*source)); }
|
2024-08-02 19:45:07 -05:00
|
|
|
directionDelta = *source - path.top();
|
|
|
|
path.pop();
|
|
|
|
if ((directionDelta.y == 1)
|
|
|
|
&& (lastKnownDirection != kDown))
|
|
|
|
{ lastKnownDirection = kUp; }
|
|
|
|
else if ((directionDelta.y == -1)
|
|
|
|
&& (lastKnownDirection != kUp))
|
|
|
|
{ lastKnownDirection = kDown; }
|
|
|
|
else if ((directionDelta.x == 1)
|
|
|
|
&& (lastKnownDirection != kRight))
|
|
|
|
{ lastKnownDirection = kLeft; }
|
|
|
|
else if ((directionDelta.x == -1)
|
|
|
|
&& (lastKnownDirection != kLeft))
|
|
|
|
{ lastKnownDirection = kRight; }
|
|
|
|
return lastKnownDirection;
|
|
|
|
}
|
2023-10-13 19:07:08 -05:00
|
|
|
|
2024-08-02 19:45:07 -05:00
|
|
|
void AISnake::UpdateProbability(int snakeSize)
|
|
|
|
{
|
|
|
|
probabilityBFS = 1 - ((double) snakeSize) / 1000;
|
|
|
|
return;
|
|
|
|
}
|
2023-10-24 00:54:30 -05:00
|
|
|
|
2024-08-02 19:45:07 -05:00
|
|
|
void AISnake::AdjustProbability(double amount)
|
|
|
|
{
|
|
|
|
probabilityBFS += amount;
|
|
|
|
if (probabilityBFS > 1.0) { probabilityBFS = 1.0; }
|
|
|
|
if (probabilityBFS < 0.0) { probabilityBFS = 0.0; }
|
|
|
|
return;
|
|
|
|
}
|
2024-02-01 12:50:10 -06:00
|
|
|
|
2024-08-03 01:26:10 -05:00
|
|
|
void AISnake::AddIteration(const int size)
|
|
|
|
{
|
|
|
|
if (size > 40)
|
|
|
|
{
|
|
|
|
UpdateAverage(size);
|
|
|
|
double adjustmentAmount = 0.002;
|
|
|
|
if (average > size) { AdjustProbability(adjustmentAmount); }
|
|
|
|
else { AdjustProbability(-adjustmentAmount); }
|
|
|
|
}
|
2024-08-03 21:18:57 -05:00
|
|
|
std::cout << "[LOG - AI] Current average: " << average << std::endl;
|
|
|
|
std::cout << "[LOG - AI] Previous iteration size: " << size << std::endl;
|
2024-08-03 01:26:10 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void AISnake::ResetPath(void) {
|
|
|
|
while (!path.empty()) { path.pop(); }
|
|
|
|
}
|
|
|
|
|
2024-08-02 19:45:07 -05:00
|
|
|
// Gets a new path for the bot to follow
|
|
|
|
// Uses DFS algorithm
|
2024-08-03 20:42:34 -05:00
|
|
|
void AISnake::GetNewPath(const sf::Vector2f& source)
|
2024-08-02 19:45:07 -05:00
|
|
|
{
|
|
|
|
// Search for food
|
|
|
|
// Probability-based approach for fun
|
|
|
|
double roll = ((double) GenerateRandomNumber(RAND_MAX)) / ((double) RAND_MAX);
|
2024-08-03 20:42:34 -05:00
|
|
|
if (roll <= probabilityBFS) { BFS(source); }
|
|
|
|
else { DFS(source); }
|
2024-08-03 01:26:10 -05:00
|
|
|
UnvisitBoard();
|
2024-08-03 20:42:34 -05:00
|
|
|
if (pathFailed) {
|
|
|
|
pathFailed = false;
|
|
|
|
EmptyPath();
|
|
|
|
path.push(GetAnyOpenPath(source));
|
|
|
|
} else {
|
|
|
|
TrimPath();
|
|
|
|
if (path.empty())
|
|
|
|
path.push(GetAnyOpenPath(source));
|
|
|
|
}
|
2024-08-02 19:45:07 -05:00
|
|
|
}
|
2023-10-13 19:22:11 -05:00
|
|
|
|
2024-08-03 20:42:34 -05:00
|
|
|
void AISnake::BFS(const sf::Vector2f& source) {
|
2024-08-02 19:45:07 -05:00
|
|
|
std::queue<sf::Vector2f> search;
|
|
|
|
search.push(source);
|
|
|
|
while (!search.empty()) {
|
|
|
|
sf::Vector2f currentLocation = search.front();
|
|
|
|
search.pop();
|
2024-08-03 20:42:34 -05:00
|
|
|
if (g_pEngine->gameBoard.at(currentLocation.y).at(currentLocation.x).m_bVisited)
|
|
|
|
continue;
|
2024-08-02 19:45:07 -05:00
|
|
|
botPathUnsanitized.push(currentLocation);
|
|
|
|
std::array<sf::Vector2f, 4> localLocations;
|
|
|
|
localLocations.fill(currentLocation);
|
|
|
|
localLocations[0].y += 1;
|
|
|
|
localLocations[1].x += 1;
|
|
|
|
localLocations[2].y -= 1;
|
|
|
|
localLocations[3].x -= 1;
|
2024-08-03 01:26:10 -05:00
|
|
|
for (sf::Vector2f nearby : localLocations) {
|
2024-08-02 19:45:07 -05:00
|
|
|
try {
|
2024-08-03 20:42:34 -05:00
|
|
|
GameSpace* space = &g_pEngine->gameBoard.at(nearby.y).at(nearby.x);
|
|
|
|
if (space->m_bFood) {
|
2024-08-03 01:26:10 -05:00
|
|
|
botPathUnsanitized.push(nearby);
|
2024-08-03 20:42:34 -05:00
|
|
|
return;
|
2023-10-13 19:07:08 -05:00
|
|
|
}
|
2024-08-03 21:18:57 -05:00
|
|
|
if (nearby.x < 1 || nearby.y < 1)
|
|
|
|
continue;
|
2024-08-03 20:42:34 -05:00
|
|
|
if (space->m_bVisited)
|
2024-08-03 01:26:10 -05:00
|
|
|
continue;
|
2024-08-03 20:42:34 -05:00
|
|
|
if (space->m_bSnake)
|
2024-08-03 01:26:10 -05:00
|
|
|
continue;
|
|
|
|
search.push(nearby);
|
2024-08-02 19:45:07 -05:00
|
|
|
} catch (const std::out_of_range& error) {
|
|
|
|
continue; // Out of bounds
|
2023-10-13 19:07:08 -05:00
|
|
|
}
|
2024-08-02 19:45:07 -05:00
|
|
|
}
|
2024-08-03 01:26:10 -05:00
|
|
|
g_pEngine->gameBoard.at(currentLocation.y).at(currentLocation.x).m_bVisited = true;
|
2023-10-13 19:22:11 -05:00
|
|
|
}
|
2024-08-03 20:42:34 -05:00
|
|
|
pathFailed = true;
|
2024-08-02 19:45:07 -05:00
|
|
|
}
|
2023-10-13 19:22:11 -05:00
|
|
|
|
2024-08-03 20:42:34 -05:00
|
|
|
void AISnake::DFS(const sf::Vector2f& source) {
|
2024-08-02 19:45:07 -05:00
|
|
|
std::stack<sf::Vector2f> search;
|
|
|
|
search.push(source);
|
|
|
|
while (!search.empty()) {
|
|
|
|
sf::Vector2f currentLocation = search.top();
|
|
|
|
search.pop();
|
2024-08-03 20:42:34 -05:00
|
|
|
if (g_pEngine->gameBoard.at(currentLocation.y).at(currentLocation.x).m_bVisited)
|
|
|
|
continue;
|
2024-08-02 19:45:07 -05:00
|
|
|
botPathUnsanitized.push(currentLocation);
|
|
|
|
std::array<sf::Vector2f, 4> localLocations;
|
|
|
|
localLocations.fill(currentLocation);
|
2024-08-03 01:26:10 -05:00
|
|
|
localLocations.at(0).y += 1;
|
|
|
|
localLocations.at(1).x += 1;
|
|
|
|
localLocations.at(2).y -= 1;
|
|
|
|
localLocations.at(3).x -= 1;
|
|
|
|
for (sf::Vector2f nearby : localLocations) {
|
2024-08-02 19:45:07 -05:00
|
|
|
try {
|
2024-08-03 20:42:34 -05:00
|
|
|
GameSpace* space = &g_pEngine->gameBoard.at(nearby.y).at(nearby.x);
|
|
|
|
if (space->m_bFood) {
|
2024-08-03 01:26:10 -05:00
|
|
|
botPathUnsanitized.push(nearby);
|
2024-08-03 20:42:34 -05:00
|
|
|
return;
|
2023-10-13 19:22:11 -05:00
|
|
|
}
|
2024-08-03 21:18:57 -05:00
|
|
|
if (nearby.x < 1 || nearby.x > g_pEngine->GetGameBoundaries().x - 2)
|
|
|
|
continue;
|
2024-08-03 20:42:34 -05:00
|
|
|
if (space->m_bVisited)
|
2024-08-03 01:26:10 -05:00
|
|
|
continue;
|
2024-08-03 20:42:34 -05:00
|
|
|
if (space->m_bSnake)
|
2024-08-03 01:26:10 -05:00
|
|
|
continue;
|
|
|
|
search.push(nearby);
|
2024-08-02 19:45:07 -05:00
|
|
|
} catch (const std::out_of_range& error) {
|
|
|
|
continue; // Out of bounds
|
2023-10-13 19:22:11 -05:00
|
|
|
}
|
2024-08-02 19:45:07 -05:00
|
|
|
}
|
2024-08-03 01:26:10 -05:00
|
|
|
g_pEngine->gameBoard.at(currentLocation.y).at(currentLocation.x).m_bVisited = true;
|
|
|
|
}
|
2024-08-03 20:42:34 -05:00
|
|
|
pathFailed = true;
|
2024-08-03 01:26:10 -05:00
|
|
|
}
|
2023-10-23 21:33:47 -05:00
|
|
|
|
2024-08-03 01:26:10 -05:00
|
|
|
sf::Vector2f AISnake::GetAnyOpenPath(const sf::Vector2f& source) {
|
|
|
|
sf::Vector2f bail;
|
2024-08-03 20:42:34 -05:00
|
|
|
std::array<sf::Vector2f, 4> paths;
|
|
|
|
paths.fill(source);
|
2024-08-03 01:26:10 -05:00
|
|
|
paths[0].x -= 1;
|
|
|
|
paths[1].x += 1;
|
|
|
|
paths[2].y -= 1;
|
|
|
|
paths[3].y += 1;
|
|
|
|
|
|
|
|
for (auto path : paths) {
|
|
|
|
try {
|
2024-08-03 21:18:57 -05:00
|
|
|
bail = path;
|
|
|
|
if (g_pEngine->gameBoard.at(path.y).at(path.x).m_bSnake)
|
2024-08-03 01:26:10 -05:00
|
|
|
continue;
|
|
|
|
return path;
|
|
|
|
} catch (const std::out_of_range& error) {
|
|
|
|
continue; // Out of bounds
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return bail; // Snake is trapped, give up and die
|
|
|
|
}
|
|
|
|
|
|
|
|
void AISnake::UnvisitBoard(void) {
|
2024-08-03 20:42:34 -05:00
|
|
|
for (std::vector<GameSpace>& i : g_pEngine->gameBoard)
|
|
|
|
for (GameSpace& j : i)
|
2024-08-03 01:26:10 -05:00
|
|
|
j.m_bVisited = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AISnake::UpdateAverage(const int size) {
|
|
|
|
totalLength += size;
|
|
|
|
amountPlayed += 1;
|
|
|
|
average = (double)totalLength / amountPlayed;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AISnake::TrimPath(void) {
|
2024-08-03 20:42:34 -05:00
|
|
|
bool reachedSnake = false;
|
|
|
|
path.push(botPathUnsanitized.top()); // Push food location
|
2024-08-03 01:26:10 -05:00
|
|
|
while (!botPathUnsanitized.empty()) {
|
2024-08-03 20:42:34 -05:00
|
|
|
if (!reachedSnake) {
|
|
|
|
sf::Vector2f location = botPathUnsanitized.top();
|
|
|
|
if (g_pEngine->gameBoard[location.y][location.x].m_bSnake)
|
|
|
|
reachedSnake = true;
|
|
|
|
sf::Vector2f deltaVector = location - path.top();
|
|
|
|
int delta = abs(deltaVector.x) + abs(deltaVector.y);
|
|
|
|
if (delta == 1)
|
|
|
|
path.push(location);
|
2023-10-13 19:07:08 -05:00
|
|
|
}
|
2024-08-03 01:26:10 -05:00
|
|
|
botPathUnsanitized.pop();
|
2023-10-13 19:07:08 -05:00
|
|
|
}
|
2024-08-03 20:42:34 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void AISnake::EmptyPath(void) {
|
|
|
|
while (!botPathUnsanitized.empty())
|
|
|
|
botPathUnsanitized.pop();
|
2023-08-19 12:56:40 -05:00
|
|
|
}
|