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

这篇文章详细描述了一个基于Vue的Tetris游戏的实现过程,涉及数组操作(如转换二进制、判断边界和消除行),以及游戏状态控制(下落、移动、旋转和判定游戏结束)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

效果预览

在这里插入图片描述

思想

首先建立两个数组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>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值