开发原则
基于以下两个原则,系统中使用如下模式:数据-界面分离模式;
- 单一职能原则:每个类只做跟它相关的一件事;
- 开闭原则:系统中的类,应该对扩展开放,对修改关闭;
传统面向对象语言,书写类的属性时,往往会进行如下操作:
- 所有的属性全部私有化;
- 使用公开的方法提供对属性的访问;
面向对象开发已经非常成熟,特别善于解决复杂问题;
- 游戏特别容易使用面向对象的思维;
- 开发:高内聚、低耦合;
工程搭建
环境:浏览器 + 模块化
webpack:构建工具,根据入口文件找寻依赖,打包
- 安装 webpack、html-webpack-plugin、clean-webpack-plugin、webpack-dev-server
- 安装 ts 的相应 loader:ts-loader,awesome-typescript-loader
项目目录
项目开发
开发小方块类
小方块类:它能处理自己的数据,知道什么时候需要显示,但不知道怎么显示
// 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 }
小方块的显示类
作用:用于显示一个小方块到页面上
// 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"));
})
开发方块组合类
组合类中的属性:
- 小方块的数组:该数组的组成不能发生变化,是只读数组;
- 形状:一个方块的组合,取决于组合的形状(一组相对坐标的组合,该组合中有一个特殊坐标,表示形状的中心)
如果知道:形状、中心点坐标、颜色,就可以设置小方块的数组
// 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
}
})
开发俄罗斯方块生产者模块
// 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
}
})
开发俄罗斯方块规则类
// 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)) {
}
}
}
实现俄罗斯方块旋转功能
旋转的本质:根据当前形状 -> 新的形状
- 有些方块是不旋转的,有些方块旋转时只有两种状态
- 旋转不能超出边界
rotate 方法有一种通用的实现方式,但是不同的情况下,会有不同的具体实现,将 SquareGroup 作为父类,其他的方块都是它的子类,子类可以重写父类的方法(处理各自的不同实现,若放在父类,父类会变的很复杂);
// 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 类清楚什么时候进行显示的切换,但不知道如何显示
// 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();
})
触底处理
触底:当前方块到达最底部
什么时候可能发生触底?(什么时候调用函数)
- 自动下落
- 玩家控制下落
触底之后做什么?(函数如何编写的问题)
- 切换方块
- 保存已落下的方块
- 消除方块的处理?(后面处理)
- 游戏是否结束?(后面处理)
// 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 坐标
// 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);
}
游戏结束判定和积分功能
// 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;
}
}
完成游戏界面
// 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());
node👉 WebSocket 原理
上一篇