Skip to content

Commit c064aba

Browse files
committed
snake game mvp
0 parents  commit c064aba

File tree

5 files changed

+283
-0
lines changed

5 files changed

+283
-0
lines changed

README.md

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Instructions
2+
3+
- Open index.html in Chrome or Firefox
4+
- Use your arrow keys to change the snake's direction: up, left, right, and down.
5+
- Eat food to gain points.
6+
- Don't run the snake into the wall, or its own tail.
7+
- Each time the snake eats, the game will get a little bit faster.
8+
9+
# Discussion
10+
11+
This is actually the first thing I've ever built using the Canvas API! It was super fun, but I definitely feel like I'm missing a couple of concepts that would be obvious with more experience.
12+
13+
I started with the simplest possible working version I could write, given the rubric. Then I eliminated most of the bugs and added a couple of easy features (speed increase, end game alert, play btn to initialize/reinitialize the game) before I ran out of time.
14+
15+
I tested this in the latest versions of Chrome, Firefox and Safari.
16+
17+
# Requirements
18+
19+
- ~3 hours of coding time
20+
- client-side app
21+
- vanilla JavaScript
22+
- ES6 where appropriate
23+
24+
# Next Steps
25+
26+
- fix end game alert timing
27+
- use requestAnimationFrame instead of setTimeout
28+
- what happens if this is loaded on a mobile device?
29+
- what should the game do if the window is resized during play?
30+
- speed value decrease actually means the game is going faster, convert this into a user-friendly unit in UI
31+
- set upper limit on snake speed to prevent setTimeout issues
32+
- canvas lines seem blurry
33+
- add game over screen instead of alert
34+
- make canvas area responsive to screen size (figure out what the ideal canvas size for this game is?)
35+
- disable play button if game in progress or allow pause?
36+
- general UI polish: colors, typography, button styling, graphics
37+
- separate canvas js from game js, break game js down into modules (snake, food, engine)
38+
- super bonus next step: refactor the whole thing with state-based functional approach

_config.yml

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
theme: jekyll-theme-cayman

index.html

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Snake Game</title>
5+
<meta
6+
name="viewport"
7+
content="width=device-width, initial-scale=1, shrink-to-fit=no"
8+
/>
9+
<link rel="stylesheet" href="styles.css" />
10+
</head>
11+
12+
<body>
13+
<section>
14+
<h1>Snake Game</h1>
15+
<ul>
16+
<li>
17+
Use your arrow keys to change the snake's direction: up, left, right,
18+
and down.
19+
</li>
20+
<li>Eat food to gain points.</li>
21+
<li>Don't run the snake into the wall, or its own tail.</li>
22+
<li>
23+
Each time the snake eats, the snake will grow, and the game will get a
24+
little bit faster.
25+
</li>
26+
</ul>
27+
</section>
28+
<section>
29+
<div class="gameData">
30+
<p>Speed: <span id="speed"></span></p>
31+
<p>Score: <span id="score"></span></p>
32+
<button type="button" id="startGame">Play!</button>
33+
</div>
34+
35+
<canvas id="gameCanvas" height="300" width="300"> </canvas>
36+
</section>
37+
<script type="text/javascript" src="snake.js"></script>
38+
</body>
39+
</html>

snake.js

+180
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
const SnakeGame = function() {
2+
const CANVAS_BORDER_COLOUR = 'black';
3+
const CANVAS_BACKGROUND_COLOUR = 'white';
4+
const SNAKE_FILL_COLOUR = 'lightGreen';
5+
const SNAKE_BORDER_COLOUR = 'darkGreen';
6+
const FOOD_FILL_COLOUR = 'red';
7+
const FOOD_BORDER_COLOUR = 'darkRed';
8+
const GAME_CANVAS = document.getElementById('gameCanvas');
9+
const CTX = GAME_CANVAS.getContext('2d');
10+
11+
let score;
12+
let snakeSpeed;
13+
let gameLoop;
14+
let snake;
15+
let velocityX;
16+
let velocityY;
17+
let foodX;
18+
let foodY;
19+
let changingDirection;
20+
let inProgress;
21+
let changeSpeedValue;
22+
let gridCellSize;
23+
let foodScoreValue;
24+
25+
const initialize = () => {
26+
score = 0;
27+
snakeSpeed = 200;
28+
snake = [
29+
{ x: 150, y: 150 },
30+
{ x: 140, y: 150 },
31+
{ x: 130, y: 150 },
32+
{ x: 120, y: 150 },
33+
{ x: 110, y: 150 }
34+
];
35+
velocityX = 10;
36+
velocityY = 0;
37+
changingDirection = false;
38+
inProgress = true;
39+
changeSpeedValue = 10;
40+
gridCellSize = 10;
41+
foodScoreValue = 10;
42+
updateGameData();
43+
};
44+
45+
//create game elements
46+
const setCanvas = () => {
47+
CTX.fillStyle = CANVAS_BACKGROUND_COLOUR;
48+
CTX.strokeStyle = CANVAS_BORDER_COLOUR;
49+
CTX.fillRect(0, 0, GAME_CANVAS.width, GAME_CANVAS.height);
50+
CTX.strokeRect(0, 0, GAME_CANVAS.width, GAME_CANVAS.height);
51+
};
52+
53+
const drawSnakeUnit = snakeUnit => {
54+
CTX.fillStyle = SNAKE_FILL_COLOUR;
55+
CTX.strokeStyle = SNAKE_BORDER_COLOUR;
56+
CTX.fillRect(snakeUnit.x, snakeUnit.y, gridCellSize, gridCellSize);
57+
CTX.strokeRect(snakeUnit.x, snakeUnit.y, gridCellSize, gridCellSize);
58+
};
59+
60+
const drawFood = () => {
61+
CTX.fillStyle = FOOD_FILL_COLOUR;
62+
CTX.strokeStyle = FOOD_BORDER_COLOUR;
63+
CTX.fillRect(foodX, foodY, gridCellSize, gridCellSize);
64+
CTX.strokeRect(foodX, foodY, gridCellSize, gridCellSize);
65+
};
66+
67+
//food logic
68+
const randomMultipleOfTen = (min, max) => {
69+
return (
70+
Math.round((Math.random() * (max - min) + min) / gridCellSize) *
71+
gridCellSize
72+
);
73+
};
74+
75+
const makeFood = () => {
76+
foodX = randomMultipleOfTen(0, GAME_CANVAS.width - gridCellSize);
77+
foodY = randomMultipleOfTen(0, GAME_CANVAS.height - gridCellSize);
78+
79+
snake.forEach(function isFoodOnSnake(part) {
80+
const foodIsOnSnake = part.x == foodX && part.y == foodY;
81+
if (foodIsOnSnake) makeFood();
82+
});
83+
};
84+
85+
//snake logic
86+
const drawSnake = () => {
87+
snake.forEach(drawSnakeUnit);
88+
};
89+
const moveSnake = () => {
90+
const head = { x: snake[0].x + velocityX, y: snake[0].y + velocityY };
91+
snake.unshift(head);
92+
93+
const snakeAteFood = snake[0].x === foodX && snake[0].y === foodY;
94+
if (snakeAteFood) {
95+
score += foodScoreValue;
96+
snakeSpeed -= changeSpeedValue;
97+
updateGameData();
98+
makeFood();
99+
} else {
100+
snake.pop();
101+
}
102+
};
103+
const changeDirection = event => {
104+
const keyPressed = event.code;
105+
const movingUp = velocityY === -gridCellSize;
106+
const movingDown = velocityY === gridCellSize;
107+
const movingRight = velocityX === gridCellSize;
108+
const movingLeft = velocityX === -gridCellSize;
109+
110+
if (changingDirection) return;
111+
changingDirection = true;
112+
113+
if (keyPressed === 'ArrowLeft' && !movingRight) {
114+
event.preventDefault();
115+
velocityX = -gridCellSize;
116+
velocityY = 0;
117+
} else if (keyPressed === 'ArrowUp' && !movingDown) {
118+
event.preventDefault();
119+
velocityX = 0;
120+
velocityY = -gridCellSize;
121+
} else if (keyPressed === 'ArrowRight' && !movingLeft) {
122+
event.preventDefault();
123+
velocityX = gridCellSize;
124+
velocityY = 0;
125+
} else if (keyPressed === 'ArrowDown' && !movingUp) {
126+
event.preventDefault();
127+
velocityX = 0;
128+
velocityY = gridCellSize;
129+
}
130+
};
131+
132+
//game logic
133+
const didGameEnd = () => {
134+
// init i at 4 because first 4 units of snake cannot collide with each other
135+
for (let i = 4; i < snake.length; i++) {
136+
const didCollide = snake[i].x === snake[0].x && snake[i].y === snake[0].y;
137+
if (didCollide) return true;
138+
}
139+
140+
const hitLeftBorder = snake[0].x < 0;
141+
const hitRightBorder = snake[0].x > GAME_CANVAS.width - gridCellSize;
142+
const hitTopBorder = snake[0].y < 0;
143+
const hitBottomBorder = snake[0].y > GAME_CANVAS.height - gridCellSize;
144+
145+
return hitLeftBorder || hitRightBorder || hitTopBorder || hitBottomBorder;
146+
};
147+
const updateGameData = () => {
148+
document.getElementById('score').innerHTML = score;
149+
document.getElementById('speed').innerHTML = snakeSpeed;
150+
};
151+
const gameEngine = () => {
152+
if (didGameEnd()) {
153+
alert('You died!');
154+
clearInterval(gameLoop);
155+
inProgress = false;
156+
return;
157+
}
158+
159+
gameLoop = setTimeout(function() {
160+
changingDirection = false;
161+
setCanvas();
162+
drawSnake();
163+
drawFood();
164+
moveSnake();
165+
gameEngine();
166+
}, snakeSpeed);
167+
};
168+
169+
document.addEventListener('keydown', changeDirection);
170+
document.getElementById('startGame').addEventListener('click', function() {
171+
if (!inProgress) {
172+
initialize();
173+
gameEngine();
174+
makeFood();
175+
} else {
176+
return;
177+
}
178+
});
179+
};
180+
SnakeGame();

styles.css

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
section {
2+
display: flex;
3+
flex-direction: column;
4+
margin: 0 auto;
5+
}
6+
7+
h1 {
8+
text-align: center;
9+
}
10+
11+
.gameData {
12+
display: flex;
13+
flex-direction: row;
14+
justify-content: space-between;
15+
}
16+
17+
#gameCanvas {
18+
align-self: center;
19+
}
20+
21+
@media only screen and (min-width: 768px) {
22+
section {
23+
width: 500px;
24+
}
25+
}

0 commit comments

Comments
 (0)