From 7f455216d70bb79019724d744a08134524421d6f Mon Sep 17 00:00:00 2001 From: Ademir <115159286+Ade-mir@users.noreply.github.com> Date: Tue, 28 Nov 2023 20:53:58 +0100 Subject: [PATCH] Add JavaScript Snake Game --- index.html | 37 +++++++ script copy.js | 203 ++++++++++++++++++++++++++++++++++++++ script.js | 224 ++++++++++++++++++++++++++++++++++++++++++ snake-game-ai-gen.png | Bin 0 -> 20320 bytes style.css | 90 +++++++++++++++++ 5 files changed, 554 insertions(+) create mode 100644 index.html create mode 100644 script copy.js create mode 100644 script.js create mode 100644 snake-game-ai-gen.png create mode 100644 style.css diff --git a/index.html b/index.html new file mode 100644 index 0000000..f5b3f3c --- /dev/null +++ b/index.html @@ -0,0 +1,37 @@ + + + + + + + + + + + + Snake Game + + +
+
+

000

+

000

+
+
+
+
+
+
+
+
+
+

Press spacebar to start the game

+ + + diff --git a/script copy.js b/script copy.js new file mode 100644 index 0000000..80f379c --- /dev/null +++ b/script copy.js @@ -0,0 +1,203 @@ +document.addEventListener('DOMContentLoaded', () => { + const board = document.getElementById('game-board'); + const score = document.getElementById('score'); + const instructionText = document.getElementById('instruction-text'); + const logo = document.getElementById('logo'); + const highScoreText = document.getElementById('highScore'); + const gridSize = 20; + let snake = [{ x: 10, y: 10 }]; + let highScore = 0; + let food = generateFood(); + let direction = 'right'; + let gameSpeedDelay = 200; + let gameStarted = false; + + function startGame() { + gameStarted = true; // Set gameStarted to true when the game starts + instructionText.style.display = 'none'; // Hide the instruction text + logo.style.display = 'none'; // Hide the instruction text + gameInterval = setInterval(() => { + move(); + checkCollision(); + draw(); + }, gameSpeedDelay); + } + + function stopGame() { + clearInterval(gameInterval); + gameStarted = false; + instructionText.style.display = 'block'; + logo.style.display = 'block'; + } + + function draw() { + board.innerHTML = ''; // Clear the board + drawSnake(); + drawFood(); + updateScore(); + } + + function drawSnake() { + snake.forEach((segment) => { + const snakeElement = createGameElement('div', 'snake'); + setPosition(snakeElement, segment); + board.appendChild(snakeElement); + }); + } + + function drawFood() { + const foodElement = createGameElement('div', 'food'); + setPosition(foodElement, food); + board.appendChild(foodElement); + } + + function createGameElement(tag, className) { + const element = document.createElement(tag); + element.className = className; + return element; + } + + function setPosition(element, position) { + element.style.gridColumn = position.x; + element.style.gridRow = position.y; + } + + // Generate food without taking into account snake position + // function generateFood() { + // const x = Math.floor(Math.random() * gridSize) + 1; + // const y = Math.floor(Math.random() * gridSize) + 1; + // return { x, y }; + // } + + function generateFood() { + let newFood; + do { + newFood = { + x: Math.floor(Math.random() * gridSize) + 1, + y: Math.floor(Math.random() * gridSize) + 1, + }; + } while (isFoodOnSnake(newFood)); + + return newFood; + } + + function isFoodOnSnake(food) { + return snake.some( + (segment) => segment.x === food.x && segment.y === food.y + ); + } + + function move() { + 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; + } + + snake.unshift(head); + + // console.log('Head Position:', head); + + if (head.x === food.x && head.y === food.y) { + food = generateFood(); + increaseSpeed(); + // console.log(gameSpeedDelay); + clearInterval(gameInterval); + gameInterval = setInterval(() => { + move(); + checkCollision(); + draw(); + }, gameSpeedDelay); + } else { + snake.pop(); + } + } + + function increaseSpeed() { + if (gameSpeedDelay > 150) { + gameSpeedDelay -= 5; + } else if (gameSpeedDelay > 100) { + gameSpeedDelay -= 3; + } else if (gameSpeedDelay > 50) { + gameSpeedDelay -= 2; + } else if (gameSpeedDelay > 25) { + gameSpeedDelay -= 1; + } + } + + function handleKeyPress(event) { + // console.log(event.key); + if ( + (!gameStarted && event.code === 'Space') || + (!gameStarted && event.key === ' ') + ) { + startGame(); // Start the game on Enter key press + } else { + switch (event.key) { + case 'ArrowUp': + direction = 'up'; + break; + case 'ArrowDown': + direction = 'down'; + break; + case 'ArrowLeft': + direction = 'left'; + break; + case 'ArrowRight': + direction = 'right'; + break; + } + } + } + + document.addEventListener('keydown', handleKeyPress); + + function checkCollision() { + const head = snake[0]; + + if (head.x < 1 || head.x > gridSize || head.y < 1 || head.y > gridSize) { + resetGame(); + } + + for (let i = 1; i < snake.length; i++) { + if (head.x === snake[i].x && head.y === snake[i].y) { + resetGame(); + } + } + } + + function updateScore() { + const currentScore = snake.length - 1; + score.textContent = currentScore.toString().padStart(3, '0'); + } + + function updateHighScore() { + const currentScore = snake.length - 1; + if (currentScore > highScore) { + highScore = currentScore; + highScoreText.textContent = highScore.toString().padStart(3, '0'); + } + highScoreText.style.display = 'block'; + } + + function resetGame() { + updateHighScore(); + stopGame(); + snake = [{ x: 10, y: 10 }]; + food = generateFood(); + direction = 'right'; + gameSpeedDelay = 200; + updateScore(); // Calling this last because we need to call it under the rest of snake variable + } +}); diff --git a/script.js b/script.js new file mode 100644 index 0000000..5e8b2cb --- /dev/null +++ b/script.js @@ -0,0 +1,224 @@ +// 1) Define DOM elements from HTML. 1 +const board = document.getElementById('game-board'); // 1 +const instructionText = document.getElementById('instruction-text'); // 12 +const logo = document.getElementById('logo'); // 12 +const score = document.getElementById('score'); // 17 +const highScoreText = document.getElementById('highScore'); // 19 + +// Define game variables +const gridSize = 20; // 7 +let snake = [{ x: 10, y: 10 }]; // 2 +let food = generateFood(); // 6 +let highScore = 0; // 19 +let direction = 'right'; // 9 +let gameInterval; +let gameSpeedDelay = 200; // 11 +let gameStarted = false; // 12 + +// 1) Draw game map, snake and food +function draw() { + board.innerHTML = ''; // Clear the board in case game has started previously. + drawSnake(); // 2) Draw snake + drawFood(); // 6 Draw food + updateScore(); +} + +// 2) Draw snake function +function drawSnake() { + snake.forEach((segment) => { + const snakeElement = createGameElement('div', 'snake'); // 3 + setPosition(snakeElement, segment); // 4 + board.appendChild(snakeElement); // 5 + }); +} + +// 3) Create a snake or food cube +function createGameElement(tag, className) { + const element = document.createElement(tag); + element.className = className; + return element; +} + +// 4) Set the position of snake or food +function setPosition(element, position) { + element.style.gridColumn = position.x; // Using CSS gridColumn property to position the cube + element.style.gridRow = position.y; +} + +// 5 Call draw as test +// draw(); + +// 6) Draw food function +function drawFood() { + // 20) INITIAL WAY BEFORE END TWEAK TO REMOVE FOOD RENDERING BEFORE START + // const foodElement = createGameElement('div', 'food'); + // setPosition(foodElement, food); + // board.appendChild(foodElement); + + if (gameStarted) { + const foodElement = createGameElement('div', 'food'); + setPosition(foodElement, food); + board.appendChild(foodElement); + } +} + +// 7) Initial way to generate food without taking into account snake position +function generateFood() { + const x = Math.floor(Math.random() * gridSize) + 1; + const y = Math.floor(Math.random() * gridSize) + 1; + return { x, y }; +} + +// 8) Moving the snake function +function move() { + const head = { ...snake[0] }; // Spread operator creates shalow copy and does not alter the original snake[0] object + switch ( + direction // 9 + ) { + case 'up': + head.y--; // starts at 1 from top. + break; + case 'down': + head.y++; + break; + case 'left': + head.x--; + break; + case 'right': + head.x++; + break; + } + + snake.unshift(head); // Adding head object to beginning of snake array. + + // snake.pop(); 10) Testing move function + + if (head.x === food.x && head.y === food.y) { + food = generateFood(); // Call function again to replace food position + increaseSpeed(); // 14) MAKE START GAME FUNCTION FIRST! Increase game speed + clearInterval(gameInterval); // Clear past interval + gameInterval = setInterval(() => { + // The interval ensures the continuous execution of the game loop, preventing it from blocking the event loop and allowing for smooth updates after the snake eats food. Else the game stops when eating food. + move(); + checkCollision(); // 15 + draw(); + }, gameSpeedDelay); // 11) + } else { + snake.pop(); // Removing last object inside snake array, if food is not eated. + } +} + +// 10) Testing the move function +// setInterval(() => { +// move(); // Move first +// draw(); // Then draw again new position +// }, 200); // The interval time in ms + +// 12) Start game function +function startGame() { + gameStarted = true; // Keep track of running game + instructionText.style.display = 'none'; // Hide text and logo on start + logo.style.display = 'none'; + gameInterval = setInterval(() => { + move(); + checkCollision(); + draw(); // REMEMBER to comment out DRAW TEST on line 48! + }, gameSpeedDelay); +} + +// 13) Keypresses +function handleKeyPress(event) { + if ( + (!gameStarted && event.code === 'Space') || + (!gameStarted && event.key === ' ') + ) { + startGame(); // Start game on enter key press + } else { + // Change direction variable based on arrows + switch (event.key) { + case 'ArrowUp': + direction = 'up'; + break; + case 'ArrowDown': + direction = 'down'; + break; + case 'ArrowLeft': + direction = 'left'; + break; + case 'ArrowRight': + direction = 'right'; + break; + } + } +} + +document.addEventListener('keydown', handleKeyPress); + +// 14) +function increaseSpeed() { + // console.log('speed'); // just to have the function before start + if (gameSpeedDelay > 150) { + gameSpeedDelay -= 5; + } else if (gameSpeedDelay > 100) { + gameSpeedDelay -= 3; + } else if (gameSpeedDelay > 50) { + gameSpeedDelay -= 2; + } else if (gameSpeedDelay > 25) { + gameSpeedDelay -= 1; + } +} + +// 15) +function checkCollision() { + // console.log('collision'); // just to have the function before start + const head = snake[0]; + + // Check if snake hits walls + if (head.x < 1 || head.x > gridSize || head.y < 1 || head.y > gridSize) { + resetGame(); + } + + // Check if snake hits itself + for (let i = 1; i < snake.length; i++) { + if (head.x === snake[i].x && head.y === snake[i].y) { + resetGame(); + } + } +} + +// 16) +function resetGame() { + updateHighScore(); // 19) + stopGame(); // 18) + snake = [{ x: 10, y: 10 }]; + food = generateFood(); + direction = 'right'; + gameSpeedDelay = 200; + updateScore(); // 17) +} + +// 17) REMEMBER TO CALL FUNCTION ON LINE 23! +function updateScore() { + const currentScore = snake.length - 1; // -1 is added otherwise it would start at 1 + score.textContent = currentScore.toString().padStart(3, '0'); +} + +// 18) +function stopGame() { + clearInterval(gameInterval); // We need to stop it else snake keeps moving. + gameStarted = false; // We need to set it to false so we can start the game with enter again + instructionText.style.display = 'block'; + logo.style.display = 'block'; +} + +// 19) +function updateHighScore() { + const currentScore = snake.length - 1; + if (currentScore > highScore) { + highScore = currentScore; + highScoreText.textContent = highScore.toString().padStart(3, '0'); + } + highScoreText.style.display = 'block'; +} + +// 20) Tweak food rendering on line 52 diff --git a/snake-game-ai-gen.png b/snake-game-ai-gen.png new file mode 100644 index 0000000000000000000000000000000000000000..f922bdafed4ba9a0dd2fa82d3280543bbb6d0e5a GIT binary patch literal 20320 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 z�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