AlgoDaily

Maze Game

Read at your own pace. Code execution, quizzes, progress tracking, and the AI tutor are available in the interactive view with JavaScript enabled.

Enter The Maze

From systems to interaction

You’ve already built:

  • Console-based Calculator — a tight input loop around operations
  • Banking System — a small domain model with state and workflows

Now we’ll combine those muscles into a Maze Game: a console project with a grid world, player movement, collision rules, and a win condition.

This is a strong portfolio project because it shows you can build more than menus—you can model a changing environment and control a game loop.

Enter The Maze

Maze Project Shape

What we are building

A console maze game usually has four core pieces:

+-------------------+
| Map / Grid        |  walls, empty spaces, exit
+-------------------+
          |
          v
+-------------------+
| Player Position   |  row + column
+-------------------+
          |
          v
+-------------------+
| Movement Rules    |  can move? blocked?
+-------------------+
          |
          v
+-------------------+
| Game Loop         |  render -> input -> update
+-------------------+

We’ll keep the scope portfolio-friendly: a terminal maze with # for walls, . for open cells, P for the player, and E for the exit.

Next lesson’s Text-based Adventure Game will focus more on narrative choices. Here, the star is spatial movement.

Maze Project Shape

Represent The Maze

Start with the grid

A maze is naturally represented as rows of characters. In C++, vector<string> is a clean fit:

  • Each string is one row
  • Each character is one cell
  • maze[row][col] gives you the tile at a location

For rendering, avoid permanently baking the player into the grid. Keep the map and the player position separate, then overlay the player when printing.

```cpp
#include <iostream>
#include <vector>
#include <string>
using namespace std;

struct Position {
    int row;
    int col;
};

void renderMaze(const vector<string>& maze, Position player) {
    for (int r = 0; r < static_cast<int>(maze.size()); r++) {
        for (int c = 0; c < static_cast<int>(maze[r].size()); c++) {
            if (r == player.row && c == player.col) {
                cout << 'P';
            } else {
                cout << maze[r][c];
            }
        }
        cout << '\n';
    }
}

int main() {
    vector<string> maze = {
        "#######",
        "#.....#",
        "#.###.#",
        "#...#E#",
        "#######"
    };

    Position player{1, 1};
    cout << "Initial maze:\n";
    renderMaze(maze, player);

    return 0;
}
```

Collision Rule

In a console maze, the player attempts to move into a target cell.

Which condition should usually block the move?

Movement Core

Movement is a proposed update

A clean movement flow is:

  1. Convert input into a row/column delta
  2. Compute the proposed next position
  3. Check boundaries and walls
  4. Commit the move only if valid

This is the same quality habit from earlier projects: separate the decision from the side effect. Don’t mutate player state until the move is confirmed.

```cpp
#include <iostream>
#include <vector>
#include <string>
using namespace std;

struct Position {
    int row;
    int col;
};

bool isInside(const vector<string>& maze, Position p) {
    return p.row >= 0 &&
           p.row < static_cast<int>(maze.size()) &&
           p.col >= 0 &&
           p.col < static_cast<int>(maze[p.row].size());
}

bool canMoveTo(const vector<string>& maze, Position p) {
    return isInside(maze, p) && maze[p.row][p.col] != '#';
}

Position nextPosition(Position current, char command) {
    Position next = current;

    if (command == 'w') next.row--;
    else if (command == 's') next.row++;
    else if (command == 'a') next.col--;
    else if (command == 'd') next.col++;

    return next;
}

int main() {
    vector<string> maze = {
        "#####",
        "#...#",
        "#.###",
        "#..E#",
        "#####"
    };

    Position player{1, 1};

    for (char command : {'d', 'd', 's'}) {
        Position proposed = nextPosition(player, command);

        cout << "Trying move '" << command << "' to ("
             << proposed.row << ", " << proposed.col << "): ";

        if (canMoveTo(maze, proposed)) {
            player = proposed;
            cout << "moved\n";
        } else {
            cout << "blocked\n";
        }
    }

    cout << "Final position: (" << player.row << ", " << player.col << ")\n";
    return 0;
}
```

Overlay The Player

True or false:

A good default design is to keep the maze grid and the player position separate, then draw P during rendering instead of permanently storing P inside the maze data.

Game Loop Skeleton

Render → input → update → repeat

A real version can read from cin, but for a lesson-safe executable example, we’ll use scripted moves. The structure is the same:

while not won:
  render maze
  get command
  propose movement
  if valid: update player
  if exit reached: win

That loop is the heart of most interactive console programs, including games, CLIs, and backend maintenance tools.

```cpp
#include <iostream>
#include <vector>
#include <string>
using namespace std;

struct Position {
    int row;
    int col;
};

void render(const vector<string>& maze, Position player) {
    for (int r = 0; r < static_cast<int>(maze.size()); r++) {
        for (int c = 0; c < static_cast<int>(maze[r].size()); c++) {
            cout << (r == player.row && c == player.col ? 'P' : maze[r][c]);
        }
        cout << '\n';
    }
}

Position nextPosition(Position p, char command) {
    if (command == 'w') p.row--;
    else if (command == 's') p.row++;
    else if (command == 'a') p.col--;
    else if (command == 'd') p.col++;
    return p;
}

bool canMove(const vector<string>& maze, Position p) {
    return p.row >= 0 &&
           p.row < static_cast<int>(maze.size()) &&
           p.col >= 0 &&
           p.col < static_cast<int>(maze[p.row].size()) &&
           maze[p.row][p.col] != '#';
}

int main() {
    vector<string> maze = {
        "#######",
        "#.....#",
        "#.###.#",
        "#...#E#",
        "#######"
    };

    Position player{1, 1};
    string commands = "ddssaadddd";

    cout << "Starting Maze Game Demo\n";
    render(maze, player);

    for (char command : commands) {
        Position proposed = nextPosition(player, command);
        cout << "\nCommand: " << command << '\n';

        if (canMove(maze, proposed)) {
            player = proposed;
            cout << "Move accepted.\n";
        } else {
            cout << "Wall hit. Staying put.\n";
        }

        render(maze, player);

        if (maze[player.row][player.col] == 'E') {
            cout << "\nYou escaped the maze!\n";
            break;
        }
    }

    return 0;
}
```

Index The Cell

Fill in the blank.

Given:

vector<string> grid;
int r;
int c;

The expression to access the cell at row r and column c is _____.

Portfolio Polish

Make it feel shipped

To turn this from an exercise into a portfolio entry, add polish around the core loop:

  • README: explain controls, rules, and how to compile/run
  • Clear files: separate rendering, movement, and game state if the project grows
  • Level design: include two or three handcrafted mazes
  • Input handling: reject unknown commands without crashing
  • Win stats: count moves taken and wall hits
  • Clean demo: include a screenshot or terminal recording

You can reuse the project habits from Banking System: keep state intentional, keep workflows readable, and ship something another developer can run quickly.

Portfolio Polish

Win Condition Choice

You want the game to end when the player reaches the exit tile.

Where is the best place to check whether the player is on E?

Open interactive view →