编写一个俄罗斯方块

编写俄罗斯方块 思路。

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>

 


<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 = true
    timers();



}

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.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个小类

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: //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  项目删掉了一些没用的东西,只保留了核心代码,需要自己去除一些错误。比如修改路径,无效的引入。