前言
来了来了,终于来了!上篇文章我们实现了方块的生成和交换,那本篇文章就到了该系列的重头戏,我们一起来实现消消乐的消除算法!
别说了,冲!!!
正文
思路讲解
1. 首先我们确定消除规则,这里我们采用和开心消消乐类似的消除规则(核心为超过 3 个连续的同类型即可消除),具体分为以下几点:
1-1. 横型和竖型;这两种种情况很简单,我们只需要遍历每一行每一列,找出那些连续超过 3 个的组合就可以了:
普通横竖型
1-2. 十字型、 T 型和 L 型;这三种情况相对比较复杂了,但是实质上他们都是由一个横型加上一个竖型组合而成的,这三种组合的不同点在于他们的共同方块的上下左右有多少方块(比如十字型的共同方块上下左右都有 1 个以上的方块),我们需要进行额外的判断:
左:十字型和 T 型 | 右:L 型
代码实现
1. 在 Enum 文件中定义一个组合类型的枚举:
// 以下为 Enum.ts 中添加的内容
/**
* 组合类型
*/
export enum CombinationType {
Horizontal = 1, // 横型
Vertical, // 竖型
Cross, // 十字型
TShape, // T 型
LShape, // L 型
}
2. 在 DataStructure 文件中定义一个组合的类:
// 以下为 DataStructure.ts 文件添加的内容
/**
* 组合
*/
export class Combination {
public coords: Coordinate[]; // 坐标集
public commonCoord: Coordinate; // 共同坐标
public type: CombinationType; // 组合类型
constructor(coords: Coordinate[]) {
this.coords = coords;
this.updateType();
}
/**
* 更新类型
*/
private updateType() {
let up = 0;
let down = 0;
let left = 0;
let right = 0;
let keyCoord = this.commonCoord ? this.commonCoord : this.coords[0]; // 关键坐标
// 收集数量
for (let i = 0; i < this.coords.length; i++) {
if (this.coords[i].compare(keyCoord)) continue; // 同一个坐标时跳过
// 判断位置
if (this.coords[i].x === keyCoord.x) {
if (this.coords[i].y > keyCoord.y) up++;
else down++;
} else {
if (this.coords[i].x < keyCoord.x) left++;
else right++;
}
}
// 判断类型
if (up === 0 && down === 0) this.type = CombinationType.Horizontal;
else if (left === 0 && right === 0) this.type = CombinationType.Vertical;
else if (up > 0 && down > 0 && left > 0 && right > 0) this.type = CombinationType.Cross;
else if ((up > 0 && down === 0 && left === 0 && right > 0) ||
(up > 0 && down === 0 && left > 0 && right === 0) ||
(up === 0 && down > 0 && left === 0 && right > 0) ||
(up === 0 && down > 0 && left > 0 && right === 0)) {
this.type = CombinationType.LShape;
} else if ((up === 0 && down > 0 && left > 0 && right > 0) ||
(up > 0 && down === 0 && left > 0 && right > 0) ||
(up > 0 && down > 0 && left === 0 && right > 0) ||
(up > 0 && down > 0 && left > 0 && right === 0)) {
this.type = CombinationType.TShape;
}
}
/**
* 组合是否包含坐标集中的任意一个,有得返回对应坐标
* @param coords 查询坐标集
*/
public include(coords: Coordinate[]): Coordinate {
for (let i = 0; i < this.coords.length; i++) {
for (let j = 0; j < coords.length; j++) {
if (this.coords[i].compare(coords[j])) return coords[j];
}
}
return null;
}
/**
* 合并组合
* @param coords 坐标集
* @param commonCoord 共同坐标
*/
public merge(coords: Coordinate[], commonCoord: Coordinate) {
for (let i = 0; i < coords.length; i++) {
if (!coords[i].compare(commonCoord))
this.coords.push(coords[i]);
}
this.commonCoord = commonCoord;
this.updateType();
}
}
3. 接下来在 GameUtil 中实现获取当前所有可消除组合的函数:
/**
* 获取可消除的组合
*/
public static getCombinations(typeMap: TileType[][]) {
let combinations: Combination[] = [];
// 逐行检测
for (let r = 0; r < GameConfig.row; r++) {
let count: number = 0;
let type: TileType = null;
for (let c = 0; c < GameConfig.col; c++) {
if (c === 0) {
count = 1; // 连续计数
type = typeMap[c][r]; // 保存类型
} else {
if (typeMap[c][r] && typeMap[c][r] === type) {
// 类型相同
count++;
// 到最后一个了,是不是有 3 个以上连续
if (c === GameConfig.col - 1 && count >= 3) {
let coords = [];
for (let i = 0; i < count; i++) {
coords.push(Coord(c - i, r));
}
combinations.push(new Combination(coords));
}
} else {
// 类型不同
if (count >= 3) {
// 已累积 3 个
let coords = [];
for (let i = 0; i < count; i++) {
coords.push(Coord(c - 1 - i, r));
}
combinations.push(new Combination(coords));
}
// 重置
count = 1;
type = typeMap[c][r];
}
}
}
}
// 逐列检测
for (let c = 0; c < GameConfig.col; c++) {
let count: number = 0;
let type: TileType = null;
for (let r = 0; r < GameConfig.row; r++) {
if (r === 0) {
count = 1;
type = typeMap[c][r];
} else {
if (typeMap[c][r] && typeMap[c][r] === type) {
count++;
if (r === GameConfig.row - 1 && count >= 3) {
let coords = [];
for (let i = 0; i < count; i++) {
coords.push(Coord(c, r - i));
}
// 是否可以和已有组合合并
let hasMerge = false;
for (let i = 0; i < combinations.length; i++) {
let common = combinations[i].include(coords);
if (common) {
combinations[i].merge(coords, common);
hasMerge = true;
break;
}
}
if (!hasMerge) combinations.push(new Combination(coords));
}
} else {
if (count >= 3) {
let coords = [];
for (let i = 0; i < count; i++) {
coords.push(Coord(c, r - 1 - i));
}
// 是否可以和已有组合合并
let hasMerge = false;
for (let i = 0; i < combinations.length; i++) {
let common = combinations[i].include(coords);
if (common) {
combinations[i].merge(coords, common);
hasMerge = true;
break;
}
}
if (!hasMerge) combinations.push(new Combination(coords));
}
count = 1;
type = typeMap[c][r];
}
}
}
}
return combinations;
}
4. 然后我们对 TileManager 进行改造;添加了 combinations 变量、更新 tryExchange 函数并加入 eliminateCombinations 和 eliminateTile 函数:
private combinations: Combination[] = null; // 可消除组合
/**
* 尝试交换方块
* @param coord1 1
* @param coord2 2
*/
private async tryExchange(coord1: Coordinate, coord2: Coordinate) {
// 交换方块
await this.exchangeTiles(coord1, coord2);
// 获取可消除组合
this.combinations = GameUtil.getCombinations(this.typeMap);
if (this.combinations.length > 0) {
// 消除!!!
this.eliminateCombinations();
} else {
// 不能消除,换回来吧
await this.exchangeTiles(coord1, coord2);
}
}
/**
* 消除组合
*/
private eliminateCombinations() {
for (let i = 0; i < this.combinations.length; i++) {
for (let j = 0; j < this.combinations[i].coords.length; j++) {
this.eliminateTile(this.combinations[i].coords[j]);
}
}
this.combinations = [];
}
/**
* 消除方块
* @param coord 坐标
*/
private eliminateTile(coord: Coordinate) {
this.getTileMap(coord).disappear(); // 方块消失
this.setTileMap(coord, null); // 数据置空
this.setTypeMap(coord, null); // 数据置空
}
5. 此时,我们的消除功能也实现了:
★ 但是现在还有一个问题,游戏开始时就随机出现了一些可消除的组合,理论上来说开局时是不能有任何消除但是同时又要存在可一步消除的情况,所以这就是我们下篇文章会讲到的东西了。