Pixel Battles Bot

Бот для автоматического рисования на Pixel Battles

当前为 2025-07-20 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @license MIT
// @name         Pixel Battles Bot
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  Бот для автоматического рисования на Pixel Battles
// @author       YourName
// @match        https://pixelbattles.ru/*
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @connect      pixelbattles.ru
// ==/UserScript==

(function() {
    'use strict';

    // ===== ГЛОБАЛЬНЫЕ НАСТРОЙКИ ===== //
    const SETTINGS = {
        modes: {
            "top-down": "⬇️ Сверху вниз",
            "bottom-up": "⬆️ Снизу вверх",
            "left-right": "➡️ Слева направо",
            "right-left": "⬅️ Справа налево",
            "random": "🎲 Случайные точки",
            "diagonal-lt-rb": "↘️ Лев.верх → прав.низ",
            "diagonal-rt-lb": "↙️ Прав.верх → лев.низ",
            "diagonal-lb-rt": "↗️ Лев.низ → прав.верх",
            "diagonal-rb-lt": "↖️ Прав.низ → лев.верх"
        },
        colors: {
            "deepcarmine": "Красный",
            "flame": "Оранжево-красный",
            "yelloworange": "Жёлто-оранжевый",
            "naplesyellow": "Жёлтый",
            "mediumseagreen": "Зелёный",
            "emerald": "Изумрудный",
            "inchworm": "Салатовый",
            "myrtlegreen": "Тёмно-зелёный",
            "verdigris": "Бирюзовый",
            "cyancobaltblue": "Синий кобальт",
            "unitednationsblue": "Синий",
            "mediumskyblue": "Голубой",
            "oceanblue": "Морской волны",
            "VeryLightBlue": "Светло-голубой",
            "grape": "Фиолетовый",
            "purpleplum": "Сливовый",
            "darkpink": "Розовый",
            "mauvelous": "Розоватый",
            "coffee": "Коричневый",
            "coconut": "Бежевый",
            "black": "Чёрный",
            "philippinegray": "Серый",
            "lightsilver": "Серебристый",
            "white": "Белый"
        },
        colorHexes: [
            "#ae233d", "#ec5427", "#f4ab3c", "#f9d759", "#48a06d",
            "#5cc87f", "#9ae96c", "#317270", "#469ca8", "#2d519e",
            "#4d90e3", "#7ee6f2", "#4440ba", "#6662f6", "#772b99",
            "#a754ba", "#eb4e81", "#f19eab", "#684a34", "#956a34",
            "#000000", "#898d90", "#d5d7d9", "#ffffff"
        ],
        color: "black",
        delay: 1000,
        isRunning: false,
        abortController: null
    };

    // ===== ФУНКЦИИ РИСОВАНИЯ ===== //
    async function placePixel(x, y, color, signal) {
        try {
            if (signal?.aborted) throw new Error("Операция прервана");

            return new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: "PUT",
                    url: "https://api.pixelbattles.ru/pix",
                    headers: {
                        "Content-Type": "application/json"
                    },
                    data: JSON.stringify({ x, y, color }),
                    onload: function(response) {
                        console.log(`✅ (${x}, ${y})`);
                        resolve(true);
                    },
                    onerror: function(error) {
                        console.error(`❌ (${x}, ${y}):`, error);
                        reject(error);
                    },
                    onabort: function() {
                        reject(new Error("AbortError"));
                    }
                });
            });
        } catch (error) {
            if (error.name !== "AbortError") console.error(`❌ (${x}, ${y}):`, error.message);
            return false;
        }
    }

    // ===== ФУНКЦИИ РИСОВАНИЯ (РЕЖИМЫ) ===== //
    const drawingModes = {
        "top-down": async function*(x, y, w, h) {
            for (let row = y; row < y + h; row++) {
                for (let col = x; col < x + w; col++) {
                    yield {x: col, y: row};
                }
            }
        },
        "bottom-up": async function*(x, y, w, h) {
            for (let row = y + h - 1; row >= y; row--) {
                for (let col = x; col < x + w; col++) {
                    yield {x: col, y: row};
                }
            }
        },
        "left-right": async function*(x, y, w, h) {
            for (let col = x; col < x + w; col++) {
                for (let row = y; row < y + h; row++) {
                    yield {x: col, y: row};
                }
            }
        },
        "right-left": async function*(x, y, w, h) {
            for (let col = x + w - 1; col >= x; col--) {
                for (let row = y; row < y + h; row++) {
                    yield {x: col, y: row};
                }
            }
        },
        "random": async function*(x, y, w, h) {
            const pixels = [];
            for (let row = y; row < y + h; row++) {
                for (let col = x; col < x + w; col++) {
                    pixels.push({x: col, y: row});
                }
            }
            for (let i = pixels.length - 1; i > 0; i--) {
                const j = Math.floor(Math.random() * (i + 1));
                [pixels[i], pixels[j]] = [pixels[j], pixels[i]];
            }
            for (const pixel of pixels) yield pixel;
        },
        "diagonal-lt-rb": async function*(x, y, w, h) {
            for (let d = 0; d < w + h - 1; d++) {
                const startCol = Math.max(0, d - h + 1);
                const endCol = Math.min(d, w - 1);
                for (let col = startCol; col <= endCol; col++) {
                    yield {x: x + col, y: y + (d - col)};
                }
            }
        },
        "diagonal-rt-lb": async function*(x, y, w, h) {
            for (let d = 0; d < w + h - 1; d++) {
                const startCol = Math.max(0, (w - 1) - d);
                const endCol = Math.min(w - 1, (w + h - 2) - d);
                for (let col = startCol; col <= endCol; col++) {
                    yield {x: x + col, y: y + (d - ((w - 1) - col))};
                }
            }
        },
        "diagonal-lb-rt": async function*(x, y, w, h) {
            for (let d = 0; d < w + h - 1; d++) {
                const startCol = Math.max(0, d - h + 1);
                const endCol = Math.min(d, w - 1);
                for (let col = startCol; col <= endCol; col++) {
                    yield {x: x + col, y: y + (h - 1) - (d - col)};
                }
            }
        },
        "diagonal-rb-lt": async function*(x, y, w, h) {
            for (let d = 0; d < w + h - 1; d++) {
                const startCol = Math.max(0, (w - 1) - d);
                const endCol = Math.min(w - 1, (w + h - 2) - d);
                for (let col = startCol; col <= endCol; col++) {
                    yield {x: x + col, y: y + (h - 1) - (d - ((w - 1) - col))};
                }
            }
        }
    };

    // ===== ФУНКЦИИ ЭКСПОРТА/ИМПОРТА ===== //
    function exportConfig() {
        const config = {
            settings: {
                mode: document.querySelector('#mode-select').value,
                color: SETTINGS.color,
                x: parseInt(document.querySelector('#start-x').value),
                y: parseInt(document.querySelector('#start-y').value),
                w: parseInt(document.querySelector('#width').value),
                h: parseInt(document.querySelector('#height').value),
                delay: parseInt(document.querySelector('#delay').value)
            }
        };

        const blob = new Blob([JSON.stringify(config, null, 2)], {type: 'application/json'});
        const url = URL.createObjectURL(blob);

        const a = document.createElement('a');
        a.href = url;
        a.download = `pixelbot_config.json`;
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(url);
    }

    function importConfig(event) {
        const file = event.target.files[0];
        if (!file) return;

        const reader = new FileReader();
        reader.onload = (e) => {
            try {
                const config = JSON.parse(e.target.result);
                if (!config.settings) {
                    throw new Error("Некорректный формат конфига");
                }

                document.querySelector('#mode-select').value = config.settings.mode;
                document.querySelector('#color-select').value = config.settings.color;
                document.querySelector('#start-x').value = config.settings.x;
                document.querySelector('#start-y').value = config.settings.y;
                document.querySelector('#width').value = config.settings.w;
                document.querySelector('#height').value = config.settings.h;
                document.querySelector('#delay').value = config.settings.delay;

                SETTINGS.color = config.settings.color;
                const colorIndex = Object.keys(SETTINGS.colors).indexOf(SETTINGS.color);
                document.querySelector('#current-color').style.background = SETTINGS.colorHexes[colorIndex];

                alert("Конфиг успешно импортирован!");
            } catch (error) {
                console.error("Ошибка импорта:", error);
                alert("Ошибка при импорте конфига: " + error.message);
            }
        };
        reader.readAsText(file);
        event.target.value = '';
    }

    // ===== ПАНЕЛЬ УПРАВЛЕНИЯ ===== //
    function createControlPanel() {
        const panel = document.createElement('div');
        panel.id = 'pixel-bot-panel';
        panel.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            background: white;
            padding: 15px;
            border: 1px solid #ddd;
            border-radius: 10px;
            z-index: 9999;
            font-family: Arial, sans-serif;
            font-size: 13px;
            width: 280px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            user-select: none;
        `;

        panel.innerHTML = `
        <div id="panel-header" style="cursor: move; padding: 8px 10px; margin: -15px -15px 15px -15px; background: #f5f5f5; border-radius: 8px 8px 0 0; font-size: 14px; display: flex; justify-content: space-between; align-items: center;">
            <strong>🎨 PixelBot v2.0</strong>
            <span id="current-color" style="display: inline-block; width: 18px; height: 18px; background: ${SETTINGS.colorHexes[Object.keys(SETTINGS.colors).indexOf(SETTINGS.color)]}; border: 1px solid #ccc; border-radius: 3px;"></span>
        </div>

        <div style="margin-bottom: 12px;">
            <label style="display: block; margin-bottom: 3px; font-weight: bold; color: #555;">Режим рисования:</label>
            <select id="mode-select" style="width: 100%; padding: 6px; border: 1px solid #ddd; border-radius: 4px; font-size: 13px; background: white;">
                ${Object.entries(SETTINGS.modes).map(([key, desc]) => 
                    `<option value="${key}">${desc}</option>`).join('')}
            </select>
        </div>

        <div style="margin-bottom: 12px;">
            <label style="display: block; margin-bottom: 3px; font-weight: bold; color: #555;">Цвет:</label>
            <select id="color-select" style="width: 100%; padding: 6px; border: 1px solid #ddd; border-radius: 4px; font-size: 13px; background: white;">
                ${Object.keys(SETTINGS.colors).map((key, index) => 
                    `<option value="${key}" ${key === SETTINGS.color ? 'selected' : ''}>
                        ${SETTINGS.colors[key]}
                        <span style="float: right; display: inline-block; width: 12px; height: 12px; background: ${SETTINGS.colorHexes[index]}; border: 1px solid #ccc; border-radius: 2px;"></span>
                    </option>`).join('')}
            </select>
        </div>

        <div style="margin-bottom: 12px;">
            <label style="display: block; margin-bottom: 3px; font-weight: bold; color: #555;">Область рисования:</label>
            <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; font-size: 13px;">
                <div>
                    <label style="font-size: 0.9em; color: #666;">X:</label>
                    <input type="number" id="start-x" value="0" style="width: 100%; padding: 6px; border: 1px solid #ddd; border-radius: 4px;">
                </div>
                <div>
                    <label style="font-size: 0.9em; color: #666;">Y:</label>
                    <input type="number" id="start-y" value="0" style="width: 100%; padding: 6px; border: 1px solid #ddd; border-radius: 4px;">
                </div>
                <div>
                    <label style="font-size: 0.9em; color: #666;">Ширина:</label>
                    <input type="number" id="width" value="10" style="width: 100%; padding: 6px; border: 1px solid #ddd; border-radius: 4px;">
                </div>
                <div>
                    <label style="font-size: 0.9em; color: #666;">Высота:</label>
                    <input type="number" id="height" value="10" style="width: 100%; padding: 6px; border: 1px solid #ddd; border-radius: 4px;">
                </div>
            </div>
        </div>

        <div style="margin-bottom: 15px;">
            <label style="display: block; margin-bottom: 3px; font-weight: bold; color: #555;">Задержка (мс):</label>
            <input type="number" id="delay" value="1000" style="width: 100%; padding: 6px; border: 1px solid #ddd; border-radius: 4px;">
        </div>

        <div style="margin: 15px 0; border-top: 1px solid #eee; padding-top: 15px;">
            <div style="display: flex; gap: 8px;">
                <button id="export-config" style="flex: 1; padding: 6px; background: #2196F3; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px;">Экспорт</button>
                <label for="import-file" style="flex: 1; padding: 6px; background: #FF9800; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; text-align: center;">
                    Импорт
                    <input type="file" id="import-file" accept=".json" style="display: none;">
                </label>
            </div>
        </div>

        <div style="margin-bottom: 15px;">
            <div id="progress-text" style="font-size: 12px; text-align: center; margin-bottom: 5px; color: #555;">Готов к работе</div>
            <div id="progress-bar" style="height: 6px; background: #eee; border-radius: 3px;">
                <div id="progress-fill" style="height: 100%; width: 0%; background: #4CAF50; border-radius: 3px; transition: width 0.3s;"></div>
            </div>
        </div>

        <div style="display: flex; gap: 8px;">
            <button id="start-btn" style="flex: 1; padding: 8px; background: #2196F3; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; font-weight: bold;">
                ▶ Начать рисование
            </button>
            <button id="stop-btn" style="flex: 1; padding: 8px; background: #f44336; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; display: none;">
                ⏹ Остановить
            </button>
        </div>
        `;

        // Элементы управления
        const startBtn = panel.querySelector('#start-btn');
        const stopBtn = panel.querySelector('#stop-btn');
        const colorSelect = panel.querySelector('#color-select');
        const currentColorIndicator = panel.querySelector('#current-color');
        const exportBtn = panel.querySelector('#export-config');
        const importInput = panel.querySelector('#import-file');

        // Обработчики событий
        colorSelect.addEventListener('change', (e) => {
            SETTINGS.color = e.target.value;
            const colorIndex = Object.keys(SETTINGS.colors).indexOf(SETTINGS.color);
            currentColorIndicator.style.background = SETTINGS.colorHexes[colorIndex];
        });

        exportBtn.addEventListener('click', exportConfig);
        importInput.addEventListener('change', importConfig);

        // Перетаскивание панели
        const header = panel.querySelector('#panel-header');
        let isDragging = false;
        let offsetX, offsetY;

        header.addEventListener('mousedown', (e) => {
            isDragging = true;
            offsetX = e.clientX - panel.getBoundingClientRect().left;
            offsetY = e.clientY - panel.getBoundingClientRect().top;
            panel.style.cursor = 'grabbing';
            e.preventDefault();
        });

        document.addEventListener('mousemove', (e) => {
            if (!isDragging) return;
            panel.style.left = `${e.clientX - offsetX}px`;
            panel.style.top = `${e.clientY - offsetY}px`;
        });

        document.addEventListener('mouseup', () => {
            isDragging = false;
            panel.style.cursor = 'default';
        });

        // Управление рисованием
        startBtn.addEventListener('click', async () => {
            if (SETTINGS.isRunning) return;

            const mode = panel.querySelector('#mode-select').value;
            const config = {
                x: parseInt(panel.querySelector('#start-x').value),
                y: parseInt(panel.querySelector('#start-y').value),
                w: parseInt(panel.querySelector('#width').value),
                h: parseInt(panel.querySelector('#height').value),
                delay: parseInt(panel.querySelector('#delay').value)
            };

            if (isNaN(config.x) || isNaN(config.y) || isNaN(config.w) || isNaN(config.h) || isNaN(config.delay)) {
                alert('Пожалуйста, введите корректные значения!');
                return;
            }

            const totalPixels = config.w * config.h;
            let processedPixels = 0;

            SETTINGS.delay = config.delay;
            SETTINGS.isRunning = true;
            startBtn.style.display = 'none';
            stopBtn.style.display = 'block';
            SETTINGS.abortController = new AbortController();

            try {
                const generator = drawingModes[mode](config.x, config.y, config.w, config.h);

                for await (const pixel of generator) {
                    if (SETTINGS.abortController.signal.aborted) break;

                    await placePixel(pixel.x, pixel.y, SETTINGS.color, SETTINGS.abortController.signal);

                    processedPixels++;
                    const progress = Math.round((processedPixels / totalPixels) * 100);
                    panel.querySelector('#progress-fill').style.width = `${progress}%`;
                    panel.querySelector('#progress-text').textContent = `Прогресс: ${progress}% (${processedPixels}/${totalPixels})`;

                    await new Promise(r => setTimeout(r, SETTINGS.delay));
                }

                panel.querySelector('#progress-text').textContent = "Рисование завершено!";
            } catch (err) {
                if (err.name !== 'AbortError') {
                    console.error(err);
                    panel.querySelector('#progress-text').textContent = "Ошибка: " + err.message;
                } else {
                    panel.querySelector('#progress-text').textContent = "Рисование прервано";
                }
            } finally {
                SETTINGS.isRunning = false;
                startBtn.style.display = 'block';
                stopBtn.style.display = 'none';
            }
        });

        stopBtn.addEventListener('click', () => {
            if (SETTINGS.abortController) {
                SETTINGS.abortController.abort();
            }
        });

        document.body.appendChild(panel);
    }

    // Добавляем стили для предотвращения конфликтов
    GM_addStyle(`
        #pixel-bot-panel * {
            box-sizing: border-box;
        }
        #pixel-bot-panel input[type="number"] {
            -moz-appearance: textfield;
        }
        #pixel-bot-panel input[type="number"]::-webkit-outer-spin-button,
        #pixel-bot-panel input[type="number"]::-webkit-inner-spin-button {
            -webkit-appearance: none;
            margin: 0;
        }
    `);

    // Ждем загрузки страницы и добавляем панель
    if (document.readyState === 'complete') {
        createControlPanel();
    } else {
        window.addEventListener('load', createControlPanel);
    }
})();