zoukankan      html  css  js  c++  java
  • ts实战-贪吃蛇

    项目搭建

      准备好之前的几个文件:

        webpack.config.js

    const path = require('path')
    const HtmlWebpackPlugin = require('html-webpack-plugin') // webpack中html插件,用来自动创建html文件
    const { CleanWebpackPlugin } = require('clean-webpack-plugin') // clean插件
    module.exports = {
      mode: 'none',
      entry: './src/index.ts', // 指定入口文件
      output: {
        path: path.resolve(__dirname, 'dist'), // 指定打包文件的目录
        filename: 'bundle.js', // 打包后文件的名称
        environment: { arrowFunction: false } // 告诉webpack打包后的【立即执行函数】不使用箭头函数(新版的webpack不支持ie11,如果需要打包后的代码支持ie11需要加上该配置)
      },
      // 指定webpack打包时要使用的模块
      module: {
        // 指定loader加载的规则
        rules: [
          {
            test: /.ts$/, // 指定规则生效的文件:以ts结尾的文件
            // use: 'ts-loader', // 要使用的loader
            // ts先由ts-loader转换成js文件,再由babel中target指定的浏览器版本,将js转成对应的语法。配置了babel后不需要考虑使用es5还是es6的版本了,在target中指定了需要兼容的浏览器版本,babel会自动帮我们转
            use: [
              {
                loader: 'babel-loader', // 指定加载器
                // 设置babel
                options: {
                  // 设置预定义的环境
                  presets: [
                    [
                      '@babel/preset-env', // 指定环境的插件
                      // 配置信息
                      {
                        targets: { chrome: 58, ie: 11 }, // 要兼容的目标浏览器及版本(ie11不支持es6语法,写上 ie: 11 打包时就会编译成支持到ie11)
                        corejs: 3, // 指定corejs的版本(根据package.json中的版本,只写整数)
                        useBuiltIns: 'usage' // 使用corejs的方式,'usage'表示按需加载
                      }
                    ]
                  ]
                }
              },
              'ts-loader'
            ],
            exclude: /node-modules/ // 要排除的文件
          }
        ]
      },
      // 配置webpack插件
      plugins: [
        new HtmlWebpackPlugin({
          title: '自定义标题', // 自定义title标签内容
          template: './src/index.html' // 以index.html文件作为模板生成dist/index.html(设置了template,title就失效了)
        }),
        new CleanWebpackPlugin()
      ],
      // 设置哪些文件类型可以作为模块被引用
      resolve: {
        extensions: ['.ts', '.js']
      }
    }
    View Code

        tsconfig.json

    {
      "compilerOptions": {
        "module": "es6",
        "target": "es6",
        "strict": true,
        "noEmitOnError": true
      }
    }
    View Code

        package.json

    {
      "name": "greedySnake",
      "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.15.8",
        "@babel/preset-env": "^7.15.8",
        "babel-loader": "^8.2.3",
        "clean-webpack-plugin": "^4.0.0",
        "core-js": "^3.19.0",
        "html-webpack-plugin": "^5.5.0",
        "ts-loader": "^9.2.6",
        "typescript": "^4.4.4",
        "webpack": "^5.60.0",
        "webpack-cli": "^4.9.1",
        "webpack-dev-server": "^4.3.1"
      }
    }
    View Code

        src/index.html

    <!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>
      
    </body>
    </html>
    View Code

        src/index.ts

    console.log(100)
    View Code

      安装依赖:npm i

      打包:npm run build

      打开dist/index.html,控制台打印100就可以了

      项目中使用less预处理语言,安装处理css和less的包:npm i -D less less-loader css-loader style-loader

        less  less核心工具包

        less-loader  将less和webpack整合

        css-loader  将css和webpack整合

        style-loader  将css引入到项目中

      根据每个loader的功能,使用时,应该先应用less-loader,再css-loader,再style-loader

      修改webpack配置,在rules中添加(loader的执行是从后往前的)

          // 设置less文件的处理
          {
            test: /.less$/,
            use: ['style-loader', 'css-loader', 'less-loader']
          }

      此时在项目中就可以使用less了,src/style/index.less

    body {
      background-color: red;
    }

      index.ts中引入less文件

    import './style/index.less'
    
    console.log(100)

      执行npm run build,打开dist/index.html可以看到less文件已生效

      安装postcss来处理css的浏览器兼容问题npm i -D postcss postcss-loader postcss-preset-env

        postcss  postcss核心工具

        postcss-loader  将postcss和webpack整合

        postcss-preset-env  设置浏览器预置环境

      修改webpack.config.js中对less文件的处理

          // 设置less文件的处理
          {
            test: /.less$/,
            use: [
              'style-loader',
              'css-loader',
              // 引入postcss
              {
                loader: 'postcss-loader',
                options: {
                  postcssOptions: {
                    plugins: [
                      ['postcss-preset-env', { browsers: 'last 2 versions' }]
                    ]
                  }
                }
              },
              'less-loader'
            ]
          }

      执行npm run build,bundle.js中,对于部分css代码已加上前缀

        

    项目界面

      src/index.html

    <!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 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>
    View Code

      src/style/index.less

    // 设置变量
    @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 #000;
      border-radius: 40px;
      display: flex;
      flex-flow: column;
      align-items: center;
      justify-content: space-around;
      #stage {
        width: 304px;
        height: 304px;
        border: 2px solid #000;
        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;
          display: flex;
          flex-wrap: wrap;
          justify-content: space-between;
          align-content: space-between;
          left: 40px;
          top: 100px;
          & > div {
            width: 4px;
            height: 4px;
            background-color: red;
            transform: rotate(45deg);
          }
        }
      }
      #score-panel {
        width: 300px;
        display: flex;
        justify-content: space-between;
      }
    }
    View Code

      效果:

        

    定义Food类

      Food类为定义事物的类

      主要实现

        获取事物的坐标

        修改食物的位置(随机生成)

    // 食物类
    class Food {
      // 定义的一个属性表示食物所对应的元素
      element: HTMLElement
      constructor() {
        this.element = document.getElementById('food')! // 获取页面中的food元素并将其赋值给element 后面加 ! 表示该值一定不为空
        this.change()
      }
      // 获取食物x轴坐标
      get X() {
        return this.element.offsetLeft
      }
      // 获取食物y轴坐标
      get Y() {
        return this.element.offsetTop
      }
      // 修改食物位置 [0, 290]
      change() {
        const top = Math.round(Math.random() * 29) * 10 // Math.floor(Math.random() * 30)  -->  [0, 30)    Math.round(Math.random() * 29)  -->  [0, 29]
        const left = Math.round(Math.random() * 29) * 10
        this.element.style.top = top + 'px'
        this.element.style.left = left + 'px'
      }
    }
    
    export default Food
    View Code

    定义ScorePanel类

      ScorePanel类为定义记分牌的类

      主要实现

        记录分数和速度等级

        实现加分功能

        实现升级功能

    // 定义表示记分牌的类
    class ScorePenel {
      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.scoreEle.innerHTML = ++this.score + ''
        if (this.score % this.upScore === 0) this.levelUp()
      }
      // 提升等级
      levelUp() {
        if (this.level < this.maxLevel) this.levelEle.innerHTML = ++this.level + ''
      }
    }
    const s = new ScorePenel()
    for (let i = 0; i < 1; i++) {
      s.addScore()
    }
    
    export default ScorePenel
    View Code

    定义Snake类

      Snake类为定义蛇的类

      主要实现:

        获取和设置蛇头的坐标

        蛇身体变长

        蛇身体移动

        蛇不能掉头

        检查蛇头是否撞到身体

    class Snake {
      head: HTMLElement // 蛇头
      bodies: HTMLCollection // 蛇的身体(包括蛇头)
      element: HTMLElement // 获取蛇的容器
      constructor() {
        this.element = document.getElementById('snake')!
        // this.head = document.getElementById('#snake > div') as HTMLElement
        this.head = <HTMLElement>document.querySelector('#snake > div')
        this.bodies = this.element.getElementsByTagName('div')
      }
      // 获取蛇头的x轴坐标
      get X() {
        return this.head.offsetLeft
      }
      // 获取蛇头的y轴坐标
      get Y() {
        return this.head.offsetTop
      }
      // 设置蛇头的x轴坐标
      set X(value: number) {
        if (this.X === value) return // 如果新值和旧值相同,则直接返回不再修改属性
        if (value < 0 || value > 290) throw new Error('蛇撞左右墙了!')
        // 修改X时,只能修改水平坐标,蛇在左右移动,蛇在向左移动时,不能向右掉头,反之亦然
        if (
          this.bodies[1] && // 有第二节身体
          (this.bodies[1] as HTMLElement).offsetLeft === value // 如果蛇头和第二节身体位置一样
        ) {
          if (value > this.X) {
            value = this.X - 10 // 如果新值value大于旧值Y,则说明蛇向右走,此时发生掉头,应该继续向右走
          } else {
            value = this.X + 10 // 向左走
          }
        }
        this.moveBody() // 移动身体
        this.head.style.left = value + 'px'
        this.checkHeadBody() // 检查有没有撞自己
      }
      // 设置蛇头的y轴坐标
      set Y(value: number) {
        if (this.Y === value) return // 如果新值和旧值相同,则直接返回不再修改属性
        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 // 如果新值value大于旧值Y,则说明蛇向下走,此时发生掉头,应该继续向下走
          } else {
            value = this.Y + 10 // 向上走
          }
        }
        this.moveBody() // 移动身体
        this.head.style.top = value + 'px'
        this.checkHeadBody() // 检查有没有撞自己
      }
      // 蛇增加身体长度
      addBody() {
        this.element.insertAdjacentHTML('beforeend', '<div></div>') // 向element中添加一个div
      }
      // 蛇身体移动
      moveBody() {
        /*
          从最后一个身体元素开始,将当前身体的位置设置为前一个身体的位置
            第 4 节 = 第 3 节
            第 3 节 = 第 2 节
            第 2 节 = 第 1 节(蛇头)
        */
        // 遍历获取所有的身体,不包括蛇头
        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++) {
          const bd = this.bodies[i] as HTMLElement
          if (this.X === bd.offsetLeft && this.Y === bd.offsetTop) throw new Error()
        }
      }
    }
    
    export default Snake
    View Code

    定义GameControl类

      GameControl类为游戏控制器,控制其他所有的类

      主要实现:

        键盘事件

        使蛇移动

        蛇撞墙

        吃食物检测

    import Food from './Food'
    import ScorePanel from './ScorePanel'
    import Snake from './Snake'
    // 游戏控制器,控制其他的所有类
    class GameControl {
      snake: Snake //
      food: Food // 食物
      scorePenel: ScorePanel // 记分牌
      direction: string = '' // 创建一个属性来记录蛇的移动方向(按键的方向)
      isLive = true // 创建一个属性来记录游戏是否结束
      constructor() {
        this.snake = new Snake()
        this.food = new Food()
        this.scorePenel = new ScorePanel(10, 10)
        this.init()
      }
      // 初始化
      init() {
        document.addEventListener('keydown', this.keydownHandle.bind(this)) // 绑定键盘按下的事件
        this.run() // 调用run,是蛇移动
      }
      keydownHandle(event: KeyboardEvent) {
        this.direction = event.key
      }
      /*
        谷歌        ie
        ArrowUp     Up
        ArrowDown   Down
        ArrowRight  Right
        ArrowLeft   Left
      */
      run() {
        // 获取蛇当前坐标
        let X = this.snake.X
        let Y = this.snake.Y
        // 根据方向(this.direction)来使蛇的位置改变
        switch (this.direction) {
          case 'ArrowUp':
          case 'Up':
            Y -= 10 // 向上移动 top 减少
            break
          case 'ArrowDown':
          case 'Down':
            Y += 10 // 向下移动 top 增加
            break
          case 'ArrowLeft':
          case 'Left':
            X -= 10 // 向左移动 left 减少
            break
          case 'ArrowRight':
          case 'Right':
            X += 10 // 向右移动 left 增加
            break
        }
        this.checkEat(X, Y) // 坚持蛇是否吃到了食物
        // 修改蛇的X和Y值
        try {
          this.snake.X = X
          this.snake.Y = Y
        } catch (e: any) {
          alert(e.message + 'GAME OVER!')
          this.isLive = false
        }
        // 开启一个定时调用
        clearTimeout()
        this.isLive &&
          setTimeout(this.run.bind(this), 300 - (this.scorePenel.level - 1) * 30)
      }
      // 检查蛇是否吃到食物
      checkEat(X: number, Y: number) {
        if (X === this.food.X && Y === this.food.Y) {
          console.log('吃到食物了')
          this.food.change() // 食物的位置重置
          this.scorePenel.addScore() // 分数增加
          this.snake.addBody() // 蛇要增加一节
        }
      }
    }
    
    export default GameControl
    View Code

    src/index.ts引入游戏控制器

    import './style/index.less'
    
    import GameControl from './modules/GameControl'
    
    new GameControl()

      

  • 相关阅读:
    机器学习-Matplotlib绘图(柱状图,曲线图,点图)
    人工智能-机器学习之numpy方法
    爬虫解码~以及我的一些小经验
    django-子项目
    Django-模板语法
    Django-cookie的保存以及删除操作
    django-路由
    响应式瀑布流
    原生js实现最简单的瀑布流布局
    原生js用div实现简单的轮播图
  • 原文地址:https://www.cnblogs.com/wuqilang/p/15479193.html
Copyright © 2011-2022 走看看