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

效果预览

在这里插入图片描述

思想

首先建立两个数组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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/426466.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python celery beat实现定时任务

在Celery在python中的应用除了实现异步任务&#xff08;async task)外也可以执行定时任务(beat) 1.Celery定时任务是什么&#xff1f; Celery默认任务单元由任务生产者触发,但有时可能需要其自动触发, 而beat进程正是负责此类任务,能够自动触发定时/周期性任务. 只需要在配置…

yolov5训练太慢的解决方案

问题原因 训练太慢大多是因为没有安装CUDA和pytorch&#xff0c;导致的只有cpu在跑&#xff0c;显卡没跑 这就是很典型的。 解决方案 第一步&#xff1a;安装CUDA 在本机上面安装CUDA,记住只有N卡可以安装&#xff0c;一开始的电脑是自带CUDA的。 如果不是自带的CUDA&…

NoSQL--2.MongoDB配置

目录 2.MongdoDB配置 2.1 Windows环境下操作 2.1.1 注册MongDB Atlas&#xff1a; 2.1.2 MongoDB Community Server Download&#xff1a; 2.1.3 启动MondgoDB服务&#xff1a; 2.1.3.1 命令行参数的方式启动MongoDB服务&#xff1a; 2.1.3.2 使用配置文件方式启动Mongo…

游戏框架搭建

使用框架的目标&#xff1a;低耦合&#xff0c;高内聚&#xff0c;表现和数据分离 耦合&#xff1a;对象&#xff0c;类的双向引用&#xff0c;循环引用 内聚&#xff1a;相同类型的代码放在一起 表现和数据分离&#xff1a;需要共享的数据放在Model里 对象之间的交互一般有三…

如何使用恢复软件恢复删除的文件?回收站文件恢复攻略

随着计算机在日常生活中的普及&#xff0c;文件的管理和存储成为我们不可或缺的技能。在Windows操作系统中&#xff0c;回收站作为一个帮助我们管理文件删除的重要工具&#xff0c;在误删了一些重要文件之后&#xff0c;我们可能会因为找不到回收站中恢复的文件而感到困惑。本文…

革命文物的新征程:SpringBoot实践

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

打造个人知识库-chatwithrtx接口研究

前言 之前安装了chatwithrtx&#xff0c;确实挺好用的。但是如果想用其对外提供服务的话&#xff0c;还需要研究是否能够提供api接口进行调用&#xff0c;所以今天来进行一下研究。 gradio介绍 web的访问是通过gradio框架进行开发的。在user_interface.py中可以发现如下引用 im…

第十六天-爬虫selenium库

目录 1.介绍 2.使用 selenium 1.安装 2.使用 1.测试打开网页&#xff0c;抓取雷速体育日职乙信息 2.通过xpath查找 3.输入文本框内容 send_keys 4.点击事件 click 5.获取网页源码&#xff1a; 6.获取cookies 7.seleniumt提供元素定位方式&#xff1a;8种 8.控制浏览…

算法刷题day20:二分

目录 引言概念一、借教室二、分巧克力三、管道四、技能升级五、冶炼金属六、数的范围七、最佳牛围栏 引言 这几天一直在做二分的题&#xff0c;都是上了难度的题目&#xff0c;本来以为自己的二分水平已经非常熟悉了&#xff0c;没想到还是糊涂了一两天才重新想清楚&#xff0…

Linux红帽rhce认证多少钱?考个RHCE难不难?

Linux作为开源操作系统的佼佼者&#xff0c;已经广泛应用于各个领域。红帽认证工程师(Red Hat Certified Engineer&#xff0c;简称RHCE)作为Linux领域权威的认证之一&#xff0c;自然成为了众多IT从业者追求的目标。那么&#xff0c;RHCE认证的培训费用是多少?考取这一认证又…

【C语言】linux内核packet_setsockopt

一、中文注释 // 发送数据包函数。它尝试通过特定的网络设备队列直接传输一个skb&#xff08;socket缓冲区&#xff09;。 static int packet_direct_xmit(struct sk_buff *skb) {return dev_direct_xmit(skb, packet_pick_tx_queue(skb)); // 调用dev_direct_xmit函数&#x…

写代码实现基金回测(一)

参考博客&#xff1a;应用实战&#xff1a;我的第一个开源项目-基金定投回测工具 这个博主的代码的目录结构还是很赞的 看一下他是如何计算收益率的 第一步&#xff1a;获取所有公募基金的基础信息 共计一万个基金 第二步&#xff1a;获取所有基金的费率信息 这里有一点需要…

Java基于springboot的个人理财系统

基于springboot的个人理财系统 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了个人理财系统的开发全过程。通过分析个人理财系统管理的不足&#xff0c;创建了一个计算机管理个人理财系统的方案。文章介绍了个…

bxCAN总线的工作模式和测试模式(STM32F4xx)

概述 本文主要介绍STM32F4XX的bxCAN知识&#xff0c;包括bxCAN的概念&#xff0c;各种工作模式下特性&#xff0c;如何配置各类工作模式等内容&#xff0c;还介绍了bxCAN的测试模式&#xff0c;bxCAN测试模式有3种工作类型&#xff0c;每种类型有什么特性&#xff0c;以及如何配…

【大厂AI课学习笔记NO.66】TensorFlow

TensorFlow 这个框架&#xff0c;实在是太有名了&#xff0c;最近周红衣都在大力的宣传和讲解。 他说的是对的&#xff0c;人工智能&#xff0c;就是大力出奇迹&#xff0c;就是大量的算力&#xff0c;大量的数据&#xff0c;加上模型的加持&#xff0c;实现的智能感觉。 Goog…

【字符串】马拉车(Manacher)算法

本篇文章参考&#xff1a;比较易懂的 Manacher&#xff08;马拉车&#xff09;算法配图详解 马拉车算法可以求出一个字符串中的最长回文子串&#xff0c;时间复杂度 O ( n ) O(n) O(n) 因为字符串长度的奇偶性&#xff0c;回文子串的中心可能是一个字符&#xff0c;也可能是…

webpack源码分析——tapable中before和stage如何改变执行顺序

一、Before用法 Before 用法 before 属性的值可以传入一个数组或者字符串,值为注册事件对象时的名称&#xff0c;它可以修改当前事件函数在传入的事件名称对应的函数之前进行执行。 示例 let hook new SyncWaterfallHook([arg1]);hook.tap(tap1, (arg)> {console.log(tap1…

安装 node 错误的配置环境变量之后使用 npm 报错

安装 node 错误的配置环境变量之后使用 npm 报错 node:internal/modules/cjs/loader:1147 throw err; ^ Error: Cannot find module ‘F:\ACodeTools\Node\node_modules\npm\bin\node_modules\npm\bin\npm-cli.js’ at Module._resolveFilename (node:internal/modules/cjs/loa…

Git推送本地仓库至阿里云仓库

Git推送本地仓库至阿里云仓库 1.安装Git 参考Git安装详解 2.生成 SSH 密钥 基于RSA算法SSH 密钥 1.管理员权限运行Git Bash 2.输入生成密钥指令点击回车&#xff0c;选择 SSH 密钥生成路径。 $ ssh-keygen -t rsa -C "2267521563qq.com"3.以 RSA算法为例&…