开发原则

  1. 基于以下两个原则,系统中使用如下模式:数据-界面分离模式;

    1. 单一职能原则:每个类只做跟它相关的一件事;
    2. 开闭原则:系统中的类,应该对扩展开放,对修改关闭;
  2. 传统面向对象语言,书写类的属性时,往往会进行如下操作:

    1. 所有的属性全部私有化;
    2. 使用公开的方法提供对属性的访问;
  3. 面向对象开发已经非常成熟,特别善于解决复杂问题;

    1. 游戏特别容易使用面向对象的思维;
    2. 开发:高内聚、低耦合;

工程搭建

  1. 环境:浏览器 + 模块化

  2. webpack:构建工具,根据入口文件找寻依赖,打包

    1. 安装 webpack、html-webpack-plugin、clean-webpack-plugin、webpack-dev-server
    2. 安装 ts 的相应 loaderts-loader,awesome-typescript-loader

项目目录

项目开发

开发小方块类

小方块类:它能处理自己的数据,知道什么时候需要显示,但不知道怎么显示

TypeScript
TypeScript
TypeScript
// types.ts
export interface Point {
    readonly x: number,
    readonly y: number
}

export interface IViewer {
    /**
     * 显示
    */
    show(): void;

    /**
     * 移除,不再显示
    */
    remove(): void;
}
// Square.ts
import { Point, IViewer } from "./types";

/**
 * 小方块
*/
export class Square {

    private _point: Point = { x: 0, y: 0 }
    private _color: string = ""

    //属性:显示者
    private _viewer?: IViewer

    public get viewer() {
        return this._viewer;
    }

    public set viewer(v) {
        this._viewer = v;
    }

    public get point() {
        return this._point;
    }
    public set point(val) {
        this._point = val;
        // 完成显示
        if (this._viewer) {
            this._viewer.show();
        }
    }

    public get color() {
        return this._color;
    }

    public set color(val) {
        this._color = val;
    }
}
// index.ts
import { Square } from "./core/Square";
import { IViewer } from "./core/types";

class SquareConsoleViewer implements IViewer {

    constructor(private square: Square) {
    }

    show(): void {
        console.log(this.square.point, this.square.color);
    }

    remove(): void {

    }
}

const sq = new Square()
sq.viewer = new SquareConsoleViewer(sq);
sq.point = { x: 5, y: 6 }
sq.point = { x: 8, y: 7 }

小方块的显示类

作用:用于显示一个小方块到页面上

TypeScript
TypeScript
TypeScript
// viewer/PageConfig.ts
export default {
    SquareSize: {
        width: 30,
        height: 30
    }
}
// viewer/SquarePageViewer.ts
import { Square } from "../Square";
import $ from "jquery"
import { IViewer } from "../types";
import PageConfig from "./PageConfig";

/**
 * 显示一个小方块到页面上
*/
export class SquarePageViewer implements IViewer {

    private dom?: JQuery<HTMLElement>
    private isRemove: boolean = false;//是否已经移除过

    show(): void {
        if (this.isRemove) {
            return;
        }
        if (!this.dom) {
            this.dom = $("<div>").css({
                position: "absolute",
                width: PageConfig.SquareSize.width,
                height: PageConfig.SquareSize.height,
                border: "1px solid #ccc",
                boxSizing: "border-box"
            }).appendTo(this.container);
        }

        // 移动
        this.dom.css({
            left: this.square.point.x * PageConfig.SquareSize.width,
            top: this.square.point.y * PageConfig.SquareSize.height,
            background: this.square.color
        })
    }

    remove(): void {
        if (this.dom && !this.isRemove) {
            this.dom.remove();
            this.isRemove = true;
        }
    }

    constructor(private square: Square, private container: JQuery<HTMLElement>) {
    }
}
// index.ts
import { Square } from "./core/Square";
import { SquarePageViewer } from "./core/viewer/SquarePageViewer";
import $ from "jquery"

const sq = new Square();
sq.viewer = new SquarePageViewer(sq, $("#root"));
sq.color = "red";
sq.point = {
    x: 3,
    y: 0
}

$("#btnDown").click(function () {
    sq.point = {
        x: sq.point.x,
        y: sq.point.y + 1
    }
})

$("#btnRemove").click(function () {
    if (sq.viewer) {
        sq.viewer.remove();
    }
})

$("#btnAdd").click(function(){
    sq.viewer = new SquarePageViewer(sq, $("#root"));
})

开发方块组合类

  1. 组合类中的属性:

    • 小方块的数组:该数组的组成不能发生变化,是只读数组;
    • 形状:一个方块的组合,取决于组合的形状(一组相对坐标的组合,该组合中有一个特殊坐标,表示形状的中心)
  2. 如果知道:形状、中心点坐标、颜色,就可以设置小方块的数组

TypeScript
TypeScript
TypeScript
// types.ts
/**
 * 形状
*/
export type Shape = Point[]
// SquareGroup.ts
import { Square } from "./Square";
import { Shape, Point } from "./types";

/**
 * 组合方块
*/
export class SquareGroup {
    private _squares: readonly Square[];

    public get squares() {
        return this._squares;
    }

    public get centerPoint(): Point {
        return this._centerPoint;
    }

    // 设置中心点,根据中心点的改变重新计算各个元素的坐标
    public set centerPoint(v: Point) {
        this._centerPoint = v;
        // 同时设置所有小方块对象的坐标
        this._shape.forEach((p, i) => {
            this._squares[i].point = {
                x: this._centerPoint.x + p.x,
                y: this._centerPoint.y + p.y
            }
        })
    }

    /**
     * @param _shape 形状
     * @param _centerPoint 中心点
     * @param _color 颜色
     */
    constructor(private _shape: Shape, private _centerPoint: Point, private _color: string) {
        //设置小方块的数组
        const arr: Square[] = [];
        this._shape.forEach(p => {
            const sq = new Square();
            sq.color = this._color;
            sq.point = {
                x: this._centerPoint.x + p.x, // 元素组合相对中心点的位置计算
                y: this._centerPoint.y + p.y
            }
            arr.push(sq);
        })
        this._squares = arr;
    }
}
// index.ts
import { SquarePageViewer } from "./core/viewer/SquarePageViewer";
import $ from "jquery"
import { SquareGroup } from "./core/SquareGroup";

const group = new SquareGroup([{ x: 0, y: -1 }, { x: -1, y: 0 }, { x: 0, y: 0 }, { x: 0, y: 1 }], { x: 3, y: 2 }, "red");
group.squares.forEach(sq => {
    sq.viewer = new SquarePageViewer(sq, $("#root"));
})

$("#btnDown").click(function () {
    //更改中心点坐标
    group.centerPoint = {
        x: group.centerPoint.x,
        y: group.centerPoint.y + 1
    }
})

$("#btnUp").click(function () {
    //更改中心点坐标
    group.centerPoint = {
        x: group.centerPoint.x,
        y: group.centerPoint.y - 1
    }
})

$("#btnLeft").click(function () {
    //更改中心点坐标
    group.centerPoint = {
        x: group.centerPoint.x - 1,
        y: group.centerPoint.y
    }
})

$("#btnRight").click(function () {
    //更改中心点坐标
    group.centerPoint = {
        x: group.centerPoint.x + 1,
        y: group.centerPoint.y
    }
})

开发俄罗斯方块生产者模块

TypeScript
TypeScript
TypeScript
// util.ts
/**
 * 根据最小值和最大值得到该范围内的随机数(无法取到最大值)
* @param min 
* @param max 
*/
export function getRandom(min: number, max: number) {
    const dec = max - min;
    return Math.floor(Math.random() * dec + min);
}
// Teris.ts
import { Shape, Point } from "./types";
import { getRandom } from "./util";
import { SquareGroup } from "./SquareGroup";

// "土"
export const TShape: Shape = [
    { x: -1, y: 0 }, { x: 0, y: 0 }, { x: 1, y: 0 }, { x: 0, y: -1 }
];

export const LShape: Shape = [
    { x: -2, y: 0 }, { x: -1, y: 0 }, { x: 0, y: 0 }, { x: 0, y: -1 }
];

export const LMirrorShape: Shape = [
    { x: 2, y: 0 }, { x: 1, y: 0 }, { x: 0, y: 0 }, { x: 0, y: -1 }
];

export const SShape: Shape = [
    { x: 0, y: 0 }, { x: 1, y: 0 }, { x: 0, y: 1 }, { x: -1, y: 1 }
];

export const SMirrorShape: Shape = [
    { x: 0, y: 0 }, { x: -1, y: 0 }, { x: 0, y: 1 }, { x: 1, y: 1 }
];

export const SquareShape: Shape = [
    { x: 0, y: 0 }, { x: 1, y: 0 }, { x: 0, y: 1 }, { x: 1, y: 1 }
];

export const LineShape: Shape = [
    { x: -1, y: 0 }, { x: 0, y: 0 }, { x: 1, y: 0 }, { x: 2, y: 0 }
];

export const shapes = [
    TShape,
    LShape,
    LMirrorShape,
    SShape,
    SMirrorShape,
    SquareShape,
    LineShape
]

export const colors = [
    "red",
    "#fff",
    "green",
    "blue",
    "orange"
]

/**
 * 随机产生一个俄罗斯方块(颜色随机、形状随机)
* @param centerPoint 
*/
export function createTeris(centerPoint: Point) {
    let index = getRandom(0, shapes.length);
    const shape = shapes[index];

    index = getRandom(0, colors.length);
    const color = colors[index];
    
    return new SquareGroup(shape, centerPoint, color);
}
// index.ts
import { SquarePageViewer } from "./core/viewer/SquarePageViewer";
import $ from "jquery"
import { SquareGroup } from "./core/SquareGroup";
import { LShape, createTeris } from "./core/Teris";

const teris = createTeris({ x: 3, y: 2 });
teris.squares.forEach(sq => {
    sq.viewer = new SquarePageViewer(sq, $("#root"));
})

$("#btnDown").click(function () {
    //更改中心点坐标
    teris.centerPoint = {
        x: teris.centerPoint.x,
        y: teris.centerPoint.y + 1
    }
})

$("#btnUp").click(function () {
    //更改中心点坐标
    teris.centerPoint = {
        x: teris.centerPoint.x,
        y: teris.centerPoint.y - 1
    }
})

$("#btnLeft").click(function () {
    //更改中心点坐标
    teris.centerPoint = {
        x: teris.centerPoint.x - 1,
        y: teris.centerPoint.y
    }
})

$("#btnRight").click(function () {
    //更改中心点坐标
    teris.centerPoint = {
        x: teris.centerPoint.x + 1,
        y: teris.centerPoint.y
    }
})

开发俄罗斯方块规则类

TypeScript
TypeScript
TypeScript
// GameConfig.ts
export default {
    panelSize: {
        width: 10,
        height: 10
    }
}
// types.ts
export enum MoveDirection {
    left,
    right,
    down
}
// TerisRule.ts
import { Shape, Point, MoveDirection } from "./types";
import GameConfig from "./GameConfig";
import { SquareGroup } from "./SquareGroup";

function isPoint(obj: any): obj is Point {
    if (typeof obj.x === "undefined") {
        return false;
    }
    return true;
}

/**
 * 该类中提供一系列的函数,根据游戏规则判断各种情况
*/
export class TerisRule {
    /**
     * 判断某个形状的方块,是否能够移动到目标位置
    */
    static canIMove(shape: Shape, targetPoint: Point): boolean {
        //假设,中心点已经移动到了目标位置,算出每个小方块的坐标
        const targetSquarePoints: Point[] = shape.map(it => {
            return {
                x: it.x + targetPoint.x,
                y: it.y + targetPoint.y
            }
        })
        //边界判断
        const result = targetSquarePoints.some(p => {
            //是否超出了边界
            return p.x < 0 || p.x > GameConfig.panelSize.width - 1 ||
                p.y < 0 || p.y > GameConfig.panelSize.height - 1;
        })
        if (result) {
            return false;
        }
        return true;
    }

    static move(teris: SquareGroup, targetPoint: Point): boolean;
    static move(teris: SquareGroup, direction: MoveDirection): boolean;
    static move(teris: SquareGroup, targetPointOrDirection: Point | MoveDirection): boolean {
        if (isPoint(targetPointOrDirection)) {
            if (this.canIMove(teris.shape, targetPointOrDirection)) {
                teris.centerPoint = targetPointOrDirection;
                return true;
            }
            return false;
        }
        else {
            const direction = targetPointOrDirection;
            let targetPoint: Point;
            if (direction === MoveDirection.down) {
                targetPoint = {
                    x: teris.centerPoint.x,
                    y: teris.centerPoint.y + 1
                }
            }
            else if (direction === MoveDirection.left) {
                targetPoint = {
                    x: teris.centerPoint.x - 1,
                    y: teris.centerPoint.y
                }
            }
            else {
                targetPoint = {
                    x: teris.centerPoint.x + 1,
                    y: teris.centerPoint.y
                }
            }
            return this.move(teris, targetPoint);
        }

    }

    /**
     * 将当前的方块,移动到目标方向的终点
    * @param teris 
    * @param direction 
    */
    static moveDirectly(teris: SquareGroup, direction: MoveDirection) {
        while (this.move(teris, direction)) {
        }
    }
}

实现俄罗斯方块旋转功能

  1. 旋转的本质:根据当前形状 -> 新的形状

    • 有些方块是不旋转的,有些方块旋转时只有两种状态
    • 旋转不能超出边界
  2. rotate 方法有一种通用的实现方式,但是不同的情况下,会有不同的具体实现,将 SquareGroup 作为父类,其他的方块都是它的子类,子类可以重写父类的方法(处理各自的不同实现,若放在父类,父类会变的很复杂);

TypeScript
TypeScript
TypeScript
TypeScript
// SquareGroup.ts
import { Square } from "./Square";
import { Shape, Point } from "./types";

/**
 * 组合方块
*/
export class SquareGroup {
    private _squares: readonly Square[];


    public get squares() {
        return this._squares;
    }

    public get shape() {
        return this._shape;
    }


    public get centerPoint(): Point {
        return this._centerPoint;
    }

    public set centerPoint(v: Point) {
        this._centerPoint = v;
        //同时设置所有小方块对象的坐标
        this.setSquarePoints();
    }

    /**
     * 根据中心点坐标,以及形状,设置每一个小方块的坐标
    */
    private setSquarePoints() {
        this._shape.forEach((p, i) => {
            this._squares[i].point = {
                x: this._centerPoint.x + p.x,
                y: this._centerPoint.y + p.y
            }
        })
    }

    constructor(
        private _shape: Shape,
        private _centerPoint: Point,
        private _color: string) {
        //设置小方块的数组
        const arr: Square[] = [];
        this._shape.forEach(p => {
            const sq = new Square();
            sq.color = this._color;
            arr.push(sq);
        })
        this._squares = arr;
        this.setSquarePoints();
    }

    /**
     * 旋转方向是否为顺时针
    */
    protected isClock = true;

    afterRotateShape(): Shape {
        if (this.isClock) {
            return this._shape.map(p => {
                const newP: Point = {
                    x: -p.y,
                    y: p.x
                }
                return newP;
            })
        }
        else {
            return this._shape.map(p => {
                const newP: Point = {
                    x: p.y,
                    y: -p.x
                }
                return newP;
            })
        }
    }

    rotate() {
        const newShape = this.afterRotateShape();
        this._shape = newShape;
        this.setSquarePoints();
    }
}
// Teris.ts
import { Point } from "./types";
import { getRandom } from "./util";
import { SquareGroup } from "./SquareGroup";

export class TShape extends SquareGroup {

    // 重写构造函数
    constructor(_centerPoint: Point, _color: string) {
        super(
            [{ x: -1, y: 0 }, { x: 0, y: 0 }, { x: 1, y: 0 }, { x: 0, y: -1 }],
            _centerPoint, _color);
    }
}

export class LShape extends SquareGroup {

    // 重写构造函数
    constructor(_centerPoint: Point, _color: string) {
        super(
            [{ x: -2, y: 0 }, { x: -1, y: 0 }, { x: 0, y: 0 }, { x: 0, y: -1 }],
            _centerPoint, _color);
    }
}

export class LMirrorShape extends SquareGroup {

    // 重写构造函数
    constructor(_centerPoint: Point, _color: string) {
        super(
            [{ x: 2, y: 0 }, { x: 1, y: 0 }, { x: 0, y: 0 }, { x: 0, y: -1 }],
            _centerPoint, _color);
    }
}

export class SShape extends SquareGroup {

    // 重写构造函数
    constructor(_centerPoint: Point, _color: string) {
        super(
            [{ x: 0, y: 0 }, { x: 1, y: 0 }, { x: 0, y: 1 }, { x: -1, y: 1 }],
            _centerPoint, _color);
    }

    rotate() {
        super.rotate();
        this.isClock = !this.isClock;
    }
}

export class SMirrorShape extends SquareGroup {

    // 重写构造函数
    constructor(_centerPoint: Point, _color: string) {
        super(
            [{ x: 0, y: 0 }, { x: -1, y: 0 }, { x: 0, y: 1 }, { x: 1, y: 1 }],
            _centerPoint, _color);
    }

    rotate() {
        super.rotate();
        this.isClock = !this.isClock;
    }
}

export class SquareShape extends SquareGroup {

    // 重写构造函数
    constructor(_centerPoint: Point, _color: string) {
        super(
            [{ x: 0, y: 0 }, { x: 1, y: 0 }, { x: 0, y: 1 }, { x: 1, y: 1 }],
            _centerPoint, _color);
    }

    // 不需要旋转,重写父类方法
    afterRotateShape() {
        return this.shape;
    }
}

export class LineShape extends SquareGroup {

    // 重写构造函数
    constructor(_centerPoint: Point, _color: string) {
        super(
            [{ x: -1, y: 0 }, { x: 0, y: 0 }, { x: 1, y: 0 }, { x: 2, y: 0 }],
            _centerPoint, _color);
    }

    rotate() {
        super.rotate();
        this.isClock = !this.isClock;
    }
}

export const shapes = [
    TShape,
    LShape,
    LMirrorShape,
    SShape,
    SMirrorShape,
    SquareShape,
    LineShape
]

export const colors = [
    "red",
    "#fff",
    "green",
    "blue",
    "orange"
]

/**
 * 随机产生一个俄罗斯方块(颜色随机、形状随机)
* @param centerPoint 
*/
export function createTeris(centerPoint: Point): SquareGroup {
    let index = getRandom(0, shapes.length);
    const shape = shapes[index];
    index = getRandom(0, colors.length);
    const color = colors[index];
    return new shape(centerPoint, color);
}
// TerisRule.ts
import { Shape, Point, MoveDirection } from "./types";
import GameConfig from "./GameConfig";
import { SquareGroup } from "./SquareGroup";

function isPoint(obj: any): obj is Point {
    if (typeof obj.x === "undefined") {
        return false;
    }
    return true;
}

/**
 * 该类中提供一系列的函数,根据游戏规则判断各种情况
*/
export class TerisRule {
    /**
     * 判断某个形状的方块,是否能够移动到目标位置
    */
    static canIMove(shape: Shape, targetPoint: Point): boolean {
        //假设,中心点已经移动到了目标位置,算出每个小方块的坐标
        const targetSquarePoints: Point[] = shape.map(it => {
            return {
                x: it.x + targetPoint.x,
                y: it.y + targetPoint.y
            }
        })
        //边界判断
        const result = targetSquarePoints.some(p => {
            //是否超出了边界
            return p.x < 0 || p.x > GameConfig.panelSize.width - 1 ||
                p.y < 0 || p.y > GameConfig.panelSize.height - 1;
        })
        if (result) {
            return false;
        }
        return true;
    }

    static move(teris: SquareGroup, targetPoint: Point): boolean;
    static move(teris: SquareGroup, direction: MoveDirection): boolean;
    static move(teris: SquareGroup, targetPointOrDirection: Point | MoveDirection): boolean {
        if (isPoint(targetPointOrDirection)) {
            if (this.canIMove(teris.shape, targetPointOrDirection)) {
                teris.centerPoint = targetPointOrDirection;
                return true;
            }
            return false;
        }
        else {
            const direction = targetPointOrDirection;
            let targetPoint: Point;
            if (direction === MoveDirection.down) {
                targetPoint = {
                    x: teris.centerPoint.x,
                    y: teris.centerPoint.y + 1
                }
            }
            else if (direction === MoveDirection.left) {
                targetPoint = {
                    x: teris.centerPoint.x - 1,
                    y: teris.centerPoint.y
                }
            }
            else {
                targetPoint = {
                    x: teris.centerPoint.x + 1,
                    y: teris.centerPoint.y
                }
            }
            return this.move(teris, targetPoint);
        }

    }

    /**
     * 将当前的方块,移动到目标方向的终点
    * @param teris 
    * @param direction 
    */
    static moveDirectly(teris: SquareGroup, direction: MoveDirection) {
        while (this.move(teris, direction)) {
        }
    }

    static rotate(teris: SquareGroup): boolean {
        const newShape = teris.afterRotateShape(); //得到旋转之后新的形状
        if (this.canIMove(newShape, teris.centerPoint)) {
            teris.rotate();
            return true;
        }
        else {
            return false;
        }
    }
}
// index.ts
import { SquarePageViewer } from "./core/viewer/SquarePageViewer";
import $ from "jquery"
import { SquareGroup } from "./core/SquareGroup";
import { LShape, createTeris } from "./core/Teris";
import { TerisRule } from "./core/TerisRule";
import { MoveDirection } from "./core/types";


const teris = createTeris({ x: 3, y: 2 });
teris.squares.forEach(sq => {
    sq.viewer = new SquarePageViewer(sq, $("#root"));
})

$("#btnDown").click(function () {
    //更改中心点坐标
    TerisRule.move(teris, MoveDirection.down);
})

$("#btnUp").click(function () {
    //更改中心点坐标
    TerisRule.move(teris, {
        x: teris.centerPoint.x,
        y: teris.centerPoint.y - 1
    });
})


$("#btnLeft").click(function () {
    //更改中心点坐标
    TerisRule.move(teris, MoveDirection.left);
})

$("#btnRight").click(function () {
    //更改中心点坐标
    TerisRule.move(teris, MoveDirection.right);
})

$("#rotate").click(function () {
    TerisRule.rotate(teris);
})

实现游戏类

Game 类清楚什么时候进行显示的切换,但不知道如何显示

TypeScript
TypeScript
TypeScript
TypeScript
TypeScript
// types.ts
export enum GameStatus {
    init, //未开始
    playing, //进行中,
    pause, //暂停
    over //游戏结束
}

export interface GameViewer {
    /**
     * 
    * @param teris 下一个方块对象
    */
    showNext(teris: SquareGroup): void;
    /**
     * 
    * @param teris 切换的方块对象
    */
    swtich(teris: SquareGroup): void;
}
// GameConfig.ts
export default {
    panelSize: {
        width: 10,
        height: 10
    },
    nextSize: {
        width: 5,
        height: 4
    }
}
// Game.ts
import { GameStatus, MoveDirection, GameViewer, Point } from "./types";
import { SquareGroup } from "./SquareGroup";
import { createTeris } from "./Teris";
import { TerisRule } from "./TerisRule";
import GameConfig from "./GameConfig";

export class Game {
    // 游戏状态
    private _gameStatus: GameStatus = GameStatus.init;
    // 当前玩家操作的方块
    private _curTeris?: SquareGroup;
    // 下一个方块
    private _nextTeris: SquareGroup = createTeris({ x: 0, y: 0 });
    // 计时器
    private _timer?: number;
    // 自动下落的间隔时间
    private _duration: number = 1000;

    constructor(private _viewer: GameViewer) {
        this.resetCenterPoint(GameConfig.nextSize.width, this._nextTeris);
        this._viewer.showNext(this._nextTeris);
    }

    /**
     * 游戏开始
    */
    start() {
        // 游戏状态的改变
        if (this._gameStatus === GameStatus.playing) {
            return;
        }
        this._gameStatus = GameStatus.playing;
        if (!this._curTeris) {
            //给当前玩家操作的方块赋值
            this.switchTeris();
        }
        this.autoDrop();
    }

    /**
     * 游戏暂停
    */
    pause() {
        if (this._gameStatus === GameStatus.playing) {
            this._gameStatus = GameStatus.pause;
            clearInterval(this._timer);
            this._timer = undefined;
        }
    }

    controlLeft() {
        if (this._curTeris && this._gameStatus === GameStatus.playing) {
            TerisRule.move(this._curTeris, MoveDirection.left);
        }
    }

    controlRight() {
        if (this._curTeris && this._gameStatus === GameStatus.playing) {
            TerisRule.move(this._curTeris, MoveDirection.right);
        }
    }

    controlDown() {
        if (this._curTeris && this._gameStatus === GameStatus.playing) {
            TerisRule.moveDirectly(this._curTeris, MoveDirection.down);
        }
    }

    controlRotate() {
        if (this._curTeris && this._gameStatus === GameStatus.playing) {
            TerisRule.rotate(this._curTeris);
        }
    }

    /**
     * 当前方块自由下落
    */
    private autoDrop() {
        if (this._timer || this._gameStatus !== GameStatus.playing) {
            return;
        }
        this._timer = setInterval(() => {
            if (this._curTeris) {
                TerisRule.move(this._curTeris, MoveDirection.down);
            }
        }, this._duration);
    }

    /**
     * 切换方块
    */
    private switchTeris() {
        this._curTeris = this._nextTeris;
        this.resetCenterPoint(GameConfig.panelSize.width, this._curTeris);
        this._nextTeris = createTeris({ x: 0, y: 0 });
        this.resetCenterPoint(GameConfig.nextSize.width, this._nextTeris);
        this._viewer.swtich(this._curTeris);
        this._viewer.showNext(this._nextTeris);
    }

    /**
     * 设置中心点坐标,已达到让该方块出现在区域的中上方
    * @param width 
    * @param teris 
    */
    private resetCenterPoint(width: number, teris: SquareGroup) {
        const x = Math.ceil(width / 2) - 1;
        const y = 0;
        teris.centerPoint = { x, y };
        while (teris.squares.some(it => it.point.y < 0)) {
            teris.squares.forEach(sq => sq.point = {
                x: sq.point.x,
                y: sq.point.y + 1
            })
        }
    }
}
// GamePageViewer.ts
import { GameViewer } from "../types";
import { SquareGroup } from "../SquareGroup";
import { SquarePageViewer } from "./SquarePageViewer";
import $ from "jquery"

export class GamePageViewer implements GameViewer {
    showNext(teris: SquareGroup): void {
        teris.squares.forEach(sq => {
            sq.viewer = new SquarePageViewer(sq, $("#next"));
        })
    }

    swtich(teris: SquareGroup): void {
        teris.squares.forEach(sq => {
            sq.viewer!.remove();
            sq.viewer = new SquarePageViewer(sq, $("#panel"));
        })
    }
}
// index.ts
import { Game } from "./core/Game";
import { GamePageViewer } from "./core/viewer/GamePageViewer";
import $ from "jquery"
var g = new Game(new GamePageViewer());

$("#btnStart").click(function () {
    g.start();
})

$("#btnPause").click(function () {
    g.pause();
})

$("#btnLeft").click(function () {
    g.controlLeft();
})

$("#btnRight").click(function () {
    g.controlRight();
})

$("#btnDown").click(function () {
    g.controlDown();
})

$("#btnRotate").click(function () {
    g.controlRotate();
})

触底处理

  1. 触底:当前方块到达最底部

  2. 什么时候可能发生触底?(什么时候调用函数)

    • 自动下落
    • 玩家控制下落
  3. 触底之后做什么?(函数如何编写的问题)

    • 切换方块
    • 保存已落下的方块
    • 消除方块的处理?(后面处理)
    • 游戏是否结束?(后面处理)
TypeScript
TypeScript
TypeScript
// TerisRule.ts
import { Shape, Point, MoveDirection } from "./types";
import GameConfig from "./GameConfig";
import { SquareGroup } from "./SquareGroup";
import { Square } from "./Square";

function isPoint(obj: any): obj is Point {
    if (typeof obj.x === "undefined") {
        return false;
    }
    return true;
}

/**
 * 该类中提供一系列的函数,根据游戏规则判断各种情况
*/
export class TerisRule {
    /**
     * 判断某个形状的方块,是否能够移动到目标位置
    */
    static canIMove(shape: Shape, targetPoint: Point, exists: Square[]): boolean {
        //假设,中心点已经移动到了目标位置,算出每个小方块的坐标
        const targetSquarePoints: Point[] = shape.map(it => {
            return {
                x: it.x + targetPoint.x,
                y: it.y + targetPoint.y
            }
        })
        //边界判断
        let result = targetSquarePoints.some(p => {
            //是否超出了边界
            return p.x < 0 || p.x > GameConfig.panelSize.width - 1 ||
                p.y < 0 || p.y > GameConfig.panelSize.height - 1;
        })
        if (result) {
            return false;
        }

        //判断是否与已有的方块有重叠
        result = targetSquarePoints
            .some(p => exists.some(sq => sq.point.x === p.x && sq.point.y === p.y))
        if (result) {
            return false;
        }
        return true;
    }

    static move(teris: SquareGroup, targetPoint: Point, exists: Square[]): boolean;
    static move(teris: SquareGroup, direction: MoveDirection, exists: Square[]): boolean;
    static move(teris: SquareGroup, targetPointOrDirection: Point | MoveDirection, exists: Square[]): boolean {
        if (isPoint(targetPointOrDirection)) {
            if (this.canIMove(teris.shape, targetPointOrDirection, exists)) {
                teris.centerPoint = targetPointOrDirection;
                return true;
            }
            return false;
        }
        else {
            const direction = targetPointOrDirection;
            let targetPoint: Point;
            if (direction === MoveDirection.down) {
                targetPoint = {
                    x: teris.centerPoint.x,
                    y: teris.centerPoint.y + 1
                }
            }
            else if (direction === MoveDirection.left) {
                targetPoint = {
                    x: teris.centerPoint.x - 1,
                    y: teris.centerPoint.y
                }
            }
            else {
                targetPoint = {
                    x: teris.centerPoint.x + 1,
                    y: teris.centerPoint.y
                }
            }
            return this.move(teris, targetPoint, exists);
        }

    }

    /**
     * 将当前的方块,移动到目标方向的终点
    * @param teris 
    * @param direction 
    */
    static moveDirectly(teris: SquareGroup, direction: MoveDirection, exists: Square[]) {
        while (this.move(teris, direction, exists)) {
        }
    }

    static rotate(teris: SquareGroup, exists: Square[]): boolean {
        const newShape = teris.afterRotateShape(); //得到旋转之后新的形状
        if (this.canIMove(newShape, teris.centerPoint, exists)) {
            teris.rotate();
            return true;
        }
        else {
            return false;
        }
    }
}
// Game.ts
import { GameStatus, MoveDirection, GameViewer, Point } from "./types";
import { SquareGroup } from "./SquareGroup";
import { createTeris } from "./Teris";
import { TerisRule } from "./TerisRule";
import GameConfig from "./GameConfig";
import { Square } from "./Square";

export class Game {
    //游戏状态
    private _gameStatus: GameStatus = GameStatus.init;
    //当前玩家操作的方块
    private _curTeris?: SquareGroup;
    //下一个方块
    private _nextTeris: SquareGroup = createTeris({ x: 0, y: 0 });
    //计时器
    private _timer?: number;
    //自动下落的间隔时间
    private _duration: number = 1000;
    //当前游戏中,已存在的小方块
    private _exists: Square[] = [];

    constructor(private _viewer: GameViewer) {
        this.resetCenterPoint(GameConfig.nextSize.width, this._nextTeris);
        this._viewer.showNext(this._nextTeris);
    }

    /**
     * 游戏开始
    */
    start() {
        //游戏状态的改变
        if (this._gameStatus === GameStatus.playing) {
            return;
        }
        this._gameStatus = GameStatus.playing;
        if (!this._curTeris) {
            //给当前玩家操作的方块赋值
            this.switchTeris();
        }
        this.autoDrop();
    }

    /**
     * 游戏暂停
    */
    pause() {
        if (this._gameStatus === GameStatus.playing) {
            this._gameStatus = GameStatus.pause;
            clearInterval(this._timer);
            this._timer = undefined;
        }
    }

    controlLeft() {
        if (this._curTeris && this._gameStatus === GameStatus.playing) {
            TerisRule.move(this._curTeris, MoveDirection.left, this._exists);
        }
    }

    controlRight() {
        if (this._curTeris && this._gameStatus === GameStatus.playing) {
            TerisRule.move(this._curTeris, MoveDirection.right, this._exists);
        }
    }

    controlDown() {
        if (this._curTeris && this._gameStatus === GameStatus.playing) {
            TerisRule.moveDirectly(this._curTeris, MoveDirection.down, this._exists);
            //触底
            this.hitBottom();
        }
    }

    controlRotate() {
        if (this._curTeris && this._gameStatus === GameStatus.playing) {
            TerisRule.rotate(this._curTeris, this._exists);
        }
    }

    /**
     * 当前方块自由下落
    */
    private autoDrop() {
        if (this._timer || this._gameStatus !== GameStatus.playing) {
            return;
        }
        this._timer = setInterval(() => {
            if (this._curTeris) {
                // 是否可移动
                if (!TerisRule.move(this._curTeris, MoveDirection.down, this._exists)) {
                    //触底
                    this.hitBottom();
                }
            }
        }, this._duration);
    }

    /**
     * 切换方块
    */
    private switchTeris() {
        this._curTeris = this._nextTeris;
        this.resetCenterPoint(GameConfig.panelSize.width, this._curTeris);
        this._nextTeris = createTeris({ x: 0, y: 0 });
        this.resetCenterPoint(GameConfig.nextSize.width, this._nextTeris);
        this._viewer.swtich(this._curTeris);
        this._viewer.showNext(this._nextTeris);
    }

    /**
     * 设置中心点坐标,已达到让该方块出现在区域的中上方
    * @param width 
    * @param teris 
    */
    private resetCenterPoint(width: number, teris: SquareGroup) {
        const x = Math.ceil(width / 2) - 1;
        const y = 0;
        teris.centerPoint = { x, y };
        while (teris.squares.some(it => it.point.y < 0)) {
            teris.squares.forEach(sq => sq.point = {
                x: sq.point.x,
                y: sq.point.y + 1
            })
        }
    }

    /**
     * 触底之后的操作
    */
    private hitBottom() {
        //将当前的俄罗斯方块包含的小方块,加入到已存在的方块数组中。
        this._exists = this._exists.concat(this._curTeris!.squares);
        //切换方块
        this.switchTeris();
    }
}
// index.ts
import { Game } from "./core/Game";
import { GamePageViewer } from "./core/viewer/GamePageViewer";
import $ from "jquery"
var g = new Game(new GamePageViewer());

$("#btnStart").click(function () {
    g.start();
})

$("#btnPause").click(function () {
    g.pause();
})

$("#btnLeft").click(function () {
    g.controlLeft();
})

$("#btnRight").click(function () {
    g.controlRight();
})

$("#btnDown").click(function () {
    g.controlDown();
})

$("#btnRotate").click(function () {
    g.controlRotate();
})

消除处理

从界面上移除、从 exists 数组中移除,改变 y 坐标

TypeScript
TypeScript
TypeScript
// Game.ts
import { GameStatus, MoveDirection, GameViewer, Point } from "./types";
import { SquareGroup } from "./SquareGroup";
import { createTeris } from "./Teris";
import { TerisRule } from "./TerisRule";
import GameConfig from "./GameConfig";
import { Square } from "./Square";

export class Game {
    //游戏状态
    private _gameStatus: GameStatus = GameStatus.init;
    //当前玩家操作的方块
    private _curTeris?: SquareGroup;
    //下一个方块
    private _nextTeris: SquareGroup = createTeris({ x: 0, y: 0 });
    //计时器
    private _timer?: number;
    //自动下落的间隔时间
    private _duration: number = 1000;
    //当前游戏中,已存在的小方块
    private _exists: Square[] = [];

    constructor(private _viewer: GameViewer) {
        this.resetCenterPoint(GameConfig.nextSize.width, this._nextTeris);
        this._viewer.showNext(this._nextTeris);
    }

    /**
     * 游戏开始
    */
    start() {
        //游戏状态的改变
        if (this._gameStatus === GameStatus.playing) {
            return;
        }
        this._gameStatus = GameStatus.playing;
        if (!this._curTeris) {
            //给当前玩家操作的方块赋值
            this.switchTeris();
        }
        this.autoDrop();
    }

    /**
     * 游戏暂停
    */
    pause() {
        if (this._gameStatus === GameStatus.playing) {
            this._gameStatus = GameStatus.pause;
            clearInterval(this._timer);
            this._timer = undefined;
        }
    }

    controlLeft() {
        if (this._curTeris && this._gameStatus === GameStatus.playing) {
            TerisRule.move(this._curTeris, MoveDirection.left, this._exists);
        }
    }

    controlRight() {
        if (this._curTeris && this._gameStatus === GameStatus.playing) {
            TerisRule.move(this._curTeris, MoveDirection.right, this._exists);
        }
    }

    controlDown() {
        if (this._curTeris && this._gameStatus === GameStatus.playing) {
            TerisRule.moveDirectly(this._curTeris, MoveDirection.down, this._exists);
            //触底
            this.hitBottom();
        }
    }

    controlRotate() {
        if (this._curTeris && this._gameStatus === GameStatus.playing) {
            TerisRule.rotate(this._curTeris, this._exists);
        }
    }

    /**
     * 当前方块自由下落
    */
    private autoDrop() {
        if (this._timer || this._gameStatus !== GameStatus.playing) {
            return;
        }
        this._timer = setInterval(() => {
            if (this._curTeris) {
                if (!TerisRule.move(this._curTeris, MoveDirection.down, this._exists)) {
                    //触底
                    this.hitBottom();
                }
            }
        }, this._duration);
    }

    /**
     * 切换方块
    */
    private switchTeris() {
        this._curTeris = this._nextTeris;
        this.resetCenterPoint(GameConfig.panelSize.width, this._curTeris);
        this._nextTeris = createTeris({ x: 0, y: 0 });
        this.resetCenterPoint(GameConfig.nextSize.width, this._nextTeris);
        this._viewer.swtich(this._curTeris);
        this._viewer.showNext(this._nextTeris);
    }

    /**
     * 设置中心点坐标,已达到让该方块出现在区域的中上方
    * @param width 
    * @param teris 
    */
    private resetCenterPoint(width: number, teris: SquareGroup) {
        const x = Math.ceil(width / 2) - 1;
        const y = 0;
        teris.centerPoint = { x, y };
        while (teris.squares.some(it => it.point.y < 0)) {
            teris.squares.forEach(sq => sq.point = {
                x: sq.point.x,
                y: sq.point.y + 1
            })
        }
    }

    /**
     * 触底之后的操作
    */
    private hitBottom() {
        //将当前的俄罗斯方块包含的小方块,加入到已存在的方块数组中。
        this._exists = this._exists.concat(this._curTeris!.squares);
        //处理移除
        const num = TerisRule.deleteSquares(this._exists);
        //切换方块
        this.switchTeris();
    }
}
// TerisRule.ts
import { Shape, Point, MoveDirection } from "./types";
import GameConfig from "./GameConfig";
import { SquareGroup } from "./SquareGroup";
import { Square } from "./Square";

function isPoint(obj: any): obj is Point {
    if (typeof obj.x === "undefined") {
        return false;
    }
    return true;
}

/**
 * 该类中提供一系列的函数,根据游戏规则判断各种情况
*/
export class TerisRule {
    /**
     * 判断某个形状的方块,是否能够移动到目标位置
    */
    static canIMove(shape: Shape, targetPoint: Point, exists: Square[]): boolean {
        //假设,中心点已经移动到了目标位置,算出每个小方块的坐标
        const targetSquarePoints: Point[] = shape.map(it => {
            return {
                x: it.x + targetPoint.x,
                y: it.y + targetPoint.y
            }
        })
        //边界判断
        let result = targetSquarePoints.some(p => {
            //是否超出了边界
            return p.x < 0 || p.x > GameConfig.panelSize.width - 1 ||
                p.y < 0 || p.y > GameConfig.panelSize.height - 1;
        })
        if (result) {
            return false;
        }

        //判断是否与已有的方块有重叠
        result = targetSquarePoints
            .some(p => exists.some(sq => sq.point.x === p.x && sq.point.y === p.y))
        if (result) {
            return false;
        }
        return true;
    }

    static move(teris: SquareGroup, targetPoint: Point, exists: Square[]): boolean;
    static move(teris: SquareGroup, direction: MoveDirection, exists: Square[]): boolean;
    static move(teris: SquareGroup, targetPointOrDirection: Point | MoveDirection, exists: Square[]): boolean {
        if (isPoint(targetPointOrDirection)) {
            if (this.canIMove(teris.shape, targetPointOrDirection, exists)) {
                teris.centerPoint = targetPointOrDirection;
                return true;
            }
            return false;
        }
        else {
            const direction = targetPointOrDirection;
            let targetPoint: Point;
            if (direction === MoveDirection.down) {
                targetPoint = {
                    x: teris.centerPoint.x,
                    y: teris.centerPoint.y + 1
                }
            }
            else if (direction === MoveDirection.left) {
                targetPoint = {
                    x: teris.centerPoint.x - 1,
                    y: teris.centerPoint.y
                }
            }
            else {
                targetPoint = {
                    x: teris.centerPoint.x + 1,
                    y: teris.centerPoint.y
                }
            }
            return this.move(teris, targetPoint, exists);
        }

    }

    /**
     * 将当前的方块,移动到目标方向的终点
    * @param teris 
    * @param direction 
    */
    static moveDirectly(teris: SquareGroup, direction: MoveDirection, exists: Square[]) {
        while (this.move(teris, direction, exists)) {
        }
    }

    static rotate(teris: SquareGroup, exists: Square[]): boolean {
        const newShape = teris.afterRotateShape(); //得到旋转之后新的形状
        if (this.canIMove(newShape, teris.centerPoint, exists)) {
            teris.rotate();
            return true;
        }
        else {
            return false;
        }
    }

    /**
     * 从已存在的方块中进行消除,并返回消除的行数
    * @param exists 
    */
    static deleteSquares(exists: Square[]): number {
        //1. 获得y坐标数组
        const ys = exists.map(sq => sq.point.y);
        //2. 获取最大和最小的y坐标
        const maxY = Math.max(...ys);
        const minY = Math.min(...ys);
        //3. 循环判断每一行是否可以消除
        let num = 0;
        for (let y = minY; y <= maxY; y++) {
            if (this.deleteLine(exists, y)) {
                num++;
            }
        }
        return num;
    }

    /**
     * 消除一行
    * @param exists 
    * @param y 
    */
    private static deleteLine(exists: Square[], y: number): boolean {
        const squares = exists.filter(sq => sq.point.y === y);
        if (squares.length === GameConfig.panelSize.width) {
            //这一行可以消除
            squares.forEach(sq => {
                //1. 从界面中移除
                if (sq.viewer) {
                    sq.viewer.remove();
                }
                //2. 剩下的,y坐标比当前的y小的方块,y+1
                exists.filter(sq => sq.point.y < y).forEach(sq => {
                    sq.point = {
                        x: sq.point.x,
                        y: sq.point.y + 1
                    }
                })
                const index = exists.indexOf(sq)
                exists.splice(index, 1);
            })

            return true;
        }
        return false;
    }
}
// Teris.ts
import { Point } from "./types";
import { getRandom } from "./util";
import { SquareGroup } from "./SquareGroup";

export class TShape extends SquareGroup {

    constructor(
        _centerPoint: Point,
        _color: string) {
        super(
            [{ x: -1, y: 0 }, { x: 0, y: 0 }, { x: 1, y: 0 }, { x: 0, y: -1 }],
            _centerPoint, _color);
    }
}

export class LShape extends SquareGroup {

    constructor(
        _centerPoint: Point,
        _color: string) {
        super(
            [{ x: -2, y: 0 }, { x: -1, y: 0 }, { x: 0, y: 0 }, { x: 0, y: -1 }],
            _centerPoint, _color);
    }
}

export class LMirrorShape extends SquareGroup {

    constructor(
        _centerPoint: Point,
        _color: string) {
        super(
            [{ x: 2, y: 0 }, { x: 1, y: 0 }, { x: 0, y: 0 }, { x: 0, y: -1 }],
            _centerPoint, _color);
    }
}

export class SShape extends SquareGroup {

    constructor(
        _centerPoint: Point,
        _color: string) {
        super(
            [{ x: 0, y: 0 }, { x: 1, y: 0 }, { x: 0, y: 1 }, { x: -1, y: 1 }],
            _centerPoint, _color);
    }

    rotate() {
        super.rotate();
        this.isClock = !this.isClock;
    }
}

export class SMirrorShape extends SquareGroup {

    constructor(
        _centerPoint: Point,
        _color: string) {
        super(
            [{ x: 0, y: 0 }, { x: -1, y: 0 }, { x: 0, y: 1 }, { x: 1, y: 1 }],
            _centerPoint, _color);
    }

    rotate() {
        super.rotate();
        this.isClock = !this.isClock;
    }
}

export class SquareShape extends SquareGroup {

    constructor(
        _centerPoint: Point,
        _color: string) {
        super(
            [{ x: 0, y: 0 }, { x: 1, y: 0 }, { x: 0, y: 1 }, { x: 1, y: 1 }],
            _centerPoint, _color);
    }

    afterRotateShape() {
        return this.shape;
    }
}

export class LineShape extends SquareGroup {

    constructor(
        _centerPoint: Point,
        _color: string) {
        super(
            [{ x: -1, y: 0 }, { x: 0, y: 0 }, { x: 1, y: 0 }, { x: 2, y: 0 }],
            _centerPoint, _color);
    }

    rotate() {
        super.rotate();
        this.isClock = !this.isClock;
    }
}

export const shapes = [
    // TShape,
    // LShape,
    // LMirrorShape,
    // SShape,
    // SMirrorShape,
    SquareShape,
    // LineShape
]

export const colors = [
    "red",
    "green",
    "blue",
    "orange"
]

/**
 * 随机产生一个俄罗斯方块(颜色随机、形状随机)
* @param centerPoint 
*/
export function createTeris(centerPoint: Point): SquareGroup {
    let index = getRandom(0, shapes.length);
    const shape = shapes[index];
    index = getRandom(0, colors.length);
    const color = colors[index];
    return new shape(centerPoint, color);
}

游戏结束判定和积分功能

TypeScript
TypeScript
// Games.ts
import { GameStatus, MoveDirection, GameViewer, Point } from "./types";
import { SquareGroup } from "./SquareGroup";
import { createTeris } from "./Teris";
import { TerisRule } from "./TerisRule";
import GameConfig from "./GameConfig";
import { Square } from "./Square";

export class Game {
    //游戏状态
    private _gameStatus: GameStatus = GameStatus.init;
    //当前玩家操作的方块
    private _curTeris?: SquareGroup;
    //下一个方块
    private _nextTeris: SquareGroup;
    //计时器
    private _timer?: number;
    //自动下落的间隔时间
    private _duration: number = 1000;
    //当前游戏中,已存在的小方块
    private _exists: Square[] = [];
    //积分
    private _score: number = 0;

    constructor(private _viewer: GameViewer) {
        this._nextTeris = createTeris({ x: 0, y: 0 });//没有实际含义的代码,只是为了不让TS报错
        this.createNext();
    }

    private createNext() {
        this._nextTeris = createTeris({ x: 0, y: 0 });
        this.resetCenterPoint(GameConfig.nextSize.width, this._nextTeris);
        this._viewer.showNext(this._nextTeris);
    }

    private init() {
        this._exists.forEach(sq => {
            if (sq.viewer) {
                sq.viewer.remove();
            }
        })
        this._exists = [];
        this.createNext();
        this._curTeris = undefined;
        this._score = 0;
    }

    /**
     * 游戏开始
    */
    start() {
        //游戏状态的改变
        if (this._gameStatus === GameStatus.playing) {
            return;
        }
        //从游戏结束到开始
        if (this._gameStatus === GameStatus.over) {
            //初始化操作
            this.init();
        }
        this._gameStatus = GameStatus.playing;
        if (!this._curTeris) {
            //给当前玩家操作的方块赋值
            this.switchTeris();
        }
        this.autoDrop();
    }

    /**
     * 游戏暂停
    */
    pause() {
        if (this._gameStatus === GameStatus.playing) {
            this._gameStatus = GameStatus.pause;
            clearInterval(this._timer);
            this._timer = undefined;
        }
    }

    controlLeft() {
        if (this._curTeris && this._gameStatus === GameStatus.playing) {
            TerisRule.move(this._curTeris, MoveDirection.left, this._exists);
        }
    }

    controlRight() {
        if (this._curTeris && this._gameStatus === GameStatus.playing) {
            TerisRule.move(this._curTeris, MoveDirection.right, this._exists);
        }
    }

    controlDown() {
        if (this._curTeris && this._gameStatus === GameStatus.playing) {
            TerisRule.moveDirectly(this._curTeris, MoveDirection.down, this._exists);
            //触底
            this.hitBottom();
        }
    }

    controlRotate() {
        if (this._curTeris && this._gameStatus === GameStatus.playing) {
            TerisRule.rotate(this._curTeris, this._exists);
        }
    }

    /**
     * 当前方块自由下落
    */
    private autoDrop() {
        if (this._timer || this._gameStatus !== GameStatus.playing) {
            return;
        }
        this._timer = setInterval(() => {
            if (this._curTeris) {
                if (!TerisRule.move(this._curTeris, MoveDirection.down, this._exists)) {
                    //触底
                    this.hitBottom();
                }
            }
        }, this._duration);
    }

    /**
     * 切换方块
    */
    private switchTeris() {
        this._curTeris = this._nextTeris;
        this._curTeris.squares.forEach(sq => {
            if (sq.viewer) {
                sq.viewer.remove();
            }
        })
        this.resetCenterPoint(GameConfig.panelSize.width, this._curTeris);
        //有可能出问题:当前方块一出现时,就已经和之前的方块重叠了
        if (!TerisRule.canIMove(this._curTeris.shape, this._curTeris.centerPoint, this._exists)) {
            //游戏结束
            this._gameStatus = GameStatus.over;
            clearInterval(this._timer);
            this._timer = undefined;
            return;
        }
        this.createNext();
        this._viewer.swtich(this._curTeris);
    }

    /**
     * 设置中心点坐标,已达到让该方块出现在区域的中上方
    * @param width 
    * @param teris 
    */
    private resetCenterPoint(width: number, teris: SquareGroup) {
        const x = Math.ceil(width / 2) - 1;
        const y = 0;
        teris.centerPoint = { x, y };
        while (teris.squares.some(it => it.point.y < 0)) {
            teris.centerPoint = {
                x: teris.centerPoint.x,
                y: teris.centerPoint.y + 1
            };
        }
    }

    /**
     * 触底之后的操作
    */
    private hitBottom() {
        //将当前的俄罗斯方块包含的小方块,加入到已存在的方块数组中。
        this._exists = this._exists.concat(this._curTeris!.squares);
        //处理移除
        const num = TerisRule.deleteSquares(this._exists);
        //增加积分
        this.addScore(num);
        //切换方块
        this.switchTeris();
    }

    private addScore(lineNum: number) {
        if (lineNum === 0) {
            return;
        }
        else if (lineNum === 1) {
            this._score += 10;
        }
        else if (lineNum === 2) {
            this._score += 25;
        }
        else if (lineNum === 3) {
            this._score += 50;
        }
        else {
            this._score += 100;
        }
    }
}
// TerisRule.ts
import { Shape, Point, MoveDirection } from "./types";
import GameConfig from "./GameConfig";
import { SquareGroup } from "./SquareGroup";
import { Square } from "./Square";

function isPoint(obj: any): obj is Point {
    if (typeof obj.x === "undefined") {
        return false;
    }
    return true;
}

/**
 * 该类中提供一系列的函数,根据游戏规则判断各种情况
*/
export class TerisRule {

    // ...

    // bug 修复
    /**
     * 消除一行
    * @param exists 
    * @param y 
    */
    private static deleteLine(exists: Square[], y: number): boolean {
        const squares = exists.filter(sq => sq.point.y === y);
        if (squares.length === GameConfig.panelSize.width) {
            //这一行可以消除
            squares.forEach(sq => {
                //1. 从界面中移除
                if (sq.viewer) {
                    sq.viewer.remove();
                }
                //2. 从数组中移除
                const index = exists.indexOf(sq)
                exists.splice(index, 1);
            })
            //2. 剩下的,y坐标比当前的y小的方块,y+1
            exists.filter(sq => sq.point.y < y).forEach(sq => {
                sq.point = {
                    x: sq.point.x,
                    y: sq.point.y + 1
                }
            })

            return true;
        }
        return false;
    }
}

完成游戏界面

TypeScript
TypeScript
TypeScript
TypeScript
// types.ts
import { SquareGroup } from "./SquareGroup";
import { Game } from "./Game";

export interface Point {
    readonly x: number,
    readonly y: number
}

export interface IViewer {
    /**
     * 显示
     */
    show(): void;

    /**
     * 移除,不再显示
     */
    remove(): void;
}

/**
 * 形状
 */
export type Shape = Point[]

export enum MoveDirection {
    left,
    right,
    down
}

export enum GameStatus {
    init, //未开始
    playing, //进行中,
    pause, //暂停
    over //游戏结束
}

export interface GameViewer {
    /**
     * 
     * @param teris 下一个方块对象
     */
    showNext(teris: SquareGroup): void;
    /**
     * 
     * @param teris 切换的方块对象
     */
    swtich(teris: SquareGroup): void;

    /**
     * 完成界面的初始化
     */
    init(game: Game): void;

    showScore(score: number): void;

    onGamePause(): void;

    onGameStart(): void;

    onGameOver(): void;
}
// GamePageViewer.ts
import { GameViewer, GameStatus } from "../types";
import { SquareGroup } from "../SquareGroup";
import { SquarePageViewer } from "./SquarePageViewer";
import $ from "jquery"
import { Game } from "../Game";
import GameConfig from "../GameConfig";
import PageConfig from "./PageConfig";

export class GamePageViewer implements GameViewer {
    onGamePause(): void {
        this.msgDom.css({
            display: "flex"
        })
        this.msgDom.find("p").html("游戏暂停");
    }
    onGameStart(): void {
        this.msgDom.hide();
    }
    onGameOver(): void {
        this.msgDom.css({
            display: "flex"
        })
        this.msgDom.find("p").html("游戏结束");
    }
    showScore(score: number): void {
        this.scoreDom.html(score.toString());
    }
    private nextDom = $("#next");
    private panelDom = $("#panel");
    private scoreDom = $("#score");
    private msgDom = $("#msg");

    init(game: Game): void {
        //1. 设置宽高
        this.panelDom.css({
            width: GameConfig.panelSize.width * PageConfig.SquareSize.width,
            height: GameConfig.panelSize.height * PageConfig.SquareSize.height,
        })
        this.nextDom.css({
            width: GameConfig.nextSize.width * PageConfig.SquareSize.width,
            height: GameConfig.nextSize.height * PageConfig.SquareSize.height,
        })

        //2. 注册键盘事件
        $(document).keydown((e: { keyCode: number; }) => {
            if (e.keyCode === 37) {
                game.controlLeft();
            }
            else if (e.keyCode === 38) {
                game.controlRotate();
            }
            else if (e.keyCode === 39) {
                game.controlRight();
            }
            else if (e.keyCode === 40) {
                game.controlDown();
            }
            else if (e.keyCode === 32) {
                if (game.gameStatus === GameStatus.playing) {
                    game.pause();
                }
                else {
                    game.start();
                }
            }
        })
    }

    showNext(teris: SquareGroup): void {
        teris.squares.forEach(sq => {
            sq.viewer = new SquarePageViewer(sq, this.nextDom);
        })
    }

    swtich(teris: SquareGroup): void {
        teris.squares.forEach(sq => {
            sq.viewer!.remove();
            sq.viewer = new SquarePageViewer(sq, this.panelDom);
        })
    }
}
// Game.ts
import { GameStatus, MoveDirection, GameViewer, Point } from "./types";
import { SquareGroup } from "./SquareGroup";
import { createTeris } from "./Teris";
import { TerisRule } from "./TerisRule";
import GameConfig from "./GameConfig";
import { Square } from "./Square";

export class Game {
    //游戏状态
    private _gameStatus: GameStatus = GameStatus.init;

    public get gameStatus() {
        return this._gameStatus;
    }
    //当前玩家操作的方块
    private _curTeris?: SquareGroup;
    //下一个方块
    private _nextTeris: SquareGroup;
    //计时器
    private _timer?: number;
    //自动下落的间隔时间
    private _duration: number;
    //当前游戏中,已存在的小方块
    private _exists: Square[] = [];
    //积分
    private _score: number = 0;
    public get score() {
        return this._score;
    }
    public set score(val) {
        this._score = val;
        this._viewer.showScore(val);
        const level = GameConfig.levels.filter(it => it.score <= val).pop()!;
        if (level.duration === this._duration) {
            return;
        }
        this._duration = level.duration;
        if (this._timer) {
            clearInterval(this._timer);
            this._timer = undefined;
            this.autoDrop();

        }
    }

    constructor(private _viewer: GameViewer) {
        this._duration = GameConfig.levels[0].duration;
        this._nextTeris = createTeris({ x: 0, y: 0 });//没有实际含义的代码,只是为了不让TS报错
        this.createNext();
        this._viewer.init(this);
        this._viewer.showScore(this.score);
    }

    private createNext() {
        this._nextTeris = createTeris({ x: 0, y: 0 });
        this.resetCenterPoint(GameConfig.nextSize.width, this._nextTeris);
        this._viewer.showNext(this._nextTeris);
    }

    private init() {
        this._exists.forEach(sq => {
            if (sq.viewer) {
                sq.viewer.remove();
            }
        })
        this._exists = [];
        this.createNext();
        this._curTeris = undefined;
        this.score = 0;
    }

    /**
     * 游戏开始
     */
    start() {
        //游戏状态的改变
        if (this._gameStatus === GameStatus.playing) {
            return;
        }
        //从游戏结束到开始
        if (this._gameStatus === GameStatus.over) {
            //初始化操作
            this.init();
        }
        this._gameStatus = GameStatus.playing;
        if (!this._curTeris) {
            //给当前玩家操作的方块赋值
            this.switchTeris();
        }
        this.autoDrop();
        this._viewer.onGameStart();
    }

    /**
     * 游戏暂停
     */
    pause() {
        if (this._gameStatus === GameStatus.playing) {
            this._gameStatus = GameStatus.pause;
            clearInterval(this._timer);
            this._timer = undefined;
            this._viewer.onGamePause();
        }
    }

    controlLeft() {
        if (this._curTeris && this._gameStatus === GameStatus.playing) {
            TerisRule.move(this._curTeris, MoveDirection.left, this._exists);
        }
    }

    controlRight() {
        if (this._curTeris && this._gameStatus === GameStatus.playing) {
            TerisRule.move(this._curTeris, MoveDirection.right, this._exists);
        }
    }

    controlDown() {
        if (this._curTeris && this._gameStatus === GameStatus.playing) {
            TerisRule.moveDirectly(this._curTeris, MoveDirection.down, this._exists);
            //触底
            this.hitBottom();
        }
    }

    controlRotate() {
        if (this._curTeris && this._gameStatus === GameStatus.playing) {
            TerisRule.rotate(this._curTeris, this._exists);
        }
    }

    /**
     * 当前方块自由下落
     */
    private autoDrop() {
        if (this._timer || this._gameStatus !== GameStatus.playing) {
            return;
        }
        this._timer = setInterval(() => {
            if (this._curTeris) {
                if (!TerisRule.move(this._curTeris, MoveDirection.down, this._exists)) {
                    //触底
                    this.hitBottom();
                }
            }
        }, this._duration);
    }

    /**
     * 切换方块
     */
    private switchTeris() {
        this._curTeris = this._nextTeris;
        this._curTeris.squares.forEach(sq => {
            if (sq.viewer) {
                sq.viewer.remove();
            }
        })
        this.resetCenterPoint(GameConfig.panelSize.width, this._curTeris);
        //有可能出问题:当前方块一出现时,就已经和之前的方块重叠了
        if (!TerisRule.canIMove(this._curTeris.shape, this._curTeris.centerPoint, this._exists)) {
            //游戏结束
            this._gameStatus = GameStatus.over;
            clearInterval(this._timer);
            this._timer = undefined;
            this._viewer.onGameOver();
            return;
        }
        this.createNext();
        this._viewer.swtich(this._curTeris);
    }

    /**
     * 设置中心点坐标,已达到让该方块出现在区域的中上方
     * @param width 
     * @param teris 
     */
    private resetCenterPoint(width: number, teris: SquareGroup) {
        const x = Math.ceil(width / 2) - 1;
        const y = 0;
        teris.centerPoint = { x, y };
        while (teris.squares.some(it => it.point.y < 0)) {
            teris.centerPoint = {
                x: teris.centerPoint.x,
                y: teris.centerPoint.y + 1
            };
        }
    }

    /**
     * 触底之后的操作
     */
    private hitBottom() {
        //将当前的俄罗斯方块包含的小方块,加入到已存在的方块数组中。
        this._exists = this._exists.concat(this._curTeris!.squares);
        //处理移除
        const num = TerisRule.deleteSquares(this._exists);
        //增加积分
        this.addScore(num);
        //切换方块
        this.switchTeris();
    }

    private addScore(lineNum: number) {
        if (lineNum === 0) {
            return;
        }
        else if (lineNum === 1) {
            this.score += 10;
        }
        else if (lineNum === 2) {
            this.score += 25;
        }
        else if (lineNum === 3) {
            this.score += 50;
        }
        else {
            this.score += 100;
        }
    }
}
// index.ts
import { Game } from "./core/Game";
import { GamePageViewer } from "./core/viewer/GamePageViewer";
new Game(new GamePageViewer());
打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

中午好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. 开发原则
  2. 2. 工程搭建
  3. 3. 项目目录
  4. 4. 项目开发
    1. 4.1. 开发小方块类
    2. 4.2. 小方块的显示类
    3. 4.3. 开发方块组合类
    4. 4.4. 开发俄罗斯方块生产者模块
    5. 4.5. 开发俄罗斯方块规则类
    6. 4.6. 实现俄罗斯方块旋转功能
    7. 4.7. 实现游戏类
    8. 4.8. 触底处理
    9. 4.9. 消除处理
    10. 4.10. 游戏结束判定和积分功能
    11. 4.11. 完成游戏界面