← back to browse
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}`);
    }
};

View raw file