Drawaria Snake Game

Crea un juego de Snake totalmente funcional y optimizado para el canvas de Drawaria.online, con escenario de jardín y un panel arrastrable.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Drawaria Snake Game
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  Crea un juego de Snake totalmente funcional y optimizado para el canvas de Drawaria.online, con escenario de jardín y un panel arrastrable.
// @author       YouTubeDrawaria
// @match        https://drawaria.online/*
// @grant        none
// @license      MIT
// @icon         https://www.google.com/s2/favicons?sz=64&domain=drawaria.online
// ==/UserScript==

(function() {
    'use strict';

    /* ---------- COMPONENTES COMPARTIDOS DEL SISTEMA ---------- */
    let drawariaSocket = null;
    let drawariaCanvas = null;
    let drawariaCtx = null;

    // Cola de comandos optimizada con agrupamiento inteligente
    const commandQueue = [];
    let batchProcessor = null;
    const BATCH_SIZE = 8;
    const BATCH_INTERVAL = 60;

    // Intercepta WebSocket para capturar el socket del juego
    const originalWebSocketSend = WebSocket.prototype.send;
    WebSocket.prototype.send = function(...args) {
        if (!drawariaSocket && this.url && this.url.includes('drawaria')) {
            drawariaSocket = this;
            console.log('🔗 Drawaria WebSocket capturado para el juego de Snake.');
            startBatchProcessor();
        }
        return originalWebSocketSend.apply(this, args);
    };

    function startBatchProcessor() {
        if (batchProcessor) return;
        batchProcessor = setInterval(() => {
            if (!drawariaSocket || drawariaSocket.readyState !== WebSocket.OPEN || commandQueue.length === 0) {
                return;
            }
            const batch = commandQueue.splice(0, BATCH_SIZE);
            batch.forEach(cmd => {
                try {
                    drawariaSocket.send(cmd);
                } catch (e) {
                    console.warn('⚠️ Fallo al enviar el comando:', e);
                }
            });
        }, BATCH_INTERVAL);
    }

    /**
     * Función unificada para encolar comandos de dibujo.
     * Utiliza una línea muy corta con grosor negativo para dibujar formas rellenas.
     * @param {number} x1 - Coordenada X inicial
     * @param {number} y1 - Coordenada Y inicial
     * @param {number} x2 - Coordenada X final
     * @param {number} y2 - Coordenada Y final
     * @param {string} color - Color del objeto (ej. '#FFFFFF')
     * @param {number} thickness - Grosor efectivo (negativo para relleno)
     */
    function enqueueDrawCommand(x1, y1, x2, y2, color, thickness) {
        if (!drawariaCanvas || !drawariaSocket) return;

        const normX1 = (x1 / drawariaCanvas.width).toFixed(4);
        const normY1 = (y1 / drawariaCanvas.height).toFixed(4);
        const normX2 = (x2 / drawariaCanvas.width).toFixed(4);
        const normY2 = (y2 / drawariaCanvas.height).toFixed(4);

        const cmd = `42["drawcmd",0,[${normX1},${normY1},${normX2},${normY2},false,${-Math.abs(thickness)},"${color}",0,0,{}]]`;
        commandQueue.push(cmd);

        // Renderizado local para retroalimentación visual inmediata
        if (drawariaCtx) {
            drawariaCtx.strokeStyle = color;
            drawariaCtx.lineWidth = thickness;
            drawariaCtx.lineCap = 'round';
            drawariaCtx.lineJoin = 'round';
            drawariaCtx.beginPath();
            drawariaCtx.moveTo(x1, y1);
            drawariaCtx.lineTo(x2, y2);
            drawariaCtx.stroke();
        }
    }

    /* ---------- LÓGICA DEL JUEGO DE SNAKE ---------- */
    class SnakeGame {
        constructor() {
            this.isActive = false;
            this.cellSize = 20; // Tamaño de cada segmento de la serpiente
            this.snake = [];
            this.food = {};
            this.direction = 'right';
            this.nextDirection = 'right';
            this.score = 0;
            this.gameInterval = null;
            this.speed = 150; // Velocidad inicial en ms
            this.isGameOver = false;

            this.init();
        }

        init() {
            const checkGameReady = () => {
                const gameCanvas = document.getElementById('canvas');
                if (gameCanvas) {
                    drawariaCanvas = gameCanvas;
                    drawariaCtx = gameCanvas.getContext('2d');
                    this.createGamePanel();
                    console.log('✅ Juego de Snake inicializado.');
                } else {
                    setTimeout(checkGameReady, 100);
                }
            };
            checkGameReady();
        }

        createGamePanel() {
            const existingPanel = document.getElementById('snake-game-panel');
            if (existingPanel) existingPanel.remove();

            const panel = document.createElement('div');
            panel.id = 'snake-game-panel';
            panel.style.cssText = `
                position: fixed !important;
                top: 250px !important;
                right: 20px !important;
                width: 250px !important;
                z-index: 2147483647 !important;
                background: linear-gradient(135deg, #2a2a3a, #1a1a2e) !important;
                border: 2px solid #5d5dff !important;
                border-radius: 12px !important;
                color: white !important;
                font-family: 'Segoe UI', Arial, sans-serif !important;
                box-shadow: 0 0 20px rgba(93, 93, 255, 0.3) !important;
                padding: 15px !important;
                text-align: center !important;
            `;

            panel.innerHTML = `
                <h3 style="margin-top: 0; color: #5d5dff;">🐍 Snake Game</h3>
                <div style="margin-bottom: 10px;">
                    Score: <span id="snake-score">0</span>
                </div>
                <button id="toggle-game" style="
                    width: 100%;
                    padding: 10px;
                    background: #5d5dff;
                    color: white;
                    border: none;
                    border-radius: 8px;
                    cursor: pointer;
                    font-size: 14px;
                    font-weight: bold;
                ">▶️ Start Game</button>
                <div id="game-message" style="
                    margin-top: 10px;
                    color: #ff4d4d;
                    font-weight: bold;
                    display: none;
                "></div>
                <div style="
                    margin-top: 15px;
                    font-size: 10px;
                    color: rgba(255,255,255,0.6);
                ">
                    Use Arrow Keys to move.<br>
                    Avoid walls and yourself!
                </div>
            `;
            document.body.appendChild(panel);
            this.setupEventListeners();
            this.makePanelDraggable(panel);
            this.addDrawGardenButton(panel);
        }

        setupEventListeners() {
            document.getElementById('toggle-game').addEventListener('click', () => this.toggleGame());
            document.addEventListener('keydown', (e) => this.handleKeyInput(e));
        }

        toggleGame() {
            if (!this.isActive) {
                this.startGame();
                document.getElementById('toggle-game').textContent = '⏸️ Pause Game';
                document.getElementById('toggle-game').style.background = '#ffc107';
                document.getElementById('game-message').style.display = 'none';
            } else {
                this.pauseGame();
                document.getElementById('toggle-game').textContent = '▶️ Resume Game';
                document.getElementById('toggle-game').style.background = '#5d5dff';
            }
        }

        startGame() {
            if (this.isActive) return;
            this.isActive = true;
            this.isGameOver = false;
            this.score = 0;
            this.direction = 'right';
            this.nextDirection = 'right';
            this.updateScoreDisplay();
            this.resetSnake();
            this.createFood();
            this.gameLoop();
        }

        pauseGame() {
            this.isActive = false;
            if (this.gameInterval) {
                clearInterval(this.gameInterval);
                this.gameInterval = null;
            }
        }

        endGame() {
            this.isGameOver = true;
            this.pauseGame();
            document.getElementById('toggle-game').textContent = '🔄 Restart Game';
            document.getElementById('toggle-game').style.background = '#cc0000';
            const messageEl = document.getElementById('game-message');
            messageEl.textContent = `GAME OVER! Score: ${this.score}`;
            messageEl.style.display = 'block';
            console.log('💀 GAME OVER! Final Score:', this.score);
        }

        resetSnake() {
            this.snake = [
                { x: 100, y: 100 },
                { x: 80, y: 100 },
                { x: 60, y: 100 }
            ];
        }

        createFood() {
            const maxX = Math.floor(drawariaCanvas.width / this.cellSize) * this.cellSize;
            const maxY = Math.floor(drawariaCanvas.height / this.cellSize) * this.cellSize;

            this.food = {
                x: Math.floor(Math.random() * (maxX / this.cellSize)) * this.cellSize,
                y: Math.floor(Math.random() * (maxY / this.cellSize)) * this.cellSize
            };
        }

        gameLoop() {
            if (!this.isActive || this.isGameOver) return;

            this.clearCanvas();
            this.updateGame();
            this.drawGame();

            this.gameInterval = setTimeout(() => this.gameLoop(), this.speed);
        }

        updateGame() {
            const head = { x: this.snake[0].x, y: this.snake[0].y };
            this.direction = this.nextDirection;

            if (this.direction === 'up') head.y -= this.cellSize;
            if (this.direction === 'down') head.y += this.cellSize;
            if (this.direction === 'left') head.x -= this.cellSize;
            if (this.direction === 'right') head.x += this.cellSize;

            this.snake.unshift(head);

            if (this.checkCollision(head)) {
                this.endGame();
                return;
            }

            if (head.x === this.food.x && head.y === this.food.y) {
                this.score++;
                this.updateScoreDisplay();
                this.createFood();
                this.speed = Math.max(50, this.speed * 0.95);
            } else {
                this.snake.pop();
            }
        }

        drawGame() {
            this.drawCell(this.food.x, this.food.y, '#FF4500'); // Naranja rojizo para la comida

            this.snake.forEach((segment, index) => {
                const color = index === 0 ? '#8BC34A' : '#8BC34A'; // Cabeza verde, cuerpo verde más claro
                this.drawCell(segment.x, segment.y, color);
            });
        }

        drawCell(x, y, color) {
            enqueueDrawCommand(x, y, x + this.cellSize, y + this.cellSize, color, this.cellSize);
        }

        clearCanvas() {
            const oldTail = this.snake[this.snake.length - 1];
            this.drawCell(oldTail.x, oldTail.y, '#4CAF50');
            this.drawCell(this.food.x, this.food.y, '#4CAF50');
        }

        checkCollision(head) {
            if (head.x < 0 || head.x >= drawariaCanvas.width || head.y < 0 || head.y >= drawariaCanvas.height) {
                return true;
            }

            for (let i = 1; i < this.snake.length; i++) {
                if (head.x === this.snake[i].x && head.y === this.snake[i].y) {
                    return true;
                }
            }
            return false;
        }

        handleKeyInput(e) {
            const key = e.key;
            if (key === 'ArrowUp' && this.direction !== 'down') this.nextDirection = 'up';
            if (key === 'ArrowDown' && this.direction !== 'up') this.nextDirection = 'down';
            if (key === 'ArrowLeft' && this.direction !== 'right') this.nextDirection = 'left';
            if (key === 'ArrowRight' && this.direction !== 'left') this.nextDirection = 'right';
        }

        updateScoreDisplay() {
            const scoreDisplay = document.getElementById('snake-score');
            if (scoreDisplay) scoreDisplay.textContent = this.score;
        }

        makePanelDraggable(panel) {
            let isDragging = false;
            let currentX;
            let currentY;
            let initialX;
            let initialY;
            let xOffset = 0;
            let yOffset = 0;

            const dragStart = (e) => {
                initialX = e.clientX - xOffset;
                initialY = e.clientY - yOffset;
                isDragging = true;
                panel.style.cursor = 'grabbing';
            };

            const dragEnd = () => {
                initialX = currentX;
                initialY = currentY;
                isDragging = false;
                panel.style.cursor = 'grab';
            };

            const drag = (e) => {
                if (isDragging) {
                    e.preventDefault();
                    currentX = e.clientX - initialX;
                    currentY = e.clientY - initialY;

                    xOffset = currentX;
                    yOffset = currentY;

                    panel.style.transform = `translate3d(${currentX}px, ${currentY}px, 0)`;
                }
            };

            const header = panel.querySelector('h3');
            if (header) {
                header.style.cursor = 'grab';
                header.addEventListener("mousedown", dragStart);
                document.addEventListener("mouseup", dragEnd);
                document.addEventListener("mousemove", drag);
            }
        }

        addDrawGardenButton(panel) {
            const newButton = document.createElement('button');
            newButton.id = 'draw-garden-btn';
            newButton.textContent = '🖼️ Draw Garden';
            newButton.style.cssText = `
                width: 100%;
                padding: 10px;
                background: #27ae60;
                color: white;
                border: none;
                border-radius: 8px;
                cursor: pointer;
                font-size: 14px;
                font-weight: bold;
                margin-top: 10px;
                transition: background 0.3s ease;
            `;
            newButton.onmouseover = function() {
                this.style.background = '#219353';
            };
            newButton.onmouseout = function() {
                this.style.background = '#27ae60';
            };

            const toggleButton = document.getElementById('toggle-game');
            panel.insertBefore(newButton, toggleButton.nextSibling);
            newButton.addEventListener('click', drawGardenScenario);
        }
    }

    // --------------------------------------------------------------------------------------------------
    // --- LÓGICA DEL ESCENARIO DE JARDÍN ---
    // --------------------------------------------------------------------------------------------------

    function drawGardenScenario() {
        if (!drawariaCanvas || !drawariaSocket) {
            console.log('Canvas or WebSocket not available.');
            return;
        }

        // Borra todo el canvas con blanco
        enqueueDrawCommand(0, 0, drawariaCanvas.width, drawariaCanvas.height, '#FFFFFF', Math.max(drawariaCanvas.width, drawariaCanvas.height));

        setTimeout(() => {
            const grassColor = '#4CAF50';
            const flowerColors = ['#FFC107', '#E91E63', '#9C27B0', '#03A9F4'];
            const cellSize = 10;

            // Dibuja el fondo verde
            enqueueDrawCommand(0, 0, drawariaCanvas.width, drawariaCanvas.height, grassColor, Math.max(9999, 9999));

            // Dibuja pequeños detalles para simular hierbas y flores
            for (let i = 0; i < 100; i++) {
                const x = Math.random() * drawariaCanvas.width;
                const y = Math.random() * drawariaCanvas.height;
                const color = flowerColors[Math.floor(Math.random() * flowerColors.length)];

                // Dibuja un pequeño círculo (simula una flor)
                enqueueDrawCommand(x, y, x + 1, y + 1, color, 8);
                // Dibuja una mini-hierba (línea vertical)
                enqueueDrawCommand(x, y, x, y + 5, '#4CAF50', 3);
            }

            console.log('✅ Escenario de jardín dibujado con éxito.');
        }, 200);
    }

    // --------------------------------------------------------------------------------------------------
    // --- FIN DE CÓDIGO AÑADIDO ---
    // --------------------------------------------------------------------------------------------------


    // Inicialización del juego
    const initSnakeGame = () => {
        const snakeGame = new SnakeGame();
    };

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initSnakeGame);
    } else {
        setTimeout(initSnakeGame, 500);
    }

})();