一、案例效果
![](https://i-blog.csdnimg.cn/blog_migrate/0e722e50a95e9227569b2cfaf7800fd6.gif)
二、HTML和CSS代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
.map {
width: 1000px;
height: 600px;
border: 5px solid #333;
margin: 50px auto;
position: relative;
background-image: url(./imgs/bg.png);
background-repeat: repeat;
background-size: 20px 20px;
}
.map>div {
width: 20px;
height: 20px;
position: absolute;
background-size: 20px 20px;
background-repeat: no-repeat;
}
.map>.food {
background-image: url(./imgs/food.png);
}
.map>.head {
background-image: url(./imgs/head.png);
}
.map>.body {
background-image: url(./imgs/body.png);
}
</style>
</head>
<body>
<button id="btn1">开始</button>
<button id="btn2">暂停</button>
<button id="btn3">重新开始</button>
<div class="map" id="map"></div>
<script type="module">
import Game from './game.js'
const g = new Game('#map')
document.querySelector('#btn1').onclick = function () {
g.start()
}
document.querySelector('#btn2').onclick = function () {
g.pause()
}
document.querySelector('#btn3').onclick = function () {
g.reload()
}
// 当按下方向按键的时候, 修改蛇的方向
document.onkeydown = function (e) {
if (e.keyCode == 37) {
// left
g.setDes('left')
} else if (e.keyCode == 38) {
// top
g.setDes('top')
} else if (e.keyCode == 39) {
// right
g.setDes('right')
} else if (e.keyCode == 40) {
// bottom
g.setDes('bottom')
}
}
// .....
</script>
<script>
/**
* 贪吃蛇分析
*
* 网页游戏: 一定要分工明确
*
* 1. 食物 类
* * 负责生成一个 食物
* * 随机出现在地图上的某一个位置
* * 根据游戏规则 通知我 然后去换位置
* 2. 蛇 类
* * 要负责生成一个蛇
* * 负责 走一步, 由游戏规则通知我多长时间走一步
* * 负责判断 蛇头是否和食物重叠
* * 负责判断 是否超界
* 3. 游戏规则 类
* * 通知 食物类 在地图上创建一个 食物
* * 通知 蛇类 在地图上创建一个 蛇
* * 开始游戏
* * 每间隔固定的时间, 通知蛇走一步
* * 暂停游戏
* * 停止通知蛇走一步
* * 重新开始
* * 重新走一遍(初始化)
* * 和页面联动
* * 监听到键盘按键, 然后通知蛇修改方向
*/
</script>
</body>
</html>
三、JS代码
1、food.js
export default class Food {
constructor(ele) {
// 1. 地图
this.map = document.querySelector(ele);
// 2.1 生成一个 DIV
this.oDiv = document.createElement("div");
// 2.2 给生成的 DIV 添加类名
this.oDiv.classList.add("food");
// 2.3 将当前div 插入到 地图中
this.map.appendChild(this.oDiv);
// 3. 记录 食物 x 和 y 的坐标
this.x = 0;
this.y = 0;
// 调用随机改变食物的位置
this.changeFood();
}
// 原型
changeFood() {
// 0. 拿到宽度和高度
const w = parseInt(window.getComputedStyle(this.map).width);
const h = parseInt(window.getComputedStyle(this.map).height);
// 1. 计算每行 可以有 多少个 小格子
const row = w / 20;
// 2. 计算每列 可以有 多少个 小格子
const clo = h / 20;
/**
* 3. 求 随机在第几个 格子上(在行上)
* Math.floor(Math.random() * (row - 1)) + 1 不含最大值,含最小值
*/
// const posX = (Math.floor(Math.random() * (row - 1)) + 1) * 20;
// const posX = (Math.floor(Math.random() * (row - 0)) + 0) * 20;
const posX = (Math.floor(Math.random() * row)) * 20;
// 4. 求 在某一列的随机坐标
// const posY = (Math.floor(Math.random() * (clo - 1)) + 1) * 20;
// const posY = (Math.floor(Math.random() * (clo - 0)) + 0) * 20;
const posY = (Math.floor(Math.random() * clo)) * 20;
// 5. 求到随即坐标后, 修改 this.x 和 this.y
this.x = posX;
this.y = posY;
// 6. 修改食物的坐标
this.oDiv.style.left = this.x + "px";
this.oDiv.style.top = this.y + "px";
}
}
2、snake.js
export default class Snake {
constructor(ele) {
// 1. 地图
this.map = document.querySelector(ele);
// 2. 方向
this.des = "bottom";
// 3. 存储一条蛇
this.snake = [];
// 4. 创建一条蛇
this.makeSnake();
}
// 1. 创建一节蛇
addSnake() {
/**
* 1. 当前 this.snake 为空数组, 此时只需要创建一节蛇头即可
* 2. 当前 this.snake 数组已经有值了, 除了要创建一节蛇头意外, 还要把原本的蛇头更改蛇身
*/
// 1. 拿到数组的第一项(约定此项为蛇头)
const head = this.snake[0];
// 2. 如果 数组内有数据, 将原本的蛇头更改为 蛇身
if (head !== undefined) {
head.className = "body";
}
// 3. 创建 蛇头
const oHaed = document.createElement("div");
oHaed.className = "head";
this.map.appendChild(oHaed); // 添加到 DOM 节点
this.snake.unshift(oHaed); // 把新建的蛇头 添加到 数组开头(约定了 蛇头添加到 数组开头)
// 4. 调整蛇头的定位
const pos = {
x: 0,
y: 0,
};
if (head !== undefined) {
/**
* 如果当前方向是 向 右, left += 20 top 不变
* 左, left -= 20 top 不变
* 下, left 不变 top += 20
* 上, left 不变 top -= 20
*/
// 获取到原本 蛇头的 偏移量
pos.x = head.offsetLeft;
pos.y = head.offsetTop;
switch (this.des) {
case "right": pos.x += 20; break;
case "left": pos.x -= 20; break;
case "bottom": pos.y += 20; break;
case "top": pos.y -= 20; break;
}
}
oHaed.style.left = pos.x + "px";
oHaed.style.top = pos.y + "px";
}
/**
* 需求: 一条蛇 默认 5节
*
* 分析: 书写一个 创建一节蛇的函数, 然后调用5次
*
* 有两个函数
* 1---创建一节蛇
* 只需要创建一个节点
* 2---创建一条蛇
* 只需要调用 创建一节蛇的函数 5次
*/
// 2. 创建一条蛇
makeSnake() {
for (let i = 0; i < 5; i++) {
this.addSnake();
}
}
// 3. 走一步
move () {
// 3.1 删除数组末尾元素
const body = this.snake.pop()
// 3.2 删除对应 DOM 节点
body.remove()
// 3.3 新增一个蛇头
this.addSnake()
}
// 4. 判断蛇头是否和食物重叠
isEat(foodX, foodY) {
const head = this.snake[0] // 因为约定了 蛇头放在 [0] 的位置上
if (head.offsetTop == foodY && head.offsetLeft == foodX) {
return true
}
return false
}
// 5. 判断蛇头 是否超界
isDie () {
// 1. 拿到蛇头
const head = this.snake[0]
if (
head.offsetTop < 0 ||
head.offsetLeft < 0 ||
head.offsetTop >= this.map.clientHeight ||
head.offsetLeft >= this.map.clientWidth
) {
return true
}
return false
}
}
3、snake.js
import Food from "./food.js";
import Snake from "./snake.js";
export default class Game {
constructor(ele) {
this.food = new Food(ele);
this.snake = new Snake(ele);
this.timer = 0; // 记录定时器ID, 用于结束定时器
this.sum = 0;
this.level = 1;
}
// 1. 开始游戏
start() {
let newTime = 400 - this.level * 100;
this.timer = setInterval(() => {
this.snake.move();
this.up();
// 判断蛇头是否和食物重叠
if (this.snake.isEat(this.food.x, this.food.y)) {
this.food.changeFood();
this.snake.addSnake();
this.sum++;
}
// 判断蛇头是否超界
if (this.snake.isDie()) {
clearInterval(this.timer);
alert("GG");
}
}, newTime);
}
// 2. 暂停游戏
pause() {
clearInterval(this.timer);
}
// 3. 重新开始
reload() {
window.location.reload();
}
// 4. 修改蛇方向
setDes(type) {
this.snake.des = type;
}
// 5.
up() {
if (this.sum == 2) {
this.level++;
this.sum = 0;
this.start();
}
}
}