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

news2024/12/26 23:13:46

效果预览

在这里插入图片描述

思想

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Flink:Temporal Table Function(时态表函数)和 Temporal Join

博主历时三年精心创作的《大数据平台架构与原型实现&#xff1a;数据中台建设实战》一书现已由知名IT图书品牌电子工业出版社博文视点出版发行&#xff0c;点击《重磅推荐&#xff1a;建大数据平台太难了&#xff01;给我发个工程原型吧&#xff01;》了解图书详情&#xff0c;…

Qt 简约美观的加载动画 第九季

这次和大家分享6个非常清爽的加载动画. &#x1f60a; 效果如下 &#x1f60a; 一共三个文件 , 可以直接编译运行的呢 //main.cpp #include "LoadingAnimWidget.h" #include <QApplication> #include <QGridLayout> int main(int argc, char *argv[]) …

Endnote x9 最快方法批量导入.enw格式文件

按照网上看到的一个方法直接选中所有enw批量拖拽到 All references 附件不行啊&#xff0c; 以为只能写bat脚本方式了 经过一番尝试&#xff0c;惊人的发现拖到下面这个符号的地方就行了&#xff01;&#xff01;&#xff01; 如果不成功的话&#xff0c;可能&#xff1a; 我…

WordPress免费的远程图片本地化下载插件nicen-localize-image

nicen-localize-image&#xff08;可在wordpress插件市场搜索下载&#xff09;&#xff0c;是一款用于本地化文章外部图片的插件&#xff0c;支持如下功能&#xff1a; 文章发布前通过编辑器插件本地化 文章手动发布时自动本地化 文章定时发布时自动本地化 针对已发布的文章…

BioTech - 药物晶型预测与剂型设计 概述

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://blog.csdn.net/caroline_wendy/article/details/136441046 药物晶型预测与剂型设计是指利用计算机模拟和优化药物分子在固态形式下的结构、性质和稳定性&#xff0c;以及与制剂工艺和质…

一维数组、内存理解图--学习JavaEE的day9

day09 一、一维数组 理解&#xff1a; 一组数据的容器 应用场景&#xff1a; 存储多个数据的时候可以考虑使用数组 概念&#xff1a; ​ 1.数组是引用数据类型 ​ 2.数组中的数据&#xff0c;称之为元素 ​ 3.元素的都有编号&#xff0c;称之为下标/索引 ​ 4.下标从0开始 ​…

【MetaGPT】配置教程

MetaGPT配置教程&#xff08;使用智谱AI的GLM-4&#xff09; 文章目录 MetaGPT配置教程&#xff08;使用智谱AI的GLM-4&#xff09;零、为什么要学MetaGPT一、配置环境二、克隆代码仓库三、设置智谱AI配置四、 示例demo&#xff08;狼羊对决&#xff09;五、参考链接 零、为什么…

HTML和CSS (前端共三篇)【详解】

目录 一、前端开发介绍 二、HTML入门 三、HTML基础标签 四、CSS样式修饰 五、HTML表格标签 六、HTML表单标签 一、前端开发介绍 web应用有BS和CS架构两种&#xff0c;其中我们主要涉及的是BS架构。而BS架构里&#xff0c;B&#xff08;Browser浏览器&#xff09;是客户端的…

Sqli-labs靶场第21、22关详解[Sqli-labs-less-21、22]自动化注入-SQLmap工具注入|sqlmap跑base64加密

Sqli-labs-Less-21、22 由于21/22雷同&#xff0c;都是需要登录后&#xff0c;注入点通过Cookie值进行测试&#xff0c;值base64加密 修改注入数据 选项&#xff1a;--tamperbase64encode #自动化注入-SQLmap工具注入 SQLmap用户手册&#xff1a;文档介绍 - sqlmap 用户手册 由…

关于硅金属电阻器?

EAK金属硅电阻器类似于陶瓷复合电阻器&#xff0c;在脉冲负载方面具有优势&#xff0c;需要高峰值功率或高电压与低电感&#xff08;如预充电电路&#xff09;的组合。硅金属电阻器具有更高的连续额定温度&#xff0c;为 350C&#xff0c;而陶瓷电阻器为 250C。这种扩展的温度范…

【java】final、finally和finalize的区别

例题&#xff1a; package com.overload;public class ExceptionTest {public static void main(String[] args) {int result test();System.out.println(result); //100}public static int test(){int i 100;try {return i;} finally {i;}} }结果为&#xff1a;100 造成结果…

JUC并发编程 深入学习Java并发编程【上】

JUC并发编程&#xff0c;深入学习Java并发编程&#xff0c;与视频每一P对应&#xff0c;全系列6w字。 P1-5 为什么学特色预备知识 进程线程概念 进程&#xff1a; 一个程序被运行&#xff0c;从磁盘加载这个程序的代码到内存&#xff0c;就开起了一个进程。 进程可以视为程…

人工智能_大模型015_RAG量化检索增强002_AIGC大模型_本地知识库实时问答_私域和实时场景_量化检索增强---人工智能工作笔记0151

由于上一节我们提到的,关键词检索的局限性,现在我们引出向量检索, 关键词检索有语义上的缺陷,因为我们说法不一样,但是意思一样的话,那么,关键词如果在es库中没有,那么会导致,找不到答案的情况.所以我们引出向量检索,要求语义一样的词,去检索都能找到答案. 我们来说一下这个文…

突破编程_前端_JS编程实例(网站标题栏TAB组件)

1 开发目标 实现如下网站标题栏 TAB 组件&#xff1a; 在点击"页面2"选项卡后&#xff0c;TAB 组件会切换对应的面板&#xff1a; 2 详细需求 网站标题栏 TAB 组件该组件需根据客户端提供的参数创建&#xff0c;具备动态构建 TAB 区域、选项卡切换及自定义内容…

Django模型进阶(Mysql配置、模型管理,表关联、一对一、一对多,多对多)

模型进阶&#xff1a; Mysql配置&#xff1a; 1.安装mysql 2安装MySQL驱动&#xff0c;使⽤mysqlclient pip install mysqlclient pip install -i https://pypi.douban.com/simple mysqlclientLinux Ubuntu下需要先安装&#xff1a;apt install libmysqld-dev 再安装: apt…

[BJDCTF2020]EzPHP1 --不会编程的崽

有一说一&#xff0c;这题还是有难度的 base32解码url编码绕过$_SERVER换行符绕过preg_match相同参数&#xff0c;post请求覆盖get请求&#xff0c;绕过$_REQUESTphp伪协议利用sha1数组绕过create_function代码注入 Level 1 右键源码里又发现&#xff0c;拿去base32解码即可…

w30使用python调用shell脚本

使用python脚本去实现永恒之蓝漏洞攻击 实验环境 攻击工具&#xff1a;pythonmsfconsole 靶场&#xff1a;win7 和 kali实验目的 演示python脚本调用过程 实验步骤 1.写一个永恒之蓝的攻击脚本&#xff0c;定义为blue.rc use exploit/windows/smb/ms17_010_eternalblue …

Microsoft PyRIT能自动化完成AI红队的任务

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

【算法集训】基础算法:枚举

一、基本理解 枚举的概念就是把满足题目条件的所有情况都列举出来&#xff0c;然后一一判定&#xff0c;找到最优解的过程。 枚举虽然看起来麻烦&#xff0c;但是有时效率上比排序高&#xff0c;也是一个不错的方法、 二、最值问题 1、两个数的最值问题 两个数的最小值&…

【Android】View事件体系基础

文章目录 坐标系View滑动layout方法offserLeftAndRight() 和 offsetTopAndBottom()LayoutParams(布局参数)View动画scrollTo/scrollBy 解析Activity的构成 坐标系 分为Android坐标系和View坐标系 可以用 getWidth() 和 getHeight() 获取View自身的宽度和高度 对于ViewgetX() …