效果预览
思想
首先建立两个数组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>