一、实现效果
源码阿里云盘地址: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
}
}
package.json
{
"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"
}
}