Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change dot cluster behavior #36

Merged
merged 21 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0cdc8fa
Generalize mazeAdapter to be reused in clustering algorithm
ll-nick Jul 24, 2024
1fb7348
Move positionConsideringTunnel into maze class
ll-nick Jul 25, 2024
64e7719
Add ClusterFinder class
ll-nick Jul 25, 2024
ea75033
Add and compute center for each cluster
ll-nick Jul 25, 2024
c0fd77d
Find clusters during init and use getter function
ll-nick Jul 25, 2024
bf3ced5
Use Positions alias
ll-nick Jul 25, 2024
529685b
rename getter functions to clarify that we are talking about dot clus…
ll-nick Jul 25, 2024
3e89d74
Add clusterFinder to environment model
ll-nick Jul 25, 2024
529558d
Power pellets can be part of a cluster
ll-nick Jul 25, 2024
4876ea5
Return the full clusters vector instead of just the cluster centers
ll-nick Jul 25, 2024
46536c2
Add convenience function to determine whether given position is withi…
ll-nick Jul 25, 2024
ce90279
Add another astar interface to the env model
orzechow Oct 4, 2024
52966c2
Add changeDotClusterBehavior
orzechow Oct 4, 2024
f199a48
Bug fix: Fix out of range error in run away and chase ghost behaviors
ll-nick Jul 25, 2024
b91d969
Add changeDotClusterBehavior to pacmanAgent
orzechow Oct 4, 2024
704d91b
Add documentation and tidy up
ll-nick Jul 25, 2024
063ae1e
Use std::optional for A* path return types
orzechow Oct 4, 2024
88d2e45
Apply suggestions from code review
ll-nick Oct 7, 2024
5a6118b
Move behavior implementation out of header
ll-nick Oct 7, 2024
0da94db
Remove non-obvious abbreviation from variable name
ll-nick Oct 7, 2024
c783b60
Generalize unit test to not assume a given order of clusters
ll-nick Oct 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions demo/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ find_package(Yaml-cpp REQUIRED)
add_library(${PROJECT_NAME} STATIC
src/astar.cpp
src/avoid_ghost_behavior.cpp
src/change_dot_cluster_behavior.cpp
src/chase_ghost_behavior.cpp
src/cluster.cpp
src/entities.cpp
src/environment_model.cpp
src/random_walk_behavior.cpp
Expand Down
59 changes: 59 additions & 0 deletions demo/include/demo/change_dot_cluster_behavior.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#pragma once

#include <arbitration_graphs/behavior.hpp>

#include "environment_model.hpp"
#include "types.hpp"
#include "utils/cluster.hpp"

namespace demo {

/**
* @brief The ChangeDotClusterBehavior makes pacman move towards the closest dot cluster
* that he is not currently inside of.
*/
class ChangeDotClusterBehavior : public arbitration_graphs::Behavior<Command> {
public:
using Ptr = std::shared_ptr<ChangeDotClusterBehavior>;
using ConstPtr = std::shared_ptr<const ChangeDotClusterBehavior>;

using Cluster = utils::Cluster;
using Clusters = utils::DotClusterFinder::Clusters;

explicit ChangeDotClusterBehavior(EnvironmentModel::Ptr environmentModel,
const std::string& name = "ChangeDotClusterBehavior")
: Behavior(name), environmentModel_{std::move(environmentModel)} {
}

Command getCommand(const Time& /*time*/) override {
std::optional<Path> pathToTargetClusterCenter = environmentModel_->pathTo(targetCluster_->center);

if (!pathToTargetClusterCenter) {
throw std::runtime_error("Failed to compute path to target cluster. Can not provide a sensible command.");
}

return Command{pathToTargetClusterCenter.value()};
}
ll-nick marked this conversation as resolved.
Show resolved Hide resolved

bool checkInvocationCondition(const Time& /*time*/) const override;

bool checkCommitmentCondition(const Time& /*time*/) const override {
Position pacmanPosition = environmentModel_->pacmanPosition();
return !targetCluster_->isInCluster(pacmanPosition);
}

void gainControl(const Time& /*time*/) override {
setTargetCluster();
}
void loseControl(const Time& /*time*/) override {
targetCluster_.reset();
}

private:
void setTargetCluster();

std::optional<Cluster> targetCluster_;
EnvironmentModel::Ptr environmentModel_;
};

} // namespace demo
25 changes: 24 additions & 1 deletion demo/include/demo/environment_model.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include "types.hpp"
#include "utils/astar.hpp"
#include "utils/cluster.hpp"
#include "utils/entities.hpp"
#include "utils/maze.hpp"

Expand All @@ -19,6 +20,8 @@ namespace demo {
* the world. */
class EnvironmentModel {
public:
using Cluster = utils::Cluster;
using Clusters = utils::DotClusterFinder::Clusters;
using Entities = utils::Entities;
using Maze = utils::Maze;
using Ghost = utils::Ghost;
Expand All @@ -32,7 +35,8 @@ class EnvironmentModel {
int distance;
};

explicit EnvironmentModel(const Game& game) : maze_(std::make_shared<Maze>(game.maze)), astar_(maze_) {
explicit EnvironmentModel(const Game& game)
: maze_{std::make_shared<Maze>(game.maze)}, astar_{maze_}, clusterFinder_{maze_} {
updateEntities(game.reg);
};

Expand All @@ -42,6 +46,7 @@ class EnvironmentModel {
void update(const Game& game) {
maze_ = std::make_shared<Maze>(game.maze);
astar_.updateMaze(maze_);
clusterFinder_ = utils::DotClusterFinder{maze_};
updateEntities(game.reg);
}

Expand All @@ -68,6 +73,16 @@ class EnvironmentModel {
*/
std::optional<GhostWithDistance> closestScaredGhost(const Time& time) const;

/**
* @brief Returns a vector of all dot clusters.
*
* A dot cluster is a set of dots (including power pellets) that can be connected by a path passing through neither
* walls nor empty space.
*/
Clusters dotCluster() const {
return clusterFinder_.clusters();
}

/**
* @brief Calculates the Manhattan distance between two positions using A* considering the maze geometry.
*
Expand All @@ -77,13 +92,20 @@ class EnvironmentModel {
return astar_.mazeDistance(start, goal);
}

std::optional<Path> pathTo(const Position& goal) {
return astar_.shortestPath(pacmanPosition(), goal);
}

std::optional<Path> pathToClosestDot(const Position& position) const {
return astar_.pathToClosestDot(position);
}

bool isWall(const Position& position) const {
return maze_->isWall(position);
}
Position positionConsideringTunnel(const Position& position) const {
return maze_->positionConsideringTunnel(position);
}

protected:
void updateEntities(const entt::Registry& registry);
Expand All @@ -94,6 +116,7 @@ class EnvironmentModel {
Maze::ConstPtr maze_;

utils::AStar astar_;
utils::DotClusterFinder clusterFinder_;
mutable util_caching::Cache<Time, GhostWithDistance> closestGhostCache_;
mutable util_caching::Cache<Time, GhostWithDistance> closestScaredGhostCache_;
};
Expand Down
4 changes: 4 additions & 0 deletions demo/include/demo/pacman_agent.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <arbitration_graphs/priority_arbitrator.hpp>

#include "avoid_ghost_behavior.hpp"
#include "change_dot_cluster_behavior.hpp"
#include "chase_ghost_behavior.hpp"
#include "eat_closest_dot_behavior.hpp"
#include "environment_model.hpp"
Expand Down Expand Up @@ -34,6 +35,7 @@ class PacmanAgent {
environmentModel_ = std::make_shared<EnvironmentModel>(game);

avoidGhostBehavior_ = std::make_shared<AvoidGhostBehavior>(environmentModel_, parameters_.avoidGhostBehavior);
changeDotClusterBehavior_ = std::make_shared<ChangeDotClusterBehavior>(environmentModel_);
chaseGhostBehavior_ = std::make_shared<ChaseGhostBehavior>(environmentModel_, parameters_.chaseGhostBehavior);
eatClosestDotBehavior_ = std::make_shared<EatClosestDotBehavior>(environmentModel_);
randomWalkBehavior_ = std::make_shared<RandomWalkBehavior>(parameters_.randomWalkBehavior);
Expand All @@ -42,6 +44,7 @@ class PacmanAgent {
rootArbitrator_ = std::make_shared<PriorityArbitrator>();
rootArbitrator_->addOption(chaseGhostBehavior_, PriorityArbitrator::Option::Flags::INTERRUPTABLE);
rootArbitrator_->addOption(avoidGhostBehavior_, PriorityArbitrator::Option::Flags::INTERRUPTABLE);
rootArbitrator_->addOption(changeDotClusterBehavior_, PriorityArbitrator::Option::Flags::INTERRUPTABLE);
rootArbitrator_->addOption(eatClosestDotBehavior_, PriorityArbitrator::Option::Flags::INTERRUPTABLE);
rootArbitrator_->addOption(randomWalkBehavior_, PriorityArbitrator::Option::Flags::INTERRUPTABLE);
rootArbitrator_->addOption(stayInPlaceBehavior_, PriorityArbitrator::Option::Flags::INTERRUPTABLE);
Expand Down Expand Up @@ -69,6 +72,7 @@ class PacmanAgent {
Parameters parameters_;

AvoidGhostBehavior::Ptr avoidGhostBehavior_;
ChangeDotClusterBehavior::Ptr changeDotClusterBehavior_;
ChaseGhostBehavior::Ptr chaseGhostBehavior_;
EatClosestDotBehavior::Ptr eatClosestDotBehavior_;
RandomWalkBehavior::Ptr randomWalkBehavior_;
Expand Down
53 changes: 13 additions & 40 deletions demo/include/utils/astar.hpp
Original file line number Diff line number Diff line change
@@ -1,70 +1,49 @@
#pragma once

#include <limits>
#include <memory>
#include <optional>
#include <queue>
#include <utility>
#include <vector>

#include <comp/position.hpp>
#include <util_caching/cache.hpp>
#include <pacman/util/grid.hpp>

#include "demo/types.hpp"
#include "utils/maze.hpp"

namespace utils {

using Move = demo::Move;
using Path = demo::Path;
using Position = demo::Position;
using TileType = demo::TileType;

struct Cell {
struct AStarCell : public BaseCell {
using Move = demo::Move;

struct CompareCells {
bool operator()(const Cell& left, const Cell& right) {
bool operator()(const AStarCell& left, const AStarCell& right) {
return (left.distanceFromStart + left.heuristic) > (right.distanceFromStart + right.heuristic);
}
};

Cell(const Position& position, const TileType& type) : position(position), type(type) {};
AStarCell(const Position& position, const TileType& type) : BaseCell(position, type) {};

Position position;
bool visited{false};
int distanceFromStart{std::numeric_limits<int>::max()};
double heuristic{std::numeric_limits<int>::max()};
bool visited{false};
TileType type;
std::optional<Move> moveFromPredecessor;

double manhattanDistance(const Position& other) const {
return std::abs(position.x - other.x) + std::abs(position.y - other.y);
}
};

class MazeAdapter {
public:
using MazeStateConstPtr = std::shared_ptr<const MazeState>;

explicit MazeAdapter(Maze::ConstPtr maze) : maze_(std::move(maze)), cells_({maze_->width(), maze_->height()}) {};

Cell& cell(const Position& position) const {
if (!cells_[{position.x, position.y}]) {
cells_[{position.x, position.y}] = Cell(position, maze_->at(position));
}

return cells_[{position.x, position.y}].value();
}

private:
Maze::ConstPtr maze_;
mutable Grid<std::optional<Cell>> cells_;
};

class AStar {
public:
using HeuristicFunction = std::function<int(const Cell&)>;
using Set = std::priority_queue<Cell, std::vector<Cell>, Cell::CompareCells>;
using AStarMazeAdapter = MazeAdapter<AStarCell>;
using Cell = AStarCell;
using HeuristicFunction = std::function<int(const AStarCell&)>;
using Set = std::priority_queue<AStarCell, std::vector<AStarCell>, AStarCell::CompareCells>;

constexpr static int NoPathFound = std::numeric_limits<int>::max();

Expand All @@ -81,7 +60,7 @@ class AStar {
/**
* @brief Returns the shortest path from the start to the goal position considering the maze geometry.
*/
Path shortestPath(const Position& start, const Position& goal) const;
std::optional<Path> shortestPath(const Position& start, const Position& goal) const;

/**
* @brief Returns the path from a given start position to the closest dot.
Expand All @@ -95,15 +74,15 @@ class AStar {
}

private:
void expandCell(Set& openSet, MazeAdapter& mazeAdapter, const HeuristicFunction& heuristic) const;
void expandCell(Set& openSet, AStarMazeAdapter& mazeAdapter, const HeuristicFunction& heuristic) const;

/**
* @brief Create a path by traversing predecessor relationships up to a goal position.
*
* Will expand the path backwards until no more predecessor relationship is available. If the cell at the goal
* position does not have a predecessor, the path will be empty.
*/
Path pathTo(const MazeAdapter& maze, const Position& goal) const;
Path extractPathTo(const AStarMazeAdapter& maze, const Position& goal) const;

/**
* @brief Approximates the distance of a given cell to a goal while considering a shortcut via the tunnel.
Expand All @@ -116,12 +95,6 @@ class AStar {
return std::min(cell.manhattanDistance(goal), maze_->width() - cell.manhattanDistance(goal));
}

/**
* @brief If we are about to step of the maze and the opposite end is passable as well,
* we assume they are connected by a tunnel and adjust the position accordingly.
*/
Position positionConsideringTunnel(const Position& position) const;

Maze::ConstPtr maze_;
mutable util_caching::Cache<std::pair<Position, Position>, int> distanceCache_;
};
Expand Down
65 changes: 65 additions & 0 deletions demo/include/utils/cluster.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#pragma once

#include <vector>

#include "demo/types.hpp"
#include "utils/maze.hpp"

namespace utils {

using Path = demo::Path;
using Position = demo::Position;
using Positions = demo::Positions;
using TileType = demo::TileType;

struct ClusterCell : public BaseCell {
ClusterCell(const Position& position, const TileType& type) : BaseCell(position, type) {};

bool visited{false};
};

/**
* @brief A cluster is defined by a set of points that can be connected by a path which passes through neither walls nor
* empty space.
*/
struct Cluster {
explicit Cluster(const int& clusterId, const std::vector<Position>& points)
ll-nick marked this conversation as resolved.
Show resolved Hide resolved
: id(clusterId), dots(points), center{findClusterCenter()} {
}
bool isInCluster(const Position& target) const {
return std::any_of(dots.begin(), dots.end(), [target](Position dot) { return dot == target; });
}

int id;
Positions dots;

Position center; ///< The dot closest to the average position of all the dots of this cluster

private:
Position findClusterCenter() const;
};

/**
* @brief Search and store all clusters of dots (including power pellets) given the maze state.
*/
class DotClusterFinder {
public:
using Cell = ClusterCell;
using Clusters = std::vector<Cluster>;
using ClusterMazeAdapter = MazeAdapter<ClusterCell>;

explicit DotClusterFinder(Maze::ConstPtr maze) : maze_(std::move(maze)), clusters_{findDotClusters()} {
}
Clusters clusters() const {
return clusters_;
}

private:
Positions expandDot(const Cell& start, const ClusterMazeAdapter& mazeAdapter) const;
Clusters findDotClusters() const;

Maze::ConstPtr maze_;
Clusters clusters_;
};

} // namespace utils
Loading