diff --git a/Domains/Frontend/MiniProjects/snake-game/index.html b/Domains/Frontend/MiniProjects/snake-game/index.html index 654a8d90..7bfbc75b 100644 --- a/Domains/Frontend/MiniProjects/snake-game/index.html +++ b/Domains/Frontend/MiniProjects/snake-game/index.html @@ -3,19 +3,54 @@ - Snake Game - + Snake Game + + -

Snake Game

-

Use the arrow keys to move the snake and eat the food!

-
-

Score: 0

- + +
+ + +
+

SNAKE GAME

+
+
+ SCORE + 0 +
+
+ HIGH SCORE + 0 +
+
+
+ + +
+ + + +
+
+

Press any arrow key to start

+
+
+ + +
+
+

GAME OVER

+

Your Score: 0

+ +
+
+
+
- \ No newline at end of file + diff --git a/Domains/Frontend/MiniProjects/snake-game/script.js b/Domains/Frontend/MiniProjects/snake-game/script.js index ee74df60..2af175e2 100644 --- a/Domains/Frontend/MiniProjects/snake-game/script.js +++ b/Domains/Frontend/MiniProjects/snake-game/script.js @@ -1,68 +1,168 @@ +// --- DOM Elements --- const canvas = document.getElementById("gameCanvas"); const ctx = canvas.getContext("2d"); +const scoreValueEl = document.getElementById("score-value"); +const highScoreValueEl = document.getElementById("high-score-value"); +const gameOverModal = document.getElementById("game-over-modal"); +const finalScoreEl = document.getElementById("final-score"); +const playAgainButton = document.getElementById("play-again-button"); +const startOverlay = document.getElementById("start-overlay"); -let snake = [{ x: 9, y: 9 }]; -let food = { x: 5, y: 5 }; -let score = 0; -let direction = { x: 0, y: 0 }; +// --- Game Constants --- +const GRID_SIZE = 20; // Size of each cell +const CANVAS_WIDTH = 600; +const CANVAS_HEIGHT = 400; +const COLS = CANVAS_WIDTH / GRID_SIZE; // 30 +const ROWS = CANVAS_HEIGHT / GRID_SIZE; // 20 +// --- Game State --- +let snake, food, score, highScore, direction, gameLoopTimeout, gameRunning; + +// --- Initialization --- +function init() { + // Set canvas dimensions + canvas.width = CANVAS_WIDTH; + canvas.height = CANVAS_HEIGHT; + + // Initial game state + snake = [{ x: 9, y: 9 }]; // Start snake + score = 0; + direction = { x: 0, y: 0 }; // Not moving + gameRunning = false; + + // Load high score from local storage + highScore = localStorage.getItem('neonSnakeHighScore') || 0; + highScoreValueEl.textContent = highScore; + scoreValueEl.textContent = score; + + // Hide game over modal, show start overlay + gameOverModal.style.display = 'none'; + startOverlay.style.display = 'flex'; + + placeFood(); + draw(); // Draw initial state (grid, snake, food) +} + +// --- Main Game Loop --- function gameLoop() { - update(); - draw(); - setTimeout(gameLoop, 300); + // Clear previous loop + clearTimeout(gameLoopTimeout); + if (!gameRunning) return; // Stop loop if game isn't running + + if (update()) { + draw(); + } + + // Control game speed + gameLoopTimeout = setTimeout(gameLoop, 100); } +// --- Update Game State --- function update() { + if (direction.x === 0 && direction.y === 0) return true; // Don't update if not started + // Move the snake const head = { x: snake[0].x + direction.x, y: snake[0].y + direction.y }; snake.unshift(head); + // Check for collisions + if (head.x < 0 || head.x >= COLS || head.y < 0 || head.y >= ROWS || isCollidingWithSelf(head)) { + resetGame(); + return false; // Stop update + } + // Check for food collision if (head.x === food.x && head.y === food.y) { score++; + scoreValueEl.textContent = score; // Update score in HTML placeFood(); } else { - snake.pop(); - } - - // Check for wall collisions - if (head.x < 0 || head.x >= 20 || head.y < 0 || head.y >= 20 || isCollidingWithSelf(head)) { - resetGame(); + snake.pop(); // Remove tail } + return true; // Continue update } +// --- Drawing Functions --- function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); - drawSnake(); + drawGrid(); drawFood(); - drawScore(); + drawSnake(); + // We no longer draw score on canvas +} + +function drawGrid() { + ctx.strokeStyle = "#1a1a3a"; // Dark blue grid lines + ctx.lineWidth = 1; + for (let x = 0; x <= CANVAS_WIDTH; x += GRID_SIZE) { + ctx.beginPath(); + ctx.moveTo(x, 0); + ctx.lineTo(x, CANVAS_HEIGHT); + ctx.stroke(); + } + for (let y = 0; y <= CANVAS_HEIGHT; y += GRID_SIZE) { + ctx.beginPath(); + ctx.moveTo(0, y); + ctx.lineTo(CANVAS_WIDTH, y); + ctx.stroke(); + } } function drawSnake() { - ctx.fillStyle = "#4CAF50"; - snake.forEach(segment => { - ctx.fillRect(segment.x * 20, segment.y * 20, 18, 18); + snake.forEach((segment, index) => { + if (index === 0) { + // --- Draw Head --- + ctx.fillStyle = "#00ff00"; // Bright neon green + ctx.shadowColor = "#00ff00"; + ctx.shadowBlur = 10; + } else { + // --- Draw Body --- + ctx.fillStyle = "#00cc00"; // Slightly darker green + ctx.shadowColor = "#00ff00"; + ctx.shadowBlur = 5; + } + ctx.fillRect(segment.x * GRID_SIZE, segment.y * GRID_SIZE, GRID_SIZE, GRID_SIZE); + + // Add a highlight for 3D effect + ctx.fillStyle = "rgba(255, 255, 255, 0.3)"; + ctx.fillRect(segment.x * GRID_SIZE + 2, segment.y * GRID_SIZE + 2, GRID_SIZE - 4, GRID_SIZE - 4); }); + // Reset shadow + ctx.shadowBlur = 0; } function drawFood() { - ctx.fillStyle = "#FF5722"; + ctx.fillStyle = "#ff0040"; // Neon red/pink + ctx.shadowColor = "#ff0040"; + ctx.shadowBlur = 15; + + // Draw a "glowing" circle ctx.beginPath(); - ctx.arc(food.x * 20 + 10, food.y * 20 + 10, 9, 0, Math.PI * 2); + ctx.arc( + food.x * GRID_SIZE + GRID_SIZE / 2, // Center of the cell + food.y * GRID_SIZE + GRID_SIZE / 2, // Center of the cell + GRID_SIZE / 2 - 2, // Radius + 0, + Math.PI * 2 + ); ctx.fill(); + + // Reset shadow + ctx.shadowBlur = 0; } -function drawScore() { - ctx.fillStyle = "#000"; - ctx.font = "24px Arial"; - ctx.fillText("Score: " + score, 10, 30); -} - +// --- Game Logic --- function placeFood() { - food = { - x: Math.floor(Math.random() * 20), - y: Math.floor(Math.random() * 20) - }; + // Keep placing food until it's not on the snake + while (true) { + food = { + x: Math.floor(Math.random() * COLS), + y: Math.floor(Math.random() * ROWS) + }; + // Check if food is on the snake + let onSnake = snake.some(segment => segment.x === food.x && segment.y === food.y); + if (!onSnake) break; + } } function isCollidingWithSelf(head) { @@ -70,13 +170,49 @@ function isCollidingWithSelf(head) { } function resetGame() { + gameRunning = false; + + // Check for new high score + if (score > highScore) { + highScore = score; + localStorage.setItem('neonSnakeHighScore', highScore); + highScoreValueEl.textContent = highScore; + } + + // Show "Game Over" modal + finalScoreEl.textContent = score; + gameOverModal.style.display = 'flex'; +} + +function startGame() { + // Reset values and hide overlays snake = [{ x: 9, y: 9 }]; score = 0; direction = { x: 0, y: 0 }; + scoreValueEl.textContent = score; + + gameOverModal.style.display = 'none'; + startOverlay.style.display = 'none'; + + gameRunning = true; placeFood(); + gameLoop(); // Start the game loop! } +// --- Event Listeners --- document.addEventListener("keydown", event => { + // Prevent page scrolling with arrow keys + if (event.key.startsWith("Arrow")) { + event.preventDefault(); + } + + // Start game on first arrow key press + if (!gameRunning && gameOverModal.style.display === 'none') { + // We only start if the game isn't running AND the game over modal isn't showing + startGame(); + } + + // Update direction switch (event.key) { case "ArrowUp": if (direction.y === 0) direction = { x: 0, y: -1 }; @@ -93,4 +229,10 @@ document.addEventListener("keydown", event => { } }); -gameLoop(); +playAgainButton.addEventListener("click", () => { + // Reset everything and show the start overlay + init(); +}); + +// --- Start Everything --- +init(); diff --git a/Domains/Frontend/MiniProjects/snake-game/style.css b/Domains/Frontend/MiniProjects/snake-game/style.css index 63a9a5d8..6518d779 100644 --- a/Domains/Frontend/MiniProjects/snake-game/style.css +++ b/Domains/Frontend/MiniProjects/snake-game/style.css @@ -1,55 +1,159 @@ -/* Basic styles for Snake Game */ +/* Import the gaming font */ +@import url('https://fonts.googleapis.com/css2?family=Bungee&display=swap'); + +/* --- Basic Setup --- */ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} body { + font-family: 'Bungee', cursive; + /* A deep, dark gradient background */ + background: linear-gradient(135deg, #1f003b 0%, #0a0a23 100%); + color: #ffffff; display: flex; - flex-direction: column; + justify-content: center; align-items: center; - height: 100vh; - margin: 0; - background-color: #f0f0f0; - font-family: Arial, sans-serif; + min-height: 100vh; + overflow: hidden; /* Prevents scrollbars */ } -.container { - text-align: center; +/* --- Game UI Wrapper --- */ +#game-wrapper { + width: 100%; + max-width: 640px; /* Slightly wider than the canvas for padding */ + padding: 20px; + background: #0d0d2e; /* Dark inner background */ + border-radius: 16px; + /* The "Neon" glow effect */ + border: 2px solid #00ffff; + box-shadow: 0 0 10px #00ffff, 0 0 15px #00ffff, inset 0 0 7px rgba(0, 255, 255, 0.3); +} + +/* --- Game Header (Title & Score) --- */ +#game-header { display: flex; - flex-direction: column; + justify-content: space-between; align-items: center; + margin-bottom: 20px; + border-bottom: 2px solid #00ffff; + padding-bottom: 15px; +} + +#game-title { + font-size: 2rem; + color: #ffffff; + /* Neon text effect */ + /* text-shadow: 0 0 5px #fff, 0 0 10px #fff, 0 0 20px #00ffff, 0 0 30px #00ffff; */ +} + +#scoreboard { + display: flex; + gap: 20px; + text-align: right; +} + +.score-item { + font-size: 0.9rem; + color: #00ffff; /* Cyan text */ + line-height: 1.3; +} + +.score-item span { + display: block; + font-size: 1.5rem; + color: #00ff00; /* Neon green for the number */ + text-shadow: 0 0 5px #00ff00; +} + +/* --- Canvas Container --- */ +#canvas-container { + position: relative; /* Crucial for placing overlays */ + /* This ensures the canvas itself stays centered if wrapper is wider */ + display: flex; + justify-content: center; } #gameCanvas { - border: 2px solid #333; - background-color: #fff; - display: grid; - grid-template-columns: repeat(20, 20px); - grid-template-rows: repeat(20, 20px); - gap: 0; - margin: 20px auto; + display: block; + border-radius: 8px; + /* We will draw the grid in JS, but set a base color */ + background: #050510; + /* A subtle inner glow for the play area */ + box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.5); + + /* Responsive size */ + width: 100%; + max-width: 600px; + height: auto; + aspect-ratio: 3 / 2; /* 600x400 ratio */ } -.cell { - width: 20px; - height: 20px; - background-color: #fff; +/* --- Overlays (Game Over / Start) --- */ +.overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + /* Dark, semi-transparent background */ + background: rgba(0, 0, 0, 0.85); + border-radius: 8px; /* Match canvas */ + + /* Center content inside */ + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + /* Hide by default */ + display: none; + + text-align: center; + color: #fff; + text-shadow: 0 0 10px #fff; } -.snake { - background-color: #4CAF50; +.overlay-content h2 { + font-size: 3rem; + color: #ff0040; /* Neon Red/Pink */ + /* text-shadow: 0 0 10px #ff0040, 0 0 20px #ff0040; */ + margin-bottom: 1rem; } -.food { - background-color: #FF5722; - border-radius: 50%; +.overlay-content p { + font-size: 1.5rem; + margin-bottom: 2rem; } -#score { - font-size: 24px; - margin-bottom: 10px; +#start-overlay h2 { + color: #00ff00; /* Neon Green */ + font-size: 1.5rem; + /* text-shadow: 0 0 10px #00ff00, 0 0 20px #00ff00; */ } -button { - padding: 10px 20px; - font-size: 16px; - margin: 5px; +/* --- Neon Button --- */ +#play-again-button { + font-family: 'Bungee', cursive; + font-size: 1.2rem; + padding: 1rem 2rem; + color: #fff; + background: #1e003b; + border: 2px solid #00ffff; + border-radius: 8px; cursor: pointer; -} \ No newline at end of file + text-shadow: 0 0 5px #00ffff; + /* box-shadow: 0 0 10px #00ffff, inset 0 0 5px rgba(0, 255, 255, 0.5); */ + transition: all 0.3s ease; +} + +#play-again-button:hover { + background: #00ffff; + color: #0a0a23; + text-shadow: none; + /* box-shadow: 0 0 20px #00ffff, 0 0 40px #00ffff; */ + transform: scale(1.05); +} +