Suddenly on a whim, spent an hour to write a snake ^_^!
The rules
- The whole picture is a 64 by 40 rectangle
- Randomly generate food at blank coordinates
- Snakes can’t bump into walls and eat themselves
- Your body grows one square for every food you eat
- The initial speed is 150ms, with more food, the speed is faster, the fastest 50ms movement
- The change of direction is controlled by the arrow keys of the keyboard and cannot be directed in the opposite direction
Implementation approach
The overall idea is as follows:
- Use Canvas as the base implementation
- The main body of the snake is drawn using a two-dimensional array
- The snake movement removes the last bit of the snake data structure, and then adds it to the main head of the snake according to the direction and the data of the snake head, and determines whether it is finished
- Is the point where the snake will move the food? If so, do not delete the last bit of the snake data
- If the length of the snake is 64 x 40, and it occupies the entire rectangle, the game is finished
html + css
<! -- css -->
<style>
body {
background-color: #eee;
}
.container {
text-align: center;
}
.top {
margin: 20px auto;
width: 640px;
}
#score {
float: left;
}
.main {
position: absolute;
left: 50%;
transform: translateX(-50%);
width: 642px;
height: 402px;
}
#snake {
border: 1px solid # 000;
width: 640px;
height: 400px;
display: inline-block;
z-index: 99;
background-color: rgba(0.0.0.1);
}
#mask {
background-color: rgba(0.0.0.5);
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 100;
display: block;
color: #fff;
line-height: 400px;
text-align: center;
font-size: 30px;
cursor: pointer;
}
</style>
<! -- DOM -->
<div class="container">
<div class="top">
<span id="score">Score: 0</span>
<button id="restart">Start all over again</button>
<button id="stop">suspended</button>
<button id="continue">Continue to</button>
</div>
<div class="main">
<canvas id="snake" width="640" height="400"></canvas>
<div id="mask">start</div>
</div>
</div>
Copy the code
Constructing rules
Define methods for all behaviors first, and then combine them.
Initial constructor
<script>
let greedySnake = null
let score = document.querySelector('#score')
let restart = document.querySelector('#restart')
let stop = document.querySelector('#stop')
let conti = document.querySelector('#continue')
let mask = document.querySelector('#mask')
class GreedySnake {
constructor() {
this.canvas = document.querySelector('#snake')
this.ctx = this.canvas.getContext('2d')
this.maxX = 64 / / the biggest
this.maxY = 40 / / the largest column
this.itemWidth = 10 // The size of each point
this.direction = 'right'// Up down right left
this.speed = 150 / / ms speed
this.isStop = false // Whether to pause
this.isOver = false // Whether to end
this.isStart = false // Whether to start
this.score = 0 / / score
this.timer = null // Move the timer
this.j = 1 // Food scintillation auxiliary variable
this.canChange = true // Whether the line of defense can be changed
this.grid = new Array(a)// Calculate all coordinate points
for (let i = 0; i < this.maxX; i++) {
for (let j = 0; j < this.maxY; j++) {
this.grid.push([i, j])
}
}
}
}
// Initialize the instantiation
greedySnake = new GreedySnake()
</script>
Copy the code
Initialize the snake data
Take a set of data in the middle and define the snake’s initial position
// Create the snake body
createSnake() {
this.snake = [
[4.25],
[3.25],
[2.25],
[1.25],
[0.25]]}Copy the code
Food to generate
// Take the coordinate points
createPos() {
let [x, y] = this.grid[(Math.random() * this.grid.length) | 0]
// The position cannot be a coordinate in the snake data
for (let i = 0; i < this.snake.length; i++) {
if (this.snake[i][0] == x && this.snake[i][1] == y) {
return this.createPos()
}
}
return [x, y]
}
// Produce food
createFood() {
this.food = this.createPos()
// The score is updated each time a food is generated indicating that one has been eaten
score.innerHTML = 'Score: '+ this.score++
// Update speed, maximum speed is 50ms
if (this.speed > 50) {
this.speed--
}
}
Copy the code
Grid line drawing
/ / grid lines
drawGridLine() {
for (let i = 1; i < this.maxY; i++) {
this.ctx.moveTo(0, i * this.itemWidth)
this.ctx.lineTo(this.canvas.width, i * this.itemWidth)
}
for (let i = 1; i < this.maxX; i++) {
this.ctx.moveTo(i * this.itemWidth, 0)
this.ctx.lineTo(i * this.itemWidth, this.canvas.height)
}
this.ctx.lineWidth = 1
this.ctx.strokeStyle = '#ddd'
this.ctx.stroke()
}
Copy the code
Draw snakes and food
/ / to draw
draw() {
// Empty the canvas
this.ctx.clearRect(0.0.this.canvas.width, this.canvas.height)
// Draw the grid
this.drawGridLine()
// Draw the food
this.ctx.fillStyle="# 000"
this.ctx.fillRect(
this.food[0] * this.itemWidth + this.j,
this.food[1] * this.itemWidth + this.j,
this.itemWidth - this.j * 2.this.itemWidth - + this.j * 2
)
this.j ^= 1
// Draw the head of the snake
this.ctx.fillStyle="green"
this.ctx.fillRect(
this.snake[0] [0] * this.itemWidth + 0.5.this.snake[0] [1] * this.itemWidth + 0.5.this.itemWidth - 1.this.itemWidth - 1
)
// Draw the body of the snake
this.ctx.fillStyle="red"
for (let i = 1; i < this.snake.length; i++) {
this.ctx.fillRect(
this.snake[i][0] * this.itemWidth + 0.5.this.snake[i][1] * this.itemWidth + 0.5.this.itemWidth - 1.this.itemWidth - 1)}}Copy the code
Pause and Continue
// Pause the game
stop() {
if (this.isOver) return
this.isStop = true
mask.style.display = 'block'
mask.innerHTML = 'suspend'
}
// Continue the game
continue() {
if (this.isOver) return
this.isStop = false
this.move()
mask.style.display = 'none'
}
Copy the code
Directional control of the snake
Listen for the press of the arrow keys to change direction
getDirection() {
// Go up 38, down 40, left 37, right 39
document.onkeydown = (e) = > {
// You cannot change direction twice in a row between moves of the snake
if (!this.canChange) return
switch(e.keyCode) {
case 37:
if (this.direction ! = ='right') {
this.direction = 'left'
this.canChange = false
}
break
case 38:
if (this.direction ! = ='down') {
this.direction = 'up'
this.canChange = false
}
break
case 39:
if (this.direction ! = ='left') {
this.direction = 'right'
this.canChange = false
}
break
case 40:
if (this.direction ! = ='up') {
this.direction = 'down'
this.canChange = false
}
break
case 32:
// Space pause and continue
if (!this.isStop) {
this.stop()
} else {
this.continue()
}
break}}}Copy the code
No Violation End
/ / end
over([x, y]) {
if (x < 0 || x >= this.maxX || y < 0 || y >= this.maxY) {
return true
}
if (this.snake.some(v= > v[0] === x && v[1] === y)) {
return true}}Copy the code
Has the clearance been completed?
/ / finish
completed() {
if (this.snake.length == this.maxX * this.maxY) {
return true}}Copy the code
The movement of the snake
Snake movement is the logical implementation of the entire snake game, and it needs to be done internally by combining various other methods.
/ / move
move() {
if (this.isStop) return
let [x, y] = this.snake[0]
switch(this.direction) {
case 'left':
x--
break
case 'right':
x++
break
case 'up':
y--
break
case 'down':
y++
break
}
// Delete the last bit of data if the next step is not the location of the food
if(x ! = =this.food[0] || y ! = =this.food[1]) {
this.snake.pop()
} else { // If it is a food, do not delete the last one and regenerate it as a food
this.createFood()
}
// Check whether it is finished
if (this.over([x, y])) {
this.isOver = true
mask.style.display = 'block'
mask.innerHTML = 'the end'
return
}
// Determine whether it is complete
if (this.completed()) {
mask.style.display = 'block'
mask.innerHTML = 'Congratulations on finishing the game.'
return
}
// Put the coordinates in the head of the snake
this.snake.unshift([x, y])
this.draw() / / to draw
this.canChange = true // You can change the direction
// Make the snake crawl recursively
this.timer = setTimeout(() = > this.move(), this.speed)
}
Copy the code
Finally, add a few buttons for interaction
restart.onclick = () = > {
if(! greedySnake.isStart)return
greedySnake.start()
}
stop.onclick = () = > {
if(greedySnake.isStop || ! greedySnake.isStart)return
greedySnake.stop()
}
conti.onclick = () = > {
if(! greedySnake.isStop || ! greedySnake.isStart)return
greedySnake.continue()
}
mask.onclick = () = > {
if(! greedySnake.isStart) { greedySnake.start() }else {
greedySnake.continue()
}
}
Copy the code
The complete code
<! DOCTYPEhtml>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="Width = device - width, initial - scale = 1.0">
<title>snake</title>
<style>
body {
background-color: #eee;
}
.container {
text-align: center;
}
.top {
margin: 20px auto;
width: 640px;
}
#score {
float: left;
}
.main {
position: absolute;
left: 50%;
transform: translateX(-50%);
width: 642px;
height: 402px;
}
#snake {
border: 1px solid # 000;
width: 640px;
height: 400px;
display: inline-block;
z-index: 99;
background-color: rgba(0.0.0.1);
}
#mask {
background-color: rgba(0.0.0.5);
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 100;
display: block;
color: #fff;
line-height: 400px;
text-align: center;
font-size: 30px;
cursor: pointer;
}
</style>
</head>
<body>
<div class="container">
<div class="top">
<span id="score">Score: 0</span>
<button id="restart">Start all over again</button>
<button id="stop">suspended</button>
<button id="continue">Continue to</button>
</div>
<div class="main">
<canvas id="snake" width="640" height="400"></canvas>
<div id="mask">start</div>
</div>
</div>
<script>
let greedySnake = null
let score = document.querySelector('#score')
let restart = document.querySelector('#restart')
let stop = document.querySelector('#stop')
let conti = document.querySelector('#continue')
let mask = document.querySelector('#mask')
restart.onclick = () = > {
if(! greedySnake.isStart)return
greedySnake.start()
}
stop.onclick = () = > {
if(greedySnake.isStop || ! greedySnake.isStart)return
greedySnake.stop()
}
conti.onclick = () = > {
if(! greedySnake.isStop || ! greedySnake.isStart)return
greedySnake.continue()
}
mask.onclick = () = > {
if(! greedySnake.isStart) { greedySnake.start() }else {
greedySnake.continue()
}
}
// The size is 64 x 40
class GreedySnake {
constructor() {
this.canvas = document.querySelector('#snake')
this.ctx = this.canvas.getContext('2d')
this.maxX = 64 / / the biggest
this.maxY = 40 / / the largest column
this.itemWidth = 10 // The size of each point
this.direction = 'right'// Up down right left
this.speed = 150 / / ms speed
this.isStop = false // Whether to pause
this.isOver = false // Whether to end
this.isStart = false // Whether to start
this.score = 0 / / score
this.timer = null // Move the timer
this.j = 1
this.canChange = true
this.grid = new Array(a)for (let i = 0; i < this.maxX; i++) {
for (let j = 0; j < this.maxY; j++) {
this.grid.push([i, j])
}
}
this.drawGridLine()
this.getDirection()
}
/ /
start() {
if (this.timer) {
clearTimeout(this.timer)
}
if (!this.isStart) {
this.isStart = true
}
this.score = 0
this.speed = 150
this.isStop = false
this.isOver = false
this.direction = 'right'
this.createSnake()
this.createFood()
this.draw()
this.move()
mask.style.display = 'none'
}
// Create the snake body
createSnake() {
this.snake = [
[4.25],
[3.25],
[2.25],
[1.25],
[0.25]]}/ / move
move() {
if (this.isStop) return
let [x, y] = this.snake[0]
switch(this.direction) {
case 'left':
x--
break
case 'right':
x++
break
case 'up':
y--
break
case 'down':
y++
break
}
// If the next step is not the location of food
if(x ! = =this.food[0] || y ! = =this.food[1]) {
this.snake.pop()
} else {
this.createFood()
}
if (this.over([x, y])) {
this.isOver = true
mask.style.display = 'block'
mask.innerHTML = 'the end'
return
}
if (this.completed()) {
mask.style.display = 'block'
mask.innerHTML = 'Congratulations on finishing the game.'
return
}
this.snake.unshift([x, y])
this.draw()
this.canChange = true
this.timer = setTimeout(() = > this.move(), this.speed)
}
// Pause the game
stop() {
if (this.isOver) return
this.isStop = true
mask.style.display = 'block'
mask.innerHTML = 'suspend'
}
// Continue the game
continue() {
if (this.isOver) return
this.isStop = false
this.move()
mask.style.display = 'none'
}
getDirection() {
// Go up 38, down 40, left 37, right 39
document.onkeydown = (e) = > {
// You cannot change direction twice in a row between moves of the snake
if (!this.canChange) return
switch(e.keyCode) {
case 37:
if (this.direction ! = ='right') {
this.direction = 'left'
this.canChange = false
}
break
case 38:
if (this.direction ! = ='down') {
this.direction = 'up'
this.canChange = false
}
break
case 39:
if (this.direction ! = ='left') {
this.direction = 'right'
this.canChange = false
}
break
case 40:
if (this.direction ! = ='up') {
this.direction = 'down'
this.canChange = false
}
break
case 32:
// Space pause and continue
if (!this.isStop) {
this.stop()
} else {
this.continue()
}
break}}}createPos() {
let [x, y] = this.grid[(Math.random() * this.grid.length) | 0]
for (let i = 0; i < this.snake.length; i++) {
if (this.snake[i][0] == x && this.snake[i][1] == y) {
return this.createPos()
}
}
return [x, y]
}
// Produce food
createFood() {
this.food = this.createPos()
// Update the score
score.innerHTML = 'Score: '+ this.score++
if (this.speed > 50) {
this.speed--
}
}
/ / end
over([x, y]) {
if (x < 0 || x >= this.maxX || y < 0 || y >= this.maxY) {
return true
}
if (this.snake.some(v= > v[0] === x && v[1] === y)) {
return true}}/ / finish
completed() {
if (this.snake.length == this.maxX * this.maxY) {
return true}}/ / grid lines
drawGridLine() {
for (let i = 1; i < this.maxY; i++) {
this.ctx.moveTo(0, i * this.itemWidth)
this.ctx.lineTo(this.canvas.width, i * this.itemWidth)
}
for (let i = 1; i < this.maxX; i++) {
this.ctx.moveTo(i * this.itemWidth, 0)
this.ctx.lineTo(i * this.itemWidth, this.canvas.height)
}
this.ctx.lineWidth = 1
this.ctx.strokeStyle = '#ddd'
this.ctx.stroke()
}
/ / to draw
draw() {
// Empty the canvas
this.ctx.clearRect(0.0.this.canvas.width, this.canvas.height)
this.drawGridLine()
this.ctx.fillStyle="# 000"
this.ctx.fillRect(
this.food[0] * this.itemWidth + this.j,
this.food[1] * this.itemWidth + this.j,
this.itemWidth - this.j * 2.this.itemWidth - + this.j * 2
)
this.j ^= 1
this.ctx.fillStyle="green"
this.ctx.fillRect(
this.snake[0] [0] * this.itemWidth + 0.5.this.snake[0] [1] * this.itemWidth + 0.5.this.itemWidth - 1.this.itemWidth - 1
)
this.ctx.fillStyle="red"
for (let i = 1; i < this.snake.length; i++) {
this.ctx.fillRect(
this.snake[i][0] * this.itemWidth + 0.5.this.snake[i][1] * this.itemWidth + 0.5.this.itemWidth - 1.this.itemWidth - 1
)
}
}
}
greedySnake = new GreedySnake()
</script>
</body>
</html>
Copy the code