Compare commits

..

1 commit

Author SHA1 Message Date
Dag-Erling Smørgrav
95f8f075db Load the font you were asked to. 2021-10-17 21:50:35 +02:00
29 changed files with 101 additions and 358 deletions

View file

@ -15,20 +15,6 @@
sudo apt-get install ninja-build pkg-config curl zip unzip tar cmake build-essential libx11-dev libxrandr-dev libxi-dev libudev-dev libgl1-mesa-dev
```
### Fedora 33 or newer
* Install the build tools
```bash
sudo dnf install ninja-build SFML-devel libXi-devel libX11-devel libXrandr-devel mesa-libGL-devel systemd-devel
```
### FreeBSD 12 or newer
* Install the build tools
```bash
sudo pkg install catch cmake libfmt ninja sfml
```
### Windows:
* Follow the instructions to install cmake (3.21) from https://cmake.org/download/
@ -46,19 +32,10 @@ brew install cmake ninja
## Configure and build project
### All Platforms, using VS Code
### All Platforms
```bash
git clone https://github.com/mod-cpp/pacman.git
cd pacman
code .
```
### Commandline based build (Not used in this training) Linux, BSD, or Mac
```bash
git clone https://github.com/mod-cpp/pacman.git
cd pacman
cmake .
make
```

View file

@ -1,5 +1,3 @@
[< Back](../README.md)
* [Exercise: Play with function parameters](parameters/README.md)
* [Exercise: Create an isWall function](create_function/README.md)

View file

@ -1,37 +0,0 @@
[< Back](../README.md)
# Exercise: Play with parameter passing
In this exercise we will look together at what happens when we change a reference
parameter to a value or a const reference.
Let's look at this function in [`Game.cpp`](../../../lib/Game.cpp)
```cpp
void Game::processEvents(InputState & inputState) {
auto event = canvas.pollEvent();
if (event && event.value().type == sf::Event::Closed) {
inputState.close = true;
return;
}
inputState.down = sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Down);
inputState.up = sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Up);
inputState.left = sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Left);
inputState.right = sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Right);
}
```
## Exercise
You can follow along locally:
1. Make `inputState` a value. What happens when you compile the code? Can you explain why?
2. Now make it a `const` reference. What happens? Can you explain why?
3. Revert it back to a non-const reference an make sure the code compile.

View file

@ -1,8 +1,13 @@
[< Back](../README.md)
# Exercise: Create a class
# Exercise: Simple class
## Exercise: Simple class
## Background:
1. Create an empty class named `Clyde` within the `.hpp` file.
2. Look at other ghost `.hpp` files and see how they define the class. It does not need to inherit from the `Ghost` base class and it does not need any functions.
4. Create an empty class named `Clyde` within the `.hpp` file. Look at other ghost `.hpp` files and see how they define
the class. It does not need to inherit from the `Ghost` base class and it does not need any functions. But if you
have extra time, try to inherit from the Ghost class and see what happens when you try to compile.
## Exercise
1.

View file

@ -2,19 +2,16 @@
# Exercise: Add Clyde as a Ghost
## Background: Clyde
## Background:
We have three ghosts within the project. Blinky, Inky and Pinky. But Clyde is missing. Implement Clyde and their
behavior.
Clyde chases PacMan so you need to know where PacMan is currently located. But Clyde gets scared and runs back to its scatter target when PacMan gets too close (less than 8 the size of a cell).
Clyde also has the scatter and eyes behaviors as the other ghosts.
Clyde always chases PacMan so the only thing you should need is where PacMan is currently located. But Clyde also has
the scatter behavior as the other ghosts.
Where are the graphics for Clyde in the assets file and how can we make sure the ghost will be correctly rendered?
## Exercise
1. Clyde should inherit from Ghost
2. Make simple implementations of the functions to make it compile
3. Clyde should start at Position { x = 15.5, y = 14 }
4. Clydes sprite is the orange one. Where are the graphics for Clyde in the assets file and how can we make sure the ghost will be correctly rendered?
5. Clydes scatter target is { x = 0, y = 30 }
6. Clyde always targets its scatter target, unless PacMan is further than 8 tiles away
1.

View file

@ -1,9 +1,13 @@
[< Back](../README.md)
# Exercise:
# Exercise: Create isIntersection function
## Exercise: Create isIntersection function
## Background:
1. In Board.cpp create an isIntersection function that returns a bool and takes a GridPosition as a parameter
2. Create an array of GridPositions around the parameter pos and array of bool and then loop through the postitions and check "isWalkableForPacMan" and store the results in the array of bools.
3. Return true if there are at least three different walkable paths
waage: Maybe the "isIntersection" for the AI. Create an array of GridPosition and array of bool and loop through to
check "isWalkableForPacMan" and then return the different kinds of intersections (top/right, right/bottom, bottom/left,
left/top)
## Exercise
1.

View file

@ -1,3 +1,4 @@
[< Back](../README.md)
* [Exercise: PacMan AI](pacman_ai/README.md)
* [Exercise: PacMan Bot](pacman_bot/README.md)

View file

@ -16,56 +16,7 @@ You only need to worry about the grid itself and any ghosts on the North/South/E
## Exercise
### isValidMove
Implement [PacManAI::isValidMove](../../../lib/PacManAI.cpp) and test your implementation with the test
in [testPacmanAI.cpp](../../../test/testPacmanAI.cpp) called _"Is valid move"_
To run the tests through CMake change the last line in [test/CMakeLists.txt](../../../test/CMakeLists.txt) to:
```
add_test(NAME pacman_tests COMMAND pacman_tests)
```
```cpp
bool PacManAI::isValidMove(const Move & move) {
return false;
}
```
<details>
<summary>Hint 1</summary>
Use [isWalkableForPacMan](../../../lib/Board.cpp) to make sure PacMan is not walking in ways that are not legal
</details>
<details>
<summary>Hint 2</summary>
Use [oppositeDirection](../../../lib/include/Direction.hpp) to make sure PacMan doesn't get stuck toggeling back and forth
</details>
### optimalDirection
Implement [PacManAI::optimalDirection](../../../lib/PacManAI.cpp) and test your implementation with the test
in [testPacmanAI.cpp](../../../test/testPacmanAI.cpp) called _"Is optimal direction"_
```cpp
Direction PacManAI::optimalDirection(const std::array<Move, 4> & moves) {
return Direction::NONE;
}
```
<details>
<summary>Hint</summary>
You can use [std::min_element](https://en.cppreference.com/w/cpp/algorithm/min_element) to find the closest pellet
</details>
### pelletClosestToPacman
### Part 1
Implement [PacManAI::pelletClosestToPacman](../../../lib/PacManAI.cpp) and test your implementation with the test
in [testPacmanAI.cpp](../../../test/testPacmanAI.cpp) called _"Find pellet closest to PacMan"_
@ -101,3 +52,36 @@ Use the [std::sort](https://en.cppreference.com/w/cpp/algorithm/sort) function t
and return true if the first parameter is closer from PacMan than the second.
</details>
### Part 2
Implement [PacManAI::isValidMove](../../../lib/PacManAI.cpp) and test your implementation with the test
in [testPacmanAI.cpp](../../../test/testPacmanAI.cpp) called _"Is valid move"_
```cpp
bool PacManAI::isValidMove(const Move & move) {
return false;
}
```
<details>
<summary>Hint</summary>
</details>
### Part 3
Implement [PacManAI::optimalDirection](../../../lib/PacManAI.cpp) and test your implementation with the test
in [testPacmanAI.cpp](../../../test/testPacmanAI.cpp) called _"Is optimal direction"_
```cpp
Direction PacManAI::optimalDirection(const std::array<Move, 4> & moves) {
return Direction::NONE;
}
```
<details>
<summary>Hint</summary>
</details>

View file

@ -1,13 +0,0 @@
bool PacManAI::isValidMove(const Move & move) {
const bool isOpposite = (move.direction == oppositeDirection(direction));
if (isOpposite) {
return false;
}
const bool canWalk = isWalkableForPacMan(move.position);
if (!canWalk) {
return false;
}
return true;
}

View file

@ -1,13 +0,0 @@
Direction PacManAI::optimalDirection(const std::array<Move, 4> & moves) {
double closestDistance = std::numeric_limits<double>::infinity();
Direction dir = Direction::NONE;
for (const auto & move : moves) {
if (move.distanceToTarget < closestDistance) {
closestDistance = move.distanceToTarget;
dir = move.direction;
}
}
return dir;
}

View file

@ -1,15 +0,0 @@
GridPosition PacManAI::pelletClosestToPacman(GridPosition pacmanGridPosition,
std::vector<GridPosition> & pellets) {
GridPosition closestPellet = { 0, 0 };
double closestDistance = std::numeric_limits<double>::infinity();
for (const auto & pellet : pellets) {
const double distance = positionDistance(pacmanGridPosition, pellet);
if (distance < closestDistance) {
closestDistance = distance;
closestPellet = pellet;
}
}
return closestPellet;
}

View file

@ -1,5 +1 @@
[< Back](../README.md)
* [Exercise: UBSan, Experiment on Compiler Explorer](https://godbolt.org/z/nT54sj4hj)
* [Exercise: ASan, Experiment on Compiler Explorer](https://godbolt.org/z/84Kr7Gcbz)
* [Exercise: MSan, Experiment on Compiler Explorer](https://godbolt.org/z/n5zWWM3aa)

View file

@ -7,8 +7,8 @@
* [Module 13: Game](13/README.md)
* [Module 14: Functions and Parameter Passing](14/README.md)
* [Module 15: Compilation, Linking and Assets](15/README.md)
* [Module 16: Classes and Structs](16/README.md)
* [Module 17: Memory and RAII](17/README.md)
* [Module 16: Memory and RAII](16/README.md)
* [Module 17: Classes and Structs](17/README.md)
## Day 2
@ -33,4 +33,4 @@ These will probably become relevant
* https://www.gamasutra.com/view/feature/3938/the_pacman_dossier.php?print=1
* https://www.slideshare.net/grimlockt/pac-man-6561257
* http://donhodges.com/pacman_pinky_explanation.htm
*
*

View file

@ -59,16 +59,13 @@ static Cell cellAtPosition(GridPosition point) {
return Cell(board[point.y][point.x]);
}
bool isWall(GridPosition point) {
return cellAtPosition(point) == Cell::wall;
}
bool isWalkableForPacMan(GridPosition point) {
return !isWall(point) && !isInPen(point);
return cellAtPosition(point) != Cell::wall && cellAtPosition(point) != Cell::pen;
}
bool isWalkableForGhost(GridPosition target_position, GridPosition current_position, bool isEyes) {
if (isWall(target_position))
const Cell cell = cellAtPosition(target_position);
if (cell == Cell::wall)
return false;
return isEyes || isInPen(current_position) || !isInPen(target_position);
}

View file

@ -36,10 +36,9 @@ void Canvas::render(const GameState & gameState) {
renderPellets(gameState.pellets);
renderSuperPellets(gameState.superPellets);
renderGhost(gameState.ghosts.blinky);
renderGhost(gameState.ghosts.pinky);
renderGhost(gameState.ghosts.inky);
renderGhost(gameState.ghosts.dave);
renderGhost(gameState.blinky);
renderGhost(gameState.pinky);
renderGhost(gameState.inky);
renderScore(gameState.score.points);
renderLives(gameState.score.lives);

View file

@ -1,38 +0,0 @@
#include "Dave.hpp"
namespace pacman {
Dave::Dave()
: Ghost(Atlas::Ghost::dave) {
pos = initialPosition();
}
double Dave::speed() const {
if (state == State::Eyes)
return 2;
if (state == State::Frightened)
return 0.5;
return 0.75;
}
void Dave::setTarget(Position pacManPos) {
if (isInPen()) {
target = penDoorPosition();
return;
}
if (positionDistance(pos, pacManPos) > 8) {
target = pacManPos;
} else {
target = scatterTarget();
}
}
Position Dave::initialPosition() const {
return { 15.5, 14 };
}
Position Dave::scatterTarget() const {
return { 0, 30 };
}
} // namespace pacman

View file

@ -40,9 +40,6 @@ void Game::processEvents(InputState & inputState) {
if (sf::Keyboard::isKeyPressed(sf::Keyboard::A)) {
inputState.enableAI = !inputState.enableAI;
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Q)) {
exit(0);
}
inputState.down = sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Down);
inputState.up = sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Up);

View file

@ -18,11 +18,18 @@ void GameState::step(std::chrono::milliseconds delta) {
if (!pacMan.hasDirection())
return;
ghosts.setTarget(pacMan.positionInGrid(), pacMan.currentDirection());
ghosts.update(delta);
blinky.setTarget(pacMan.position());
blinky.update(delta);
pinky.setTarget(pacMan.positionInGrid(), pacMan.currentDirection());
pinky.update(delta);
inky.setTarget(pacMan.positionInGrid(), pacMan.currentDirection(), blinky.positionInGrid());
inky.update(delta);
fruit.update(delta, score.eatenPellets);
ghosts.checkCollision(*this);
checkCollision(blinky);
checkCollision(pinky);
checkCollision(inky);
eatPellets();
eatFruit();
@ -48,7 +55,9 @@ void GameState::handleDeathAnimation(std::chrono::milliseconds delta) {
timeSinceDeath += delta;
if (timeSinceDeath.count() > 1000) {
ghosts.reset();
blinky.reset();
pinky.reset();
inky.reset();
pacMan.reset();
pacManAI.reset();
timeSinceDeath = std::chrono::milliseconds(0);
@ -65,7 +74,10 @@ void GameState::eatPellets() {
if (superPellets.eatPelletAtPosition(pos)) {
score.eatenPellets++;
score.points += POWER_PELLET_POINTS;
ghosts.frighten();
blinky.frighten();
pinky.frighten();
inky.frighten();
}
}

View file

@ -1,41 +0,0 @@
#include "GhostState.hpp"
#include "GameState.hpp"
namespace pacman {
void GhostState::setTarget(GridPosition pacManPosition, Direction pacManDirection) {
blinky.setTarget(gridPositionToPosition(pacManPosition));
pinky.setTarget(pacManPosition, pacManDirection);
inky.setTarget(pacManPosition, pacManDirection, blinky.positionInGrid());
dave.setTarget(gridPositionToPosition(pacManPosition));
}
void GhostState::update(std::chrono::milliseconds delta) {
blinky.update(delta);
pinky.update(delta);
inky.update(delta);
dave.update(delta);
}
void GhostState::checkCollision(GameState & gameState) {
gameState.checkCollision(blinky);
gameState.checkCollision(pinky);
gameState.checkCollision(inky);
gameState.checkCollision(dave);
}
void GhostState::reset(void) {
blinky.reset();
pinky.reset();
inky.reset();
dave.reset();
}
void GhostState::frighten(void) {
blinky.frighten();
pinky.frighten();
inky.frighten();
dave.frighten();
}
} // namespace pacman

View file

@ -16,28 +16,22 @@ Direction PacManAI::suggestedDirection() const {
// This function is not yet implemented.
// You will implement it as part of module 25.
GridPosition PacManAI::pelletClosestToPacman(GridPosition position,
std::vector<GridPosition> & pellets) {
if (pellets.empty())
return { 0, 0 };
return *std::min_element(pellets.begin(), pellets.end(), [position](GridPosition a, GridPosition b) {
return positionDistance(a, position) < positionDistance(b, position);
});
GridPosition PacManAI::pelletClosestToPacman(GridPosition,
std::vector<GridPosition> &) {
return {0, 0};
}
// This function is not yet implemented.
// You will implement it as part of module 25.
bool PacManAI::isValidMove(const Move & move) {
return isWalkableForPacMan(move.position) && move.direction != oppositeDirection(direction);
bool PacManAI::isValidMove(const Move &) {
return false;
}
// This function is not yet implemented.
// You will implement it as part of module 25.
Direction PacManAI::optimalDirection(const std::array<Move, 4> & moves) {
auto bestMove = std::min_element(moves.begin(), moves.end(), [](Move a, Move b) {
return a.distanceToTarget < b.distanceToTarget;
});
return bestMove->direction;
Direction PacManAI::optimalDirection(const std::array<Move, 4> &) {
return Direction::NONE;
}
void PacManAI::update(const PacMan & pacMan, const Pellets & pellets) {

View file

@ -12,7 +12,7 @@ enum class Ghost : unsigned int {
blinky = 2,
pinky = 3,
inky = 4,
dave = 5,
clyde = 5,
};
constexpr GridPosition pacman_right_wide = { 0, 0 };
@ -46,7 +46,7 @@ constexpr GridPosition eyeSprite(Direction direction) {
}
constexpr GridPosition ghostSprite(Ghost ghost, Direction direction, bool alternative) {
assert(ghost >= Ghost::blinky && ghost <= Ghost::dave && "Invalid Ghost");
assert(ghost >= Ghost::blinky && ghost <= Ghost::clyde && "Invalid Ghost");
auto y = static_cast<size_t>(ghost);
size_t x = 0;
switch (direction) {

View file

@ -8,7 +8,6 @@
namespace pacman {
bool isWall(GridPosition point);
bool isWalkableForPacMan(GridPosition point);
bool isWalkableForGhost(GridPosition target_position, GridPosition current_position, bool isEyes);
bool isInPen(GridPosition point);

View file

@ -1,20 +0,0 @@
#pragma once
#include "Ghost.hpp"
namespace pacman {
class Dave final : public Ghost {
public:
Dave();
void setTarget(Position pacManPos);
protected:
double speed() const override;
Position initialPosition() const override;
private:
Position scatterTarget() const;
};
} // namespace pacman

View file

@ -1,10 +1,8 @@
#pragma once
#include "Blinky.hpp"
#include "Dave.hpp"
#include "Fruits.hpp"
#include "Ghost.hpp"
#include "GhostState.hpp"
#include "Inky.hpp"
#include "InputState.hpp"
#include "PacMan.hpp"
@ -19,7 +17,9 @@ namespace pacman {
struct GameState {
void step(std::chrono::milliseconds delta);
GhostState ghosts;
Blinky blinky;
Pinky pinky;
Inky inky;
PacMan pacMan;
PacManAI pacManAI;

View file

@ -1,26 +0,0 @@
#pragma once
#include "Blinky.hpp"
#include "Dave.hpp"
#include "Inky.hpp"
#include "Pinky.hpp"
namespace pacman {
class GameState;
class GhostState {
public:
void setTarget(GridPosition pacManPosition, Direction pacManDirection);
void update(std::chrono::milliseconds delta);
void checkCollision(GameState & gameState);
void reset();
void frighten();
Blinky blinky;
Pinky pinky;
Inky inky;
Dave dave;
};
} // namespace pacman

View file

@ -1,6 +1,4 @@
#pragma once
#include "Direction.hpp"
namespace pacman {

View file

@ -8,4 +8,4 @@ target_link_libraries(pacman_tests Catch2::Catch2 libpacman)
# This setup the tests on CI. We disable the AI tests
# because you will have to implement them.
add_test(NAME pacman_tests COMMAND pacman_tests)
add_test(NAME pacman_tests COMMAND pacman_tests "~[AI]")

View file

@ -65,15 +65,4 @@ TEST_CASE("Teleport", "[board]") {
const pacman::GridPosition result = pacman::teleport(portalLeft);
REQUIRE(result.x == portalRight.x);
}
}
TEST_CASE("Is wall", "[board]") {
REQUIRE(pacman::isWall(pacman::GridPosition{ 0, 0 }));
REQUIRE(pacman::isWall(pacman::GridPosition{ 27, 30 }));
REQUIRE(!pacman::isWall(pacman::GridPosition{ 1, 1 }));
REQUIRE(!pacman::isWall(pacman::GridPosition{ 26, 29 }));
REQUIRE(!pacman::isWall(pacman::GridPosition{ 0, 14 }));
REQUIRE(!pacman::isWall(pacman::GridPosition{ 27, 14 }));
REQUIRE(!pacman::isWall(pacman::GridPosition{ 11, 13 }));
REQUIRE(!pacman::isWall(pacman::GridPosition{ 16, 15 }));
}
}

View file

@ -7,7 +7,6 @@ TEST_CASE("Find pellet closest to PacMan", "[AI]") {
PacManAI AI;
using TestData = std::tuple<GridPosition, std::vector<GridPosition>, GridPosition>;
auto data = GENERATE(
TestData{{5, 5}, {}, {0, 0}},
TestData{{5, 5}, {{5, 6}}, {5, 6}},
TestData{{5, 5}, {{5, 5}}, {5, 5}},
TestData{{5, 5}, {{0, 0}, {5, 6}}, {5, 6}},