TypeScript__实战一

207 阅读5分钟

一、项目搭建

package.json

  •   {
        "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"
        }
      }
    
  • 配置好这些文件之后,npm i安装依赖

tsconfig.json

  •   {
        "compilerOptions": {
          "module": "ES2015",
          "target": "ES2015",
          "strict": true,
          "noEmitOnError": true
        }
      }
    

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 = {
          // 指定入口文件
          entry: "./src/index.ts",
    
          // 指定打包文件所在目录
          output: {
              // 指定打包文件的目录
              path: path.resolve(__dirname, 'dist'),
              // 打包后文件的文件
              filename: "bundle.js",
    
              // 告诉webpack不使用箭头,不使用const
              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文件的处理,
                  // 1、前提安装好六个包(如果package.json已经写好了,按老规矩)
                  //    npm i -D less less-loader css-loader style-loader
                  //    postcss postcss-loader postcss-loader
                  // 2、ess-loader加载器,帮我们把webpack和less做整合,
                  //    把这两个工具连结在一起;
                  // 3、css-loader,处理css代码;
                  // 4、style-loader,把css引进我们的项目里;
                  // 5、postcss,核心工具;
                  // 6、postcss-loader加载器,帮我们把webpack和postcss做整合;
                  // 7、postcss-preset-env,postcss提供浏览器预制环境;
                  //   (和babel很像吧!postcss让低版本浏览器兼容新的css)
                  // 8、去webpack.config.js里添加配置,顺序不可乱,如下所示
                  
                  {
                      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']
          }
      };
    

Food.ts

  •   // 定义食物类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(){
              // 生成一个随机的位置
              // 食物的位置最小是0 最大是290
              // 蛇移动一次就是一格,一格的大小就是10,所以就要求食物的坐标必须是整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';
          }
      }
    
      // 测试代码
      // const food =  new Food();
      // console.log(food.X, food.Y);
      // food.change();
      // console.log(food.X, food.Y);
    
      export default Food;
    

Snake.ts

  •   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时,是在修改水平坐标,蛇在左右移动,蛇在向左移动时,不能向右掉头,反之亦然
              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;
    

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,2);
    
              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的值是否合法(用户是否按了正确的按键)
              // 修改direction属性
              this.direction = event.key;
          }
    
          // 创建一个控制蛇移动的方法
          run() {
              /*
              *   根据方向(this.direction)来使蛇的位置改变
              *       向上 top 减少
              *       向下 top 增加
              *       向左  left 减少
              *       向右  left 增加
              * */
              // 获取蛇现在坐标
              let X = this.snake.X;
              let Y = this.snake.Y;
    
    
              // 根据按键方向来修改X值和Y值
              switch (this.direction) {
                  case "ArrowUp":
                  case "Up":
                      // 向上移动 top 减少
                      Y -= 10;
                      break;
                  case "ArrowDown":
                  case "Down":
                      // 向下移动 top 增加
                      Y += 10;
                      break;
                  case "ArrowLeft":
                  case "Left":
                      // 向左移动 left 减少
                      X -= 10;
                      break;
                  case "ArrowRight":
                  case "Right":
                      // 向右移动 left 增加
                      X += 10;
                      break;
              }
    
              // 检查蛇是否吃到了食物
             this.checkEat(X, Y);
    
              //修改蛇的X和Y值
              try{
                  this.snake.X = X;
                  this.snake.Y = Y;
              }catch (e){
                  // 进入到catch,说明出现了异常,游戏结束,弹出一个提示信息
                  alert(e.message+' GAME OVER!');
                  // 将isLive设置为false
                  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){
                  // 食物的位置要进行重置
                  this.food.change();
                  // 分数增加
                  this.scorePanel.addScore();
                  // 蛇要增加一节
                  this.snake.addBody();
              }
          }
    
    
      }
    
      export default GameControl;
    

ScorePanel.ts

  •   // 定义表示记分牌的类
      class ScorePanel{
          // score和level用来记录分数和等级
          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 + '';
              }
          }
      }
    
      export default ScorePanel;
    
    
      // 测试代码
      // const scorePanel = new ScorePanel(100, 2);
      // for(let i=0; i<200; i++){
      //     scorePanel.addScore();
      // }
    

index.ts

  •   // 引入样式
      import './style/index.less';
      // import Food from './moduls/Food';
      // const food =  new Food();
      // console.log(food.X, food.Y);
      // food.change();
      // console.log(food.X, food.Y);
      import GameControl from "./moduls/GameControl";
      const gameControl = new GameControl();
    
      // setInterval(()=>{
      //     console.log(gameControl.direction);
      // }, 1000);
    

index.html

  •   <!DOCTYPE html>
      <html lang="zh">
      <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>
    
          <!--设置游戏的积分牌-->
          <div id="score-panel">
              <div>
                  SCORE:<span id="score">0</span>
              </div>
              <div>
                  level:<span id="level">1</span>
              </div>
          </div>
      </div>
      </body>
      </html>
    

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 black;
        // 设置圆角
        border-radius: 40px;
    
        // 开启弹性盒模型,IE9浏览器不支持,有需要要另外找办法比如用浮动定位,
        //本例没有给出这一点的解决方案,自己想。。。
        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;
        }
      }  
    

image.png