使用typescript完成简单的贪吃蛇功能

参考资料

开发工具

  • vscode
  • 推荐插件 Live Server 可以监控文件的变化,并进行刷新

界面效果

在这里插入图片描述

界面index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>贪吃蛇</title>
  <link rel="stylesheet" href="css/index.css">
</head>
<body>
  <div id="main">
    <!-- 游戏的主要界面 -->
    <div id="stage">
      <!-- 设置蛇 -->
      <div id="snake">
        <!-- 表示蛇的各部分 -->
        <div></div>
      </div>
      <!--设置食物,4个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>
      <!-- 等级,默认10, 等级高速度快 -->
      <div>LEVEL:<span id="level">1</span></div>
    </div>

  </div>
  <script src="TS/index.js"></script>
</body>
</html>

index.css

  • 设置页面的样式,在模块化开发中可以使用less
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
} /* 清楚默认样式 */

body {
  font: bold 20px "Courier"
}

#main {
  width: 360px;
  height: 420px;
  background-color: #b7d4a8;
  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: black;
  border: 1px solid  #b7d4a8;
  position: absolute;
}

#food {
  width: 10px;
  height: 10px;
  position: absolute;
  left: 40px;
  top: 100px;
  display: flex;
  flex-flow: row wrap; /* 设置主轴为横轴 超出部分换行 */
  /* 设置主轴和侧轴的空白分配到元素之间 */
  justify-content: space-between;
  align-items: center;
}

#food > div {
  width: 4px;
  height: 4px;
  background-color: black;
  transform: rotate(45deg);
}

#score-panel {
  width: 300px;
  display: flex;
  justify-content: space-between;
}

index.ts

  • 在模块化开发中可以把每个类单独放在一个文件中
  • 在这里我使用一个文件,可以使用 命令进行便于 tsc -t ES6 -w index.ts 表示使用ES6进行编译,并且对文件进行监控
class Food {
  element : HTMLElement
  width: number
  size: number 
  constructor (id:string, width = 29, size = 10 ) {
    this.element=document.getElementById(id)
    this.width = width
    this.size = size
  }
  get X(){
    return this.element.offsetLeft
  }
  get Y() {
    return this.element.offsetTop
  }
  change() {
    // 生成随机的位置 范围在[0,width*10] 但要求为整数 且是10的倍数
    this.element.style.left = Math.round(Math.random()*this.width)*this.size + 'px'
    this.element.style.top = Math.round(Math.random()*this.width)*this.size + 'px'
  } // 修改食物的位置
}

class ScorePanel {
  score = 0 
  level = 1
  scoreEle : HTMLElement
  levelEle : HTMLElement   
  static maxLevel = 10
  static upScore = 1 // 设置升1级所需要的分数
  constructor (scoreId:string, levelId:string) {
    this.scoreEle = document.getElementById(scoreId)
    this.levelEle = document.getElementById(levelId)
    this.scoreEle.innerText = this.score + ''
    this.levelEle.innerText = this.level + ''
  }
  addScore (d = 1) {
    this.score += 1
    this.scoreEle.innerText = this.score + ''
    if (this.score % ScorePanel.upScore === 0) this.addLevel()
  }
  addLevel () {
    if (this.level < ScorePanel.maxLevel) {
      this.levelEle.innerText = ++this.level + ''
    }
  }
}


class Snake {
  head: HTMLElement // 表示蛇头的元素
  bodies: HTMLCollection // 蛇的身体包含蛇头
  element: HTMLElement // 获取蛇的容器 
  range: number // 蛇头所能在的范围
  constructor(id, range = 290) {
    this.element = document.getElementById(id)
    this.element.innerHTML = '<div></div>' // 初始化
    this.bodies = this.element.getElementsByTagName('div')
    this.head = this.bodies[0] as HTMLElement
    this.range = range
  }
  get X() {
    return this.head.offsetLeft
  } // 获取蛇头x坐标
  get Y() {
    return this.head.offsetTop
  } // 获取蛇头y坐标

  set X(value:number) {
    if (this.X === value) return
    this.judge(value)
    this.move()
    this.head.style.left = value + 'px'
  }
  set Y(value:number) {
    if (this.Y === value) return
    this.judge(value)
    this.move()
    this.head.style.top = value + 'px'
    
  }
  addBody () {
    this.element.insertAdjacentHTML("beforeend","<div></div>")
  }
  move() {
    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'
    }
  }
  judge(value:number) {
    if ( value < 0 || value > this.range) {
      throw new Error('蛇撞墙了')
    }
    for(let i= this.bodies.length-1;i>0;--i) {
      let x = (this.bodies[i] as HTMLElement).offsetLeft
      let y = (this.bodies[i] as HTMLElement).offsetTop;
      if (x===this.X && y === this.Y) throw new Error('不能吃自己')
    }
  }
}

class GameControl {
  snake:Snake
  scorePanel: ScorePanel
  food: Food
  direction:string // 蛇头移动方向
  isLive = true // 标记当前蛇仍然存在=
  static directionKey = new Set(['w','a','s','d',' '])  // 蛇头移动方向可能取值
  static speed = 200 // 设置初始速度
  constructor() {
    this.scorePanel = new ScorePanel('score','level')
    this.food = new Food("food")
    this.snake = new Snake("snake", this.food.size * this.food.width)
    this.direction = ' '
    this.init()
  }
  init() {
    this.food.change()
    console.log(this.food.X,this.food.Y)
    document.addEventListener('keydown',this.keydownHandler.bind(this))
    this.run()
  }// 游戏初始化
  keydownHandler(event:KeyboardEvent) {
    let key = event.key
    if (GameControl.directionKey.has(key)){
      // 不能向反方向移动
      if (this.direction === 'w' && key === 's') return
      if (this.direction === 'a' && key === 'd') return
      if (this.direction === 's' && key === 'w') return
      if (this.direction === 'd' && key === 'a') return
      this.direction = event.key
    }
  }//创建一个键盘按下的响应的函数
  run(){
    let x = this.snake.X, y = this.snake.Y
    switch(this.direction) {
      case 'w': 
        y -= 10
        break // 向上移动
      case 'a':
        x -= 10
        break // 向左移动
      case 's':
        y += 10
        break // 向下移动
      case 'd':
        x += 10
        break // 向右移动
      case ' ':
        break // 空格表示暂停或者继续
    }
    try {
      this.checkEat(x, y)
      this.snake.X = x
      this.snake.Y = y
    }catch (err){
      this.isLive = false
      if (window.confirm(err.message + ' 是否继续游戏')) {
        game = new GameControl()
      }
      return
    }
  
    this.isLive && setTimeout(this.run.bind(this),GameControl.speed / this.scorePanel.level) // 开启定时调用
  }// 蛇移动的方法
  checkEat (x:number, y:number) {
    if  (x === this.food.X && y === this.food.Y) {
      this.food.change()
      this.scorePanel.addScore()
      this.snake.addBody()
    }
  } // 检查蛇是否迟到食物
} // 游戏控制器

let game: GameControl = new GameControl()

附录

  • 老师的webpack.config.js 配置文件,其他一部分是我添加
const path = require('path')
const HTMLWebpackPlugin = require('html-webpack-plugin')
const {CleanWebpackPlugin} = require('clean-webpack-plugin')

module.exports = {
  context: path.resolve(__dirname, ''), //  基础目录,绝对路径,用于从配置中解析入口起点 (entry point) 和加载器 (loader)。
  entry: "./src/index.ts", // 指定入口文件
  output: {
    path: path.resolve(__dirname,'dist') , // 拼接目录
    filename: "bundle.js" ,// 打包后文件名
    environment: {
      arrowFunction: false
    } // 输出文件的语法环境
  }, // 指定打包文件所在目录
  module: {
    rules:[
      { 
        test: /\.tsx?$/, // 指定规则生效的文件
        use: [
          {
            loader: "babel-loader",
            options: {
              persets: [
                "@babel/preset-env", // 指定环境的插件
                {
                  targets: {
                    "chrome": "88",
                    "ie": "10"
                  }, // 要兼容的浏览器
                  "corejs": "3",
                  "useBuiltIns" : "usage", // 表示按需加载所需要的资源
                }
              ] // 设置预定义的环境 
            } // 对应加载器的配置项,每个加载器都有自己的配置项
          },
          "ts-loader"
        ] ,//  数组的方式使用多个加载器
        exclude: "/node-modules/" // 要排除的文件夹
      }
    ] // 指定要加载的规则
  }, // 指定webpack打包时要使用的模块
  resolve: {
    extensions: ["", ".webpack.js", ".web.js", ".ts", ".tsx", ".js"],
    alias: {
      '@': resolve('src')
    }, // 配置别名把原来导入路径映射成一个新的导入路径
  }, // 配置 webpack 如何寻找模块对应的文件
  plugins: [
    new CleanWebpackPlugin(),
    new HTMLWebpackPlugin({
      title : 'ts项目构建', // index.html 标题
      template: "", // 定义模板位置 
    })
  ],
  devServer: {
    
  } // 提供虚拟服务器,让我们进行开发和调试。需要安装对应插件 webpack-dev-server
}
  • 部分package.json 所需要安装的依赖
{
  "script":{
    "build": "webpack",
    "start": "webpack serve --open chrome.exe"
  },
  "devDependencies": {
    "clean-webpack-plugin" : "", //  webpack中的清除插件,每次构建都会先清除目录
    "html-webpack-plugin":"", //  webpack中html插件,用来自动创建html文件
    "ts-loader":"", // ts加载器,用于在webpack中编译ts文件
    "typescript":"", //  ts编译器
    "webpack": "", //  构建工具webpack
    "webpack-cli": "", // webpack的命令行工具
    "webpack-dev-server": "",
    "@babel/core": "",
    "@babel/preset-enb": "",
    "babel-loader":"",
    "core-js": ""
  }
}
  • webpack-dev-server : 拥有实时重载能力的静态资源服务器
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值