Vue2模拟俄罗斯方块小游戏

news/2024/11/28 7:34:56/

目录

一、效果展示

二、代码分享

三、原理分析

3.1、界面搭建

3.2、方块创建

3.3、方块旋转

3.4、方块移动

3.5、移动判断

3.6、下落判断与清除

3.7、得分计算

一、效果展示

二、代码分享

<template><div class="game"><div class="game-div"><div class="game-min"><div class="row" v-for="(row, i) in frame" :key="i"><pclass="element"v-for="(col, j) in row":key="j":style="{ background: col.bg }"></p></div></div><!-- 小屏幕 --><div class="right-div"><div class="ass"><div class="row" v-for="(row, i) in ass" :key="i"><pclass="element"v-for="(col, j) in row":key="j":style="{ background: col.bg }"></p></div></div><!-- 分数计算 --><div class="score-div"><div><p><span>得分:</span>&emsp;<span style="color: red">{{score}}</span></p></div><div><p><span>等级:</span>&emsp;<span style="color: red">{{level}}</span></p></div><div><p><span>消除:</span>&emsp;<span style="color: red">{{times}}</span></p></div><div class="ztks" @click="stopGame">暂停/开始</div></div></div></div><!-- 控制台 --><div class="control"><p @click="change1">变换</p><div class="control-center"><div @click="moveLeft">向左</div><div @click="moveRight">向右</div></div><p @click="moveDown">向下</p></div></div>
</template><script>
import { color, blockMod, transition } from "@/utils/ykdata.js";
export default {data() {return {row: 20, //行col: 10, //列frame: [], //界面ass: [], //副屏幕bg: "#eee",block: [], //基本方块集合now: { b: 0, c: 0 }, //当前方块以及其旋转角度next: { b: 0, c: 0 }, //下一个方块以及其旋转角度nowBlock: [], //当前形状数据nextBlock: [], //下一个形状数据xz: 0, //当前旋转角度timer: "", //自动下落speed: 800, //速度score: 0, //得分level: 1, //等级times: 0, //消除次数stop: true, //是否停止removeRow: [], //消除的行记录};},mounted() {this.gameFrame();this.getBlock(0);this.getNext();this.init();},methods: {// 游戏框架gameFrame() {//主屏幕for (let i = 0; i < this.row; i++) {let a = [];for (let j = 0; j < this.col; j++) {let b = {data: 0,bg: this.bg,};a.push(b);}this.frame.push(a);}//副屏幕for (let i = 0; i < 4; i++) {let a = [];for (let j = 0; j < 4; j++) {let b = {data: 0,bg: this.bg,};a.push(b);}this.ass.push(a);}// 模拟格子被占用// this.frame[4][4].bg = "#00aaee";// this.frame[4][4].data = 1;},// 渲染方块getBlock(e) {this.block = blockMod(color[e]);},// 下一个形状async getNext() {// 随机获取形状this.next.b = Math.floor(Math.random() * this.block.length);this.next.c = Math.floor(Math.random() * 4);},// 渲染当前形状init() {// 获取到下一个形状数据this.now = JSON.parse(JSON.stringify(this.next));this.xz = this.now.c;// 当前形状数据this.nowBlock = JSON.parse(JSON.stringify(this.block[this.now.b]));//   渲染形状数据//   let c = this.nowBlock.site;//   for (let i = 0; i < c.length; i += 2) {//     this.frame[c[i]][c[i + 1]].bg = this.nowBlock.color;//   }this.renderBlock(this.nowBlock, this.frame, 1);// 旋转if (this.now.c > 0) {for (let i = 0; i < this.now.c; i++) {this.change(this.nowBlock, this.frame, this.now, i);}}this.getNext().then(() => {if (this.nextBlock.site) {this.renderBlock(this.nextBlock, this.ass, 0);}//   下一个形状this.nextBlock = JSON.parse(JSON.stringify(this.block[this.next.b]));this.renderBlock(this.nextBlock, this.ass, 1);// 旋转if (this.next.c > 0) {for (let i = 0; i < this.next.c; i++) {this.change(this.nextBlock, this.ass, this.next, i);}}});},// 渲染形状// b:方块,d:位置,n:0擦除,1生成,2确定落到最下层renderBlock(b, d, n) {let c = b.site;if (n == 0) {for (let i = 0; i < c.length; i += 2) {d[c[i]][c[i + 1]].bg = this.bg; //0擦除}} else if (n == 1) {for (let i = 0; i < c.length; i += 2) {d[c[i]][c[i + 1]].bg = b.color; //1生成}} else if (n == 2) {for (let i = 0; i < c.length; i += 2) {d[c[i]][c[i + 1]].data = 1; //2确定落到最下层}}},// 旋转 b:当前方块   d:渲染的位置   z:渲染的对象现在还是下一个  xz:当前旋转角度change(b, d, z, xz) {this.renderBlock(b, d, 0); //先清空再旋转// 记录第一块位置const x = b.site[0];const y = b.site[1];for (let i = 0; i < b.site.length; i += 2) {let a = b.site[i];b.site[i] = b.site[i + 1] - y + x + transition[z.b][xz].x;b.site[i + 1] = -(a - x) + y + transition[z.b][xz].y;}xz++;if (xz == 4) {xz = 0;}this.renderBlock(b, d, 1);},// 向下移动moveDown() {if (this.canMove(3)) {// 先清空this.renderBlock(this.nowBlock, this.frame, 0);for (let i = 0; i < this.nowBlock.site.length; i += 2) {// 向下移动一位this.nowBlock.site[i]++;}this.renderBlock(this.nowBlock, this.frame, 1); //再渲染数据} else {// 已经不能下落了this.renderBlock(this.nowBlock, this.frame, 2);// 判断是否可以消除this.removeBlock();}},// 向左移动moveLeft() {if (this.canMove(2)) {// 先清空this.renderBlock(this.nowBlock, this.frame, 0);for (let i = 0; i < this.nowBlock.site.length; i += 2) {// 向左移动一位this.nowBlock.site[i + 1]--;}this.renderBlock(this.nowBlock, this.frame, 1); //再渲染数据}},// 向右移动moveRight() {if (this.canMove(1)) {this.renderBlock(this.nowBlock, this.frame, 0);for (let i = 0; i < this.nowBlock.site.length; i += 2) {this.nowBlock.site[i + 1]++;}this.renderBlock(this.nowBlock, this.frame, 1); //再渲染数据}},// 是否可移动判断// 预判能否移动或变化   e:1右移  2左移  3下移  4变化canMove(e) {let c = this.nowBlock.site;let d = 0;switch (e) {case 1:for (let i = 0; i < c.length; i += 2) {if (c[i + 1] >= this.col - 1) {return false;}// 判断下一个位置是否被占用d += this.frame[c[i]][c[i + 1] + 1].data;}if (d > 0) {return false;}break;case 2:for (let i = 0; i < c.length; i += 2) {if (c[i + 1] <= 0) {return false;}// 判断下一个位置是否被占用d += this.frame[c[i]][c[i + 1] - 1].data;}if (d > 0) {return false;}break;case 3:for (let i = 0; i < c.length; i += 2) {if (c[i] >= this.row - 1) {return false;}// 判断下一个位置是否被占用(防穿透)d += this.frame[c[i] + 1][c[i + 1]].data;}if (d > 0) {return false;}break;// case 4://   for (let i = 0; i < c.length; i += 2) {//     if (c[i + 1] >= this.col - 1) {//       return false;//     }//   }//   break;}return true;},// 下落时旋转// 旋转 b:当前方块  xz:当前旋转角度change1() {const b = JSON.parse(JSON.stringify(this.nowBlock));// 记录第一块位置const x = b.site[0];const y = b.site[1];let n = true;for (let i = 0; i < b.site.length; i += 2) {let a = b.site[i];b.site[i] = b.site[i + 1] - y + x + transition[this.now.b][this.xz].x;b.site[i + 1] = -(a - x) + y + transition[this.now.b][this.xz].y;// 判断旋转后该点是否合理if (b.site[i + 1] < 0 ||b.site[i + 1] >= this.col ||b.site[i] >= this.row ||this.frame[b.site[i]][b.site[i + 1]].data > 0) {n = false;}}// 符合条件if (n) {this.renderBlock(this.nowBlock, this.frame, 0); //先清空this.xz++;if (this.xz == 4) {this.xz = 0;}this.nowBlock = b;this.renderBlock(this.nowBlock, this.frame, 1); //再旋转}},// 到底部:确定位置不能再动,保证上面其他方块下落时不会将它穿透// 自动下落autoMoveDown() {this.timer = setInterval(() => {this.moveDown();}, this.speed);},// 开始与暂停stopGame() {this.stop = !this.stop;if (this.stop) {clearInterval(this.timer);} else {this.autoMoveDown();}},// 判断是否可以消除removeBlock() {// 遍历整个界面for (let i = 0; i < this.row; i++) {let a = 0;for (let j = 0; j < this.col; j++) {if (this.frame[i][j].data == 1) {a++;}}if (a == this.col) {// 说明该行已经占满可以消除this.removeRow.push(i);}}// 获取是否可以消除行if (this.removeRow.length > 0) {let l = this.removeRow;for (let i = 0; i < l.length; i++) {let j = 0;let timer = setInterval(() => {this.frame[l[i]][j] = { bg: this.bg, data: 0 };j++;if (j == this.col) {clearInterval(timer);}}, 20);}setTimeout(() => {// 上面方块下移,从下往上判断for (let i = this.row - 1; i >= 0; i--) {let a = 0;for (let j = 0; j < l.length; j++) {if (l[j] > i) {a++;}}if (a > 0) {for (let k = 0; k < this.col; k++) {if (this.frame[i][k].data == 1) {// 先向下移动this.frame[i + a][k] = this.frame[i][k];// 再清除当前this.frame[i][k] = { bg: this.bg, data: 0 };}}}}this.removeRow = []; //清除行记录// 生成下一个this.init();}, 20 * this.col);// 数据处理// 消除次数+1this.times++;// 等级向下取整+1let lev =  Math.floor(this.times / 10) + 1;if (lev > this.level) {this.level = lev;// 速度if (this.level < 21) {// 20级以内做减法this.speed = 800 - (this.level - 1) * 40;} else {this.speed = 30;}// 清除当前下落clearInterval(this.timer);// 加速this.autoMoveDown();}this.level = this.times;// 分数消除一行100,两行300,三行600,四行1000this.score += ((l.length * (l.length + 1)) / 2) * 100 * this.level;} else {this.init();}},},
};
</script><style lang='less' scoped>
.game {.game-div {display: flex;.game-min {.row {display: flex;padding-top: 2px;.element {width: 20px;height: 20px;padding: 0;margin: 0 2px 0 0;}}}.right-div {padding-left: 20px;.ass {.row {display: flex;padding-top: 2px;.element {width: 20px;height: 20px;padding: 0;margin: 0 2px 0 0;}}}.score-div {div {height: 20px;line-height: 20px;}.ztks {width: 100px;height: 40px;margin-bottom: 10px;background-color: palevioletred;text-align: center;line-height: 40px;}}}}.control {width: 220px;p {width: 220px;height: 40px;text-align: center;line-height: 40px;background-color: #B940EF;margin-bottom: 20px;}.control-center {align-items: center;display: flex;justify-content: space-between;margin-bottom: 20px;div {width: 90px;height: 40px;text-align: center;line-height: 40px;background-color: #B940EF;}}}
}
</style>

 工具函数:

//渐变色
export const color = [['linear-gradient(180deg, #FFA7EB 0%, #F026A8 100%)','linear-gradient(180deg, #DFA1FF 0%, #9A36F0 100%)','linear-gradient(180deg, #9EAAFF 0%, #3846F4 100%)','linear-gradient(180deg, #7BE7FF 2%, #1E85E2 100%)','linear-gradient(180deg, #89FED8 0%, #18C997 100%)','linear-gradient(180deg, #FFED48 0%, #FD9E16 100%)','linear-gradient(180deg, #FFBA8D 1%, #EB6423 100%)',],['#2B7AF5','#2B9DF5','#79CFFF','#1B67DD','#4F94FF','#2180F2','#3FD0FF',],
];//7种方块元素
export const blockMod = (color) => {let a = {site: [0, 1, 0, 2, 1, 2, 2, 2],color: color[0],};let b = {site: [0, 1, 1, 1, 1, 2, 2, 2],color: color[1],};let c = {site: [1, 1, 1, 2, 2, 1, 2, 2],color: color[2],};let d = {site: [1, 0, 1, 1, 1, 2, 1, 3],color: color[3],};let e = {site: [0, 2, 1, 1, 1, 2, 2, 1],color: color[4],};let f = {site: [0, 1, 0, 2, 1, 1, 2, 1],color: color[5],};let g = {site: [1, 1, 2, 0, 2, 1, 2, 2],color: color[6],};return ([a, b, c, d, e, f, g]);
};//旋转规则
export const transition = [[{x: 1, y: 1,}, {x: 1, y: 0,}, {x: 0, y: -2,}, {x: -2, y: 1,}],[{x: 1, y: 1,}, {x: 1, y: 0,}, {x: 0, y: -2,}, {x: -2, y: 1,}],[{x: 0, y: 1,}, {x: 1, y: 0,}, {x: 0, y: -1,}, {x: -1, y: 0,}],[{x: -1, y: 2,}, {x: 1, y: 1,}, {x: 2, y: -1,}, {x: -2, y: -2,}],[{x: 2, y: 0,}, {x: 0, y: -1,}, {x: -1, y: -1,}, {x: -1, y: 2,}],[{x: 1, y: 1,}, {x: 1, y: 0,}, {x: 0, y: -2,}, {x: -2, y: 1,}],[{x: 0, y: 0,}, {x: 1, y: 0,}, {x: -1, y: 0,}, {x: 0, y: 0,}],
]

三、原理分析

3.1、界面搭建

  主界面的20X10,类似贪吃蛇,副界面的随机方块,则是4x4,都是双重for循环。初始化的时候调用gameFrame()即可。

3.2、方块创建

  主要说明一下随机生成的方块,每个都是有4个小方格组成,组成了7种基本样式,在自身基础上进行四个方向的旋转,就是下落的所有可能性。参考坐标如右图所示:

3.3、方块旋转

  旋转 b:当前方块   d:渲染的位置   z:渲染的对象现在还是下一个  xz:当前旋转角度,在change(b, d, z, xz)里进行旋转,这里用到了工具函数里的transition,旋转核心就是找到一个固定的点,看出x和Y坐标的变化,即(x=y,y=-x),加上工具函数里的blockMod,就可以依次生成对应的下落方块。

3.4、方块移动

  在生成一个的同时,要考虑的下一个方块的生成与随机旋转,所以 b:方块,d:位置,n:0擦除,1生成,2确定落到最下层,在renderBlock(b, d, n)方法里进行形状的渲染。

  我们有三个方向:moveDown()、moveLeft()、moveRight(),这里的原理和贪吃蛇基本类似,不过在向下移动时考虑因素较多,下落时旋转要考虑 b:当前方块  xz:当前旋转角度change1()方法,为了保持原来的形状,所以多处用到了深拷贝。

3.5、移动判断

  预判能否移动或变化   e:1右移  2左移  3下移  4变化,在 canMove(e)方法里实现。主要是判断下一个位置是否被占用,到了底部:确定位置不能再动,保证上面其他方块下落时不会将它穿透,是要考虑的问题。

3.6、下落判断与清除

  判断是否可以消除:遍历整个界面,当一行被占满,将该行的方块依次删去,并保留上面附着的方块位置不变,形成“被吞噬”的现象。在消掉满了的一行时,要让上面方块下移,从下往上判断,再清除当前方块,避免冲突。

3.7、得分计算


  分数消除一行100,两行300,三行600,四行1000,消掉一行,带分数增加,以此同时,随着等级的增加,速度也会越来越快,难度增加。


http://www.ppmy.cn/news/350501.html

相关文章

ipv6端口阻塞(例如比特彗星黄灯)

如果是tplink路由器&#xff0c;可能会遇到路由器自带的ipv6防火墙&#xff0c;不关闭的话永远是黄灯&#xff0c;win10系统关闭防火墙、upnp、虚拟服务器、DMZ都是没用的。5430已经在最新固件中新增手动关闭防火墙功能。5480等暂时没有&#xff0c;官方表示后续固件会更新此功…

win10,使用bitcomet外网阻塞(黄灯)的解决方法

2019独角兽企业重金招聘Python工程师标准>>> 1.路由开启UPNP功能&#xff0c;各自路由情况不一。 2.开启网络发现。 3.打开相应的端口。 转载于:https://my.oschina.net/u/261479/blog/738041

bitcomet端口阻塞解决方案

看了很多方法尝试了都不行。因为他们的环境跟我的都不一样。我比大部分网上的教程多了一个自购路由器且PC是直接用网线连接自购路由器。 因此&#xff0c;我的方法是在两个地方添加端口映射。一个是天翼网关&#xff0c;一个是附赠的路由器。 前期准备是请打电话去电信客服获取…

Windows Upnp 服务补丁——UpnpFix V1.0

先来介绍下Upnp服务是干啥的&#xff08;迅雷官网摘录&#xff09;&#xff1a; UPNP的英文全称是Universal Plug and Play&#xff0c;即通用即插即用协议,是为了实现电脑与智能的电器设备对等网络连接的体系结构。而内网地址与网络地址的转换就是基于此协议的&#xff0c;因此…

【工具】BT - 比特彗星(端口监听(UPnP、ipv6、防火墙)、反吸血、tracker、杀毒)

【比特彗星】官方文档&#xff1a; archive - http://www.bitcomet.com/en/archive全局设置 - http://wiki-zh.bitcomet.com/bitcomet全局选项 相关 【官方】BitComet(比特彗星)软件使用帮助 【官方】BT中常见术语解释 BT 比特彗星 优化教程 BT 比特彗星 设置指南、选项释义…

UPNP 相关资料(转)

UPNP的全称是 Universal plug-and-play( 通用即插即用).UPnP 是针对智能家电、无线设备以及各种外观尺寸的个人电脑的普遍对等&#xff08;peer-to-peer&#xff09;网络连接而设计的一种架构。它旨在为家庭、小型企业、公共场所中或连接到互联网的ad-hoc 网或未管理网络提供易…

(转)UPNP协议细节

(From: http://www.cnblogs.com/semo/archive/2008/07/21/1247950.html)UPNP的全称是 Universal plug-and-play( 通用即插即用).UPnP 是针对智能家电、无线设备以及各种外观尺寸的个人电脑的普遍对等&#xff08;peer-to-peer&#xff09;网络连接而设计的一种架构。它旨在为家…

实现upnp ssdp来查找局域网内的其他节点

upnp协议常用于一些智能家居产品中&#xff0c;这些产品连上家里局域网后&#xff0c;用同样连入家中局域网的手机就能很快检测到此产品了。在区块链技术中&#xff0c;upnp也被应用于寻找同一局域网内的其他节点。 关于upnp的具体描述&#xff0c;这篇文章有很好的介绍&#x…