一、实现效果
源码阿里云盘地址:https://www.aliyundrive.com/s/UkdDRX6DNjA
二、文件目录
三、布局文件及样式
布局文件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>贪吃蛇</title> </head> <body> <!--游戏主容器--> <div id="main"> <!--设置游戏的舞台--> <div id="stage"> <!-- 设置蛇--> <div id="snake"> <!-- snake内部的div 表示蛇的各个部分--> <div></div> </div> <!-- 设置事物--> <div id="food"> <div></div> <div></div> <div></div> <div></div> </div> </div> <!-- 设置游戏的积分牌--> <div id="score-panel"> <div> SCORE:<span id="score">0 </span> </div> <div> LEVEL:<span id="level">1 </span> </div> </div> </div> </body> </html>
样式
//清除默认样式 * { margin: 0; padding: 0; // 改变何止模型计算方式 box-sizing: border-box; } body { font: bold 20px "Courier"; } //设置变量 @bg-color: #b7d4a8; //解决滚动条的问题:#main -> margin: calc((100vh - 420px) / 2) auto; //设置主窗口样式 #main { margin: calc((100vh - 420px) / 2) auto; width: 360px; height: 420px; //设置背景颜色 background-color: @bg-color; // 设置居中 margin: 100px auto; border: 10px solid black; // 设置圆角 border-radius: 40px; //开启弹性盒模型 display: flex; //设置主轴方向 flex-flow: column; //设置侧轴的对齐方式 align-items: center; //设置主轴对齐方式 justify-content: space-around; //舞台 #stage { width: 304px; height: 304px; border: 2px solid black; //开启相对定位 position: relative; // 设置蛇的样式 #snake { & > div { width: 10px; height: 10px; background-color: #000; //margin: 1px; border:2px solid @bg-color; //开启绝对定位 position: absolute; } } & > #food { width: 10px; height: 10px; //background-color: red; //border-color: 1px solid @bg-color; //开启绝对定位 position: absolute; left: 40px; top: 100px; //开启弹性盒 display: flex; //设置主轴为横轴 wrap自动换行 flex-flow: row wrap; //设置主轴和侧轴的空白空间分配到元素之间 justify-content: space-between; align-content: space-between; & > div { width: 4px; height: 4px; background-color: black; //元素旋转45° transform: rotate(45deg); } } } // 记分牌 #score-panel { width: 300px; display: flex; // 设置主轴的对齐方式 justify-content: space-around; } }
三、主要程序文件
index.ts
import "./style/index.less" import GameControl from "./moduls/GameControl"; new GameControl();
GameControl.ts
//引入其他的类 import Snake from "./Snake"; import Food from "./Food"; import ScorePanel from "./ScorePanel"; //游戏控制器,控制其他所有类 class GameControl { // 定义三个属性 // 蛇 snake: Snake; // 食物 food: Food; // 记分牌 scorePanel: ScorePanel; // 创建一个属性来存储蛇的移动方向(按键方向) direction: string = ""; // 创建一个属性来记录游戏是否结束 isLive = true; constructor() { this.snake = new Snake(); this.food = new Food(); this.scorePanel = new ScorePanel(10,1); this.init() } // 游戏控制的初始化方法,调用后游戏开始 init() { // 绑定键盘按键按下的事件 document.addEventListener("keydown", this.keydownHandler.bind(this)) // 调用run方法 this.run(); } // 创建一个键盘按下的响应函数 /* ArrowUp Up ArrowDown Down ArrowLeft Left ArrowRight Right */ keydownHandler(event: KeyboardEvent) { // 检查event.key的值,是否合法(用户是否按了正确的按键) if (this.snake.bodies.length > 1) { switch (event.key) { case "ArrowUp": case "Up": if (this.direction === "ArrowDown" || this.direction === "Down") { return; } break; case "ArrowDown": case "Down": if (this.direction === "ArrowUp" || this.direction === "Up") { return; } break; case "ArrowLeft": case "Left": if (this.direction === "ArrowRight" || this.direction === "Right") { return; } break; case "ArrowRight": case "Right": if (this.direction === "ArrowLeft" || this.direction === "Left") { return; } break; } } // 修改direction属性 this.direction = event.key; } // 创建一个控制蛇移动的方法 run() { /* *根据方向(this.direction)来改变蛇的位置 *向上 top 减少 * 向下top 增加 * 向左left 减少 * 向右left 增加 */ // 获取蛇的现在位置 let X = this.snake.X; let Y = this.snake.Y; // 根据放向,重新计算坐标 switch (this.direction) { case "ArrowUp": case "Up": // 向上移动 Y -= 10; break; case "ArrowDown": case "Down": // 向下移动 Y += 10 break; case "ArrowLeft": case "Left": // 向左移动 X -= 10; break; case "ArrowRight": case "Right": // 向右移动 X += 10; break; case "R": case "r": this.isLive = true; } //判断是否迟到食物 this.checkEat(X, Y); // 修改蛇的坐标 try { this.snake.X = X; this.snake.Y = Y; } catch (e: any) { alert(e.message); this.isLive = false; } //开启一个定时器 this.isLive && setTimeout(this.run.bind(this), 300 - (this.scorePanel.level - 1) * 30) } // 检查蛇是否迟到食物 checkEat(X: number, Y: number) { if (this.food.X === X && this.food.Y === Y) { // 重置食物 this.food.change(); // 增加分数 this.scorePanel.addScore(); // 蛇增加一节 this.snake.addBody(); } } } export default GameControl;
Snake.ts
class Snake { // 容器 element: HTMLElement; // 表示蛇头的元素 head: HTMLElement; // 蛇的身体(包含头部) bodies: HTMLCollectionOf<HTMLElement>; constructor() { this.element = document.getElementById("snake")!; // querySelector 获取第一个元素 this.head = document.querySelector('#snake>div') as HTMLElement; // 获取snake下的所有div this.bodies = this.element.getElementsByTagName("div"); } // 获取坐标 get X() { return this.head.offsetLeft; } // 定义一个获取食物Y轴坐标的方法 get Y() { return this.head.offsetTop; } // 设置坐标 set X(value) { // 如果新值和旧值相同,则直接返回不再修改 if (this.X == value) { return; } // 判断合法值 if (value < 0 || value > 290) { // 抛出异常 throw new Error("撞墙了!!"); } // 移动身体; this.moveBody(); this.head.style.left = value + "px"; // 碰撞检查 this.checkHeadBody() } // 定义一个获取食物Y轴坐标的方法 set Y(value) { // 如果新值和旧值相同,则直接返回不再修改 if (this.Y == value) { return; } // 判断合法值 if (value < 0 || value > 290) { // 抛出异常 throw new Error("撞墙了!!"); } // 移动身体; this.moveBody(); this.head.style.top = value + "px"; // 碰撞检查 this.checkHeadBody() } checkHeadBody() { let length = this.bodies.length; if (length < 5) { return } for (let i = 1; i < length; i++) { let temEle = this.bodies[i]; if (this.X === temEle.offsetLeft && this.Y == temEle.offsetTop) { throw new Error("撞自己了!") } } // for (let i = this.bodies.length - 1; i > 0; i--) { // // 获取前一个身体的位置 // let X = (this.bodies[i - 1] as HTMLElement).offsetLeft; // let Y = (this.bodies[i - 1] as HTMLElement).offsetTop; // if (this.X === X && this.Y === Y) { // throw new Error("撞自己了!") // } // } } // 增加蛇的身体方法 addBody() { // 向element中添加一个div this.element.insertAdjacentHTML("beforeend", "<div></div>"); (this.bodies[this.bodies.length - 1] as HTMLElement).style.top = this.Y + "px"; (this.bodies[this.bodies.length - 1] as HTMLElement).style.left = this.X + "px"; } // 添加一个蛇身体移动的方法 moveBody() { /* *将后边身体设置为前边身体的位置 */ for (let i = this.bodies.length - 1; i > 0; i--) { // 获取前一个身体的位置 let X = (this.bodies[i - 1] as HTMLElement).offsetLeft; let Y = (this.bodies[i - 1] as HTMLElement).offsetTop; (this.bodies[i] as HTMLElement).style.left = X + "px"; (this.bodies[i] as HTMLElement).style.top = Y + "px"; } } } export default Snake;
Food.ts
// 定义实物类Food class Food { // 定义一个属性表示实物所对应的元素 element: HTMLElement; constructor() { //获取页面food的元素 ! 不会为空 this.element = document.getElementById("food")!; } // 定义一个获取食物X轴坐标的方法 get X() { return this.element.offsetLeft; } // 定义一个获取食物Y轴坐标的方法 get Y() { return this.element.offsetTop; } // 修改食物位置 change() { // 生成一个随机位置,最小值0,最大值290,步长为10 let top = Math.round(Math.random() * 29) * 10; let left = Math.round(Math.random() * 29) * 10; this.element.style.left = left + "px"; this.element.style.top = top + "px"; } } export default Food;
ScorePanel.ts
// 定义记分牌 class ScorePanel { // 记录分数 score = 0; // 记录等级 level = 1; // 分数元素 scoreEle: HTMLElement; // 等级元素 levelEle: HTMLElement; // 设置等级上限 maxLevel: number; // 设置分数升级步长 upScore:number; constructor(maxLevel: number = 10,upScore:number=10) { this.scoreEle = document.getElementById("score")!; this.levelEle = document.getElementById("level")!; this.maxLevel = maxLevel; this.upScore=upScore; } // 加分方法 addScore() { this.score++; this.scoreEle.innerHTML = this.score + ""; // 每十分升级一次 if (this.score % this.upScore === 0) { this.levelUp(); } } // 等级提升 levelUp() { if (this.level < this.maxLevel) { this.levelEle.innerHTML = ++this.level + ""; } } } export default ScorePanel;
五、配置文件
webpack.config.js
//引入一个包 const path = require('path'); // 引入html插件 const HTMLWebpackPlugin = require('html-webpack-plugin'); // 引入clean插件 const {CleanWebpackPlugin} = require('clean-webpack-plugin') // webpack中所有配置信息写在module.exports module.exports = { // 开发配置 mode: 'development', // 指定入口文件 entry: "./src/index.ts", // 指定打包文件所在目录 output: { // 指定打包文件所在目录 // path: "./dist" path: path.resolve(__dirname, 'dist'), // 打包后文件的名称 filename: "bundle.js", // 不适用webpack箭头函数 environment: { arrowFunction: false, const: false } }, // 指定webpack打包时要使用模块 module: { // 指定要加载的规则 rules: [ { // test指定规则生成文件 test: /\.ts$/, // 要使用的loader,加载顺序,数组倒序 use: [ { // 指定加载器 loader: "babel-loader", // 设置babel options: { // 设置预定义的环境 presets: [[ "@babel/preset-env", // 配置信息 { // 要兼容目标浏览器 targets: { "chrome": "88" }, // corejs版本 "corejs": "3", // 使用corejs的方式“usage" 表示按需加载 "useBuiltIns": "usage" } ] ], } }, 'ts-loader', ], // 要排除的文件 exclude: /node_modules/ }, // 设置less文件的处理 { test: /\.less$/, use: [ "style-loader", "css-loader", // 引入postcss { loader: "postcss-loader", options: { postcssOptions: { plugin: [ "postcss-preset-env", { browsers: "last 2 versions" } ] } } }, "less-loader" ] } ] }, // 配置webpack插件 plugins: [ // html插件 new HTMLWebpackPlugin({ // 定义网页标题 // title: "title" // 定义生成模板 template: "./src/index.html" }), // clean插件 new CleanWebpackPlugin() ], // 设置引用模块 resolve: { extensions: ['.ts', '.js'] } }
tsconfig.json
{ "compilerOptions": { "module": "ES2015", "target": "ES2015", "strict": true, "noEmitOnError": true } }
1.{ "name": "snake", "version": "1.0.0", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack", "start": "webpack serve --open" }, "keywords": [], "author": "", "license": "ISC", "description": "", "devDependencies": { "@babel/core": "^7.20.5", "@babel/preset-env": "^7.20.2", "babel-loader": "^9.1.0", "clean-webpack-plugin": "^4.0.0", "core-js": "^3.26.1", "css-loader": "^6.7.2", "html-webpack-plugin": "^5.5.0", "less": "^4.1.3", "less-loader": "^11.1.0", "postcss": "^8.4.19", "postcss-loader": "^7.0.2", "postcss-preset-env": "^7.8.3", "style-loader": "^3.3.1", "ts-loader": "^9.4.2", "typescript": "^4.9.4", "webpack": "^5.75.0", "webpack-cli": "^5.0.1", "webpack-dev-server": "^4.11.1" } }