Змейка с частицами, скинами и ежедневными квестами
// ==UserScript==
// @name Ultra Snake: Customization & Effects
// @namespace http://tampermonkey.net/
// @version 1.2
// @description Змейка с частицами, скинами и ежедневными квестами
// @author AI
// @license MIT
// @match *://*/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
const GRID_SIZE = 20;
const TILE_COUNT = 20;
// Стили
const style = document.createElement('style');
style.innerHTML = `
#snake-game-launcher {
position: fixed; bottom: 20px; right: 20px; width: 60px; height: 60px;
background: linear-gradient(135deg, #00ff88, #00bdff);
border-radius: 50%; cursor: pointer; box-shadow: 0 4px 15px rgba(0,0,0,0.3);
display: flex; align-items: center; justify-content: center; z-index: 999999;
transition: transform 0.3s;
}
#snake-game-launcher:hover { transform: scale(1.1); }
#snake-game-container {
position: fixed; bottom: 90px; right: 20px; width: 400px; height: 520px;
background: #121212; border-radius: 20px; box-shadow: 0 15px 50px rgba(0,0,0,0.6);
display: none; flex-direction: column; overflow: hidden; z-index: 999999;
font-family: 'Segoe UI', sans-serif; border: 1px solid #333; color: white;
}
#snake-header {
padding: 15px; background: #1a1a1a; display: flex;
justify-content: space-between; align-items: center; border-bottom: 1px solid #333;
}
.snake-screen {
flex: 1; display: none; flex-direction: column; align-items: center;
justify-content: center; padding: 20px; text-align: center; overflow-y: auto;
}
.snake-screen.active { display: flex; }
.snake-btn {
background: linear-gradient(135deg, #00ff88, #00bdff); border: none;
padding: 12px 25px; border-radius: 12px; color: #111; font-weight: bold;
cursor: pointer; margin: 6px; transition: all 0.2s; min-width: 160px;
text-transform: uppercase; font-size: 12px;
}
.snake-btn:hover { filter: brightness(1.2); transform: translateY(-1px); }
.snake-btn:disabled { background: #444; color: #888; cursor: not-allowed; }
.skin-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 15px; }
.skin-item {
background: #222; padding: 10px; border-radius: 10px; border: 2px solid transparent;
cursor: pointer; transition: 0.2s;
}
.skin-item.active { border-color: #00ff88; background: #2a2a2a; }
.skin-preview { width: 30px; height: 30px; border-radius: 5px; margin: 5px auto; }
.task-card {
background: #1a1a1a; width: 90%; padding: 10px; border-radius: 10px;
margin: 5px 0; border-left: 4px solid #00ff88; text-align: left;
}
.task-progress { font-size: 12px; color: #00ff88; }
#snake-canvas-container { position: relative; width: 400px; height: 400px; display: none; }
canvas { display: block; background-color: #0a0a0a; }
#snake-ui-overlay {
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.9); display: none; flex-direction: column;
align-items: center; justify-content: center;
}
`;
document.head.appendChild(style);
// HTML Структура
const container = document.createElement('div');
container.id = 'snake-game-container';
container.innerHTML = `
<div id="snake-header">
<span id="user-display">Snake Elite</span>
<div id="snake-score-display" style="display:none; color: #00ff88; font-weight:bold;">0</div>
<span class="close-snake" style="cursor:pointer; font-size:20px;">×</span>
</div>
<div id="screen-profile" class="snake-screen">
<h2>Создание профиля</h2>
<input type="text" id="snake-nick-input" class="snake-btn" style="background:#222; color:white; text-transform:none;" placeholder="Никнейм..." maxlength="12">
<button class="snake-btn" id="btn-save-profile">Начать приключение</button>
</div>
<div id="screen-menu" class="snake-screen">
<h1 id="welcome-msg">Привет!</h1>
<button class="snake-btn" id="btn-start-game">Играть</button>
<button class="snake-btn" id="btn-show-skins">🎨 Скины</button>
<button class="snake-btn" id="btn-show-achievements">🏆 Достижения</button>
</div>
<div id="screen-skins" class="snake-screen">
<h2>Выберите стиль</h2>
<div class="skin-grid" id="skin-list"></div>
<button class="snake-btn" id="btn-back-menu-skins">Назад</button>
</div>
<div id="screen-achievements" class="snake-screen">
<h2>Ваш прогресс</h2>
<div id="achievements-data" style="margin-bottom:15px;"></div>
<h3>Ежедневные задачи</h3>
<div id="tasks-list"></div>
<button class="snake-btn" id="btn-back-menu-ach">Назад</button>
</div>
<div id="snake-canvas-container">
<canvas id="snakeCanvas" width="400" height="400"></canvas>
<div id="snake-ui-overlay">
<h2 id="snake-result-title">Игра окончена</h2>
<p id="snake-final-score">Счет: 0</p>
<button class="snake-btn" id="snake-restart">Снова</button>
<button class="snake-btn" id="snake-to-menu">В меню</button>
</div>
</div>
`;
document.body.appendChild(container);
// Параметры игры и частицы
const canvas = document.getElementById('snakeCanvas');
const ctx = canvas.getContext('2d');
let snake = [], food = {}, dx = 1, dy = 0, score = 0, gameInterval = null;
let particles = [];
const SKINS = [
{ id: 'classic', name: 'Неон', color: '#00ff88', req: 0 },
{ id: 'gold', name: 'Золото', color: '#ffd700', req: 150 },
{ id: 'plasma', name: 'Плазма', color: '#ff00ff', req: 300 },
{ id: 'rainbow', name: 'Радуга', color: 'rainbow', req: 500 }
];
const Storage = {
get: (key, def) => JSON.parse(localStorage.getItem(key) || JSON.stringify(def)),
set: (key, val) => localStorage.setItem(key, JSON.stringify(val)),
updateTasks: (scoreInc) => {
let data = Storage.get('snake_tasks', { date: new Date().toDateString(), eaten: 0, games: 0 });
if (data.date !== new Date().toDateString()) {
data = { date: new Date().toDateString(), eaten: 0, games: 0 };
}
if (scoreInc > 0) data.eaten++;
Storage.set('snake_tasks', data);
}
};
function createParticles(x, y, color) {
for (let i = 0; i < 12; i++) {
particles.push({
x: x * GRID_SIZE + 10,
y: y * GRID_SIZE + 10,
vx: (Math.random() - 0.5) * 8,
vy: (Math.random() - 0.5) * 8,
life: 1.0,
color: color
});
}
}
function drawParticles() {
particles.forEach((p, i) => {
p.x += p.vx; p.y += p.vy;
p.life -= 0.05;
if (p.life <= 0) particles.splice(i, 1);
else {
ctx.fillStyle = p.color;
ctx.globalAlpha = p.life;
ctx.fillRect(p.x, p.y, 4, 4);
}
});
ctx.globalAlpha = 1.0;
}
function render() {
ctx.fillStyle = '#0a0a0a';
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawParticles();
const currentSkinId = Storage.get('snake_current_skin', 'classic');
const skin = SKINS.find(s => s.id === currentSkinId);
// Еда
ctx.fillStyle = '#ff4444';
ctx.shadowBlur = 15; ctx.shadowColor = '#ff4444';
ctx.beginPath();
ctx.arc(food.x * GRID_SIZE + 10, food.y * GRID_SIZE + 10, 7, 0, Math.PI * 2);
ctx.fill();
ctx.shadowBlur = 0;
// Змейка
snake.forEach((seg, i) => {
let color = skin.color;
if (color === 'rainbow') color = `hsl(${(Date.now() / 10 + i * 20) % 360}, 100%, 50%)`;
ctx.fillStyle = color;
if (i === 0) {
ctx.shadowBlur = 10; ctx.shadowColor = color;
}
ctx.beginPath();
ctx.roundRect(seg.x * GRID_SIZE + 1, seg.y * GRID_SIZE + 1, 18, 18, 4);
ctx.fill();
ctx.shadowBlur = 0;
});
}
function initGame() {
showScreen('game');
snake = [{x: 10, y: 10}, {x: 9, y: 10}];
generateFood();
dx = 1; dy = 0; score = 0;
document.getElementById('snake-score-display').innerText = 0;
document.getElementById('snake-ui-overlay').style.display = 'none';
let tasks = Storage.get('snake_tasks', { date: new Date().toDateString(), eaten: 0, games: 0 });
tasks.games++;
Storage.set('snake_tasks', tasks);
if (gameInterval) clearInterval(gameInterval);
gameInterval = setInterval(() => {
move();
render();
}, 100);
}
function move() {
const head = { x: snake[0].x + dx, y: snake[0].y + dy };
if (head.x < 0 || head.x >= TILE_COUNT || head.y < 0 || head.y >= TILE_COUNT ||
snake.some(s => s.x === head.x && s.y === head.y)) {
return gameOver();
}
snake.unshift(head);
if (head.x === food.x && head.y === food.y) {
score += 10;
document.getElementById('snake-score-display').innerText = score;
const currentSkin = SKINS.find(s => s.id === Storage.get('snake_current_skin', 'classic'));
createParticles(food.x, food.y, currentSkin.color === 'rainbow' ? '#fff' : currentSkin.color);
Storage.updateTasks(10);
generateFood();
} else {
snake.pop();
}
}
function generateFood() {
food = { x: Math.floor(Math.random() * TILE_COUNT), y: Math.floor(Math.random() * TILE_COUNT) };
if (snake.some(s => s.x === food.x && s.y === food.y)) generateFood();
}
function gameOver() {
clearInterval(gameInterval);
const highscore = Storage.get('snake_highscore', 0);
if (score > highscore) Storage.set('snake_highscore', score);
document.getElementById('snake-final-score').innerText = `Счет: ${score}`;
document.getElementById('snake-ui-overlay').style.display = 'flex';
}
function showScreen(id) {
document.querySelectorAll('.snake-screen').forEach(s => s.classList.remove('active'));
const canvasCont = document.getElementById('snake-canvas-container');
const scoreDisp = document.getElementById('snake-score-display');
canvasCont.style.display = id === 'game' ? 'block' : 'none';
scoreDisp.style.display = id === 'game' ? 'block' : 'none';
if (id !== 'game') document.getElementById(id).classList.add('active');
}
// Обработка скинов
function updateSkinList() {
const list = document.getElementById('skin-list');
const highscore = Storage.get('snake_highscore', 0);
const current = Storage.get('snake_current_skin', 'classic');
list.innerHTML = SKINS.map(s => {
const locked = highscore < s.req;
return `
<div class="skin-item ${current === s.id ? 'active' : ''}" data-id="${s.id}" ${locked ? 'style="opacity:0.5"' : ''}>
<div class="skin-preview" style="background:${s.color === 'rainbow' ? 'linear-gradient(to right, red, orange, yellow, green, blue, violet)' : s.color}"></div>
<div style="font-size:12px">${s.name}</div>
<div style="font-size:10px; color:${locked ? '#ff4444' : '#00ff88'}">${locked ? 'Рекорд ' + s.req : 'Открыто'}</div>
</div>
`;
}).join('');
document.querySelectorAll('.skin-item').forEach(el => {
el.onclick = () => {
const s = SKINS.find(x => x.id === el.dataset.id);
if (highscore >= s.req) {
Storage.set('snake_current_skin', s.id);
updateSkinList();
}
};
});
}
// Инициализация интерфейса
document.getElementById('snake-game-launcher').onclick = () => {
const cont = document.getElementById('snake-game-container');
cont.style.display = cont.style.display === 'flex' ? 'none' : 'flex';
const nick = Storage.get('snake_nick', null);
if (!nick) showScreen('screen-profile');
else {
document.getElementById('welcome-msg').innerText = `Привет, ${nick}!`;
showScreen('screen-menu');
}
};
document.getElementById('btn-save-profile').onclick = () => {
const val = document.getElementById('snake-nick-input').value.trim();
if (val) { Storage.set('snake_nick', val); showScreen('screen-menu'); }
};
document.getElementById('btn-start-game').onclick = initGame;
document.getElementById('snake-restart').onclick = initGame;
document.getElementById('btn-show-skins').onclick = () => { showScreen('screen-skins'); updateSkinList(); };
document.getElementById('btn-back-menu-skins').onclick = () => showScreen('screen-menu');
document.getElementById('btn-back-menu-ach').onclick = () => showScreen('screen-menu');
document.getElementById('snake-to-menu').onclick = () => showScreen('screen-menu');
document.querySelector('.close-snake').onclick = () => {
document.getElementById('snake-game-container').style.display = 'none';
clearInterval(gameInterval);
};
document.getElementById('btn-show-achievements').onclick = () => {
const highscore = Storage.get('snake_highscore', 0);
const tasks = Storage.get('snake_tasks', { eaten: 0, games: 0 });
document.getElementById('achievements-data').innerHTML = `Рекорд: <b style="color:#00ff88">${highscore}</b>`;
document.getElementById('tasks-list').innerHTML = `
<div class="task-card">
<div>Собрать 20 еды за день</div>
<div class="task-progress">${Math.min(tasks.eaten, 20)} / 20</div>
</div>
<div class="task-card" style="border-color: #00bdff">
<div>Сыграть 5 раз</div>
<div class="task-progress">${Math.min(tasks.games, 5)} / 5</div>
</div>
`;
showScreen('screen-achievements');
};
// Управление
window.addEventListener('keydown', (e) => {
const keys = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'KeyW', 'KeyS', 'KeyA', 'KeyD'];
if (keys.includes(e.code) && document.getElementById('snake-canvas-container').style.display === 'block') {
e.preventDefault();
switch(e.code) {
case 'ArrowUp': case 'KeyW': if (dy !== 1) { dx = 0; dy = -1; } break;
case 'ArrowDown': case 'KeyS': if (dy !== -1) { dx = 0; dy = 1; } break;
case 'ArrowLeft': case 'KeyA': if (dx !== 1) { dx = -1; dy = 0; } break;
case 'ArrowRight': case 'KeyD': if (dx !== -1) { dx = 1; dy = 0; } break;
}
}
});
const launcher = document.createElement('div');
launcher.id = 'snake-game-launcher';
launcher.innerHTML = `<svg viewBox="0 0 24 24" style="width:30px; fill:white;"><path d="M7,5V9H17V5H7M7,11V15H17V11H7M7,17V21H17V17H7Z" /></svg>`;
document.body.appendChild(launcher);
})();