goat
command
maze
Play maze with adjustable difficulty.
0
views
0
likes
0
installs
raw source
const { createCanvas } = require('canvas');
const fs = require('fs-extra');
const path = require('path');
exports.config = {
name: "maze",
author: "allou moha",//updated by NeoKEX
role: 0,
countDown: 40,
description: "Play maze with adjustable difficulty.",
version: "1.0.3",
guide: "{pn} [1-10] or {pn} [easy|medium|hard]",
category: "game",
};
function generateMazeImage(difficulty = 15, grid = null, cols = null, highlightPath = null, wrongPath = null, currentPosition = null, progressPath = null) {
difficulty = Math.max(1, Math.min(difficulty, 15));
const base = 10;
const scale = 0.4;
const size = Math.floor(base + difficulty * scale);
let rows;
const cellSize = 30;
if (!grid) {
cols = size;
rows = size;
grid = [];
const stack = [];
for (let y = 0; y < rows; y++) {
for (let x = 0; x < cols; x++) {
grid.push({
x,
y,
walls: { top: true, right: true, bottom: true, left: true },
visited: false
});
}
}
function index(x, y) {
if (x < 0 || y < 0 || x >= cols || y >= rows) return -1;
return x + y * cols;
}
function getNeighbors(cell) {
const neighbors = [];
const { x, y } = cell;
const top = grid[index(x, y - 1)];
const right = grid[index(x + 1, y)];
const bottom = grid[index(x, y + 1)];
const left = grid[index(x - 1, y)];
if (top && !top.visited) neighbors.push(top);
if (right && !right.visited) neighbors.push(right);
if (bottom && !bottom.visited) neighbors.push(bottom);
if (left && !left.visited) neighbors.push(left);
return neighbors;
}
function removeWalls(a, b) {
const dx = a.x - b.x;
const dy = a.y - b.y;
if (dx === 1) {
a.walls.left = false;
b.walls.right = false;
} else if (dx === -1) {
a.walls.right = false;
b.walls.left = false;
}
if (dy === 1) {
a.walls.top = false;
b.walls.bottom = false;
} else if (dy === -1) {
a.walls.bottom = false;
b.walls.top = false;
}
}
let current = grid[0];
current.visited = true;
while (true) {
const neighbors = getNeighbors(current);
if (neighbors.length > 0) {
stack.push(current);
const next = neighbors[Math.floor(Math.random() * neighbors.length)];
removeWalls(current, next);
next.visited = true;
current = next;
} else if (stack.length > 0) {
current = stack.pop();
} else {
break;
}
}
} else {
rows = grid.length / cols;
}
const width = cols * cellSize;
const height = rows * cellSize;
const canvas = createCanvas(width, height);
const ctx = canvas.getContext('2d');
const start = grid[0];
const end = grid[grid.length - 1];
function index(x, y) {
if (x < 0 || y < 0 || x >= cols || y >= rows) return -1;
return x + y * cols;
}
function solveMaze() {
const queue = [start];
const visited = new Set([index(start.x, start.y)]);
const parent = {};
while (queue.length > 0) {
const cell = queue.shift();
if (cell === end) break;
const { x, y, walls } = cell;
const moves = [
!walls.top && grid[index(x, y - 1)],
!walls.right && grid[index(x + 1, y)],
!walls.bottom && grid[index(x, y + 1)],
!walls.left && grid[index(x - 1, y)]
];
for (const next of moves) {
if (!next) continue;
const idx = index(next.x, next.y);
if (!visited.has(idx)) {
visited.add(idx);
parent[idx] = cell;
queue.push(next);
}
}
}
const path = [];
let cur = end;
while (cur !== start) {
path.push(cur);
const parentIndex = index(cur.x, cur.y);
if (!parent[parentIndex]) break;
cur = parent[parentIndex];
}
path.push(start);
return path.reverse();
}
const solutionPath = solveMaze();
function getSolutionCode(path) {
let code = "";
for (let i = 0; i < path.length - 1; i++) {
const a = path[i];
const b = path[i + 1];
if (b.x === a.x + 1) code += "r";
else if (b.x === a.x - 1) code += "l";
else if (b.y === a.y + 1) code += "d";
else if (b.y === a.y - 1) code += "u";
}
return code;
}
const solutionCode = getSolutionCode(solutionPath);
ctx.fillStyle = "#ffffff";
ctx.fillRect(0, 0, width, height);
if (progressPath && progressPath.length > 1) {
ctx.strokeStyle = "rgba(255,235,59,0.8)";
ctx.lineWidth = 6;
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.beginPath();
for (let i = 0; i < progressPath.length; i++) {
const cell = progressPath[i];
const cx = cell.x * cellSize + cellSize / 2;
const cy = cell.y * cellSize + cellSize / 2;
if (i === 0) ctx.moveTo(cx, cy);
else ctx.lineTo(cx, cy);
}
ctx.stroke();
}
if (highlightPath && highlightPath.length > 1) {
ctx.strokeStyle = "rgba(76,175,80,0.7)";
ctx.lineWidth = 6;
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.beginPath();
for (let i = 0; i < highlightPath.length; i++) {
const cell = highlightPath[i];
const cx = cell.x * cellSize + cellSize / 2;
const cy = cell.y * cellSize + cellSize / 2;
if (i === 0) ctx.moveTo(cx, cy);
else ctx.lineTo(cx, cy);
}
ctx.stroke();
}
if (wrongPath && wrongPath.length > 1) {
ctx.strokeStyle = "rgba(244,67,54,0.7)";
ctx.lineWidth = 6;
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.beginPath();
for (let i = 0; i < wrongPath.length; i++) {
const cell = wrongPath[i];
const cx = cell.x * cellSize + cellSize / 2;
const cy = cell.y * cellSize + cellSize / 2;
if (i === 0) ctx.moveTo(cx, cy);
else ctx.lineTo(cx, cy);
}
ctx.stroke();
}
ctx.fillStyle = "#d0d0d0";
const markerSize = cellSize * 0.15;
grid.forEach(cell => {
const cx = cell.x * cellSize + cellSize / 2;
const cy = cell.y * cellSize + cellSize / 2;
ctx.fillRect(cx - markerSize / 2, cy - markerSize / 2, markerSize, markerSize);
});
ctx.font = `${Math.floor(cellSize * 0.7)}px Arial`;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = "#87a8f2";
ctx.fillRect(start.x * cellSize, start.y * cellSize, cellSize, cellSize);
ctx.fillStyle = "#000000";
ctx.fillText("A", start.x * cellSize + cellSize / 2, start.y * cellSize + cellSize / 2);
ctx.fillStyle = "#f28b82";
ctx.fillRect(end.x * cellSize, end.y * cellSize, cellSize, cellSize);
ctx.fillStyle = "#000000";
ctx.fillText("B", end.x * cellSize + cellSize / 2, end.y * cellSize + cellSize / 2);
if (currentPosition) {
ctx.strokeStyle = "#000000";
ctx.lineWidth = 3;
ctx.beginPath();
ctx.arc(currentPosition.x * cellSize + cellSize / 2, currentPosition.y * cellSize + cellSize / 2, cellSize * 0.4, 0, 2 * Math.PI);
ctx.fillStyle = "rgba(0, 0, 0, 0.4)";
ctx.fill();
ctx.stroke();
}
ctx.strokeStyle = "#000";
ctx.lineWidth = 2;
grid.forEach(cell => {
const x = cell.x * cellSize;
const y = cell.y * cellSize;
const w = cell.walls;
if (w.top) {
ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(x + cellSize, y); ctx.stroke();
}
if (w.right) {
ctx.beginPath(); ctx.moveTo(x + cellSize, y); ctx.lineTo(x + cellSize, y + cellSize); ctx.stroke();
}
if (w.bottom) {
ctx.beginPath(); ctx.moveTo(x, y + cellSize); ctx.lineTo(x + cellSize, y + cellSize); ctx.stroke();
}
if (w.left) {
ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(x, y + cellSize); ctx.stroke();
}
});
return {
image: canvas.createPNGStream(),
solution: solutionCode,
solutionPath,
grid,
cols
};
}
function trans(emos) {
return emos
.replace(/⬆️/gi, "u")
.replace(/➡️/gi, "r")
.replace(/⬇️/gi, "d")
.replace(/⬅️/gi, "l");
}
function getPathFromCode(code, grid, cols, startCell) {
const rows = grid.length / cols;
const path = [startCell];
let current = startCell;
function index(x, y) {
if (x < 0 || y < 0 || x >= cols || y >= rows) return -1;
return x + y * cols;
}
for (const move of code) {
let nx = current.x;
let ny = current.y;
if (move === "u") ny--;
if (move === "d") ny++;
if (move === "l") nx--;
if (move === "r") nx++;
const idx = index(nx, ny);
if (idx === -1) return path;
const next = grid[idx];
const valid =
(move === "u" && !current.walls.top) ||
(move === "d" && !current.walls.bottom) ||
(move === "l" && !current.walls.left) ||
(move === "r" && !current.walls.right);
if (!valid) return path;
path.push(next);
current = next;
}
return path;
}
function isPartialSolutionCorrect(userPath, solutionPath, fullCode) {
if (userPath.length !== fullCode.length + 1) return false;
for (let i = 0; i < userPath.length; i++) {
if (!solutionPath[i] || userPath[i].x !== solutionPath[i].x || userPath[i].y !== solutionPath[i].y) {
return false;
}
}
return true;
}
exports.onStart = async ({ args, message, event, commandName }) => {
let difficultyLevel = 8;
let difficultyMessage = "Medium";
if (args.length > 0) {
const input = args[0].toLowerCase();
const inputNumber = parseInt(input);
if (!isNaN(inputNumber)) {
difficultyLevel = Math.max(1, Math.min(10, inputNumber));
difficultyMessage = `Level ${difficultyLevel}`;
} else if (input === 'easy') {
difficultyLevel = 4;
difficultyMessage = "Easy (Level 4)";
} else if (input === 'medium') {
difficultyLevel = 8;
difficultyMessage = "Medium (Level 8)";
} else if (input === 'hard') {
difficultyLevel = 13;
difficultyMessage = "Hard (Level 13)";
}
}
const data = generateMazeImage(difficultyLevel);
const imagePath = path.join(__dirname, global.utils.randomString(4) + ".png");
const writeStream = fs.createWriteStream(imagePath);
data.image.pipe(writeStream);
await new Promise((resolve) => writeStream.on('finish', resolve));
const reply = await message.reply({
body: `🧩 Solve the maze! Difficulty: ${difficultyMessage}\n\n• Send your path in one message (e.g., ➡️➡️⬇️...)\n• A is the start, B is the end.\n• You have 3 attempts for wrong answers.`,
attachment: fs.createReadStream(imagePath)
});
fs.unlinkSync(imagePath);
global.GoatBot.onReply.set(reply.messageID, {
commandName,
au: event.senderID,
solution: data.solution,
solutionPath: data.solutionPath,
grid: data.grid,
cols: data.cols,
attempts: 0,
currentProgress: "",
currentPosition: data.grid[0],
// Store the final calculated difficulty level for reward scaling
finalDifficulty: difficultyLevel
});
};
exports.onReply = async ({ message, event, Reply, usersData }) => {
const { au, solution, solutionPath, grid, cols, currentProgress, finalDifficulty } = Reply;
if (event.senderID !== au) return;
const userEmoji = event.body.trim();
const userCode = trans(userEmoji);
if (!/^[urdl⬆️➡️⬇️⬅️]+$/i.test(userEmoji)) {
return message.reply(`Please only use valid move emojis (⬆️ ➡️ ⬇️ ⬅️) or their corresponding letters (u, r, d, l) in one sequence.`);
}
if (!/^[urdl]+$/i.test(userCode)) {
return message.reply(`Please only use valid move emojis (⬆️ ➡️ ⬇️ ⬅️) or their corresponding letters (u, r, d, l) in one sequence.`);
}
const fullCode = currentProgress + userCode;
const startCell = grid[0];
const userPath = getPathFromCode(fullCode, grid, cols, startCell);
const isCorrectContinuation = isPartialSolutionCorrect(userPath, solutionPath, fullCode);
if (isCorrectContinuation && userPath.length === solutionPath.length) {
// Dynamic Reward Calculation: Base 20000 / 8 * finalDifficulty
// Minimum reward is set to 2500 (1 * 2500)
const baseCoinPerLevel = 2500;
const rewardAmount = Math.max(2500, Math.floor(baseCoinPerLevel * (finalDifficulty || 8)));
try {
const userData = await usersData.get(event.senderID);
await usersData.set(event.senderID, {
money: userData.money + rewardAmount,
exp: userData.exp,
data: userData.data
});
const data = generateMazeImage(15, grid, cols, solutionPath, null);
const imagePath = path.join(__dirname, global.utils.randomString(4) + ".png");
const writeStream = fs.createWriteStream(imagePath);
data.image.pipe(writeStream);
await new Promise((resolve) => writeStream.on('finish', resolve));
await message.reply({
body: `🎉 Correct! You solved the maze and earned $${rewardAmount.toLocaleString()}! The solution is shown in green.`,
attachment: fs.createReadStream(imagePath)
});
fs.unlinkSync(imagePath);
global.GoatBot.onReply.delete(event.messageID);
} catch (e) {
message.reply(`🎉 You solved the maze! (Error crediting money: ${e.message})`);
global.GoatBot.onReply.delete(event.messageID);
}
return;
}
if (isCorrectContinuation) {
// CORRECT CONTINUATION
const currentCell = userPath[userPath.length - 1];
Reply.currentProgress = fullCode;
Reply.currentPosition = currentCell;
Reply.attempts = 0;
const data = generateMazeImage(5, grid, cols, null, null, currentCell, userPath);
const imagePath = path.join(__dirname, global.utils.randomString(4) + ".png");
const writeStream = fs.createWriteStream(imagePath);
data.image.pipe(writeStream);
await new Promise((resolve) => writeStream.on('finish', resolve));
global.GoatBot.onReply.delete(event.messageID);
const newReply = await message.reply({
body: `✅ Correct path! Continue from your position.\n\n📍 Progress: ${fullCode.length}/${solution.length} moves\n🎯 Keep going to reach point B!`,
attachment: fs.createReadStream(imagePath)
});
fs.unlinkSync(imagePath);
global.GoatBot.onReply.set(newReply.messageID, Reply);
return;
}
// WRONG PATH/MOVE
Reply.attempts++;
if (Reply.attempts >= 3) {
// GAME OVER
const data = generateMazeImage(15, grid, cols, solutionPath, userPath);
const imagePath = path.join(__dirname, global.utils.randomString(4) + ".png");
const writeStream = fs.createWriteStream(imagePath);
data.image.pipe(writeStream);
await new Promise((resolve) => writeStream.on('finish', resolve));
await message.reply({
body: `❌ Game over! You ran out of attempts.\n\n✅ Correct solution shown in green\n❌ Your incorrect path shown in red`,
attachment: fs.createReadStream(imagePath)
});
fs.unlinkSync(imagePath);
global.GoatBot.onReply.delete(event.messageID);
} else {
// WRONG MOVE, ATTEMPTS REMAINING
await message.reply(`❌ Wrong path or move! Try again from your last checkpoint.\n\n🔄 Attempts remaining: ${3 - Reply.attempts}`);
}
};