diff --git a/project_specs/team-1-specs.md b/project_specs/team-1-specs.md index 29e5d500..90c1bf52 100644 --- a/project_specs/team-1-specs.md +++ b/project_specs/team-1-specs.md @@ -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 diff --git a/source/Agents/AStarAgent.h b/source/Agents/AStarAgent.h new file mode 100644 index 00000000..b11aa671 --- /dev/null +++ b/source/Agents/AStarAgent.h @@ -0,0 +1,83 @@ +/** + * @file AStarAgent.h + * @author Matt Kight + */ +#pragma once + +#include +#include "../core/AgentBase.hpp" +#include "AgentLibary.h" +#include +namespace cse491 +{ + namespace walle + { + /** + * Class that describes a AStarAgent class + */ + class AStarAgent : public cse491::AgentBase + { + private: + std::vector 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 + } + }; + }; +} diff --git a/source/Agents/AgentLibary.h b/source/Agents/AgentLibary.h new file mode 100644 index 00000000..7e7046cf --- /dev/null +++ b/source/Agents/AgentLibary.h @@ -0,0 +1,51 @@ +// +// Created by Matthew Kight on 9/24/23. +// +#pragma once + +#include "../core/AgentBase.hpp" +#include "../core/WorldBase.hpp" +#include +#include +#include +#include +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 parent; + + Node(cse491::GridPosition position, double g, double h, std::shared_ptr 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 a, const std::shared_ptr b) const + { + return a->f() > b->f(); + } + }; + + } +} diff --git a/source/Agents/AgentMonika.hpp b/source/Agents/AgentMonika.hpp deleted file mode 100644 index 8ead81e7..00000000 --- a/source/Agents/AgentMonika.hpp +++ /dev/null @@ -1,32 +0,0 @@ -/** - * This file is part of the Fall 2023, CSE 491 course project - Group 1 fork - monika_k branch. - * @brief An Agent that will walk. - * @note Status: PROPOSAL - **/ - -#pragma once - -#include "../core/AgentBase.hpp" - -namespace cse491 { - class BasicAgent : public AgentBase { - protected: - std::unordered_map a_star_map; // Map of the tiles on the board with their corresponding values for a* search - bool isMoving = false; // Is the agent moving? - int intelligenceLevel = 0; // Depending on the type of agent, mobility varies - // Measure of intelligence; if agent is an enemy, are they "smart enough" to be able to use a*, or would they resort to - // another search method? If the agent is the user/player's agent they would have maximum intelligence. - int healthPoints = 10; - int mobility = 20; // Number of tiles the agent can move in a given turn? - int level = 1; // Level of character; scales the rest of their attributes. - - public: - BasicAgent(size_t id, const std::string & name) : AgentBase(id, name) { } - ~BasicAgent() = default; - - // a_star function here - - // could also add getters/setters here for different attributes - - }; -} \ No newline at end of file diff --git a/source/Worlds/MazeWorld.hpp b/source/Worlds/MazeWorld.hpp index 23fab2e9..26c7c68a 100644 --- a/source/Worlds/MazeWorld.hpp +++ b/source/Worlds/MazeWorld.hpp @@ -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 diff --git a/source/core/WorldBase.hpp b/source/core/WorldBase.hpp index 6a57d289..f6ac51b2 100644 --- a/source/core/WorldBase.hpp +++ b/source/core/WorldBase.hpp @@ -10,11 +10,13 @@ #include #include #include +#include #include "AgentBase.hpp" #include "Data.hpp" #include "Entity.hpp" #include "WorldGrid.hpp" +#include "../Agents/AgentLibary.h" namespace cse491 { @@ -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 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 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> cost(rows, std::vector(cols, INT_MAX)); + + // Create an open list as a priority queue + std::priority_queue, std::vector>, walle::CompareNodes> openList; + + // Create the start and end nodes + auto startNode = std::make_shared(start, 0, 0, nullptr); + auto endNode = std::make_shared(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(newPos, newG, newH, current); + openList.push(neighbor); + cost[newPos.GetX()][newPos.GetY()] = newG + newH; + } + } + } + } + + return path; + } }; } // End of namespace cse491