Skip to content

Commit d8efa75

Browse files
Gin5567Seniru
andauthored
feat: random maze mode to MazeEscape (#86)
* ✨ Add random maze mode to MazeEscape * fix: syntaxerrors with merge --------- Co-authored-by: Seniru Pasan Indira <[email protected]>
1 parent 8e82218 commit d8efa75

File tree

6 files changed

+167
-66
lines changed

6 files changed

+167
-66
lines changed

MazeEscape/Hero.js

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
11
const symbols = require('./maps').symbols;
22

33
class Hero {
4-
54
constructor(map) {
6-
this.x = 0;
7-
this.y = 0;
85
this.map = map;
96
this.won = false;
7+
8+
// find the player position
9+
for (let y = 0; y < map.length; y++) {
10+
for (let x = 0; x < map[y].length; x++) {
11+
if (map[y][x] === "♟") {
12+
this.x = x;
13+
this.y = y;
14+
return;
15+
}
16+
}
17+
}
18+
19+
throw new Error("Player ♟ not found in map.");
1020
}
1121

1222
moveUp() {
@@ -31,6 +41,7 @@ class Hero {
3141
}
3242

3343
moveRight() {
44+
//console.log(`[DEBUG] Trying to move RIGHT from (${this.x}, ${this.y})`);
3445
if (this.canGo(this.x + 1, this.y)) {
3546
this.leave();
3647
this.goto(this.x + 1, this.y);
@@ -51,11 +62,11 @@ class Hero {
5162
}
5263

5364
canGo(x, y) {
54-
return this.map[y] && (this.map[y][x] == " " || this.map[y][x] == symbols.door);
65+
return this.map[y] && (this.map[y][x] === " " || this.map[y][x] === symbols.door);
5566
}
5667

5768
isWinningPosition(x, y) {
58-
return this.map[y][x] == symbols.door;
69+
return this.map[y][x] === symbols.door;
5970
}
6071

6172
hasWon() {
@@ -64,11 +75,21 @@ class Hero {
6475

6576
nextMap(map) {
6677
this.map = map;
67-
this.x = 0;
68-
this.y = 0;
6978
this.won = false;
70-
}
7179

80+
// find the new position of ♟
81+
for (let y = 0; y < map.length; y++) {
82+
for (let x = 0; x < map[y].length; x++) {
83+
if (map[y][x] === "♟") {
84+
this.x = x;
85+
this.y = y;
86+
return;
87+
}
88+
}
89+
}
90+
91+
throw new Error("Player ♟ not found in new map.");
92+
}
7293
}
7394

7495
module.exports = Hero;

MazeEscape/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@ You have to follow these steps in order to play this game
2525

2626
<hr>
2727

28+
### 🌀 New Feature: Random Maze Mode (2025/05/14)
29+
30+
We've added a brand new **Random Maze Mode**!
31+
32+
When starting game, you can press **2** to enter this mode. In this mode, the game will generate a random maze for you to escape from. Each time you play, you'll face a different maze layout, making the game even more challenging and exciting.
33+
34+
But if you prefer the classic experience, you can still choose the **1** option to play the original maze.
35+
36+
No more predictable maps — enjoy endless variety!
37+
2838
## Contributing
2939

3040
Want to contribute this game? Great! Here are some ways that you can help us with this.

MazeEscape/generateRandomMap.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
function generateRandomMaze(width, height) {
2+
// initialize the maze with walls
3+
const maze = Array.from({ length: height }, () => Array(width).fill("▉"));
4+
5+
// DFS carving algorithm
6+
function carve(x, y) {
7+
maze[y][x] = " ";
8+
const dirs = [
9+
[0, -2], [2, 0], [0, 2], [-2, 0]
10+
].sort(() => Math.random() - 0.5);
11+
12+
for (const [dx, dy] of dirs) {
13+
const nx = x + dx, ny = y + dy;
14+
if (ny > 0 && ny < height - 1 && nx > 0 && nx < width - 1 && maze[ny][nx] === "▉") {
15+
maze[y + dy / 2][x + dx / 2] = " ";
16+
carve(nx, ny);
17+
}
18+
}
19+
}
20+
21+
// start carving from (1, 1)
22+
carve(1, 1);
23+
24+
// BFS
25+
const visited = Array.from({ length: height }, () => Array(width).fill(false));
26+
const queue = [[1, 1]];
27+
visited[1][1] = true;
28+
let farthest = [1, 1];
29+
30+
while (queue.length) {
31+
const [x, y] = queue.shift();
32+
farthest = [x, y];
33+
for (const [dx, dy] of [[0,1], [1,0], [-1,0], [0,-1]]) {
34+
const nx = x + dx, ny = y + dy;
35+
if (maze[ny]?.[nx] === " " && !visited[ny][nx]) {
36+
visited[ny][nx] = true;
37+
queue.push([nx, ny]);
38+
}
39+
}
40+
}
41+
42+
// place the hero and the goal
43+
maze[1][1] = "♟";
44+
const [fx, fy] = farthest;
45+
maze[fy][fx] = "▣";
46+
47+
return maze;
48+
}
49+
50+
module.exports = { generateRandomMaze };

MazeEscape/index.js

Lines changed: 69 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,80 +2,96 @@ const readline = require('readline');
22
const esc = require('ansi-escapes');
33
const { maps, symbols } = require('./maps.json');
44
const Hero = require('./Hero');
5+
const { generateRandomMaze } = require('./generateRandomMap');
56

67
let round = 0;
7-
let map = maps[round];
8-
let hero = new Hero(map);
8+
let map = null;
9+
let hero = null;
910
let gameStart = false;
11+
let useRandom = false;
1012
let moveCount = 0;
1113
let totalMoves = 0;
1214

1315
readline.emitKeypressEvents(process.stdin);
1416
process.stdin.setRawMode(true);
1517

1618
process.stdin.on('keypress', (str, key) => {
17-
if (key.name == 'z' && key.ctrl) {
19+
if (!gameStart) {
20+
if (key.name === '1') {
21+
useRandom = false;
22+
startGame();
23+
} else if (key.name === '2') {
24+
useRandom = true;
25+
startGame();
26+
}
27+
return;
28+
}
29+
30+
if (key.name === 'q' || (key.name === 'z' && key.ctrl)) {
31+
console.log("Thank you for playing!");
1832
console.log(esc.cursorShow);
1933
process.exit(0);
20-
} else if (!gameStart) {
21-
gameStart = true;
22-
welcome();
23-
} else if (hero.hasWon()) {
24-
if (round + 1 < maps.length) {
34+
}
35+
36+
if (hero.hasWon()) {
37+
if (!useRandom && round + 1 < maps.length) {
2538
round++;
2639
map = maps[round];
2740
hero.nextMap(map);
28-
moveCount = 0;
41+
} else if (useRandom) {
42+
console.log(clr("🎉 Congratulations! You escaped the maze!", "green"));
43+
console.log(esc.cursorShow);
44+
process.exit(0);
2945
} else {
30-
console.log(clr("Congratulations! You've completed all the maps", "green"));
31-
console.log(clr("Your number of moves is " + totalMoves,"blue"));
46+
console.log(clr("🎉 Congratulations! You've completed all the maps", "green"));
47+
console.log(clr("Your number of moves is " + totalMoves, "blue"));
48+
console.log(esc.cursorShow);
3249
process.exit(0);
3350
}
34-
} else if (key.name === 'q') {
35-
console.log("Thank you for playing!");
36-
console.log(esc.cursorShow);
37-
process.exit(0);
38-
} else {
39-
if (gameStart) {
40-
switch (key.name) {
41-
case "up":
42-
hero.moveUp();
43-
moveCount++;
44-
totalMoves++;
45-
break;
46-
case "down":
47-
hero.moveDown();
48-
moveCount++;
49-
totalMoves++;
50-
break;
51-
case "left":
52-
hero.moveLeft();
53-
moveCount++;
54-
totalMoves++;
55-
break;
56-
case "right":
57-
hero.moveRight();
58-
moveCount++;
59-
totalMoves++;
60-
break;
61-
}
62-
}
63-
}
51+
draw();
52+
return;
53+
}
54+
55+
switch (key.name) {
56+
case "up":
57+
hero.moveUp(); break;
58+
case "down":
59+
hero.moveDown(); break;
60+
case "left":
61+
hero.moveLeft(); break;
62+
case "right":
63+
hero.moveRight(); break;
64+
default:
65+
return;
66+
}
67+
68+
moveCount++;
69+
totalMoves++;
6470
draw();
6571
});
6672

73+
function startGame() {
74+
if (useRandom) {
75+
map = generateRandomMaze(15, 15);
76+
} else {
77+
map = maps[round];
78+
}
79+
hero = new Hero(map);
80+
gameStart = true;
81+
welcome();
82+
draw();
83+
}
84+
6785
function draw() {
6886
let res = " " + (symbols.wall + " ").repeat(map[0].length) + "\n";
6987
for (let row of map) {
7088
res += " " + symbols.wall + " ";
71-
for (let col of row) {
72-
res += col + " ";
73-
}
74-
res += symbols.wall + '\n';
89+
for (let col of row) res += col + " ";
90+
res += symbols.wall + "\n";
7591
}
7692
res += " " + (symbols.wall + " ").repeat(map[0].length) + "\n";
7793
res = res.replace("♟", clr("♟", "yellow")).replace(//g, clr("▣", "green"));
78-
94+
7995
res += `\n${clr("Moves:", "blue")} ${moveCount}\n`;
8096
res += `${clr("Total Moves:", "blue")} ${totalMoves}\n`;
8197

@@ -87,36 +103,34 @@ function draw() {
87103
}
88104

89105
function welcome() {
90-
91106
const logo = `
92107
__ __ __ ____ ____ ____ ___ ___ __ ____ ____
93108
( \\/ ) /__\\ (_ )( ___) ( ___)/ __) / __) /__\\ ( _ \\( ___)
94109
) ( /(__)\\ / /_ )__) )__) \\__ \\( (__ /(__)\\ )___/ )__)
95-
(_/\\/\\_)(__)(__)(____)(____) (____)(___/ \\___)(__)(__)(__) (____) !!!
96-
110+
(_/\\/\\_)(__)(__)(____)(____) (____)(___/ \\___)(__)(__)(__) (____)
97111
`;
98112
print(`
99-
100113
Welcome to
101114
${clr(logo, 'yellow')}
102-
103115
Maze escape game for Salif's CLI game collection.
104116
105117
${clr('Help', 'green')}
106118
- This is you: ♟
107119
- This is your goal: ▣
108120
109-
Move your character using arrow keys and reach your goal while avoiding walls.
121+
Move your character using arrow keys and reach your goal while avoiding walls.
110122
111-
${clr('Press any key to continue...', 'cian')}
123+
${clr('Select game mode:', 'blue')}
124+
${clr('1.', 'cian')} Play with existing maps (from maps.json)
125+
${clr('2.', 'cian')} Play with random maze (increasing size)
112126
127+
${clr('Press 1 or 2 to start...', 'yellow')}
113128
`);
114129
}
115130

116-
/*Salif's coloring function*/
117131
function clr(text, color) {
118132
const code = { red: 91, green: 92, blue: 34, cian: 96, yellow: 93 }[color];
119-
if (code) return "\x1b[" + code + "m" + text + "\x1b[0m";
133+
return code ? `\x1b[${code}m${text}\x1b[0m` : text;
120134
}
121135

122136
function print(str, hide = true) {

MazeEscape/package-lock.json

Lines changed: 6 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

MazeEscape/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"author": "Seniru Pasan",
1616
"license": "Apache-2.0",
1717
"dependencies": {
18-
"ansi-escapes": "^4.2.1"
18+
"ansi-escapes": "^4.2.1",
19+
"maze-escape": "file:"
1920
}
20-
}
21+
}

0 commit comments

Comments
 (0)