es6面向对象+数据驱动《俄罗斯方块小游戏》

心血来潮搞了个俄罗斯方块,发上来给大家分享分享!

项目地址

demo

首先,确定模块:

    1、舞台场景模块:要在这里面玩啊。。。

    2、随机图形模块:不然玩什么啊。。。

    3、控制模块:不然怎么玩啊。。。

    4、时间计时器模块:得让2自动跑啊。。。

    5、逻辑判断等等等等。。。。。。

然后,整理开发思路:

    1、俄罗斯方块是典型的矩阵类型的小游戏,所以数据类型就选择矩阵来做……(也考虑过链表、图等数据结构,但可能对于这种类型的数据还是矩阵来的容易点)

    2、数据类型确定了,面向对象也是必须的:我这里把整个游戏场景作为一个类,大部分的处理逻辑,游戏功能等都在这里面;把图形对象作为另外一个类,具备的功能更简单,就是描述这个图形长什么样,还有就是记录id(这个后边在源码里解释)

    3、渲染部分不多说,选择vue来做渲染器,利用数据驱动视图的特点。。。可以选择任何一种前段框架,或者原生dom,canvas的各种框架都可以,这里不做解释了

还是上代码吧:

首先是自增id和图形类:

let id = -1

/**
 * 自增ID
 * @returns {number}
 */
function getId () {
  return id++;
}

/**
 * 图形对象
 */
class Cube {

  constructor (index, arr) {
    this.index = index
    this.arr = arr
    this.id = getId()
  }

}

这样我就有了一个每次实例化都能自增唯一id的一个图形对象了!

这里主要讲解一下矩阵旋转,涉及到一些高数的东西,并不是特别难:

我想把一个矩阵顺时针旋转90°,我需要做的是,首先将矩阵的竖向顺序倒序,然后再求倒序之后矩阵的转置(具体可以百度,其实就是把矩阵沿对角线翻转),得到的矩阵就是旋转之后的了。

假设矩阵:

【 1 ,2 ,3】                  【7 ,8 ,9】                 【7 ,4 ,1】

【4 ,5 ,6】  =》倒序 =》【4 ,5 ,6】=》转置=》 【8 ,5 ,2】=》成功!

【7 ,8 ,9】                   【 1 ,2 ,3】                【9 ,6 ,3】

主要判断逻辑都在canPut()这个方法上,判断一个图形可不可以放在特定位置上,下面代码敬上!

/**
 * 舞台对象
 */
class Tetris {

  constructor () {
    /**
     * 舞台的矩阵数据
     * @type {null}
     */
    this.cube = null
    /**
     * 当前正在操纵的图形对象
     * @type {null}
     */
    this.controlling = null
    /**
     * 全局计时器
     * @type {null}
     */
    this.ticker = null
    /**
     * 游戏状态
     * @type {boolean}
     */
    this.isPlaying = true
    /**
     * 图形仓库
     * @type {[null,null,null,null,null]}
     */
    this.shapes = [
      [[0, 1], [0, 1], [0, 1, 1]],
      [[0, 1, 0], [1, 1, 1]],
      [[0, 1, 0], [0, 1, 0], [0, 1, 0], [0, 1, 0]],
      [[0, 1], [1, 1], [1, 0]],
      [[1, 0], [1, 1], [0, 1]]
    ]
    /**
     * 下一个要出现的图形
     * 要利用随机数在上方仓库中随机产生一个
     * @type {Cube}
     */
    this.next = new Cube({x: 4, y: 0}, this.shapes[parseInt(Math.random() * this.shapes.length)])
    /**
     * 分数 消去一行就++
     * @type {number}
     */
    this.score = 0
    /**
     * 是否已经死掉了。。。
     * @type {boolean}
     */
    this.dead = false
  }

  /**
   * 初始化舞台
   * @param a 几行
   * @param b 几列
   */
  init (a, b) {
    let arr = []
    for (let i = 0; i < a; i++) {
      arr[i] = []
      for (let j = 0; j < b; j++) {
        arr[i][j] = 0
      }
    }
    this.cube = arr
  }

  /**
   * 将图形渲染到舞台上
   * 其实就是把图形的1写到舞台的对应位置上
   * @param cube
   */
  render (cube) {
    let length = Math.max(cube.arr.length, cube.arr[0].length)
    for (let i = 0; i < length; i++) {
      if (cube.arr[i] === undefined) cube.arr[i] = []
      for (let j = 0; j < length; j++) {
        cube.arr[i][j] = cube.arr[i][j] === undefined ? 0 : cube.arr[i][j]
        if (this.cube[i + cube.index.y] && this.cube[i + cube.index.y][j + cube.index.x] === 1 || cube.arr[i][j] === 0) continue
        this.cube[i + cube.index.y][j + cube.index.x] = cube.arr[i][j]
      }
    }
  }

  /**
   * 清除舞台
   */
  clean () {
    let temp = []
    this.cube.map((val,ind)=>{
      temp[ind] = []
      val.map((v,i)=>{
        temp[ind][i] = 0
      })
    })
    this.cube = temp
  }

  /**
   * 产生下一个图形
   */
  getNext () {
    this.next = new Cube({x: 4, y: 0}, this.shapes[parseInt(Math.random() * this.shapes.length)])
  }

  /**
   * 将图形添加到舞台上
   * @param cube
   * @returns {*}
   */
  add (cube) {
    let temp = cube || this.next
    if (!this.canPut(temp)) {
      this.end()
      return false
    }
    this.render(temp)
    this.update()
    this.getNext()
    return temp
  }

  /**
   * 更新舞台数据
   * 之所以清空数组是为了触发vue的变化。。。这里可以不这么写。。。
   */
  update () {
    // this.check()
    let tempt = this.cube
    this.cube = []
    this.cube = tempt
  }

  /**
   * 开始游戏
   */
  start () {
    this.dead = false
    this.clean()
    this.stageTicker((e) => {
      if (this.controlling === null) return
      this.bottom()
    })
    this.controlling = this.add()
  }

  /**
   * 结束游戏
   */
  end(){
    this.dead = true
    this.destroy()
    console.log('ends')
  }

  /**
   * 删除第i行
   * @param i
   */
  removeLine (i) {
    this.cube.splice(i, 1)
  }

  /**
   * 添加一行空行到第i个位置
   * @param i
   */
  addLine (i) {
    let temp = []
    for (let i = 0; i < this.cube[0].length; i++) {
      temp[i] = 0
    }
    this.cube.splice(i, 0, temp)
  }

  /**
   * 将舞台中的cube图形删除
   * @param cube
   */
  remove (cube) {
    for (let i = 0; i < cube.arr.length; i++) {
      for (let j = 0; j < cube.arr[i].length; j++) {
        if (this.cube[i + cube.index.y] && this.cube[i + cube.index.y][j + cube.index.x] === cube.arr[i][j]) {
          this.cube[i + cube.index.y][j + cube.index.x] = 0
        }
      }
    }
  }

  /**
   * 判断是否可以渲染在当前位置
   * @param cube
   * @returns {boolean}
   */
  canPut (cube) {
    for (let i = 0; i < cube.arr.length; i++) {
      if (this.cube[i + cube.index.y] === undefined && cube.arr[i].find(val => {return val === 1}) !== undefined) {
        return false
      }
      for (let j = 0; j < cube.arr[i].length; j++) {
        if (this.cube[i + cube.index.y] && this.cube[i + cube.index.y][j + cube.index.x] === undefined && cube.arr.find(val => {return val[j] === 1}) !== undefined) return false
        if (this.cube[i + cube.index.y] && this.cube[i + cube.index.y][j + cube.index.x] === 0) {
          continue
        } else {
          if (cube.arr[i][j] === 1) {
            return false
          } else {
            continue
          }
        }
      }
    }
    return true
  }

  /**
   * 检查舞台状态,如果有满行就删除并计分
   */
  check () {
    this.cube.map((val, i) => {
      if (val.find(v => {return v === 0}) === undefined) {
        this.removeLine(i)
        this.addLine(0)
        this.score++
      }
    })
  }

  /**
   * 计时器模块(本来想单独写一个类,后来发现一个方法就足够了。。。有更复杂的需求再说)
   * 可传回调方法cb
   * @param cb
   */
  stageTicker (cb) {
    let i = 0
    this.ticker = setInterval(() => {
      this.update()
      i++
      if (typeof cb === 'function' && this.isPlaying) {
        cb(i++)
      }
    }, 500)
  }

  /**
   * 暂停和继续
   */
  pause () {
    this.isPlaying = !this.isPlaying
  }

  /**
   * 销毁计时器,可以再多些功能
   * TODO 完善功能
   */
  destroy () {
    if(this.ticker!==null) clearInterval(this.ticker)
  }

  /**
   * 数组深度拷贝方法
   * @param from
   * @returns {Array}
   */
  arrayCopy (from) {
    let temp = []
    for (let i = 0; i < from.length; i++) {
      if (Array.isArray(from[i])) {
        temp[i] = this.arrayCopy(from[i])
      } else {
        temp[i] = from[i]
      }
    }
    return temp
  }

  /**
   * 矩阵旋转方法
   */
  translate () {
    let cube = this.controlling
    let temp = this.arrayCopy(cube.arr)
    temp = temp.reverse()
    for (let i = 0; i < temp.length; i++) {
      for (let j = 0; j < temp[i].length; j++) {
        if (i >= j) continue
        let tt = temp[i][j] === undefined ? 0 : temp[i][j]
        if (temp[j] === undefined) temp[j] = []
        temp[i][j] = temp[j][i] === undefined ? 0 : temp[j][i]
        temp[j][i] = tt
      }
    }
    this.remove(cube)
    if (!this.canPut({index: cube.index, arr: temp})) {
      this.add(cube.index, cube.arr)
      return
    }
    cube.arr = temp
    this.render(cube)
    this.update()
  }


  left () {
    if(!this.controlling) return
    let temp = this.controlling.index.x
    this.remove(this.controlling)
    this.controlling.index.x--
    if (!this.canPut(this.controlling)) this.controlling.index.x = temp
    this.render(this.controlling)
    this.update()
  }

  top () {
    if(!this.controlling) return
    let temp = this.controlling.index.y
    this.remove(this.controlling)
    this.controlling.index.y--
    if (!this.canPut(this.controlling)) this.controlling.index.y = temp
    this.render(this.controlling)
    this.update()
  }

  right () {
    if(!this.controlling) return
    let temp = this.controlling.index.x
    this.remove(this.controlling)
    this.controlling.index.x++
    if (!this.canPut(this.controlling)) this.controlling.index.x = temp
    this.render(this.controlling)
    this.update()
  }

  bottom () {
    if(!this.controlling) return
    let temp = this.controlling.index.y
    this.remove(this.controlling)
    this.controlling.index.y++
    if (!this.canPut(this.controlling)) {
      this.controlling.index.y = temp
      this.render(this.controlling)
      this.check()
      this.controlling = this.add()
      this.update()
      return null
    } else {
      this.render(this.controlling)
    }
    this.update()
  }

  down(){
    for(let i =0;i<this.cube.length;i++){
      if(this.bottom() === null){
        break;
      }
    }
  }
}

 

转载于:https://my.oschina.net/u/3718019/blog/1785844

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值