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.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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);
    }

})();