效果预览
思想
首先建立两个数组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-numberv-model="widthNum":min="10":max="15"@change="handleChange":disabled="isStart"/>高:<el-input-numberv-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"><divclass="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 = truescore.value = 0board.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 = falsebreak}}
}
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 = tempTetrisbreak}}
}
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 = tempTetrisbreak}}
}
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 = tempTetrisbreak}}
}
const listenser = (e) => {switch (e.keyCode) {case 37:left()breakcase 40:down()breakcase 39:right()breakcase 38:up()breakdefault: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 = radiuslet chaju = 0if (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.lengthconst 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>