Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 43 additions & 8 deletions Domains/Frontend/MiniProjects/snake-game/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,54 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Snake Game</title>

<title> Snake Game</title>
<!-- Import a "pro" gaming font -->
<link href="https://fonts.googleapis.com/css2?family=Bungee&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css" />
</head>
<body>
<h1>Snake Game</h1>
<p>Use the arrow keys to move the snake and eat the food!</p>

<div class="container">
<p>Score: <span id="score">0</span></p>
<canvas id="gameCanvas" width="400" height="400"></canvas>
<!-- This wrapper holds the entire game UI -->
<div id="game-wrapper">

<!-- The header for title and scores -->
<header id="game-header">
<h1 id="game-title">SNAKE GAME</h1>
<div id="scoreboard">
<div class="score-item">
SCORE
<span id="score-value">0</span>
</div>
<div class="score-item">
HIGH SCORE
<span id="high-score-value">0</span>
</div>
</div>
</header>

<!-- The game canvas area. Note the new position:relative -->
<div id="canvas-container">
<canvas id="gameCanvas"></canvas>

<!-- Hidden "Start" overlay -->
<div id="start-overlay" class="overlay">
<div class="overlay-content">
<h2>Press any arrow key to start</h2>
</div>
</div>

<!-- Hidden "Game Over" modal -->
<div id="game-over-modal" class="overlay">
<div class="overlay-content">
<h2>GAME OVER</h2>
<p>Your Score: <span id="final-score">0</span></p>
<button id="play-again-button">Play Again</button>
</div>
</div>
</div>

</div>

<script src="script.js"></script>
</body>
</html>
</html>
204 changes: 173 additions & 31 deletions Domains/Frontend/MiniProjects/snake-game/script.js
Original file line number Diff line number Diff line change
@@ -1,82 +1,218 @@
// --- 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) {
return snake.slice(1).some(segment => segment.x === head.x && segment.y === head.y);
}

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 };
Expand All @@ -93,4 +229,10 @@ document.addEventListener("keydown", event => {
}
});

gameLoop();
playAgainButton.addEventListener("click", () => {
// Reset everything and show the start overlay
init();
});

// --- Start Everything ---
init();
Loading
Loading