• 巧用二进制实现俄罗斯方块小游戏


    效果预览

    在这里插入图片描述

    思想

    首先建立两个数组board、tetris用来存储当前已经堆积在棋盘的方块与正在下落的方块。
    这两个是一维数组当需要在页面画棋盘时就对其每一项转成二进制(看计算属性tetrisBoard),其中1(红色)0(白色)。
    判断是否可以下落:对board、tetris每一项 &(与操作),如果都为0则还可以下落,否则停止下落。
    判断是否触底:tetris的最后一项是否为0如果不为0则说明已经触底了
    判断是否可以左(右)移: :对board、tetris每一项 &(与操作),如果都为0则还可以移动,否则停止移动
    判断是否已经触碰右边界:对tetris每一项同二进制的0b00001进行与操作,如果是0则未触碰边界,相反则已经触碰边界了。
    判断是否已经触碰左边界:对tetris每一项同二进制的0b100000进行与操作,如果是0则未触碰边界,相反则已经触碰边界了。
    判断是否可以消除:循环board中的每一项与二进制0b11111(对应计算属性allOnesInBinaryDecimal)是否大小相同,相同的话就说明这行已经满了,满的话就将这项变为0,并把其上面的项向下移,同时给最上面补0。

    代码

    <template>
      <div class="tetris-box">
        <div class="top-operator">
          <el-button type="primary" size="default" @click="init">
            {{ isStart ? '重新开始' : '开始' }}
          </el-button>
          宽:
          <el-input-number
            v-model="widthNum"
            :min="10"
            :max="15"
            @change="handleChange"
            :disabled="isStart"
          />
          高:
          <el-input-number
            v-model="heightNum"
            :min="15"
            :max="20"
            @change="handleChange"
            :disabled="isStart"
          />
          <span>Score: {{ score }}</span>
        </div>
        <div class="game-container">
          <div class="row" v-for="(rowItem, index) in tetrisBoard" :key="index">
            <div
              class="cell"
              :style="{ background: cellItem === '1' ? 'red' : 'white' }"
              v-for="(cellItem, index) in rowItem"
              :key="index"
            ></div>
          </div>
        </div>
      </div>
    </template>
    
    <script setup lang="ts">
    import { ref, computed, onMounted, watch, nextTick, onBeforeUnmount } from 'vue'
    import { cloneDeep } from 'lodash'
    import { Scale } from 'canvg'
    const widthNum = ref(10)
    const heightNum = ref(14)
    let score = ref(0)
    const board = ref<number[]>([])
    const tetris = ref<number[]>([])
    let timer: number | null = null
    let isStart = ref(false)
    const allOnesInBinaryDecimal = computed(() => {
      return (1 << widthNum.value) - 1
    })
    const boardNum = computed(() => {
      // 将tetris中的每一项转成对应的2进制
      return board.value?.map((item, index) => {
        return item + tetris.value[index]
      })
    })
    // 用来画棋盘的二维数组
    const tetrisBoard = computed({
      get() {
        // 将tetrisNum中的每一项转成对应的2进制
        return boardNum.value.map((item) => {
          return item.toString(2).padStart(widthNum.value, '0').split('')
        })
      },
      set(value) {},
    })
    const action = () => {
      timer = setInterval(() => {
        down()
      }, 1000)
    }
    onMounted(() => {
      // 棋盘初始化
      board.value = Array(heightNum.value).fill(0)
    })
    onBeforeUnmount(() => {
      clearInterval(timer as number)
      removeEventListener('keydown', listenser)
    })
    const init = () => {
      isStart.value = true
      score.value = 0
      board.value = Array(heightNum.value).fill(0)
      removeEventListener('keydown', listenser)
      clearInterval(timer as number)
      initTetris()
      action()
      document.addEventListener('keydown', listenser)
    }
    const initTetris = () => {
      const tetrisArr = [
        [1, 1, 1, 1],
        [2, 3, 1],
        [3, 2, 2],
        [3, 1, 1],
        [2, 2, 3],
        [1, 1, 3],
        [1, 3, 2],
        [1, 3, 1],
        [2, 3, 2],
        [7, 1],
        [7, 2],
        [7, 4],
        [1, 7],
        [3, 6],
        [6, 3],
        [3, 3],
        [4, 7],
        [2, 7],
        [15],
      ]
      let tempTetris = tetrisArr[Math.floor(Math.random() * tetrisArr.length)]
      const zeroArr = Array(heightNum.value - tempTetris.length).fill(0)
      tempTetris = tempTetris.concat(zeroArr)
      tetris.value = tempTetris
      // 让方块随机右移出现
      let rightMoveNum = Math.floor(Math.random() * widthNum.value)
      for (let i = 0; i < rightMoveNum; i++) {
        right()
      }
      let leftMoveNum = Math.floor(Math.random() * widthNum.value)
      for (let i = 0; i < leftMoveNum; i++) {
        left()
      }
      // 判断是否有哪一行已经满了就可以消除了
      board.value.forEach((item, index) => {
        if (item === allOnesInBinaryDecimal.value) {
          board.value.splice(index, 1)
          board.value.unshift(0)
          score.value += widthNum.value
        }
      })
      // 判断是否结束游戏
      for (let i = 0; i < tetris.value.length; i++) {
        if (tetris.value[i] & board.value[i]) {
          clearInterval(timer as number)
          removeEventListener('keydown', listenser)
          board.value = Array(heightNum.value).fill(0)
          alert('游戏结束')
          tetris.value = Array(heightNum.value).fill(0)
          isStart.value = false
          break
        }
      }
    }
    const down = () => {
      const tempTetris = cloneDeep(tetris.value)
      tetris.value = [0].concat(tetris.value.splice(0, tetris.value.length - 1))
      // 判断是否可以下落
      for (let i = 0; i < tetris.value.length; i++) {
        // 如果有碰撞或者已经触底了就用board存储目前已经堆积的方块
        // 并重新在最上方生成一个新的方块
        if (tetris.value[i] & board.value[i] || tempTetris[tempTetris.length - 1]) {
          board.value = board.value?.map((item, index) => {
            return item + tempTetris[index]
          })
          initTetris()
          break
        }
      }
    }
    const right = () => {
      const tempTetris = cloneDeep(tetris.value)
      tetris.value = tetris.value.map((item, index) => {
        return item >> 1
      })
      // 判断是否可以右移
      for (let i = 0; i < tetris.value.length; i++) {
        // 如果触发边界就不再移动
        if (tetris.value[i] & board.value[i] || tempTetris[i] & 1) {
          tetris.value = tempTetris
          break
        }
      }
    }
    const left = () => {
      const tempTetris = cloneDeep(tetris.value)
      tetris.value = tetris.value.map((item, index) => {
        return item << 1
      })
      // 判断是否可以左移
      for (let i = 0; i < tetris.value.length; i++) {
        // 如果触发边界就不再移动
        if (
          tetris.value[i] & board.value[i] ||
          tempTetris[i] & (1 << (widthNum.value - 1))
        ) {
          tetris.value = tempTetris
          break
        }
      }
    }
    const up = () => {
      const tempTetris = cloneDeep(tetris.value)
      // tetris.value = tetris.value.map((item, index) => {
      //   return item ^ 1
      // })
      let temp = tetris.value.map((item) => {
        return item.toString(2).padStart(widthNum.value, '0').split('')
      })
      temp = rotateMatrix90(temp)
      tetris.value = temp.map((item) => {
        return parseInt(item.join(''), 2)
      })
      // 判断是否可以旋转
      for (let i = 0; i < tetris.value.length; i++) {
        if (tetris.value[i] & board.value[i]) {
          tetris.value = tempTetris
          break
        }
      }
    }
    const listenser = (e) => {
      switch (e.keyCode) {
        case 37:
          left()
          break
        case 40:
          down()
          break
        case 39:
          right()
          break
        case 38:
          up()
          break
        default:
          break
      }
    }
    const handleChange = () => {
      board.value = Array(heightNum.value).fill(0)
    }
    
    // ---------下面都是旋转逻辑---------
    // 找出非零最小正方形区域
    const findMinSquare = (matrix) => {
      let top = matrix.length,
        left = matrix[0].length,
        bottom = 0,
        right = 0
    
      // 寻找非零元素的边界
      for (let i = 0; i < matrix.length; i++) {
        for (let j = 0; j < matrix[i].length; j++) {
          if (matrix[i][j] !== '0') {
            top = Math.min(top, i)
            left = Math.min(left, j)
            bottom = Math.max(bottom, i)
            right = Math.max(right, j)
          }
        }
      }
    
      // 返回最小正方形区域
      let result = {
        top: top,
        left: left,
        width: right - left + 1,
        height: bottom - top + 1,
        square: [],
        radius: 0,
        chaju: 0,
      }
      // 半径
      let radius = Math.max(result.width, result.height)
      result.radius = radius
      let chaju = 0
      if (left + radius > widthNum.value) {
        chaju = left + radius - widthNum.value
      }
      result.chaju = chaju
      // 如果需要返回实际的最小正方形子矩阵,请添加以下代码
      for (let i = 0; i < radius; i++) {
        const row = []
        for (let j = 0; j < radius; j++) {
          row.push(matrix[top + i][left + j - chaju])
        }
        result.square.push(row)
      }
      return result
    }
    // 对正方形区域进行旋转90度
    const rotateMinSquareMatrix90 = (matrix) => {
      const n = matrix.length
      const rotatedMatrix = Array.from({ length: n }, () => new Array(n).fill(null))
    
      for (let i = 0; i < n; i++) {
        for (let j = 0; j < n; j++) {
          rotatedMatrix[j][n - i - 1] = matrix[i][j]
        }
      }
      return rotatedMatrix
    }
    // 旋转矩阵90度的主函数
    const rotateMatrix90 = (matrix) => {
      const tempMatrix = cloneDeep(matrix)
      let result = findMinSquare(tempMatrix)
      result.square = rotateMinSquareMatrix90(result.square)
    
      for (let i = 0; i < result.radius; i++) {
        for (let j = 0; j < result.radius; j++) {
          tempMatrix[result.top + i][result.left + j - result.chaju] =
            result.square[i][j]
        }
      }
      return tempMatrix
    }
    </script>
    
    <style scoped lang="scss">
    .tetris-box {
      display: flex;
      flex-direction: column;
      align-items: center;
      .game-container {
      }
    
      .row {
        display: flex;
      }
    
      .cell {
        width: 35px;
        height: 35px;
        background: pink;
        border: 0.5px solid #000;
      }
    }
    </style>
    </script>
    
    <style scoped lang="scss">
    .tetris-box {
      display: flex;
      flex-direction: column;
      align-items: center;
      .game-container {
      }
    
      .row {
        display: flex;
      }
    
      .cell {
        width: 35px;
        height: 35px;
        background: pink;
        border: 0.5px solid #000;
      }
    }
    </style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
  • 相关阅读:
    LeetCode220828_89、数组中的第K个最大元素
    Java线程未捕获异常处理 UncaughtExceptionHandler
    Leetcode 73 矩阵置0
    什么品牌的台灯适合学生用?适合学生党用的台灯推荐
    前端(二十一)——WebSocket:实现实时双向数据传输的Web通信协议
    FastReport Desktop 2023Microsoft SQL存储过程的支持
    使用CMD修复和恢复病毒感染文件
    离散卡尔曼滤波器算法详解及重要参数(Q、R、P)的讨论
    STM32 HAL库 利用CH376进行USB文件读写
    java毕业设计网上书城网站源码+lw文档+mybatis+系统+mysql数据库+调试
  • 原文地址:https://blog.csdn.net/xinbaiyu/article/details/136431171