├── README.md ├── assets ├── bite.mp3 ├── favicon.png └── caret.svg ├── index.html ├── style.css └── game.js /README.md: -------------------------------------------------------------------------------- 1 | # https://2dsnake.netlify.app 2 | -------------------------------------------------------------------------------- /assets/bite.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhijeetSinghRajput/snake-game/HEAD/assets/bite.mp3 -------------------------------------------------------------------------------- /assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhijeetSinghRajput/snake-game/HEAD/assets/favicon.png -------------------------------------------------------------------------------- /assets/caret.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | snake game 10 | 11 | 12 | 13 |
14 |
15 |

Snake Game

16 |
17 |
18 | high score 19 | 526 20 |
21 | 22 |
23 | score 24 | 526 25 |
26 |
27 |
28 |
29 |
30 | 31 | 32 | 33 | 34 |
35 |
36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); 2 | 3 | *{ 4 | margin: 0; 5 | padding: 0; 6 | box-sizing: border-box; 7 | font-family: "Press Start 2P", sans-serif; 8 | list-style: none; 9 | text-decoration: none; 10 | } 11 | 12 | body{ 13 | width: 100vw; 14 | height: 100svh; 15 | background-color: #1f1f1f; 16 | color: greenyellow; 17 | display: flex; 18 | align-items: center; 19 | justify-content: center; 20 | overflow: hidden; 21 | } 22 | main{ 23 | width: 100vmin; 24 | height: 100vmin; 25 | display: flex; 26 | flex-direction: column; 27 | align-items: center; 28 | justify-content: space-between; 29 | /* border: 1px solid white; */ 30 | } 31 | header{ 32 | text-align: center; 33 | margin-bottom: 20px; 34 | padding: 20px; 35 | width: 100%; 36 | } 37 | header h1{ 38 | font-size: 22px; 39 | } 40 | 41 | .score-board{ 42 | display: flex; 43 | align-items: center; 44 | justify-content: space-between; 45 | font-size: 12px; 46 | gap: 20px; 47 | margin-top: 20px; 48 | } 49 | 50 | .game-board{ 51 | background-color: #333; 52 | aspect-ratio: 1; 53 | flex-grow: 1; 54 | display: grid; 55 | grid-template-columns: repeat(20, 1fr); 56 | grid-template-rows: repeat(20, 1fr); 57 | } 58 | .snake{ 59 | background-color: greenyellow; 60 | border: .2vmin solid black; 61 | color: black; 62 | font-family: sans-serif; 63 | font-size: 10px; 64 | } 65 | .food{ 66 | background-color: crimson; 67 | border: .2vmin solid black; 68 | } 69 | .head{ 70 | border-top-right-radius: 100px; 71 | border-bottom-right-radius: 100px; 72 | position: relative; 73 | } 74 | .head::before, 75 | .head::after{ 76 | content: ''; 77 | position: absolute; 78 | border-radius: 50%; 79 | border: .3vmin solid white; 80 | background-color: black; 81 | width: 30%; 82 | aspect-ratio: 1; 83 | top: 0; 84 | right: 0; 85 | } 86 | .head::before{ 87 | top: auto; 88 | bottom: 0; 89 | } 90 | .head.down{ 91 | transform: rotate(90deg); 92 | } 93 | .head.up{ 94 | transform: rotate(-90deg); 95 | } 96 | .head.left{ 97 | transform: rotate(-180deg); 98 | } 99 | .head.right{ 100 | transform: rotate(0deg); 101 | } 102 | 103 | .controlls{ 104 | width: 100px; 105 | height: 100px; 106 | display: grid; 107 | grid-template-columns: 1fr 1fr; 108 | grid-gap: 5px; 109 | overflow: hidden; 110 | border-radius: 50%; 111 | transform: rotate(45deg); 112 | position: relative; 113 | z-index: 100; 114 | } 115 | button{ 116 | cursor: pointer; 117 | background-color: greenyellow; 118 | border: none; 119 | outline: none; 120 | border-radius: 10px; 121 | } 122 | 123 | button#up img{ 124 | transform: rotate(-45deg); 125 | } 126 | button#left img{ 127 | transform: rotate(-135deg); 128 | } 129 | button#right img{ 130 | transform: rotate(45deg); 131 | } 132 | button#down img{ 133 | transform: rotate(135deg); 134 | } 135 | 136 | -------------------------------------------------------------------------------- /game.js: -------------------------------------------------------------------------------- 1 | const gameBoard = document.querySelector('.game-board'); 2 | let highScore = document.querySelector('.high-score .value'); 3 | let score = document.querySelector('.score .value'); 4 | 5 | highScore.textContent = localStorage.getItem('snakeHighScore') || 0; 6 | score.textContent = 0; 7 | let gridNum = 20; 8 | let scoreIncrement = 5; 9 | 10 | let snakeBody = [ 11 | { x: 10, y: 13 }, 12 | { x: 10, y: 12 }, 13 | { x: 10, y: 11 }, 14 | ]; 15 | 16 | let snakeSpeed = 5; 17 | const expansionRate = 1; 18 | let snakeDirection = 'right'; 19 | const directions = [ 20 | { x: -1, y: 0 }, 21 | { x: 0, y: 1 }, 22 | { x: 1, y: 0 }, 23 | { x: 0, y: -1 }, 24 | ]; 25 | const [up, right, down, left] = directions; 26 | 27 | let grid = []; 28 | for (let i = 1; i <= gridNum; ++i) { 29 | for (let j = 1; j <= gridNum; ++j) { 30 | grid.push({ x: i, y: j }); 31 | } 32 | } 33 | let foodPosition = getRandomFood(); 34 | 35 | let lastRenderTime = 0; 36 | function main(currTime) { 37 | let sec_delay = (currTime - lastRenderTime) / 1000; 38 | requestAnimationFrame(main); 39 | if (sec_delay < (1 / snakeSpeed)) return; 40 | 41 | moveSnake(); 42 | drawSnake(); 43 | drawFood(); 44 | lastRenderTime = currTime; 45 | } 46 | requestAnimationFrame(main); 47 | 48 | function incScore() { 49 | let value = +score.textContent; 50 | let bonus = 0; 51 | 52 | //level up 53 | if(value > 200) bonus = 10, snakeSpeed = 9; 54 | else if(value > 150) bonus = 10, snakeSpeed = 8; 55 | else if(value > 100) bonus = 5, snakeSpeed = 7; 56 | else if(value > 50) bonus = 2, snakeSpeed = 6; 57 | 58 | score.textContent = value + scoreIncrement + bonus; 59 | 60 | if (+score.textContent > +highScore.textContent) { 61 | highScore.textContent = score.textContent; 62 | localStorage.setItem('snakeHighScore', highScore.textContent); 63 | } 64 | } 65 | 66 | function drawSnake() { 67 | gameBoard.innerHTML = ''; 68 | snakeBody.forEach(({ x, y }, i) => { 69 | let snake = document.createElement('div'); 70 | snake.className = "snake"; 71 | snake.style.gridRowStart = x; 72 | snake.style.gridColumnStart = y; 73 | if (i === 0) snake.classList.add('head', snakeDirection); 74 | gameBoard.appendChild(snake); 75 | }); 76 | } 77 | 78 | function drawFood() { 79 | let food = document.createElement('div'); 80 | food.className = 'food'; 81 | food.style.gridRowStart = foodPosition.x; 82 | food.style.gridColumnStart = foodPosition.y; 83 | gameBoard.appendChild(food); 84 | } 85 | 86 | const biteSound = new Audio('./assets/bite.mp3'); 87 | function move(direction, str) { 88 | // prevent 180-degree turn 89 | if (str === 'up' && snakeDirection === 'down') return; 90 | if (str === 'left' && snakeDirection === 'right') return; 91 | if (str === 'right' && snakeDirection === 'left') return; 92 | if (str === 'down' && snakeDirection === 'up') return; 93 | 94 | snakeDirection = str; 95 | } 96 | 97 | function moveSnake() { 98 | let direction; 99 | switch (snakeDirection) { 100 | case 'up': 101 | direction = up; 102 | break; 103 | case 'down': 104 | direction = down; 105 | break; 106 | case 'left': 107 | direction = left; 108 | break; 109 | case 'right': 110 | direction = right; 111 | break; 112 | } 113 | 114 | for (let i = snakeBody.length - 1; i > 0; --i) { 115 | snakeBody[i] = { ...snakeBody[i - 1] }; 116 | } 117 | 118 | snakeBody[0].x += direction.x; 119 | snakeBody[0].y += direction.y; 120 | 121 | // teleport 122 | if (snakeBody[0].x > gridNum) snakeBody[0].x = 1; 123 | if (snakeBody[0].y > gridNum) snakeBody[0].y = 1; 124 | if (snakeBody[0].x < 1) snakeBody[0].x = gridNum; 125 | if (snakeBody[0].y < 1) snakeBody[0].y = gridNum; 126 | 127 | if (checkIntersection()) { 128 | alert('game over'); 129 | newGame(); 130 | return; 131 | } 132 | if (isEqual(snakeBody[0], foodPosition)) { 133 | expand(expansionRate); 134 | biteSound.play(); 135 | foodPosition = getRandomFood(); 136 | incScore(); 137 | } 138 | } 139 | 140 | function checkIntersection() { 141 | for (let i = 1; i < snakeBody.length; ++i) { 142 | if (isEqual(snakeBody[0], snakeBody[i])) { 143 | return true; 144 | } 145 | } 146 | return false; 147 | } 148 | 149 | function expand(expansionRate) { 150 | while (expansionRate--) { 151 | snakeBody.push({ ...snakeBody[snakeBody.length - 1] }); 152 | } 153 | } 154 | 155 | function getRandomFood() { 156 | const remainingCell = grid.filter((cell) => { 157 | for (let i = 0; i < snakeBody.length; ++i) { 158 | if (isEqual(cell, snakeBody[i])) { 159 | return false; 160 | } 161 | } 162 | return true; 163 | }); 164 | if (remainingCell.length === 0) { 165 | alert('you do not know how elite you are'); 166 | newGame(); 167 | } 168 | let randomIndex = Math.floor(Math.random() * remainingCell.length); 169 | return remainingCell[randomIndex]; 170 | } 171 | 172 | function isEqual(pos1, pos2) { 173 | return pos1.x === pos2.x && pos1.y === pos2.y; 174 | } 175 | 176 | function newGame() { 177 | 178 | snakeBody = [ 179 | { x: 10, y: 13 }, 180 | { x: 10, y: 12 }, 181 | { x: 10, y: 11 }, 182 | ]; 183 | snakeSpeed = 5; 184 | snakeDirection = 'right'; 185 | foodPosition = getRandomFood(); 186 | score.textContent = 0; 187 | } 188 | 189 | window.addEventListener('keydown', ({ key }) => { 190 | switch (key) { 191 | case 'ArrowUp': 192 | move(up, 'up'); 193 | break; 194 | case 'ArrowDown': 195 | move(down, 'down'); 196 | break; 197 | case 'ArrowLeft': 198 | move(left, 'left'); 199 | break; 200 | case 'ArrowRight': 201 | move(right, 'right'); 202 | break; 203 | default: 204 | break; 205 | } 206 | }); 207 | --------------------------------------------------------------------------------