编写俄罗斯方块 思路。
1、创建容器数组,方块,
2、下落,左右移动,旋转,判断结束,消除。
定义一个20行10列的数组表示游戏区。初始这个数组里用0填充,1表示有一个方块,2表示该方块固定了,
然后随机出一个方块,操作左右转,触底变2后,再随机下一个方块,循环直到判定结束。
<template><div><div class="gamebox"><div class="table"><ul><li v-for="item in elsStore.gameArray">{{ item }}</li></ul></div><div class="next"><ul><li v-for="item in elsStore.nextArray">{{ item }}</li></ul><p>消除:{{ elsStore.score }}</p></div></div><div class="toolbar"><div><el-button type="success" @click="gameStart">开始</el-button><el-button type="success" @click="gameReset">重置</el-button></div></div></div>
</template><script setup lang="ts">
import useElsStore from '@/stores/els';
const elsStore = useElsStore();
elsStore.resetTable
// // I:一次最多消除四层
// // L(左右):最多消除三层,或消除二层
// // O:消除一至二层
// // S(左右):最多二层,容易造成孔洞
// // Z(左右):最多二层,容易造成孔洞
// // T:最多二层
let intervalDown: NodeJS.Timer;
const gameStart = () => {// 开始游戏 当前游戏中,需要先重置游戏,// 放置next,并开始游戏逻辑elsStore.randNext;intervalDown = setInterval(startDown, 1000);}
const gameReset = () => {clearInterval(intervalDown);elsStore.resetTable}const startDown = () => {console.log("down....");}</script><style scoped lang="scss">
.gamebox {display: flex;justify-content: flex-start;.next {margin-left: 20px;p {margin-top: 20px;}}
}.toolbar {display: flex;width: 100vw;justify-content: center;margin-top: 10px;}
</style>
//定义关于counter的store
import { defineStore } from 'pinia'
import { enumStoreName } from "../index";
import { ElsTable } from '@/api/room/type';//defineStore 是返回一个函数 函数命名最好有use前缀,根据函数来进行下一步操作
const useElsStore = defineStore(enumStoreName.elsStore, {state: (): ElsTable => {return {// 主要区域gameArray: new Array<Array<number>>(),// 下一个图形nextArray: new Array<Array<number>>(),// 定义俄罗斯方块游戏区域大小rows: 20, columns: 10,// 对应值 0 空,1有活动方块 , 2有固定方块value: 0,// 游戏分数score: 0}},actions: {// 初始化界面resetTable() {this.gameArray = new Array<Array<number>>();this.nextArray = new Array<Array<number>>();// reset mainfor (let i = 0; i < this.rows; i++) {this.gameArray.push(new Array<number>());}for (let i = 0; i < this.gameArray.length; i++) {for (let j = 0; j < this.columns; j++) {this.gameArray[i].push(this.value);}}// reset nextfor (let i = 0; i < 4; i++) {this.nextArray.push(new Array<number>());}for (let i = 0; i < this.nextArray.length; i++) {for (let j = 0; j < 4; j++) {this.nextArray[i].push(this.value);}}},randNext(){}},getters: {getAddress(): string {return ""},},persist: {key: enumStoreName.elsStore,storage: localStorage,},
})export default useElsStore
第二阶段:改版后的情况
1、编写ui部分
<div><div class="gamebox"><div class="table"><ul><li v-for="item in elsStore.els.getShowPlate() "><span v-for="x in item.split(',')"><div class="box" v-if="x != '0'":style="'background-color:' + elsStore.els.next_plate.color + ';'"></div></span></li></ul></div><div class="next"><div class="top"><ul><li v-for="item in elsStore.els.next_plate.currentString().split('@')"><span v-for="x in item.split(',')"><div class="box" v-if="x != '0'":style="'background-color:' + elsStore.els.next_plate.color + ';'"></div></span></li></ul><p>消除: {{ elsStore.els.score }}</p></div><div class="bottom"><div class="btn"><el-button type="success" @click="gameStart">开始</el-button><el-button type="success" @click="gameReset">重置</el-button></div></div></div></div><div class="toolbar"><div class="btn"><el-button type="success" @click="leftClick" :icon="ArrowLeftBold">左移</el-button><el-button type="success" @click="rightClick" :icon="ArrowRightBold">右移</el-button><el-button type="success" @click="rotateClick" :icon="Refresh">旋转</el-button><el-button type="success" @click="downClick" :icon="Refresh">下落</el-button></div> </div> </div>
<style scoped lang="scss">
.gamebox {display: flex;justify-content: flex-start;.table {ul {width: 60vw;border: solid 1px;margin: 20px;li {display: flex;width: 60vw;span {width: 6vw;height: 6vw;.box {width: 100%;height: 100%;border: 1px solid #000;}}}}}.next {display: flex;flex-direction: column;justify-content: space-between;align-items: center;margin-top: 40px;.top {ul {width: 24vw;li {display: flex;width: 24vw;span {width: 6vw;height: 6vw;border: solid 1px;.box {width: 100%;height: 100%;}}}}}p {margin-top: 20px;}.bottom {margin-bottom: 148px;.btn {display: flex;flex-direction: column;align-items: flex-end;justify-content: space-around;button {margin-bottom: 5px;}}}}
}.toolbar {display: flex;width: 100vw;justify-content: center;margin-top: 10px;flex-direction: column;.btn {display: flex;justify-content: center;}}.el-button {height: 70px;
}
</style>
主要逻辑部分
import { ArrowLeftBold, Refresh, ArrowRightBold } from '@element-plus/icons-vue'
import { GameControl } from "./class/GameControl";
import { reactive } from "vue";const elsStore = reactive({els: new GameControl()
}) const rotateClick = () => {console.log("向右旋转");elsStore.els.rotate()
}
const leftClick = () => {elsStore.els.current_plate.position.x =(elsStore.els.current_plate.position.x > 0) ? elsStore.els.current_plate.position.x - 1 : 0}
const rightClick = () => {elsStore.els.current_plate.position.x =(elsStore.els.current_plate.position.x + elsStore.els.current_plate.getPlateSize().width < 10)? elsStore.els.current_plate.position.x + 1 : elsStore.els.current_plate.position.x}
const downClick = () => {elsStore.els.current_plate.position.y += 1
}const timers = () => {console.log("游戏循环开始");// 检查当前盘是否有重叠的,因为最后一步是让动块下降一格。// 因此,如果最后一步没有重叠,那么说明盘已经到底了,游戏结束// console.log('currentBox' + elsStore.els.current_plate.name, elsStore.els.current_plate.position.y);if (!elsStore.els.checkShowPlateIsOK()) {console.log("游戏结束");elsStore.els.started = false;return false}// 在Main盘面合法的情况下,需要检查即将触底或碰撞,就触发Lock更新if (elsStore.els.willPong()) {// console.log("====================Lock================");elsStore.els.lock_plate = elsStore.els.getShowPlate().join("@")// 消除elsStore.els.checkAndClear();// 负责下一块给当前动块,并随机一个下一块。elsStore.els.newCurrentPlate();} else {elsStore.els.current_plate.position.y += 1;}setTimeout(() => {if (elsStore.els.started) { timers(); }}, 500);};const gameStart = () => {console.log('游戏开始');if (elsStore.els.started) {return false;}elsStore.els.next_plate = elsStore.els.newRndPlate()elsStore.els.started = truetimers();}const gameReset = () => {console.log('重置游戏');elsStore.els = new GameControl()elsStore.els.started = false}
可以看到主要是循环部分。然后就是调gameControl部分
import { Config } from "../config";
import { Plate } from "./Plate";export class GameControl {next_plate: Plate;current_plate: Plate;lock_plate: string;started: boolean;score: number;constructor() {this.next_plate = this.newRndPlate()this.current_plate = this.copyNextToCurrent();this.lock_plate = Config.defuaultLockPlatethis.started = falsethis.score = 0this.init()}init() {// 初始化游戏 console.log("初始化游戏");// 显示一个等待方块,并等待用户按下开始按钮。 }// 生成一个随机盘子newRndPlate() {return new Plate(Plate.PlateType[Math.floor(Math.random() * 6)]);}// 复制下一个盘子到当前盘子private copyNextToCurrent(): Plate {let plate = new Plate(this.next_plate.name);plate.position.x = 3plate.position.y = 0return plate}// 合并盘子 ,用给定的Plate和Lock进行合并,不用检查是否重叠private margePlate(plate: Plate) {let tmp_plate = plate.currentStringMax().split("@");let lockList = this.lock_plate.split("@");let newLockList: string[] = []// console.log({ tmp_plate, lockList, newLockList });// 跟lock合并for (let i = 0; i < lockList.length; i++) {let lockListi = lockList[i].split(",");let tmp_platei = tmp_plate[i].split(",");let newLockLine: string[] = []for (let j = 0; j < lockListi.length; j++) {newLockLine.push("" + eval(lockListi[j] + '+' + tmp_platei[j]))}newLockList.push(newLockLine.join(","))}// console.log({ newLockList });return newLockList;}// 检查给定数组是否有重叠private checkMainOK(main: string[]): boolean {for (let i = 0; i < main.length; i++) {const boxList = main[i].split(",")for (let j = 0; j < boxList.length; j++) {if (eval(boxList[j]) > 1) {return false;}}}return true;}willPong(): boolean {let tmp_plate = new Plate(this.current_plate.name);tmp_plate.position.x = this.current_plate.position.xtmp_plate.position.y = this.current_plate.position.y + 1tmp_plate.direction = this.current_plate.directionlet height = tmp_plate.getPlateSize().height;if (tmp_plate.position.y + height > 20) {return true}let newLockList = this.margePlate(tmp_plate);return !this.checkMainOK(newLockList);}getShowPlate(): string[] {if (!this.started) {return this.lock_plate.split("@")}// console.log("====================");// console.log({ current_plate:this.current_plate,lock_plate:this.lock_plate});let newLockList = this.margePlate(this.current_plate);// console.log({ newLockList});// // 跟lock合并// for (let i = 0; i < lockList.length; i++) {// let lockListi = lockList[i].split(",");// let tmp_platei = tmp_plate[i].split(",");// let newLockLine: string[] = []// for (let j = 0; j < lockListi.length; j++) {// newLockLine.push("" + eval(lockListi[j] + '+' + tmp_platei[j]))// }// newLockList.push(newLockLine.join(","))// }// for (let i = 0; i < lockList.length; i++) {// if (i < tmp_plate.length) {// let lockListi = lockList[i].split(",");// let tmp_platei = tmp_plate[i].split(",");// let newLockLine: string[] = []// for (let j = 0; j < lockListi.length; j++) {// newLockLine.push("" + eval(lockListi[j] + '+' + tmp_platei[j]))// }// newLockList.push(newLockLine.join(","))// } else {// let lockListi = lockList[i].split(",");// let newLockLine: string[] = []// for (let j = 0; j < lockListi.length; j++) {// newLockLine.push(lockListi[j])// }// newLockList.push(newLockLine.join(","))// }// }return newLockList;}// 检查getShowPlate是否有大于1的块checkShowPlateIsOK() {return this.checkMainOK(this.getShowPlate());}// newCurrentPlate 函数newCurrentPlate() {this.current_plate = this.copyNextToCurrent();this.next_plate = this.newRndPlate()}// 旋转后的dirrotate() {// 如果超界或重叠就不让旋转 仅下部分超界就不让。this.current_plate.direction = (this.current_plate.direction + 1) % 4if (this.current_plate.position.y + this.current_plate.getPlateSize().height > 20 || (!this.checkShowPlateIsOK())) {this.current_plate.direction = (this.current_plate.direction - 1) % 4}}// 消除checkAndClear() {// 更新locklet lockList = this.lock_plate.split("@");let tmpList:string[] = []lockList.forEach((item ) => {if(item!="1,1,1,1,1,1,1,1,1,1"){tmpList.push(item)}});for (let index = 0; index < 20-tmpList.length; index++) {this.score ++tmpList = ['0,0,0,0,0,0,0,0,0,0'].concat(tmpList)}this.lock_plate = tmpList.join("@");}}
最后就是2个小类
export class Box {color: string;icon: string;disabled: boolean;constructor(color: string ) { this.color = color; this.icon = "Grid"; this.disabled = true; } }
const defuaultLockPlate: string = "0,0,0,0,0,0,0,0,0,0@0,0,0,0,0,0,0,0,0,0@0,0,0,0,0,0,0,0,0,0@0,0,0,0,0,0,0,0,0,0@0,0,0,0,0,0,0,0,0,0@0,0,0,0,0,0,0,0,0,0@0,0,0,0,0,0,0,0,0,0@0,0,0,0,0,0,0,0,0,0@0,0,0,0,0,0,0,0,0,0@0,0,0,0,0,0,0,0,0,0@0,0,0,0,0,0,0,0,0,0@0,0,0,0,0,0,0,0,0,0@0,0,0,0,0,0,0,0,0,0@0,0,0,0,0,0,0,0,0,0@0,0,0,0,0,0,0,0,0,0@0,0,0,0,0,0,0,0,0,0@0,0,0,0,0,0,0,0,0,0@0,0,0,0,0,0,0,0,0,0@0,0,0,0,0,0,0,0,0,0@0,0,0,0,0,0,0,0,0,0"; export const Config = {defuaultLockPlate}
import { Box } from "./Box";interface Pos {x: number;y: number;
}
export class Plate extends Box {// I:一次最多消除四层 (状态横竖2种)// L(左):L最多消除三层,或消除二层 (状态横竖4种)// R(右):反L最多消除三层,或消除二层 (状态横竖4种)// O:消除一至二层 (状态1种)// S(左右):最多二层,容易造成孔洞 (状态横竖2种)// Z(左右):最多二层,容易造成孔洞 (状态横竖2种)// T:最多二层 (状态横竖4种)name: string;// 字符串数组arrString: string[];// currentString: string;// 位置position: Pos;// 方向direction: number;// 是否锁住lock: boolean;static PlateType = ["I", "L", "O", "S", "Z", "T"]constructor(name: string) {let colors = ["red", "yellow", "blue", "green", "purple", "orange"];switch (name) {case "I":super(colors[0]);this.name = name;this.arrString = ["0,0,0,0@1,1,1,1@0,0,0,0@0,0,0,0","0,1,0,0@0,1,0,0@0,1,0,0@0,1,0,0","0,0,0,0@1,1,1,1@0,0,0,0@0,0,0,0","0,1,0,0@0,1,0,0@0,1,0,0@0,1,0,0"]break;case "L":super(colors[1]);this.name = name;this.arrString = ["0,1,1,1@0,1,0,0@0,0,0,0@0,0,0,0","0,0,1,0@1,1,1,0@0,0,0,0@0,0,0,0","0,1,0,0@0,1,0,0@0,1,1,0@0,0,0,0","0,1,1,0@0,0,1,0@0,0,1,0@0,0,0,0"]break;case "O":super(colors[2]);this.name = name;this.arrString = ["0,1,1,0@0,1,1,0@0,0,0,0@0,0,0,0","0,1,1,0@0,1,1,0@0,0,0,0@0,0,0,0","0,1,1,0@0,1,1,0@0,0,0,0@0,0,0,0","0,1,1,0@0,1,1,0@0,0,0,0@0,0,0,0",]break;case "S":super(colors[3]);this.name = name;this.arrString = ["0,0,1,1@0,1,1,0@0,0,0,0@0,0,0,0","0,1,0,0@0,1,1,0@0,0,1,0@0,0,0,0","0,0,1,1@0,1,1,0@0,0,0,0@0,0,0,0","0,1,0,0@0,1,1,0@0,0,1,0@0,0,0,0"]break;case "Z":super(colors[4]);this.name = name;this.arrString = ["0,1,1,0@0,0,1,1@0,0,0,0@0,0,0,0","0,0,1,0@0,1,1,0@0,1,0,0@0,0,0,0","0,1,1,0@0,0,1,1@0,0,0,0@0,0,0,0","0,0,1,0@0,1,1,0@0,1,0,0@0,0,0,0"]break;default: //Tsuper(colors[5]);this.name = name;this.arrString = ["0,0,1,0@0,1,1,0@0,0,1,0@0,0,0,0","0,0,1,0@0,1,1,1@0,0,0,0@0,0,0,0","0,1,0,0@0,1,1,0@0,1,0,0@0,0,0,0","0,1,1,1@0,0,1,0@0,0,0,0@0,0,0,0"]break;}this.position = {x: -1,y: -1}this.direction = Math.floor(Math.random() * 4)this.lock = falseconsole.log('创建了一个' + this.name + ' 颜色:' + this.color + ' 方向:' + this.direction);}// 4*4大小public currentString(): string {return this.arrString[this.direction]}// 精简块的内容 最小 化块public currentStringMin(): string {let plateStr = this.arrString[this.direction]let plates: string[] = [];// 去掉多余的 行plateStr.split("@").forEach((item) => {if (eval(item.replace(/,/g, "+")) > 0) {plates.push(item);}});// 去掉多余的 列 就是裁剪前面的0和后面的0// 计算是块的valueCount 如果少了,就不能裁剪。const countPlateValue = (plates: string[]) => {let tmpPlateList = plates.map((item) => {const sum = item.split(",").reduce(function (prev, cur) {return eval(prev + "+" + cur);});return sum})return tmpPlateList.reduce(function (prev, cur) {return eval(prev + "+" + cur);});}// console.log("test value", countPlateValue(plates));// 裁剪前面的0 const cuxsuff = (plates: string[]): string[] => {if (plates[0].split(",").length == 1) return plates// 尝试裁剪 ,如果长度为1,就不用裁剪了let tmpPlateList: string[] = plates.map((item) => {let t = item.split(",")t.shift()return t.join(",")})if (countPlateValue(tmpPlateList) == countPlateValue(plates)) {return cuxsuff(tmpPlateList)} else {return plates}}// 裁剪后面的0const cuxdiff = (plates: string[]): string[] => {if (plates[0].split(",").length == 1) return plates// 尝试裁剪 ,如果长度为1,就不用裁剪了let tmpPlateList: string[] = plates.map((item) => {let t = item.split(",")t.pop()return t.join(",")})if (countPlateValue(tmpPlateList) == countPlateValue(plates)) {return cuxdiff(tmpPlateList)} else {return plates}}const remainingPlates = cuxdiff(cuxsuff(plates)).join("@");return remainingPlates;}// 格式化成 Mian大小 的块public currentStringMax(): string {let currentString = this.currentStringMin() let maxY = 20 - this.getPlateSize().height;let maxX = 10 - this.getPlateSize().width;this.position.x = this.position.x >= maxX ? maxX : this.position.x;this.position.y = this.position.y >= maxY ? maxY : this.position.y;let x = this.position.xlet y = this.position.ylet tmpPlateList = currentString.split("@").map((item) => {let prefix: string[] = [];let suffix: string[] = [];for (let i = 0; i < x; i++) {prefix.push("0")}for (let i = 0; i < 10 - item.split(",").length - x; i++) {suffix.push("0")}return prefix.concat(item.split(",").concat(suffix)).join(",");});for (let index = 0; index < y; index++) {tmpPlateList = ['0,0,0,0,0,0,0,0,0,0'].concat(tmpPlateList)}for (let index = 0; index < 20 - y - currentString.split("@").length; index++) {tmpPlateList = tmpPlateList.concat(['0,0,0,0,0,0,0,0,0,0'])}return tmpPlateList.join("@")}// 获取长和高public getPlateSize(): { width: number; height: number; } {return {width: this.currentStringMin().split("@")[0].split(",").length,height: this.currentStringMin().split("@").length}}}
最后是完整的源码下 http s://gitcode.net/ldy889/game-els 项目删掉了一些没用的东西,只保留了核心代码,需要自己去除一些错误。比如修改路径,无效的引入。