Add JavaScript Snake Game
This commit is contained in:
37
index.html
Normal file
37
index.html
Normal file
@@ -0,0 +1,37 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Press+Start+2P&family=VT323&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200"
|
||||
/>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script src="script.js" defer></script>
|
||||
<title>Snake Game</title>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<div class="scores">
|
||||
<h1 id="score">000</h1>
|
||||
<h1 id="highScore">000</h1>
|
||||
</div>
|
||||
<div class="game-border-1">
|
||||
<div class="game-border-2">
|
||||
<div class="game-border-3">
|
||||
<div id="game-board"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h1 id="instruction-text">Press spacebar to start the game</h1>
|
||||
<img id="logo" src="snake-game-ai-gen.png" alt="snake logo" />
|
||||
</body>
|
||||
</html>
|
||||
203
script copy.js
Normal file
203
script copy.js
Normal file
@@ -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
|
||||
}
|
||||
});
|
||||
224
script.js
Normal file
224
script.js
Normal file
@@ -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
|
||||
BIN
snake-game-ai-gen.png
Normal file
BIN
snake-game-ai-gen.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
90
style.css
Normal file
90
style.css
Normal file
@@ -0,0 +1,90 @@
|
||||
body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
font-family: 'VT323', monospace;
|
||||
}
|
||||
|
||||
body,
|
||||
.snake {
|
||||
background-color: #414141;
|
||||
}
|
||||
|
||||
#game-board {
|
||||
border-radius: 100px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(20, 20px);
|
||||
grid-template-rows: repeat(20, 20px);
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.game-border-1 {
|
||||
border: #595f43 solid 10px;
|
||||
border-radius: 30px;
|
||||
box-shadow: inset 0 0 0 10px #595f43;
|
||||
}
|
||||
|
||||
.game-border-2 {
|
||||
border: #abb78a solid 8px;
|
||||
border-radius: 26px;
|
||||
box-shadow: inset 0 0 0 10px #abb78a;
|
||||
}
|
||||
|
||||
.game-border-3 {
|
||||
border: #8b966c solid 30px;
|
||||
border-radius: 20px;
|
||||
box-shadow: inset 0 0 0 5px #5b6448;
|
||||
}
|
||||
|
||||
#instruction-text {
|
||||
position: absolute;
|
||||
top: 60%;
|
||||
color: #333;
|
||||
width: 300px;
|
||||
text-align: center;
|
||||
text-transform: capitalize;
|
||||
padding: 30px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.scores {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
#score {
|
||||
color: #abb78a;
|
||||
}
|
||||
|
||||
#score,
|
||||
#highScore {
|
||||
font-size: 40px;
|
||||
font-weight: bolder;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
#highScore {
|
||||
color: #d8ddca;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.game-border-3,
|
||||
#logo {
|
||||
background-color: #c4cfa3;
|
||||
}
|
||||
|
||||
.snake {
|
||||
background-color: #414141;
|
||||
border: #5a5a5a 1px dotted;
|
||||
}
|
||||
|
||||
.food {
|
||||
background-color: #dedede;
|
||||
border: #999 5px solid;
|
||||
}
|
||||
|
||||
#logo {
|
||||
position: absolute;
|
||||
}
|
||||
Reference in New Issue
Block a user