TS实战项目--贪吃蛇

项目搭建

项目依赖

{
  "name": "part2",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack",
    "start": "webpack serve --open chrome.exe"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.12.9",
    "@babel/preset-env": "^7.12.7",
    "babel-loader": "^8.2.2",
    "clean-webpack-plugin": "^3.0.0",
    "core-js": "^3.8.0",
    "css-loader": "^5.0.1",
    "html-webpack-plugin": "^4.5.0",
    "less": "^3.12.2",
    "less-loader": "^7.1.0",
    "postcss": "^8.1.13",
    "postcss-loader": "^4.1.0",
    "postcss-preset-env": "^6.7.0",
    "style-loader": "^2.0.0",
    "ts-loader": "^8.0.11",
    "typescript": "^4.1.2",
    "webpack": "^5.6.0",
    "webpack-cli": "^4.2.0",
    "webpack-dev-server": "^3.11.0"
  }
}

webpack打包规则

// 引入一个包
const path = require('path');
// 引入html插件
const HTMLWebpackPlugin = require('html-webpack-plugin');
// 引入clean插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

// webpack中的所有的配置信息都应该写在module.exports中
module.exports = {
    // 指定入口文件
    entry: "./src/index.ts",

    // 指定打包文件所在目录
    output: {
        // 指定打包文件的目录
        path: path.resolve(__dirname, 'dist'),
        // 打包后文件的文件
        filename: "bundle.js",

        // 告诉webpack不使用箭头
        environment:{
            arrowFunction: false,
            const: false
        }
    },

    // 指定webpack打包时要使用模块
    module: {
        // 指定要加载的规则
        rules: [
            {
                // test指定的是规则生效的文件
                test: /\.ts$/,
                // 要使用的loader
                use: [
                     // 配置babel
                     {
                         // 指定加载器
                         loader:"babel-loader",
                         // 设置babel
                         options: {
                             // 设置预定义的环境
                             presets:[
                                 [
                                     // 指定环境的插件
                                     "@babel/preset-env",
                                     // 配置信息
                                     {
                                         // 要兼容的目标浏览器
                                         targets:{
                                             "chrome":"58",
                                             "ie":"11"
                                         },
                                         // 指定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:{
                                plugins:[
                                    [
                                        "postcss-preset-env",
                                        {
                                            browsers: 'last 2 versions'
                                        }
                                    ]
                                ]
                            }
                        }
                    },
                    "less-loader"
                ]
            }
        ]
    },

    // 配置Webpack插件
    plugins: [
        new CleanWebpackPlugin(),
        new HTMLWebpackPlugin({
            // title: "这是一个自定义的title"
            template: "./src/index.html"
        }),
    ],

    // 用来设置引用模块
    resolve: {
        extensions: ['.ts', '.js']
    }

};

项目界面

布局

<!DOCTYPE html>
<html lang="zh-CN">

<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 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>
        <!-- 游戏记分牌 -->
        <div id="score-panel">
            <div>
                SCORE:<span id="score">0</span>
            </div>
            <div>
                level:<span id="level">1</span>
            </div>
        </div>
    </div>
</body>

</html>

样式

// 设置变量
@bg-color: #b7d4a8;

//清除默认样式
*{
  margin: 0;
  padding: 0;
  // 改变盒子模型的计算方式
  box-sizing: border-box;
}

body{
  font: bold 20px "Courier";
}

//设置主窗口的样式
#main{
  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;
        border: 1px solid @bg-color;
        // 开启绝对定位
        position: absolute;
      }
    }

    // 设置食物
    #food{
      width: 10px;
      height: 10px;
      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;

        // 使四个div旋转45度
        transform: rotate(45deg);
      }
    }
  }

  // 记分牌
  #score-panel{
    width: 300px;
    display: flex;
    // 设置主轴的对齐方式
    justify-content: space-between;
  }
}

定义食物类

  • 定义一个属性表示食物对应的元素,获取x轴, y轴坐标
  • 生成随机位置,食物的位置最小时0最大是290,
  • 蛇移动一次就是一格(10),所以要求食物的坐标必须为整10(向上取整)
//定义食物类
class Food {
    //定义一个属性表示食物对应的元素
    element: HTMLElement;
    constructor() {
        //获取页面食物元素并将其赋值给element
        this.element = document.getElementById("food")!
    }
    //定义一个获取食物x轴坐标的方法
    get x() {
        return this.element.offsetLeft;
    }
    //定义一个获取食物y轴坐标的方法
    get y() {
        return this.element.offsetTop;
    }
    //修改食物位置的方法
    chhange() {
        //生成随机位置,食物的位置最小时0最大是290,
        // 蛇移动一次就是一格(10),所以要求食物的坐标必须为整10(向上取整)
        let top = Math.round(Math.random() * 29) * 10
        let left = Math.round(Math.random() * 29) * 10

        this.element.style.top = top + 'px'
        this.element.style.left = left + 'px'
    }
}

计分器类

属性

  •  score记录分数 lebel 等级
  •  scoreEle,lebelEle 分数和等级所在的元素
  • maxLebel来限制等级, upScore设置多少分升级

方法

  • 设置加分的方法 分数自增  提升等级的方法 分数取余upScore(默认10)调用 等级方法自增
//定义计分牌的类
class ScorePanel {
    //用来记录分数和等级
    score = 0;
    lebel = 1;

    //分数和等级所在的元素,在构造函数中进行初始化
    scoreEle: HTMLElement;
    lebelEle: HTMLElement;
    //设置一个变量来限制等级
    maxLebel: number
    //设置一个变量设置多少分升级
    upScore: number
    constructor(maxLebel: number = 10, upScore: number = 10) {
        this.scoreEle = document.getElementById('score')!
        this.lebelEle = document.getElementById('level')!
        this.maxLebel = maxLebel
        this.upScore = upScore
    }

    //设置加分的方法
    addScore() {
        //使分数自增
        this.score++
        this.scoreEle.innerHTML = this.score + ''
        //判断分数是多少
        if (this.score % this.upScore == 0) {
            this.lebelUp();
        }
    }

    //提升等级的方法
    lebelUp() {
        if (this.lebel < this.maxLebel) {
            this.lebel++
            this.lebelEle.innerHTML = this.lebel + ''
        }

    }

}
export default ScorePanel

蛇类

属性

  •  表示蛇头的元素 head
  • 蛇的身体(包括蛇头) bodies
  •  获取蛇的容器  element:

方法

 getx gety 获取蛇的坐标(蛇头坐标)

 set x set y( 设置蛇头的坐标)

  • 先设置蛇头 value为设置的新值,
  • 如果新值和旧值相同,则直接返回不再修改,
  •  蛇撞墙逻辑处理新值的取值范围0-290之间超出这个范围则视为撞墙

  • 不能反方向移动, 如果发生了掉头,让蛇向反方向继续移动 如果新值value大于旧值X,则说明蛇在向右走,此时发生掉头,应该使蛇继续向左走

  • 移动身体

  •  检查有没有撞到自己

addBody蛇增加身体的方法  向element中添加一个div

 moveBody 蛇身体移动的方法

  • 将后边的身体设置为前边身体的位置 举例子 第4节 = 第3节的位置   第3节 = 第2节的位置   第2节 = 蛇头的位置
  • 遍历获取所有的身体,获取前边身体的位置,将值设置到当前身体上

checkHeadBody 检查蛇头是否撞到身体的方法 获取所有的身体,检查其是否和蛇头的坐标发生重叠  进入判断说明蛇头撞到了身体,游戏结束

class Snake {
    // 表示蛇头的元素
    head: HTMLElement;
    // 蛇的身体(包括蛇头)
    bodies: HTMLCollection;
    // 获取蛇的容器
    element: HTMLElement;

    constructor() {
        this.element = document.getElementById('snake')!
        this.head = document.querySelector('#snake > div') as HTMLElement;
        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;
        }

        // X的值的合法范围0-290之间
        if (value < 0 || value > 290) {
            // 进入判断说明蛇撞墙了
            throw new Error('蛇撞墙了!');
        }

        // 修改x时,是在修改水平坐标,蛇在左右移动,蛇在向左移动时,不能向右掉头,反之亦然
        //this.bodies[1] 第一节身体是否存在,
        if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetLeft === value) {
            // 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';
        // 检查有没有撞到自己
        this.checkHeadBody();
    }

    set y(value) {
        // 如果新值和旧值相同,则直接返回不再修改
        if (this.y === value) {
            return;
        }

        // Y的值的合法范围0-290之间
        if (value < 0 || value > 290) {
            // 进入判断说明蛇撞墙了,抛出一个异常
            throw new Error('蛇撞墙了!');
        }

        // 修改y时,是在修改垂直坐标,蛇在上下移动,蛇在向上移动时,不能向下掉头,反之亦然
        if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetTop === value) {
            if (value > this.y) {
                value = this.y - 10;
            } else {
                value = this.y + 10;
            }
        }

        // 移动身体
        this.moveBody();
        this.head.style.top = value + 'px';
        // 检查有没有撞到自己
        this.checkHeadBody();
    }

    // 蛇增加身体的方法
    addBody() {
        // 向element中添加一个div
        this.element.insertAdjacentHTML("beforeend", "<div></div>");

    }

    // 添加一个蛇身体移动的方法
    moveBody() {
        /*
        *   将后边的身体设置为前边身体的位置
        *       举例子:
        *           第4节 = 第3节的位置
        *           第3节 = 第2节的位置
        *           第2节 = 蛇头的位置
        * */
        // 遍历获取所有的身体
        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++) {
            let bd = this.bodies[i] as HTMLElement;
            if (this.x === bd.offsetLeft && this.y === bd.offsetTop) {
                // 进入判断说明蛇头撞到了身体,游戏结束
                throw new Error('撞到自己了!');
            }
        }
    }
}

export default Snake;

游戏控制器

属性

  • 导入食物 蛇 计分器 控制所有类 
  •  directionL存储蛇移动的方向(也就是按键的方向)
  • isLive用来记录游戏是否结束

方法

init/游戏初始化方法 监听键盘事件 键盘按下 ,来绑定键盘按下的事件,注意this指向问题 this应该为GameControl类 ,调用run方法使蛇移动

run 创建一个控制蛇移动的方法(根据方向来改变蛇的位置 directionL)

  • 获取蛇现在的坐标
  • 根据按键方向修改x值y值  ,向上 top减少,向下top增加,  向左left减少 , 向右left增加
  • 检测蛇是否吃到了食物 传入蛇的当前坐标
  • 修改蛇的x,y 值,进入catch 说明出现异常,游戏结弹出提示信息(异常情况 撞墙,身体重合在蛇类抛出了异常在这里做处理 catah接错)

checkEat  测蛇是否吃到了食物  蛇的坐标跟食物坐标做对比是否坐标相等

  •  食物位置进行重置
  • 分数增加
  • 蛇增加一结
//导入其他类
import Snake from "./snake"
import Food from "./Food"
import ScorePanel from "./ScorePanel"
//游戏控制器,控制其他所有类
class GameControl {
    snake: Snake;
    food: Food;
    Scorepanel: ScorePanel;

    //创建一个属性来存储蛇移动的方向(也就是按键的方向)
    directionL = ''

    //创建一个属性用来记录游戏是否结束
    isLive = true;

    constructor() {
        this.snake = new Snake()
        this.food = new Food()
        this.Scorepanel = new ScorePanel()
        this.init()
    }
    //游戏初始化方法 ,调用后游戏即开始 
    init() {
        // 来绑定键盘按下的事件,注意this指向问题 this应该为GameControl类
        document.addEventListener('keydown', this.keydownHandler.bind(this))
        // 调用run方法使蛇移动
        this.run()
    }
    //创建键盘按下的响应函数
    keydownHandler(e: KeyboardEvent) {
        //需要检测event.key是否合法(用户按了正确的按键)
        //修改directionL属性
        this.directionL = e.key
    }
    // 创建一个控制蛇移动的方法(根据方向来改变蛇的位置 directionL)
    run() {
        //获取蛇现在的坐标
        let x = this.snake.x
        let y = this.snake.y
        //根据按键方向修改x值y值
        switch (this.directionL) {
            // 向上 top减少 
            case 'ArrowUp':
                y -= 10;
                break;
            case 'ArrowDown':
                // 向下top增加
                y += 10
                break;
            case 'ArrowLeft':
                // 向左left减少
                x -= 10
                break;
            case 'ArrowRight':
                // 向右left增加
                x += 10
                break;

        }
        //检测蛇是否吃到了食物
        this.checkEat(x, y)

        //修改蛇的x,y 值
        try {
            this.snake.x = x
            this.snake.y = y

        } catch (e: any) {
            //进入catch 说明出现异常,游戏结弹出提示信息
            alert(e.message)
            this.isLive = false
        }
        this.isLive && setTimeout(this.run.bind(this), 300 - (this.Scorepanel.lebel - 1) * 30)
    }
    //检测蛇是否吃到了食物 蛇的坐标
    checkEat(x: number, y: number) {
        if (x == this.food.x && y == this.food.y) {
            //食物位置进行重置
            this.food.chhange();
            //分数增加
            this.Scorepanel.addScore()
            //蛇增加一结
            this.snake.addBody()
        }
    }


}
export default GameControl

index.ts

创建控制器实例开始游戏

import './style/index.less'
// import Food from './moduls/Food';
import GameControl from "./moduls/GameControl";
new GameControl()

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值