242 lines
7.6 KiB
JavaScript
242 lines
7.6 KiB
JavaScript
document.addEventListener('DOMContentLoaded', () => {
|
|
const canvas = document.getElementById('gameCanvas');
|
|
const ctx = canvas.getContext('2d');
|
|
const scoreEl = document.getElementById('score');
|
|
const bestScoreEl = document.getElementById('best-score');
|
|
const playBtn = document.getElementById('play-btn');
|
|
const startScreen = document.getElementById('start-screen');
|
|
|
|
const settingsBtn = document.getElementById('settings-btn');
|
|
const closeSettingsBtn = document.getElementById('close-settings-btn');
|
|
const settingsBackdrop = document.getElementById('settings-backdrop');
|
|
const difficultyBtns = document.getElementById('difficulty-btns');
|
|
const themeToggle = document.getElementById('theme-toggle');
|
|
|
|
const gridSize = 20;
|
|
canvas.width = 600;
|
|
canvas.height = 600;
|
|
|
|
let snake = [{ x: 15, y: 15 }];
|
|
let food = {};
|
|
let direction = 'right';
|
|
let score = 0;
|
|
let bestScore = 0;
|
|
let speed = 200;
|
|
let gameInterval;
|
|
let levelThreshold = 5;
|
|
let gameRunning = false;
|
|
let isGamePausedBySettings = false;
|
|
|
|
// --- Settings ---
|
|
function openSettings() {
|
|
if (gameRunning) {
|
|
clearInterval(gameInterval);
|
|
isGamePausedBySettings = true;
|
|
}
|
|
settingsBackdrop.classList.remove('hidden');
|
|
}
|
|
|
|
function closeSettings() {
|
|
if (isGamePausedBySettings) {
|
|
gameInterval = setInterval(update, speed);
|
|
isGamePausedBySettings = false;
|
|
}
|
|
settingsBackdrop.classList.add('hidden');
|
|
}
|
|
|
|
function setDifficulty(difficulty) {
|
|
switch (difficulty) {
|
|
case 'easy':
|
|
speed = 250;
|
|
break;
|
|
case 'normal':
|
|
speed = 150;
|
|
break;
|
|
case 'hard':
|
|
speed = 75;
|
|
break;
|
|
}
|
|
document.querySelectorAll('.difficulty-btn').forEach(btn => {
|
|
btn.classList.toggle('active', btn.dataset.difficulty === difficulty);
|
|
});
|
|
localStorage.setItem('snakeDifficulty', difficulty);
|
|
}
|
|
|
|
function applyTheme(theme) {
|
|
document.body.classList.toggle('light-theme', theme === 'light');
|
|
document.body.classList.toggle('dark-theme', theme === 'dark');
|
|
themeToggle.checked = theme === 'dark';
|
|
localStorage.setItem('snakeTheme', theme);
|
|
}
|
|
|
|
settingsBtn.addEventListener('click', openSettings);
|
|
closeSettingsBtn.addEventListener('click', closeSettings);
|
|
settingsBackdrop.addEventListener('click', (e) => {
|
|
if (e.target === settingsBackdrop) {
|
|
closeSettings();
|
|
}
|
|
});
|
|
|
|
difficultyBtns.addEventListener('click', (e) => {
|
|
if (e.target.classList.contains('difficulty-btn')) {
|
|
setDifficulty(e.target.dataset.difficulty);
|
|
}
|
|
});
|
|
|
|
themeToggle.addEventListener('change', () => {
|
|
applyTheme(themeToggle.checked ? 'dark' : 'light');
|
|
});
|
|
|
|
const savedDifficulty = localStorage.getItem('snakeDifficulty') || 'normal';
|
|
setDifficulty(savedDifficulty);
|
|
|
|
const savedTheme = localStorage.getItem('snakeTheme') || 'dark';
|
|
applyTheme(savedTheme);
|
|
|
|
bestScore = localStorage.getItem('snakeBestScore') || 0;
|
|
bestScoreEl.textContent = bestScore;
|
|
|
|
|
|
// --- Game Logic ---
|
|
function placeFood() {
|
|
food = {
|
|
x: Math.floor(Math.random() * (canvas.width / gridSize)),
|
|
y: Math.floor(Math.random() * (canvas.height / gridSize))
|
|
};
|
|
for (let segment of snake) {
|
|
if (segment.x === food.x && segment.y === food.y) {
|
|
placeFood();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
function draw() {
|
|
const canvasBg = getComputedStyle(document.body).getPropertyValue('--canvas-bg').trim();
|
|
|
|
ctx.fillStyle = canvasBg;
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
|
|
snake.forEach((segment, index) => {
|
|
const hue = (segment.x * 5 + segment.y * 5) % 360;
|
|
ctx.fillStyle = `hsl(${hue}, 100%, 50%)`;
|
|
if (index === 0) {
|
|
ctx.fillRect(segment.x * gridSize, segment.y * gridSize, gridSize, gridSize);
|
|
} else {
|
|
ctx.fillRect(segment.x * gridSize + 1, segment.y * gridSize + 1, gridSize - 2, gridSize - 2);
|
|
}
|
|
});
|
|
|
|
const foodX = food.x * gridSize;
|
|
const foodY = food.y * gridSize;
|
|
const foodRadius = gridSize / 2.5;
|
|
|
|
ctx.save();
|
|
|
|
ctx.shadowColor = '#A020F0';
|
|
ctx.shadowBlur = 20;
|
|
|
|
const gradient = ctx.createRadialGradient(
|
|
foodX + foodRadius, foodY + foodRadius, foodRadius * 0.1,
|
|
foodX + foodRadius, foodY + foodRadius, foodRadius
|
|
);
|
|
gradient.addColorStop(0, '#E0B0FF');
|
|
gradient.addColorStop(1, '#8A2BE2');
|
|
|
|
ctx.fillStyle = gradient;
|
|
ctx.beginPath();
|
|
ctx.arc(foodX + gridSize / 2, foodY + gridSize / 2, foodRadius, 0, 2 * Math.PI);
|
|
ctx.fill();
|
|
|
|
ctx.restore();
|
|
}
|
|
|
|
function update() {
|
|
if (!gameRunning) return;
|
|
|
|
const head = { ...snake[0] };
|
|
switch (direction) {
|
|
case 'up': head.y--; break;
|
|
case 'down': head.y++; break;
|
|
case 'left': head.x--; break;
|
|
case 'right': head.x++; break;
|
|
}
|
|
|
|
if (head.x < 0 || head.x >= canvas.width / gridSize || head.y < 0 || head.y >= canvas.height / gridSize) {
|
|
return gameOver();
|
|
}
|
|
|
|
for (let i = 1; i < snake.length; i++) {
|
|
if (head.x === snake[i].x && head.y === snake[i].y) {
|
|
return gameOver();
|
|
}
|
|
}
|
|
|
|
snake.unshift(head);
|
|
|
|
if (head.x === food.x && head.y === food.y) {
|
|
score++;
|
|
scoreEl.textContent = score;
|
|
placeFood();
|
|
if (score % levelThreshold === 0) {
|
|
increaseSpeed();
|
|
}
|
|
} else {
|
|
snake.pop();
|
|
}
|
|
|
|
draw();
|
|
}
|
|
|
|
function increaseSpeed() {
|
|
clearInterval(gameInterval);
|
|
speed = Math.max(50, speed * 0.9);
|
|
gameInterval = setInterval(update, speed);
|
|
}
|
|
|
|
function gameOver() {
|
|
clearInterval(gameInterval);
|
|
isGamePausedBySettings = false;
|
|
|
|
if (score > bestScore) {
|
|
bestScore = score;
|
|
localStorage.setItem('snakeBestScore', bestScore);
|
|
bestScoreEl.textContent = bestScore;
|
|
}
|
|
|
|
startScreen.style.display = 'flex';
|
|
startScreen.querySelector('h1').textContent = 'Game Over';
|
|
playBtn.textContent = 'Play Again';
|
|
gameRunning = false;
|
|
}
|
|
|
|
function startGame() {
|
|
snake = [{ x: 15, y: 15 }];
|
|
direction = 'right';
|
|
score = 0;
|
|
scoreEl.textContent = score;
|
|
gameRunning = true;
|
|
isGamePausedBySettings = false;
|
|
startScreen.style.display = 'none';
|
|
|
|
const currentDifficulty = localStorage.getItem('snakeDifficulty') || 'normal';
|
|
setDifficulty(currentDifficulty);
|
|
|
|
placeFood();
|
|
clearInterval(gameInterval);
|
|
gameInterval = setInterval(update, speed);
|
|
}
|
|
|
|
playBtn.addEventListener('click', startGame);
|
|
|
|
document.addEventListener('keydown', e => {
|
|
const key = e.key;
|
|
if ((key === 'ArrowUp' || key.toLowerCase() === 'w') && direction !== 'down') direction = 'up';
|
|
if ((key === 'ArrowDown' || key.toLowerCase() === 's') && direction !== 'up') direction = 'down';
|
|
if ((key === 'ArrowLeft' || key.toLowerCase() === 'a') && direction !== 'right') direction = 'left';
|
|
if ((key === 'ArrowRight' || key.toLowerCase() === 'd') && direction !== 'left') direction = 'right';
|
|
});
|
|
|
|
placeFood();
|
|
draw();
|
|
}); |