编写俄罗斯方块 思路。
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 main
for (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 next
for (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>
css
<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>
主要逻辑部分
TypeScript
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 = true
timers();
}
const gameReset = () => {
console.log('重置游戏');
elsStore.els = new GameControl()
elsStore.els.started = false
}
可以看到主要是循环部分。然后就是调gameControl部分
TypeScript
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.defuaultLockPlate
this.started = false
this.score = 0
this.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 = 3
plate.position.y = 0
return 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.x
tmp_plate.position.y = this.current_plate.position.y + 1
tmp_plate.direction = this.current_plate.direction
let 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()
}
// 旋转后的dir
rotate() {
// 如果超界或重叠就不让旋转 仅下部分超界就不让。
this.current_plate.direction = (this.current_plate.direction + 1) % 4
if (this.current_plate.position.y + this.current_plate.getPlateSize().height > 20 || (!this.checkShowPlateIsOK())) {
this.current_plate.direction = (this.current_plate.direction - 1) % 4
}
}
// 消除
checkAndClear() {
// 更新lock
let 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个小类
TypeScript
export class Box {
color: string;
icon: string;
disabled: boolean;
constructor(color: string ) { this.color = color; this.icon = "Grid"; this.disabled = true; }
}
TypeScript
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
}
TypeScript
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: //T
super(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 = false
console.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
}
}
// 裁剪后面的0
const 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.x
let y = this.position.y
let 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 项目删掉了一些没用的东西,只保留了核心代码,需要自己去除一些错误。比如修改路径,无效的引入。