//定义食物类food
class Food {
//定义一个属性表示食物对应的元素
element: HTMLElement;
constructor() {
//获取页面中的food元素并赋值给element
this.element = document.getElementById('food');
}
//定义一个获取食物X轴坐标的方法
get X() {
return this.element.offsetLeft;
}
//定义一个获取食物Y轴坐标的方法
get Y() {
return this.element.offsetTop;
}
//修改食物位置的方法
change() {
// left:0-290,top:0-290 需要被10整除,蛇移动一次就是一格(10)。
//
let left = Math.round(Math.random() * 29) * 10;
let top = Math.round(Math.random() * 29) * 10;
this.element.style.left = `${left}px`
this.element.style.top = `${top}px`
}
}
export default Food
//游戏控制器,控制其他所有类
import Snake from "./Snake";
import Food from "./Food";
import ScorePanel from "./ScorePanel";
class GameControl {
//定义三个属性
//蛇
snake: Snake;
food: Food;
scorePanel: ScorePanel;
//创建一个属性存储蛇的移动放心
direction: string = '';
//创建一个属性记录游戏是否结束
isLive: boolean = true;
constructor() {
this.snake = new Snake();
this.food = new Food();
this.scorePanel = new ScorePanel(10,2);
this.init()
}
//游戏初始化方法调用后游戏就开始
init() {
//绑定键盘按下的事件
document.addEventListener('keydown', this.keydowHandler.bind(this))
this.run()
}
//创建一个键盘按下的响应函数
keydowHandler(e) {
console.log(e.key);
//需要检验key的值是否合法(用户是否按了正确的案件)
this.direction = e.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':
//向上移动TOP减小
y -= 10;
break;
case 'ArrowDown':
case 'Down':
//向下移动TOP增加
y += 10;
break;
case 'ArrowLeft':
case 'Left':
//向左移动x减小
x -= 10;
break;
case 'ArrowRight':
case 'Right':
//向右移动x增加
x += 10;
break;
}
//检查蛇是否吃到了食物
this.checkEat(x,y)
//修改蛇的位置
try {
this.snake.X = x;
this.snake.Y = y;
} catch (e) {
//进入catch,说明除了异常,游戏结束,弹出一个信息提示
alert(e.message + 'GAME OVER !');//弹出提示
this.isLive = false;//结束游戏
}
//开启一个定时器,让蛇能够自己董
this.isLive && setTimeout(this.run.bind(this), 300 - (this.scorePanel.level - 1) * 30);
}
//定义一个方法,来检查蛇是否吃到食物
checkEat(X: Number, Y: Number) {
//检查蛇是否吃到了食物
if (X === this.food.X && Y === this.food.Y) {
console.log('吃到食物了');
this.food.change();//改变食物的位置
this.scorePanel.addScore();//增加分数
this.snake.addBody();//增加蛇的长度
}
}
}
export default GameControl
//定义记分牌类
class ScorePanel {
maxLevel:number//限制最大等级
upScore:number//多少分升级
//score和level用来记录分数和等级
private _score: number = 0;
private _level: number = 1;
//分数和等级所在元素
scoreEle: HTMLElement;
levelEle: HTMLElement;
constructor(maxlevel:number = 10,upScore:number=10) {
this.maxLevel = maxlevel;
this.upScore = upScore
this.scoreEle = document.getElementsByClassName('score')[0] as HTMLAreaElement;
this.levelEle = document.getElementsByClassName('level')[0] as HTMLAreaElement;
}
get score() {
return this._score
}
get level() {
return this._level
}
//设置一个加分的方法
addScore(){
let score = ++this._score;
this.scoreEle.innerHTML = `${score}`
//判断分数是多少,来判断是否升级
if(this._score %this.upScore ===0){
this.levelUp()
}
}
//提升等级的方法
levelUp(){
if(this._level <this.maxLevel){
this.levelEle.innerHTML = `${++this._level}`
}
}
}
export default ScorePanel
class Snake {
//表示蛇头的元素
head: HTMLElement;
//表示身体的元素
bodies: HTMLCollection;
//获取蛇的容器
element: HTMLElement
constructor() {
this.head = document.querySelector('#snake > div')
this.bodies = document.getElementById('snake').getElementsByTagName('div')
this.element = document.getElementById('snake');
}
//获取蛇的坐标(蛇头坐标)
get X() {
return this.head.offsetLeft;
}
get Y() {
return this.head.offsetTop;
}
set X(value: number) {
if (this.X == value) {
return;
}
//X的值的合法区间0-290
if (value < 0 || value > 290) {
//蛇撞墙的处理
throw new Error('蛇撞墙了')
}
//修改X时,是在修改水平坐标,蛇在左右移动,蛇向左移动时,不能向右移动,反之亦然
if (this.bodies[1]) {
//判断是否有第二截身体
if (value === (this.bodies[1] as HTMLElement).offsetLeft) {
//判断即将改变的蛇头的坐标是否和第二截身体的坐标相同,相同就发生了掉头
//console.log('发生了掉头');
//如果发生了掉头,让蛇向反方向继续移动
if(value>this.X){
//如果value大于X,则说明蛇在向右走
value = this.X-10
}else{
value = this.X+10
}
}
}
//移动身体
this.moveBody()
this.head.style.left = `${value}px`;
try {
this.checkHeadBody()
} catch (error) {
alert(error.message+'GAME OVER !')
}
}
set Y(value: number) {
if (this.Y == value) {
return;
}
if (value < 0 || value > 290) {
//蛇撞墙的处理
throw new Error('蛇撞墙了')
}
//修改Y时,是在修改蛇的垂直坐标,蛇在上下移动,蛇向上移动时,不能向下移动,反之亦然
if (this.bodies[1]) {
//判断是否有第二截身体
if (value === (this.bodies[1] as HTMLElement).offsetTop) {
//判断即将改变的蛇头的坐标是否和第二截身体的坐标相同,相同就发生了掉头
//console.log('发生了掉头');
//如果发生了掉头,让蛇向反方向继续移动
if(value>this.Y){
//如果value大于Y,则说明蛇在向上走
value = this.Y-10
}else{
value = this.Y+10
}
}
}
//移动身体
this.moveBody()
this.head.style.top = `${value}px`;
try {
this.checkHeadBody()
} catch (error) {
alert(error.message+'GAME OVER !')
}
}
//蛇增加身体的方法
addBody() {
this.element.insertAdjacentHTML('beforeend', '<div></div>')
}
//添加一个蛇身体移动的方法
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`;
}
}
//检查头和身体是否相撞的方法
checkHeadBody(){
//获取所有的身体检查是否和蛇头的坐标发生重叠
for(let i=1;i<this.bodies.length;i++){
if(this.X === (this.bodies[i] as HTMLElement).offsetLeft&&this.Y===(this.bodies[i] as HTMLElement).offsetTop){
throw new Error('撞到身体了')
}
}
}
}
export default Snake
<!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>贪吃蛇</title>
</head>
<body>
<!-- 主容器 -->
<div class="main">
<!-- 舞台 -->
<div class="stage">
<!-- 蛇容器 -->
<div id="snake">
<!-- 蛇的各个部分 -->
<div></div>
</div>
<div id="food">
<!-- 添加4个div设置蛇的样式 -->
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
<!-- 计分板 -->
<div class="score-panel">
<div>
SCORE:<span class="score">0</span>
</div>
<div>
LEVEL:<span class="level">1</span>
</div>
</div>
</div>
</body>
</html>
import './style/index.less'
import GameControl from './moduls/GameControl'
new GameControl();
@bgcl: #b7d4a8;
* {
margin: 0;
padding: 0;
//改变盒子模型的计算方式
box-sizing: border-box;
}
.main {
width: 360px;
height: 420px;
background-color: @bgcl;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border: solid 10px #000000;
border-radius: 5%;
}
.stage {
width: 304px;
height: 304px;
border: 2px solid black;
margin: 20px 18px;
//开启相对定位
position: relative;
#snake>div {
width: 10px;
height: 10px;
background-color: #000000;
border: 1px solid @bgcl;
//开启绝对定位
position: absolute;
}
}
#food{
width: 10px;
height: 10px;
border: 1px solid @bgcl;
position: absolute;
left: 200px;
top: 100px;
display: flex;
flex-flow: row wrap;
justify-content: space-between;
align-content: space-between;
&>div{
width: 3px;
height: 3px;
background-color: #000000;
transform: rotate(45deg);
}
}
.score-panel {
display: flex;
width: 85%;
margin: 10px auto;
justify-content: space-between;
}
{
"name": "part3",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --mode production",
"dev": "webpack --mode development",
"start": "webpack serve --open "
},
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.20.7",
"@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.3",
"html-webpack-plugin": "^5.5.0",
"less": "^4.1.3",
"less-loader": "^11.1.0",
"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"
},
"dependencies": {
"-": "^0.0.1",
"D": "^1.0.0",
"postcss": "^8.4.21",
"postcss-loader": "^7.0.2",
"postcss-preset-env": "^7.8.3"
}
}
{
"compilerOptions": {
"module": "ES2015",
"target": "ES6",
// "sourceMap": false,
// "strict": true
},
"exclude": [
"node_modules"
]
}
//引入path模块,管理路径
// import { Configuration } from 'webpack';//运行webpack时注释掉
/**
* @type {Configuration}
*/
const path = require('path');
//引入html-webpack-plugin
const HTMLWEBPACKPLUGIN = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require("clean-webpack-plugin")
//webpack中的所有配置信息都应写在module.exports中
module.exports = {
//指定入口文件
entry: './src/index.ts',
//指定打包文件所在目录
output: {
//指定打包文件的目录
// path:'./dist'
path: path.resolve(__dirname, 'dist'),
//打包后文件的文件名
filename: "bundle.js",
environment: {
//配置打包的环境
arrowFunction: false,//告诉webpack不使用箭头函数
const:false,//告诉webpack不使用const
}
},
//指定webpack打包时要使用的模块
module: {
//指定加载的规则
rules: [
{
//指定规则生效的文件
test: /\.ts$/,
//要使用的loader,执行顺序从后往前
use: [
{//配置babel
//指定加载器
loader: "babel-loader",
//设置babel
options: {
//设置预定义环境
presets: [
[
//指定环境插件
"@babel/preset-env",
//配置信息
{
//要兼容的目标浏览器
targets: {
"chrome": "88",
"ie": "11"
},
"corejs": "3",//指定core.js的版本
// //使用core.js的方式
"useBuiltIns": "usage",//"usage"表示按需加载
},
]
]
}
},
"ts-loader"
],
//指定要排除的文件
exclude: /node_modules/,
},
//设置less文件的处理
{
test: /\.less$/,
use: [
"style-loader",
"css-loader",
{
loader:"postcss-loader",
options:{
postcssOptions:{
plugins:[
[
"postcss-preset-env",
{
browsers:'last 2 versions'
}
]
]
}
}
},
"less-loader"
]
}
]
},
//配置webpack的插件
plugins: [
new CleanWebpackPlugin(),
new HTMLWEBPACKPLUGIN({
// title:'这是一个自定义的title'//用来配置自动生成的html文件的title
template: "./src/index.html"//用来配置自动生成的html文件的模板
})
],
mode: 'development',
resolve: {
//设置引用模块
extensions: ['.ts', '.js']//告诉webpack哪些后缀的文件可以被当做模块引用
}
}