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.

// ==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);
    }

})();