Бот для автоматического рисования на Pixel Battles
当前为
// ==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);
}
})();