├── assets
└── audio.mp3
├── css
└── style.css
├── index.html
└── js
└── script.js
/assets/audio.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manualdodev/snake-game/ea197dc70519aa12922c6011caf4ef31db5d63f9/assets/audio.mp3
--------------------------------------------------------------------------------
/css/style.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700;800&display=swap");
2 |
3 | * {
4 | margin: 0;
5 | padding: 0;
6 | box-sizing: border-box;
7 | font-family: "Poppings", sans-serif;
8 | }
9 |
10 | body {
11 | background-color: #191919;
12 | display: flex;
13 | min-height: 100vh;
14 | align-items: center;
15 | justify-content: center;
16 | flex-direction: column;
17 | color: white;
18 | }
19 |
20 | canvas {
21 | background-color: #111;
22 | }
23 |
24 | .score {
25 | display: flex;
26 | flex-direction: column;
27 | align-items: center;
28 | font-size: 1.8rem;
29 | }
30 |
31 | .score--value {
32 | font-weight: 700;
33 | font-size: 4.5rem;
34 | display: block;
35 | margin-top: -10px;
36 | }
37 |
38 | .menu-screen {
39 | position: absolute;
40 | display: none;
41 | flex-direction: column;
42 | align-items: center;
43 | z-index: 1;
44 | }
45 |
46 | .game-over {
47 | text-transform: uppercase;
48 | font-weight: 700;
49 | font-size: 3rem;
50 | }
51 |
52 | .final-score {
53 | font-weight: 500;
54 | font-size: 1.5rem;
55 | }
56 |
57 | .btn-play {
58 | border: none;
59 | border-radius: 100px;
60 | padding: 10px 15px 10px 12px;
61 | font-size: 1rem;
62 | font-weight: 600;
63 | color: #333;
64 | margin-top: 40px;
65 | display: flex;
66 | align-items: center;
67 | justify-content: center;
68 | gap: 5px;
69 | cursor: pointer;
70 | }
71 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Snake Game
12 |
13 |
14 | score: 00
15 |
16 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/js/script.js:
--------------------------------------------------------------------------------
1 | const canvas = document.querySelector("canvas")
2 | const ctx = canvas.getContext("2d")
3 |
4 | const score = document.querySelector(".score--value")
5 | const finalScore = document.querySelector(".final-score > span")
6 | const menu = document.querySelector(".menu-screen")
7 | const buttonPlay = document.querySelector(".btn-play")
8 |
9 | const audio = new Audio("../assets/audio.mp3")
10 |
11 | const size = 30
12 |
13 | const initialPosition = { x: 270, y: 240 }
14 |
15 | let snake = [initialPosition]
16 |
17 | const incrementScore = () => {
18 | score.innerText = +score.innerText + 10
19 | }
20 |
21 | const randomNumber = (min, max) => {
22 | return Math.round(Math.random() * (max - min) + min)
23 | }
24 |
25 | const randomPosition = () => {
26 | const number = randomNumber(0, canvas.width - size)
27 | return Math.round(number / 30) * 30
28 | }
29 |
30 | const randomColor = () => {
31 | const red = randomNumber(0, 255)
32 | const green = randomNumber(0, 255)
33 | const blue = randomNumber(0, 255)
34 |
35 | return `rgb(${red}, ${green}, ${blue})`
36 | }
37 |
38 | const food = {
39 | x: randomPosition(),
40 | y: randomPosition(),
41 | color: randomColor()
42 | }
43 |
44 | let direction, loopId
45 |
46 | const drawFood = () => {
47 | const { x, y, color } = food
48 |
49 | ctx.shadowColor = color
50 | ctx.shadowBlur = 6
51 | ctx.fillStyle = color
52 | ctx.fillRect(x, y, size, size)
53 | ctx.shadowBlur = 0
54 | }
55 |
56 | const drawSnake = () => {
57 | ctx.fillStyle = "#ddd"
58 |
59 | snake.forEach((position, index) => {
60 | if (index == snake.length - 1) {
61 | ctx.fillStyle = "white"
62 | }
63 |
64 | ctx.fillRect(position.x, position.y, size, size)
65 | })
66 | }
67 |
68 | const moveSnake = () => {
69 | if (!direction) return
70 |
71 | const head = snake[snake.length - 1]
72 |
73 | if (direction == "right") {
74 | snake.push({ x: head.x + size, y: head.y })
75 | }
76 |
77 | if (direction == "left") {
78 | snake.push({ x: head.x - size, y: head.y })
79 | }
80 |
81 | if (direction == "down") {
82 | snake.push({ x: head.x, y: head.y + size })
83 | }
84 |
85 | if (direction == "up") {
86 | snake.push({ x: head.x, y: head.y - size })
87 | }
88 |
89 | snake.shift()
90 | }
91 |
92 | const drawGrid = () => {
93 | ctx.lineWidth = 1
94 | ctx.strokeStyle = "#191919"
95 |
96 | for (let i = 30; i < canvas.width; i += 30) {
97 | ctx.beginPath()
98 | ctx.lineTo(i, 0)
99 | ctx.lineTo(i, 600)
100 | ctx.stroke()
101 |
102 | ctx.beginPath()
103 | ctx.lineTo(0, i)
104 | ctx.lineTo(600, i)
105 | ctx.stroke()
106 | }
107 | }
108 |
109 | const chackEat = () => {
110 | const head = snake[snake.length - 1]
111 |
112 | if (head.x == food.x && head.y == food.y) {
113 | incrementScore()
114 | snake.push(head)
115 | audio.play()
116 |
117 | let x = randomPosition()
118 | let y = randomPosition()
119 |
120 | while (snake.find((position) => position.x == x && position.y == y)) {
121 | x = randomPosition()
122 | y = randomPosition()
123 | }
124 |
125 | food.x = x
126 | food.y = y
127 | food.color = randomColor()
128 | }
129 | }
130 |
131 | const checkCollision = () => {
132 | const head = snake[snake.length - 1]
133 | const canvasLimit = canvas.width - size
134 | const neckIndex = snake.length - 2
135 |
136 | const wallCollision =
137 | head.x < 0 || head.x > canvasLimit || head.y < 0 || head.y > canvasLimit
138 |
139 | const selfCollision = snake.find((position, index) => {
140 | return index < neckIndex && position.x == head.x && position.y == head.y
141 | })
142 |
143 | if (wallCollision || selfCollision) {
144 | gameOver()
145 | }
146 | }
147 |
148 | const gameOver = () => {
149 | direction = undefined
150 |
151 | menu.style.display = "flex"
152 | finalScore.innerText = score.innerText
153 | canvas.style.filter = "blur(2px)"
154 | }
155 |
156 | const gameLoop = () => {
157 | clearInterval(loopId)
158 |
159 | ctx.clearRect(0, 0, 600, 600)
160 | drawGrid()
161 | drawFood()
162 | moveSnake()
163 | drawSnake()
164 | chackEat()
165 | checkCollision()
166 |
167 | loopId = setTimeout(() => {
168 | gameLoop()
169 | }, 300)
170 | }
171 |
172 | gameLoop()
173 |
174 | document.addEventListener("keydown", ({ key }) => {
175 | if (key == "ArrowRight" && direction != "left") {
176 | direction = "right"
177 | }
178 |
179 | if (key == "ArrowLeft" && direction != "right") {
180 | direction = "left"
181 | }
182 |
183 | if (key == "ArrowDown" && direction != "up") {
184 | direction = "down"
185 | }
186 |
187 | if (key == "ArrowUp" && direction != "down") {
188 | direction = "up"
189 | }
190 | })
191 |
192 | buttonPlay.addEventListener("click", () => {
193 | score.innerText = "00"
194 | menu.style.display = "none"
195 | canvas.style.filter = "none"
196 |
197 | snake = [initialPosition]
198 | })
199 |
--------------------------------------------------------------------------------