Skip to content

Commit

Permalink
Merge pull request #1 from mdkdoc15/matt_k
Browse files Browse the repository at this point in the history
Added A* agent
  • Loading branch information
mdkdoc15 authored Oct 2, 2023
2 parents 3383e42 + 767a204 commit af4c9b1
Show file tree
Hide file tree
Showing 6 changed files with 225 additions and 33 deletions.
5 changes: 5 additions & 0 deletions project_specs/team-1-specs.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,8 @@ modules.
ii. Params and returns will be added after discussion with other groups to
see if this is wanted


#### Challenge
1. Creating an Autonomous agent that is capable of moving around an interacting
with the world
2. Working with other agent teams to implement functionality they need for their agents
83 changes: 83 additions & 0 deletions source/Agents/AStarAgent.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* @file AStarAgent.h
* @author Matt Kight
*/
#pragma once

#include <vector>
#include "../core/AgentBase.hpp"
#include "AgentLibary.h"
#include <iostream>
namespace cse491
{
namespace walle
{
/**
* Class that describes a AStarAgent class
*/
class AStarAgent : public cse491::AgentBase
{
private:
std::vector<GridPosition> path; // Path this agent is taking
cse491::GridPosition goal_position; // Where the agent wants to end up
int recalculate_after_x_turns = 100; // How often agent recalculates moves
int current_move_num = 0; // What move # we are currently on
WorldBase *world = nullptr;

public:
AStarAgent(size_t id, const std::string &name) : AgentBase(id, name)
{
}
~AStarAgent() = default;

/**
* @brief Set the word object
*
* @param world world this agent is a part of
*/
void SetWorld(WorldBase *world) { this->world = world; }

/// @brief This agent needs a specific set of actions to function.
/// @return Success.
bool Initialize() override
{
return HasAction("up") && HasAction("down") && HasAction("left") && HasAction("right");
}

void SetGoalPosition(const GridPosition gp)
{
goal_position = gp;
}
/// Choose the action to take a step in the appropriate direction.
size_t SelectAction(const WorldGrid & /*grid*/,
const type_options_t & /* type_options*/,
const item_set_t & /* item_set*/,
const agent_set_t & /* agent_set*/) override
{
// TODO REMOVE THIS
// We are taking an action so another turn has passed
++(this->current_move_num);
// If the last step failed, or we need a new path the then regenerate the path
if (action_result == 0 || this->path.empty() || this->current_move_num > this->recalculate_after_x_turns)
{
this->path = this->world->shortest_path(GetPosition(), this->goal_position);
}
// Return whatever action gets us closer to our goal
if (!this->path.empty())
{
auto pos = path.back();
path.pop_back();
if (pos.GetX() == position.GetX() && pos.GetY() == position.GetY() - 1)
return action_map["up"];
if (pos.GetX() == position.GetX() && pos.GetY() == position.GetY() + 1)
return action_map["down"];
if (pos.GetX() == position.GetX() - 1 && pos.GetY() == position.GetY())
return action_map["left"];
if (pos.GetX() == position.GetX() + 1 && pos.GetY() == position.GetY())
return action_map["right"];
}
return 0; // If no path then do not do anything
}
};
};
}
51 changes: 51 additions & 0 deletions source/Agents/AgentLibary.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//
// Created by Matthew Kight on 9/24/23.
//
#pragma once

#include "../core/AgentBase.hpp"
#include "../core/WorldBase.hpp"
#include <vector>
#include <map>
#include <queue>
#include <tuple>
namespace cse491
{
namespace walle
{

/**
* @brief Node class to make A* search easier
*
*/
struct Node
{
cse491::GridPosition position; // Where node is located
int g; // Cost from start to current node
int h; // Heuristic (estimated cost from current node to goal)
std::shared_ptr<Node> parent;

Node(cse491::GridPosition position, double g, double h, std::shared_ptr<Node> parent)
: position(position), g(g), h(h), parent(parent) {}

// Calculate the total cost (f) of the node
int f() const
{
return g + h;
}
};

/**
* @brief Custom comparison function for priority queue
*
*/
struct CompareNodes
{
bool operator()(const std::shared_ptr<walle::Node> a, const std::shared_ptr<walle::Node> b) const
{
return a->f() > b->f();
}
};

}
}
32 changes: 0 additions & 32 deletions source/Agents/AgentMonika.hpp

This file was deleted.

3 changes: 2 additions & 1 deletion source/Worlds/MazeWorld.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,11 @@ namespace cse491 {

// Set the agent to its new postion.
agent.SetPosition(new_position);

return true;
}

/// Can walk on all tiles except for walls
bool IsWalkable (cse491::GridPosition pos) override {return main_grid.At(pos) != wall_id;}
};

} // End of namespace cse491
84 changes: 84 additions & 0 deletions source/core/WorldBase.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@
#include <memory>
#include <string>
#include <vector>
#include <queue>

#include "AgentBase.hpp"
#include "Data.hpp"
#include "Entity.hpp"
#include "WorldGrid.hpp"
#include "../Agents/AgentLibary.h"

namespace cse491 {

Expand Down Expand Up @@ -160,6 +162,88 @@ namespace cse491 {
if (id >= type_options.size()) return type_options[0].symbol;
return type_options[id].symbol;
}

/// @brief Determine if this tile is able to be walked on, defaults to every
/// tile is walkable
/// @author @mdkdoc15
/// @param pos The grid position we are checking
/// @return If an agent should be allowed on this square
virtual bool IsWalkable(GridPosition /*pos*/) { return true; }

/// @brief Uses A* to return a list of grid positions
/// @author @mdkdoc15
/// @param start Starting position for search
/// @param end Ending position for the search
/// @return vector of A* path from start to end, empty vector if no path
/// exist
std::vector<GridPosition> shortest_path(GridPosition start, GridPosition end)
{
// TODO remove the use of new and this

// Generated with the help of chat.openai.com
const int rows = this->main_grid.GetWidth();
const int cols = this->main_grid.GetHeight();
std::vector<GridPosition> path;
// If the start or end is not valid then return empty list
if (!(this->main_grid.IsValid(start) && this->main_grid.IsValid(end)))
return path;

// Define possible movements (up, down, left, right)
const int dx[] = {-1, 1, 0, 0};
const int dy[] = {0, 0, -1, 1};

// Create a 2D vector to store the cost to reach each cell
std::vector<std::vector<int>> cost(rows, std::vector<int>(cols, INT_MAX));

// Create an open list as a priority queue
std::priority_queue<std::shared_ptr<walle::Node>, std::vector<std::shared_ptr<walle::Node>>, walle::CompareNodes> openList;

// Create the start and end nodes
auto startNode = std::make_shared<walle::Node>(start, 0, 0, nullptr);
auto endNode = std::make_shared<walle::Node>(end, 0, 0, nullptr);

openList.push(startNode);
cost[start.GetX()][start.GetY()] = 0;

while (!openList.empty())
{
auto current = openList.top();
openList.pop();

if (current->position == endNode->position)
{

// Reached the goal, reconstruct the path
while (current != nullptr)
{
path.push_back(current->position);
current = current->parent;
}
break;
}

// Explore the neighbors
for (int i = 0; i < 4; ++i)
{
GridPosition newPos(current->position.GetX() + dx[i], current->position.GetY() + dy[i]);
// Check if the neighbor is within bounds and is a valid move
if (this->main_grid.IsValid(newPos) && this->IsWalkable(newPos))
{
int newG = current->g + 1; // Assuming a cost of 1 to move to a neighbor
int newH = std::abs(newPos.GetX() - endNode->position.GetX()) + std::abs(newPos.GetY() - endNode->position.GetY()); // Manhattan distance

if (newG + newH < cost[newPos.GetX()][newPos.GetY()])
{
auto neighbor = std::make_shared<walle::Node>(newPos, newG, newH, current);
openList.push(neighbor);
cost[newPos.GetX()][newPos.GetY()] = newG + newH;
}
}
}
}

return path;
}
};

} // End of namespace cse491

0 comments on commit af4c9b1

Please sign in to comment.