思路:
- 绘制一张40×40的格子,一共1600个格子,也就是一个长度为1600的数组,每个格子代表数组里的一个值,作为游戏区。
- 蛇的默认值为:[[0, 12], [0, 13], [0, 14]]的一个二位数组,每一项都代表着蛇身的一部分,例如:[0,12]代表从左上角开始x轴坐标为12,y轴坐标为0的哪一个格子,数组的最后一项为蛇头。
- 食物:随机生成一个二维数组,例如:[[5,5]]
- 绘制:从第一个格子开始绘制,碰到蛇身变色,碰到蛇头变色,碰到食物变色。
- 移动:监听键盘的方向键,判断当前方向,例如:按上键,从蛇数组最后一项开始,蛇头x-1,y不变,以后每一项等于前一项,就形成了蛇前进的效果,,不允许直接掉头。
- 设定计时器,每隔一定时间重新绘制一次,就形成了,蛇一直移动的效果。
- 蛇头碰到食物,从蛇头开始成长。
- 蛇碰到游戏区边缘或者碰到自己均死亡。
1. html及样式代码
<!DOCTYPE html>
<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>Document</title>
<style>
.box {
width: 400px;
height: 400px;
border: 1px #999 solid;
margin: 10px auto 0;
}
.item {
width: 9px;
height: 9px;
background-color: #fff;
float: left;
margin: 0 1px 1px 0;
}
.snakeHead {
background-color: #f00;
}
.snakeBody {
background-color: #f0f;
}
.food {
background-color: #ff0;
}
.score {
margin: 10px auto;
text-align: center;
font-size: 26px;
font-weight: 600;
}
.bot-box {
display: flex;
justify-content: space-around;
}
.handle-box {
display: grid;
grid-template-columns: repeat(3, 1fr);
width: 140px;
grid-gap: 10px;
height: 100px;
}
.handle-box div,
.key {
border: 1px #f00 solid;
width: 40px;
height: 40px;
display: flex;
justify-content: center;
align-items: center;
font-size: 18px;
font-weight: 600;
}
.direction-1 {
grid-column: span 3 / auto;
margin: 0 auto;
}
.key-box {
display: flex;
align-items: center;
margin: 10px 0;
}
.key-box .key {
margin-right: 10px;
}
</style>
</head>
<body>
<!-- 游戏区 -->
<div class="box" id="box"></div>
<!-- 得分 -->
<div class="score">0</div>
<!-- 操作按钮提示 -->
<div class="bot-box">
<!-- 方向键 -->
<div class="handle-box">
<div class="direction-1">↑</div>
<div class="direction-2">←</div>
<div class="direction-3">↓</div>
<div class="direction-4">→</div>
</div>
<!-- 功能键 -->
<div>
<div class="key-box">
<div class="key">J</div>
<span>:开始游戏</span>
</div>
<div class="key-box">
<div class="key">K</div>
<span>:游戏暂停</span>
</div>
<div class="key-box">
<div class="key">R</div>
<span>:重新开始游戏</span>
</div>
</div>
</div>
</body>
</html>
2、 设置初始值设置
// 游戏区dom
const box = document.querySelector('.box')
// 得分dom
const score = document.querySelector('.score')
// 游戏格子
let gameBox = [40, 40]
// 游戏开始标志(计时器)
let start = null
// 当前速度
let velocity = 200
// 食物
let food = [[5, 5]]
// 蛇身
let snake = [[0, 12], [0, 13], [0, 14]]
// 方向
let direction = 3
3. 绘制方法
由于y轴对应的游戏区位置是40的倍数,所以使用transferPosition方法进行坐标转换,例如点[5,6],对应的游戏区格子是第40*5+6个格子。
// 数组转化为相应点坐标
function transferPosition(array) {
let ary = []
for (let i = 0; i < array.length; i++) {
const e = [...array[i]];
ary.push(e[0] * gameBox[0] + e[1])
}
return ary
}
// 绘制
function draw() {
// 蛇身+蛇头对应的坐标
const snakeAry = transferPosition(snake)
// 食物对应的坐标
const foodAry = transferPosition(food)
box.innerHTML = ''
// 游戏区格子总长度
const allBox = gameBox[0] * gameBox[0]
for (let i = 0; i < allBox; i++) {
let div = document.createElement('div')
if (snakeAry.indexOf(i) === snakeAry.length - 1) {
// 蛇头样式
div.className = 'item snakeHead'
} else if (snakeAry.indexOf(i) !== -1) {
// 蛇身样式
div.className = 'item snakeBody'
} else if (foodAry.indexOf(i) !== -1) {
// 食物样式
div.className = 'item food'
} else {
// 空白区
div.className = 'item'
}
box.appendChild(div)
}
}
4. 蛇移动
蛇移动前要先记录蛇当前的位置,蛇头按照方向键的方向进行前进,蛇身要按照蛇身轨迹进行移动,例如:向下走一步,[[0, 12], [0, 13], [0, 14]] ---->[[0, 13], [0, 14], [1, 14]]
// 蛇移动
function move() {
// 深拷贝蛇行走之前的数组
let inventedSnake = []
for (let n = 0; n < snake.length; n++) {
const e = snake[n];
inventedSnake.push([...e])
}
// 判断行进方向,蛇身前进 direction:1左 2上 3右 4下
const lng = snake.length;
for (let i = lng - 1; i >= 0; i--) {
if (i === lng - 1) { // 蛇头前进
if (direction === 1) {
snake[i][1] = snake[i][1] - 1
} else if (direction === 2) {
snake[i][0] = snake[i][0] - 1
} else if (direction === 3) {
snake[i][1] = snake[i][1] + 1
} else if (direction === 4) {
snake[i][0] = snake[i][0] + 1
}
}
else { // 蛇身前进
snake[i] = inventedSnake[i + 1]
}
}
}
5. 随机生成食物
此处每次仅生成一个食物
// 生成随机数
function randomNumber() {
return Number.parseInt(Math.random() * 40)
}
// 随机生成食物
function createFood() {
food = [[randomNumber(), randomNumber()]]
}
6. 判断吃到食物,进行成长
// 判断是否吃到食物
function eatFood() {
if (food.length === 0) return
// 蛇头和食物重合及吃到食物
if (snake[snake.length - 1].toString() === food[0].toString()) {
// 成长
growUp()
// 重新生成食物
createFood()
}
}
// 吃到食物 进行成长
function growUp() {
let snakeFood = []
let [x, y] = snake[snake.length - 1]
// 按照蛇头的方向在蛇头处成长
if (direction === 1) {
snakeFood = [x, y - 1]
} else if (direction === 2) {
snakeFood = [x - 1, y]
} else if (direction === 3) {
snakeFood = [x, y + 1]
} else if (direction === 4) {
snakeFood = [x + 1, y]
}
snake.push(snakeFood)
// 重算积分(积分=蛇当前长度-初始长度)
score.innerHTML = snake.length - 3
}
7. 判断死亡
// 判断死亡
function judgingDeath() {
// 撞墙死亡
if (
snake[snake.length - 1][0] > (gameBox[0] - 1) ||
snake[snake.length - 1][0] < 0 ||
snake[snake.length - 1][1] > (gameBox[1] - 1) ||
snake[snake.length - 1][1] < 0
) {
// 游戏结束
endGame()
return true
}
// 咬到自己死亡
for (let i = 0; i < snake.length; i++) {
for (let j = 0; j < i - 1; j++) {
if (snake[i].toString() === snake[j].toString()) {
// 游戏结束
endGame()
return true
}
}
}
return false
}
8. 游戏结束
// 游戏结束(蛇死亡)
function endGame() {
clearInterval(start)
alert(`本局得分:${snake.length - 3}分,再接再厉`, '游戏结束', {
confirmButtonText: '确定',
callback: action => {
}
});
}
9. 游戏开始、暂停、重新开始
// 游戏开始 蛇开始爬行
function beginGame() {
// 游戏开始计时器
start = setInterval(() => {
// 判断死亡
if (judgingDeath()) return clearInterval(start)
// 蛇移动
move()
// 判断是否吃到食物
eatFood()
// 重绘
draw()
}, velocity);
}
// 游戏暂停
function suspendGame() {
clearInterval(start)
}
// 重新开始
function reBeginGame() {
food = [[5, 5]]
snake = [[0, 12], [0, 13], [0, 14]]
direction = 3
score.innerHTML = 0
beginGame()
}
10. 全部代码
<!DOCTYPE html>
<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>Document</title>
<style>
.box {
width: 400px;
height: 400px;
border: 1px #999 solid;
margin: 10px auto 0;
}
.item {
width: 9px;
height: 9px;
background-color: #fff;
float: left;
margin: 0 1px 1px 0;
}
.snakeHead {
background-color: #f00;
}
.snakeBody {
background-color: #f0f;
}
.food {
background-color: #ff0;
}
.score {
margin: 10px auto;
text-align: center;
font-size: 26px;
font-weight: 600;
}
.bot-box {
display: flex;
justify-content: space-around;
}
.handle-box {
display: grid;
grid-template-columns: repeat(3, 1fr);
width: 140px;
grid-gap: 10px;
height: 100px;
}
.handle-box div,
.key {
border: 1px #f00 solid;
width: 40px;
height: 40px;
display: flex;
justify-content: center;
align-items: center;
font-size: 18px;
font-weight: 600;
}
.direction-1 {
grid-column: span 3 / auto;
margin: 0 auto;
}
.key-box {
display: flex;
align-items: center;
margin: 10px 0;
}
.key-box .key {
margin-right: 10px;
}
</style>
</head>
<body>
<!-- 游戏区 -->
<div class="box" id="box"></div>
<!-- 得分 -->
<div class="score">0</div>
<!-- 操作按钮提示 -->
<div class="bot-box">
<!-- 方向键 -->
<div class="handle-box">
<div class="direction-1">↑</div>
<div class="direction-2">←</div>
<div class="direction-3">↓</div>
<div class="direction-4">→</div>
</div>
<!-- 功能键 -->
<div>
<div class="key-box">
<div class="key">J</div>
<span>:开始游戏</span>
</div>
<div class="key-box">
<div class="key">K</div>
<span>:游戏暂停</span>
</div>
<div class="key-box">
<div class="key">R</div>
<span>:重新开始游戏</span>
</div>
</div>
</div>
</body>
</html>
<script type="text/javascript">
(function () {
// 游戏区dom
const box = document.querySelector('.box')
// 分值dom
const score = document.querySelector('.score')
// 游戏格子
let gameBox = [40, 40]
// 游戏开始标志(计时器)
let start = null
// 当前速度
let velocity = 200
// 食物
let food = [[5, 5]]
// 蛇身
let snake = [[0, 12], [0, 13], [0, 14]]
// 方向
let direction = 3
// 数组转化为相应点坐标add
function transferPosition(array) {
let ary = []
for (let i = 0; i < array.length; i++) {
const e = [...array[i]];
ary.push(e[0] * gameBox[0] + e[1])
}
return ary
}
// 随机生成食物
function createFood() {
food = [[randomNumber(), randomNumber()]]
}
// 生成随机数
function randomNumber() {
return Number.parseInt(Math.random() * 40)
}
// 绘制
function draw() {
// 蛇身+蛇头对应的坐标
const snakeAry = transferPosition(snake)
// 食物对应的坐标
const foodAry = transferPosition(food)
box.innerHTML = ''
// 游戏区格子总长度
const allBox = gameBox[0] * gameBox[0]
for (let i = 0; i < allBox; i++) {
let div = document.createElement('div')
if (snakeAry.indexOf(i) === snakeAry.length - 1) {
// 蛇头样式
div.className = 'item snakeHead'
} else if (snakeAry.indexOf(i) !== -1) {
// 蛇身样式
div.className = 'item snakeBody'
} else if (foodAry.indexOf(i) !== -1) {
// 食物样式
div.className = 'item food'
} else {
// 空白区
div.className = 'item'
}
box.appendChild(div)
}
}
// 开始游戏之前先绘制一次
draw()
// 游戏结束(蛇死亡)
function endGame() {
clearInterval(start)
alert(`本局得分:${snake.length - 3}分,再接再厉`, '游戏结束', {
confirmButtonText: '确定',
callback: action => {
}
});
}
// 蛇移动
function move() {
// 深拷贝蛇行走之前的数组
let inventedSnake = []
for (let n = 0; n < snake.length; n++) {
const e = snake[n];
inventedSnake.push([...e])
}
// 判断行进方向,蛇身前进 direction:1左 2上 3右 4下
const lng = snake.length;
for (let i = lng - 1; i >= 0; i--) {
if (i === lng - 1) { // 蛇头前进
if (direction === 1) {
snake[i][1] = snake[i][1] - 1
} else if (direction === 2) {
snake[i][0] = snake[i][0] - 1
} else if (direction === 3) {
snake[i][1] = snake[i][1] + 1
} else if (direction === 4) {
snake[i][0] = snake[i][0] + 1
}
}
else { // 蛇身前进
snake[i] = inventedSnake[i + 1]
}
}
}
// 判断死亡
function judgingDeath() {
// 撞墙死亡
if (
snake[snake.length - 1][0] > (gameBox[0] - 1) ||
snake[snake.length - 1][0] < 0 ||
snake[snake.length - 1][1] > (gameBox[1] - 1) ||
snake[snake.length - 1][1] < 0
) {
// 游戏结束
endGame()
return true
}
// 咬到自己死亡
for (let i = 0; i < snake.length; i++) {
for (let j = 0; j < i - 1; j++) {
if (snake[i].toString() === snake[j].toString()) {
// 游戏结束
endGame()
return true
}
}
}
return false
}
// 游戏开始 蛇开始爬行
function beginGame() {
// 游戏开始计时器
start = setInterval(() => {
// 判断死亡
if (judgingDeath()) return clearInterval(start)
// 蛇移动
move()
// 判断是否吃到食物
eatFood()
// 重绘
draw()
}, velocity);
}
// 重新开始
function reBeginGame() {
food = [[5, 5]]
snake = [[0, 12], [0, 13], [0, 14]]
direction = 3
score.innerHTML = 0
beginGame()
}
// 游戏暂停
function suspendGame() {
clearInterval(start)
}
// 监听键盘事件 爬行方向
window.onkeydown = function (e) {
const { keyCode } = e
if (keyCode === 74) {
return beginGame()
}
if (keyCode === 75) {
return suspendGame()
}
if (keyCode === 82) {
return reBeginGame()
}
if (keyCode === 37 && direction !== 3) {
return direction = 1
}
if (keyCode === 38 && direction !== 4) {
return direction = 2
}
if (keyCode === 39 && direction !== 1) {
return direction = 3
}
if (keyCode === 40 && direction !== 2) {
return direction = 4
}
}
// 判断是否吃到食物
function eatFood() {
if (food.length === 0) return
// 蛇头和食物重合及吃到食物
if (snake[snake.length - 1].toString() === food[0].toString()) {
// 成长
growUp()
// 重新生成食物
createFood()
}
}
// 吃到食物 进行成长
function growUp() {
let snakeFood = []
let [x, y] = snake[snake.length - 1]
// 按照蛇头的方向在蛇头处成长
if (direction === 1) {
snakeFood = [x, y - 1]
} else if (direction === 2) {
snakeFood = [x - 1, y]
} else if (direction === 3) {
snakeFood = [x, y + 1]
} else if (direction === 4) {
snakeFood = [x + 1, y]
}
snake.push(snakeFood)
// 重算积分(积分=蛇当前长度-初始长度)
score.innerHTML = snake.length - 3
}
})()
</script>
后续继续补充…