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()

      

  • 相关阅读:
    pandas Dataframe filter
    process xlsx with pandas
    data manipulate in excel with easyExcel class
    modify registry in user environment
    add number line in vim
    java import webservice
    ctypes MessageBoxA
    music 163 lyrics
    【python实例】自动贩卖机
    【python基础】sys模块(库)方法汇总
  • 原文地址:https://www.cnblogs.com/wuqilang/p/15479193.html
Copyright © 2011-2022 走看看