The Design Patterns book chapter 3 about Creational Patterns starts with a maze game. They use the maze as an example throughout the chapter and instead of rooms they have also things like enchanted rooms and rooms with a bomb.
The book is from the 90s and it uses raw pointers. There is vector of rooms in the maze and they will be polymorphic so I have to use pointers.
I implemented it using smart pointers. Because of the cyclic structure of rooms and doors, I think I have to use shared_ptr and weak_ptr. I have only used unique_ptr before in my projects.
I would like to get feedback about pointers. I am not sure when I should use shared_ptr instead of unique_ptr. I think the maze could own all map sites and have a vector of unique_ptrs instead. Maybe give tips for both approaches.
I don't see any reason why the player or the maze should be pointers. Also, in the function Room& getRoom() const I return a reference instead of a smart pointer because it doesn't have to share ownership.
The example looks like this:
enum Direction {North, South, East, West};
class MapSite {
public:
virtual void Enter() = 0;
};
class Room : public MapSite {
public:
Room(int roomNo);
MapSite* GetSide(Direction) const;
void SetSide(Direction, MapSite*);
virtual void Enter();
private:
MapSite* _sides[4];
int _roomNumber;
class Wall : public MapSite {
public:
Wall();
virtual void Enter();
};
class Door : public MapSite {
public:
Door(Room* = 0, Room* = 0);
virtual void Enter();
Room* OtherSideFrom(Room*);
private:
Room* _room1;
Room* _room2;
bool _isOpen;
};
class Maze {
public:
Maze();
void AddRoom(Room*);
Room* RoomNo(int) const;
};
Maze* MazeGame::CreateMaze() {
Maze* aMaze = new Maze;
Room* r1 = new Room(1);
Room* r2 = new Room(2);
Door* theDoor = new Door(r1, r2);
aMaze.AddRoom(r1);
aMaze.AddRoom(r2);
r1->SetSide(North, new Wall);
r1->SetSide(East, theDoor);
r1->SetSide(South, new Wall);
r1->SetSide(West, new Wall);
r2->SetSide(North, new Wall);
r2->SetSide(East, new Wall);
r2->SetSide(South, new Wall);
r2->SetSide(West, theDoor);
return aMaze;
}
Here is my implementation.
maze.h
#ifndef MAZE_H
#define MAZE_H
#include <vector>
#include <array>
#include <memory>
enum Direction { north, south, east, west };
class Player;
class MapSite {
public:
virtual ~MapSite() = default;
virtual std::string enter(Player& player) = 0;
};
class Room : public MapSite {
public:
Room(int id);
MapSite* getSide(Direction direction) const;
void setSide(Direction direction, std::shared_ptr<MapSite> mapSite);
std::string enter(Player& player) override;
friend bool operator==(const Room& left, const Room& right);
private:
std::array<std::shared_ptr<MapSite>, 4> sides;
int id;
};
class Wall : public MapSite {
public:
std::string enter(Player& player) override;
};
class Door : public MapSite {
public:
Door(std::shared_ptr<Room> room1, std::shared_ptr<Room> room2);
std::string enter(Player& player) override;
private:
std::weak_ptr<Room> room1;
std::weak_ptr<Room> room2;
};
class Maze {
public:
void addRoom(std::shared_ptr<Room> room);
private:
std::vector<std::shared_ptr<Room>> rooms;
};
class Player {
public:
Player(std::shared_ptr<Room> room);
Room& getRoom() const;
std::string enter(std::shared_ptr<Room> r);
std::string move(Direction direction);
private:
std::shared_ptr<Room> room;
};
class MazeGame {
public:
MazeGame(Maze maze, Player player);
std::string move(Direction direction);
private:
Maze maze;
Player player;
};
MazeGame createMaze();
#endif
maze.cpp
#include <iostream>
#include "maze.h"
constexpr char nl = '\n';
Room::Room(int id): id(id) {}
MapSite* Room::getSide(Direction direction) const {
return sides[direction].get();
}
void Room::setSide(Direction direction, std::shared_ptr<MapSite> mapSite) {
sides[direction] = mapSite;
}
std::string Room::enter(Player& player) {
return "entered room " + std::to_string(id);
}
bool operator==(const Room& left, const Room& right) {
return left.id == right.id;
}
std::string Wall::enter(Player& player) {
return "walked into a wall";
}
Door::Door(std::shared_ptr<Room> room1, std::shared_ptr<Room> room2):
room1(std::move(room1)), room2(std::move(room2))
{}
std::string Door::enter(Player& player) {
if (player.getRoom() == *room1.lock()) return player.enter(room2.lock());
if (player.getRoom() == *room2.lock()) return player.enter(room1.lock());
return "the door is locked";
}
void Maze::addRoom(std::shared_ptr<Room> room) {
rooms.push_back(std::move(room));
}
Player::Player(std::shared_ptr<Room> room): room(room) {}
Room& Player::getRoom() const {
return *room;
}
std::string Player::enter(std::shared_ptr<Room> r) {
room = r;
return room->enter(*this);
}
std::string Player::move(Direction direction) {
return room->getSide(direction)->enter(*this);
}
MazeGame::MazeGame(Maze maze, Player player):
maze(maze), player(player)
{}
std::string MazeGame::move(Direction direction) {
return player.move(direction);
}
MazeGame createMaze() {
Maze maze {};
std::shared_ptr<Room> r1 = std::make_shared<Room>(1);
std::shared_ptr<Room> r2 = std::make_shared<Room>(2);
std::shared_ptr<Door> door = std::make_shared<Door>(r1, r2);
maze.addRoom(r1);
maze.addRoom(r2);
r1->setSide(north, std::make_shared<Wall>());
r1->setSide(east, door);
r1->setSide(south, std::make_shared<Wall>());
r1->setSide(west, std::make_shared<Wall>());
r2->setSide(north, std::make_shared<Wall>());
r2->setSide(east, std::make_shared<Wall>());
r2->setSide(south, std::make_shared<Wall>());
r2->setSide(west, door);
return MazeGame { maze, Player { r1 } };
}
main.cpp
#include <iostream>
#include "maze.h"
constexpr char nl = '\n';
int main() {
MazeGame mazeGame = createMaze();
std::string line;
std::cout << "> ";
while (std::getline(std::cin, line)) {
if (line.empty()) break;
switch (line[0]) {
case 'n':
std::cout << mazeGame.move(north) << nl;
break;
case 'e':
std::cout << mazeGame.move(east) << nl;
break;
case 's':
std::cout << mazeGame.move(south) << nl;
break;
case 'w':
std::cout << mazeGame.move(west) << nl;
break;
}
std::cout << "> ";
}
}