Skip to content

Commit

Permalink
Add solutions to all tasks
Browse files Browse the repository at this point in the history
  • Loading branch information
ll-nick committed Oct 24, 2024
1 parent 2e60a9e commit 675a92d
Show file tree
Hide file tree
Showing 6 changed files with 347 additions and 0 deletions.
56 changes: 56 additions & 0 deletions docs/tasks/1_implement_behavior_component.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,62 @@ Finish the implementation of the `checkInvocationCondition` and `getCommand` fun
- Implement the missing piece. Take a look at the implementation of `AvoidGhostBehavior::getCommand` if you need inspiration.
- Compile and run the unit tests for the `ChaseGhost` behavior component to verify that your implementation is correct.

## Solution

<details>
<summary>Click here to expand the solution</summary>

Fix the invocation condition in `src/chase_ghost_behavior.cpp`:
```cpp
bool ChaseGhostBehavior::checkInvocationCondition(const Time& time) const {
return environmentModel_->closestScaredGhost(time).has_value() &&
environmentModel_->closestScaredGhost(time)->ghost.scaredCountdown > parameters_.minScaredTicksLeft &&
environmentModel_->closestScaredGhost(time)->distance < parameters_.invocationMinDistance; // Only applicable if a ghost is close by
}
```
Add the missing pice of the getCommand function in `src/chase_ghost_behavior.cpp`:
```cpp
Command ChaseGhostBehavior::getCommand(const Time& time) {
auto pacmanPosition = environmentModel_->pacmanPosition();
auto closestScaredGhost = environmentModel_->closestScaredGhost(time);
if (!closestScaredGhost) {
throw std::runtime_error("Can not compute command to chase ghost because there are no scared ghosts.");
}
auto ghostPosition = closestScaredGhost->ghost.position;
std::optional<Direction> direction;
// Add this part:
// Chose the direction moving pacman towards the closest scared ghost
double minDistance = std::numeric_limits<double>::max();
for (const auto& move : Move::possibleMoves()) {
auto nextPosition = environmentModel_->positionConsideringTunnel(pacmanPosition + move.deltaPosition);
if (environmentModel_->isWall(nextPosition)) {
continue;
}
// Chose the direction moving pacman towards the closest scared ghost (considering ghost movement)
auto nextDistance = environmentModel_->mazeDistance(nextPosition, ghostPosition);
if (nextDistance < minDistance) {
direction = move.direction;
minDistance = nextDistance;
}
}
if (!direction) {
throw std::runtime_error("Failed to compute direction to chase the closest ghost.");
}
return Command{direction.value()};
}
```
</details>


---
[Tutorial Home](../Tutorial.md)
Expand Down
35 changes: 35 additions & 0 deletions docs/tasks/2_extend_arbitration_graph.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,41 @@ Integrate the `ChaseGhost` behavior component into the arbitration graph defined
- Add a new option to the priority arbitrator.
- Run the game, take a look at the new arbitration graph and observe how PacMan behaves.

## Solution

<details>
<summary>Click here to expand the solution</summary>

Include the header of the `ChaseGhost` behavior component in `include/demo/pacman_agent.hpp`:
```cpp
#include "chase_ghost_behavior.hpp"
```

Add the `ChaseGhost` behavior component as a new member of the `PacmanAgent` class:
```cpp
private:
ChaseGhostBehavior::Ptr chaseGhostBehavior_;
```

In the constructor of the `PacmanAgent` class, initialize the `ChaseGhost` behavior component and add it to the priority arbitrator:
```cpp
explicit PacmanAgent(const entt::Game& game)
: parameters_{}, environmentModel_{std::make_shared<EnvironmentModel>(game)} {

avoidGhostBehavior_ = std::make_shared<AvoidGhostBehavior>(environmentModel_, parameters_.avoidGhostBehavior);
// Initialize the ChaseGhost behavior component
chaseGhostBehavior_ = std::make_shared<ChaseGhostBehavior>(environmentModel_, parameters_.chaseGhostBehavior);
moveRandomlyBehavior_ = std::make_shared<MoveRandomlyBehavior>(parameters_.moveRandomlyBehavior);

rootArbitrator_ = std::make_shared<PriorityArbitrator>("Pacman");
// Add the ChaseGhost behavior component to the priority arbitrator (before the AvoidGhost behavior component!)
rootArbitrator_->addOption(chaseGhostBehavior_, PriorityArbitrator::Option::Flags::INTERRUPTABLE);
rootArbitrator_->addOption(avoidGhostBehavior_, PriorityArbitrator::Option::Flags::INTERRUPTABLE);
rootArbitrator_->addOption(moveRandomlyBehavior_, PriorityArbitrator::Option::Flags::INTERRUPTABLE);
}
```
</details>
---
[← Previous task](1_implement_behavior_component.md)
Expand Down
37 changes: 37 additions & 0 deletions docs/tasks/3_add_more_behaviors.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,43 @@ Integrate the `EatClosestDot` behavior component into the arbitration graph defi
- Integrate the new component just like you did in the last task.
- Start the game and see how PacMan stop wandering around aimlessly and starts eating dots.

## Solution

<details>
<summary>Click here to expand the solution</summary>

Include the header of the `EatClosestDot` behavior component in `include/demo/pacman_agent.hpp`:
```cpp
#include "eat_closest_dot_behavior.hpp"
```

Add the `ChaseGhost` behavior component as a new member of the `PacmanAgent` class:
```cpp
private:
EatClosestDotBehavior::Ptr eatClosestDotBehavior_;
```

In the constructor of the `PacmanAgent` class, initialize the `ChaseGhost` behavior component and add it to the priority arbitrator:
```cpp
explicit PacmanAgent(const entt::Game& game)
: parameters_{}, environmentModel_{std::make_shared<EnvironmentModel>(game)} {

avoidGhostBehavior_ = std::make_shared<AvoidGhostBehavior>(environmentModel_, parameters_.avoidGhostBehavior);
chaseGhostBehavior_ = std::make_shared<ChaseGhostBehavior>(environmentModel_, parameters_.chaseGhostBehavior);
// Initialize the EatClosestDot behavior component
eatClosestDotBehavior_ = std::make_shared<EatClosestDotBehavior>(environmentModel_);
moveRandomlyBehavior_ = std::make_shared<MoveRandomlyBehavior>(parameters_.moveRandomlyBehavior);

rootArbitrator_ = std::make_shared<PriorityArbitrator>("Pacman");
rootArbitrator_->addOption(chaseGhostBehavior_, PriorityArbitrator::Option::Flags::INTERRUPTABLE);
rootArbitrator_->addOption(avoidGhostBehavior_, PriorityArbitrator::Option::Flags::INTERRUPTABLE);
// Add the EatClosestDot behavior component to the priority arbitrator (after the ghost behavior components!)
rootArbitrator_->addOption(eatClosestDotBehavior_, PriorityArbitrator::Option::Flags::INTERRUPTABLE);
rootArbitrator_->addOption(moveRandomlyBehavior_, PriorityArbitrator::Option::Flags::INTERRUPTABLE);
}
```
</details>
---
Expand Down
56 changes: 56 additions & 0 deletions docs/tasks/4_nested_arbitrators.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,62 @@ Add the `EatClosestDot` and `ChangeDotCluster` behavior components to a random a
- Add the random arbitrator as an option to the root arbitrator.
- Run the game and observe how PacMan behaves.

## Solution

<details>
<summary>Click here to expand the solution</summary>

Include the header of the `ChangeDotCluster` behavior component and the random arbitrator in `include/demo/pacman_agent.hpp`:
```cpp
#include <arbitration_graphs/random_arbitrator.hpp>

#include "change_dot_cluster_behavior.hpp"
```

For easier to read code, add the following alias near the top of the class definition:
```cpp
using RandomArbitrator = arbitration_graphs::RandomArbitrator<Command, Command>;
```

Add the `ChangeDotCluster` behavior component and the `RandomArbitrator` as a new members of the `PacmanAgent` class:
```cpp
private:
ChangeDotClusterBehavior::Ptr changeDotClusterBehavior_;

RandomArbitrator::Ptr eatDotsArbitrator_;
```

In the constructor of the `PacmanAgent` class, initialize the `ChangeDotCluster` behavior component and the `RandomArbitrator`:
Add the `EatClosestDot` and `ChangeDotCluster` behavior components as options to the random arbitrator.
Finally, add the random arbitrator as an option to the root arbitrator:
```cpp
explicit PacmanAgent(const entt::Game& game)
: parameters_{}, environmentModel_{std::make_shared<EnvironmentModel>(game)} {

avoidGhostBehavior_ = std::make_shared<AvoidGhostBehavior>(environmentModel_, parameters_.avoidGhostBehavior);
// Initialize the ChangeDotCluster behavior component
changeDotClusterBehavior_ = std::make_shared<ChangeDotClusterBehavior>(environmentModel_);
chaseGhostBehavior_ = std::make_shared<ChaseGhostBehavior>(environmentModel_, parameters_.chaseGhostBehavior);
eatClosestDotBehavior_ = std::make_shared<EatClosestDotBehavior>(environmentModel_);
moveRandomlyBehavior_ = std::make_shared<MoveRandomlyBehavior>(parameters_.moveRandomlyBehavior);

// Initialize the random arbitrator and add the EatClosestDot and ChangeDotCluster behavior components as options
eatDotsArbitrator_ = std::make_shared<RandomArbitrator>("EatDots");
eatDotsArbitrator_->addOption( changeDotClusterBehavior_, RandomArbitrator::Option::Flags::INTERRUPTABLE);
eatDotsArbitrator_->addOption( eatClosestDotBehavior_, RandomArbitrator::Option::Flags::INTERRUPTABLE);

rootArbitrator_ = std::make_shared<PriorityArbitrator>("Pacman");
rootArbitrator_->addOption(chaseGhostBehavior_, PriorityArbitrator::Option::Flags::INTERRUPTABLE);
rootArbitrator_->addOption(avoidGhostBehavior_, PriorityArbitrator::Option::Flags::INTERRUPTABLE);
// The EatDot arbitrator is itself an option of the root arbitrator
rootArbitrator_->addOption(eatDotsArbitrator_, PriorityArbitrator::Option::Flags::INTERRUPTABLE);
rootArbitrator_->addOption(moveRandomlyBehavior_, PriorityArbitrator::Option::Flags::INTERRUPTABLE);
}
```
</details>
---
[← Previous task](3_add_more_behaviors.md)
Expand Down
83 changes: 83 additions & 0 deletions docs/tasks/5_cost_arbitration.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,89 @@ Finish the implementation of the `CostEstimator` and replace the random arbitrat
- Add an instance of the `CostEstimator` to the `PacmanAgent` class and initialize it in the constructor.
- Replace the random arbitrator with a cost arbitrator in the `PacmanAgent` class. Pass the `CostEstimator` instance to the `addOption` method.

## Solution

<details>
<summary>Click here to expand the solution</summary>

Finish the implementation of the `CostEstimator` class in `cost_estimator.cpp`:
```cpp
double CostEstimator::estimateCost(const Command& command, bool /*isActive*/) {
Positions absolutePath = utils::toAbsolutePath(command.path, environmentModel_);

// Compute the number of dots along the path and in the neighborhood of the path end using helper functions
const int nDotsAlongPath = utils::dotsAlongPath(absolutePath, environmentModel_);
const int nDotsInRadius =
utils::dotsInRadius(absolutePath.back(), environmentModel_, parameters_.pathEndNeighborhoodRadius);
const int nDots = nDotsAlongPath + nDotsInRadius;

if (nDots == 0) {
return std::numeric_limits<double>::max();
}

// Compute the size of the path and the neighborhood of the path end
const int pathLength = static_cast<int>(absolutePath.size());
const int neighborhoodSize = static_cast<int>(std::pow(2 * parameters_.pathEndNeighborhoodRadius + 1, 2));
const int nCells = pathLength + neighborhoodSize;

// We can define a cost as the inverse of a benefit.
// Our benefit is a dot density (number of dots / number of examined cells)
return static_cast<double>(nCells) / nDots;
}
```
Replace the include of the random arbitrator with the cost arbitrator in `include/demo/pacman_agent.hpp`.
Also, include `cost_estimator.hpp`:
```cpp
#include <arbitration_graphs/cost_arbitrator.hpp>
#include "cost_estimator.hpp"
```

Change the type of the `eatDotsArbitrator_` member in the `PacmanAgent` class to `CostArbitrator` and add an instance of the `CostEstimator`:
```cpp
private:
CostArbitrator::Ptr eatDotsArbitrator_;

CostEstimator::Ptr costEstimator_;
```

As always, the magic happens in the constructor of the `PacmanAgent` class.
Instantiate the cost estimator and pass it in the `addOption` calls:
```cpp
explicit PacmanAgent(const entt::Game& game)
: parameters_{}, 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_);
moveRandomlyBehavior_ = std::make_shared<MoveRandomlyBehavior>(parameters_.moveRandomlyBehavior);

// This is now a cost arbitrator
eatDotsArbitrator_ = std::make_shared<CostArbitrator>("EatDots");
// Construct the cost estimator
costEstimator_ = std::make_shared<CostEstimator>(environmentModel_, parameters_.costEstimator);
// Add the ChangeDotCluster and EatClosestDot behavior components as options to the
// cost arbitrator while also passing the cost estimator
eatDotsArbitrator_->addOption(
changeDotClusterBehavior_, CostArbitrator::Option::Flags::INTERRUPTABLE, costEstimator_);
eatDotsArbitrator_->addOption(
eatClosestDotBehavior_, CostArbitrator::Option::Flags::INTERRUPTABLE, costEstimator_);

rootArbitrator_ = std::make_shared<PriorityArbitrator>("Pacman");
rootArbitrator_->addOption(chaseGhostBehavior_, PriorityArbitrator::Option::Flags::INTERRUPTABLE);
rootArbitrator_->addOption(avoidGhostBehavior_, PriorityArbitrator::Option::Flags::INTERRUPTABLE);
rootArbitrator_->addOption(eatDotsArbitrator_, PriorityArbitrator::Option::Flags::INTERRUPTABLE);
rootArbitrator_->addOption(moveRandomlyBehavior_, PriorityArbitrator::Option::Flags::INTERRUPTABLE);
rootArbitrator_->addOption(stayInPlaceBehavior_,
PriorityArbitrator::Option::Flags::INTERRUPTABLE |
PriorityArbitrator::Option::FALLBACK);
}
```
</details>
---
[← Previous task](4_nested_arbitrators.md)
Expand Down
80 changes: 80 additions & 0 deletions docs/tasks/6_verification.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,86 @@ Add the `MoveRandomly` behavior component as a last resort fallback layer.
- Try breaking a behavior component on purpose and see how the system reacts.
(Try throwing an exception in the `getCommand` method of a behavior component or returning a command that will lead to a collision with a wall.)

## Solution

<details>
<summary>Click here to expand the solution</summary>

In the `Verifier::analyze` method (in `include/demo/verifier.hpp`), we simply check if the command would lead to an invalid position:
```cpp
VerificationResult analyze(const Time /*time*/, const Command& command) const {
Move nextMove = Move{command.path.front()};
Position nextPosition = environmentModel_->pacmanPosition() + nextMove.deltaPosition;

// The command is considered safe if the next position is in bounds and not a wall
if (environmentModel_->isPassableCell(nextPosition)) {
return VerificationResult{true};
}

return VerificationResult{false};
}
```
Include the verifier header you just implemented, in `include/demo/pacman_agent.hpp`.
Also, include `stay_in_place_behavior.hpp`.
```cpp
#include "stay_in_place_behavior.hpp"
#include "verifier.hpp"
```

Adjust the template parameters in the alias definitions to contain the verifier types:
```cpp
public:
using CostArbitrator = arbitration_graphs::CostArbitrator<Command, Command, Verifier, VerificationResult>;
using PriorityArbitrator = arbitration_graphs::PriorityArbitrator<Command, Command, Verifier, VerificationResult>;
```

Add the verifier and the fallback behavior component as members of the `PacmanAgent` class:
```cpp
private:
StayInPlaceBehavior::Ptr stayInPlaceBehavior_;

Verifier verifier_;
```

In the constructor of the `PacmanAgent` class, initialize the verifier and the `StayInPlace` behavior component.
Make sure to also pass the verifier to the arbitrator constructors:
```cpp
explicit PacmanAgent(const entt::Game& game)
: parameters_{},
environmentModel_{std::make_shared<EnvironmentModel>(game)},
verifier_{environmentModel_} // We can initialize the verifier in the member initializer list {

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_);
moveRandomlyBehavior_ = std::make_shared<MoveRandomlyBehavior>(parameters_.moveRandomlyBehavior);
// Initialize the StayInPlace behavior component
stayInPlaceBehavior_ = std::make_shared<StayInPlaceBehavior>(environmentModel_);

// Pass the verifier instance to the cost arbitrator
eatDotsArbitrator_ = std::make_shared<CostArbitrator>("EatDots", verifier_);
costEstimator_ = std::make_shared<CostEstimator>(environmentModel_, parameters_.costEstimator);
eatDotsArbitrator_->addOption(
changeDotClusterBehavior_, CostArbitrator::Option::Flags::INTERRUPTABLE, costEstimator_);
eatDotsArbitrator_->addOption(
eatClosestDotBehavior_, CostArbitrator::Option::Flags::INTERRUPTABLE, costEstimator_);

// Pass the verifier instance to the priority arbitrator
rootArbitrator_ = std::make_shared<PriorityArbitrator>("Pacman", verifier_);
rootArbitrator_->addOption(chaseGhostBehavior_, PriorityArbitrator::Option::Flags::INTERRUPTABLE);
rootArbitrator_->addOption(avoidGhostBehavior_, PriorityArbitrator::Option::Flags::INTERRUPTABLE);
rootArbitrator_->addOption(eatDotsArbitrator_, PriorityArbitrator::Option::Flags::INTERRUPTABLE);
rootArbitrator_->addOption(moveRandomlyBehavior_, PriorityArbitrator::Option::Flags::INTERRUPTABLE);
// Add the StayInPlace behavior component. Mark it as a last resort fallback layer using the FALLBACK flag.
rootArbitrator_->addOption(stayInPlaceBehavior_,
PriorityArbitrator::Option::Flags::INTERRUPTABLE |
PriorityArbitrator::Option::FALLBACK);
}
```
</details>
---
[← Previous task](5_cost_arbitration.md)
Expand Down

0 comments on commit 675a92d

Please sign in to comment.