游戏简介
Columns是一款经典的益智消块游戏,玩法类似于俄罗斯方块但又有独特之处。彩色宝石从顶部以三格一列的形式下落,玩家可以左右移动和旋转列内宝石的顺序。当三个或更多相同颜色的宝石在水平、垂直或对角线方向上相连时,这些宝石会被消除,上方的宝石在重力作用下下落,可能引发连锁消除反应。
本文将用Java实现Columns游戏的核心引擎,重点讲解DFS连通块检测算法用于识别可消除的宝石组合,以及级联消除的递归处理逻辑。
核心算法设计
1. 游戏板与数据结构
Columns的游戏板是一个二维网格,每个格子记录宝石的颜色编号(0表示空)。我们用int[][] board表示当前状态,用常量定义宝石颜色。
public class ColumnsGame {
// 游戏板尺寸
private static final int ROWS = 13;
private static final int COLS = 6;
// 宝石颜色:0=空, 1=红, 2=绿, 3=蓝, 4=黄, 5=紫, 6=青
private static final int EMPTY = 0;
private static final int COLORS = 6;
private int[][] board; // 当前游戏板状态
private Random random;
public ColumnsGame() {
this.board = new int[ROWS][COLS];
this.random = new Random();
}
/**
* 打印当前游戏板状态,用于调试
*/
public void printBoard() {
for (int r = ROWS - 1; r >= 0; r--) {
for (int c = 0; c < COLS; c++) {
System.out.print(board[r][c] + " ");
}
System.out.println();
}
System.out.println("--------");
}
}
2. DFS连通块检测
消除规则要求三个及以上同色宝石在任意方向上相邻(水平、垂直、对角线共8个方向)。我们使用深度优先搜索(DFS)遍历每个方向的相邻格子,找到所有连通的同色宝石。
public class ColumnsGame {
// 8个方向的偏移量:上、下、左、右、4个对角线
private static final int[][] DIRS = {
{-1, 0}, {1, 0}, {0, -1}, {0, 1},
{-1, -1}, {-1, 1}, {1, -1}, {1, 1}
};
/**
* 使用DFS检测从(r,c)出发的所有连通同色宝石
* @param r 起始行
* @param c 起始列
* @param color 目标颜色
* @param visited 访问标记数组
* @param group 存储连通块坐标的列表
*/
private void dfs(int r, int c, int color, boolean[][] visited, List<int[]> group) {
// 边界检查:越界、已访问、颜色不同、空格子
if (r < 0 || r >= ROWS || c < 0 || c >= COLS
|| visited[r][c] || board[r][c] != color || color == EMPTY) {
return;
}
visited[r][c] = true;
group.add(new int[]{r, c});
// 向8个方向递归搜索
for (int[] d : DIRS) {
int nr = r + d[0];
int nc = c + d[1];
dfs(nr, nc, color, visited, group);
}
}
/**
* 扫描整个游戏板,找到所有需要消除的宝石组
* @return 所有可消除的坐标集合
*/
public Set<String> findMatches() {
boolean[][] visited = new boolean[ROWS][COLS];
Set<String> toEliminate = new HashSet<>();
for (int r = 0; r < ROWS; r++) {
for (int c = 0; c < COLS; c++) {
if (board[r][c] != EMPTY && !visited[r][c]) {
List<int[]> group = new ArrayList<>();
dfs(r, c, board[r][c], visited, group);
// 只有连通块大小>=3才需要消除
if (group.size() >= 3) {
for (int[] pos : group) {
toEliminate.add(pos[0] + "," + pos[1]);
}
}
}
}
}
return toEliminate;
}
}
3. 重力下落与级联消除
消除宝石后,上方宝石受重力影响下落填补空位。下落完成后,可能形成新的连通组合,需要递归检测直到没有可消除的宝石为止。
public class ColumnsGame {
/**
* 应用重力:让每列中的宝石下落到最低的空位置
*/
public void applyGravity() {
for (int c = 0; c < COLS; c++) {
int writePos = 0; // 从底部开始填充
// 先收集该列所有非空宝石(从底向上)
List<Integer> columnGems = new ArrayList<>();
for (int r = 0; r < ROWS; r++) {
if (board[r][c] != EMPTY) {
columnGems.add(board[r][c]);
}
}
// 清空该列
for (int r = 0; r < ROWS; r++) {
board[r][c] = EMPTY;
}
// 从底部重新放置宝石
for (int i = 0; i < columnGems.size(); i++) {
board[i][c] = columnGems.get(i);
}
}
}
/**
* 消除指定坐标的宝石
* @param toEliminate 待消除的坐标集合
* @return 消除的宝石数量
*/
public int eliminate(Set<String> toEliminate) {
int count = 0;
for (String key : toEliminate) {
String[] parts = key.split(",");
int r = Integer.parseInt(parts[0]);
int c = Integer.parseInt(parts[1]);
board[r][c] = EMPTY;
count++;
}
return count;
}
/**
* 级联消除:递归执行消除-下落-再检测,直到没有可消除的宝石
* @return 总消除的宝石数量
*/
public int cascadeEliminate() {
int totalEliminated = 0;
int chain = 0;
while (true) {
Set<String> matches = findMatches();
if (matches.isEmpty()) {
break; // 没有可消除的宝石,结束级联
}
chain++;
int eliminated = eliminate(matches);
totalEliminated += eliminated * chain; // 连锁倍率加分
applyGravity();
System.out.println("连锁" + chain + ":消除了" + eliminated + "个宝石");
}
return totalEliminated;
}
}
4. 三格列的下落与旋转
玩家控制的三格列可以左右移动,也可以循环旋转列内三个宝石的顺序。
public class ColumnsGame {
/**
* 当前下落的三格列,数组长度为3
*/
private int[] currentColumn;
private int currentCol; // 当前所在列位置
private int currentRow; // 当前底部所在行
/**
* 生成新的三格列,随机三种颜色
*/
public void spawnColumn() {
currentColumn = new int[3];
for (int i = 0; i < 3; i++) {
currentColumn[i] = random.nextInt(COLORS) + 1;
}
currentCol = COLS / 2; // 从中间列开始
currentRow = ROWS - 1; // 从顶部开始下落
}
/**
* 旋转三格列:将宝石顺序循环上移
* 例如 [红,绿,蓝] -> [绿,蓝,红]
*/
public void rotateColumn() {
int temp = currentColumn[0];
currentColumn[0] = currentColumn[1];
currentColumn[1] = currentColumn[2];
currentColumn[2] = temp;
}
/**
* 将当前三格列固定到游戏板上
* @return 是否成功固定(如果顶部被堵住则游戏结束)
*/
public boolean placeColumn() {
for (int i = 0; i < 3; i++) {
int targetRow = currentRow - i;
if (targetRow < 0 || targetRow >= ROWS) continue;
if (board[targetRow][currentCol] != EMPTY) {
return false; // 碰撞,游戏结束
}
board[targetRow][currentCol] = currentColumn[i];
}
return true;
}
/**
* 尝试将列向左/右移动
* @param delta -1表示左移,1表示右移
* @return 是否移动成功
*/
public boolean moveColumn(int delta) {
int newCol = currentCol + delta;
if (newCol < 0 || newCol >= COLS) {
return false; // 超出边界
}
// 检查目标列是否有足够的空间
for (int i = 0; i < 3; i++) {
int targetRow = currentRow - i;
if (targetRow >= 0 && targetRow < ROWS && board[targetRow][newCol] != EMPTY) {
return false; // 有障碍物
}
}
currentCol = newCol;
return true;
}
}
5. 完整游戏循环
public class ColumnsGame {
private boolean gameOver;
private int score;
public ColumnsGame() {
this.board = new int[ROWS][COLS];
this.random = new Random();
this.gameOver = false;
this.score = 0;
}
/**
* 执行一步:下落并固定列,然后触发级联消除
* @return 本轮获得的分数
*/
public int step() {
if (gameOver) return 0;
spawnColumn();
// 这里简化处理:直接让列下落到最底部可放置位置
// 实际游戏中玩家可以控制下落过程
while (canFall()) {
currentRow--;
}
if (!placeColumn()) {
gameOver = true;
System.out.println("游戏结束!最终得分:" + score);
return 0;
}
int roundScore = cascadeEliminate();
score += roundScore;
return roundScore;
}
/**
* 检查当前列是否还能继续下落
*/
private boolean canFall() {
if (currentRow - 3 < 0) return false; // 已经到达底部
for (int i = 0; i < 3; i++) {
int belowRow = currentRow - 3 - i;
if (belowRow >= 0 && board[belowRow][currentCol] != EMPTY) {
return false;
}
}
return true;
}
public static void main(String[] args) {
ColumnsGame game = new ColumnsGame();
// 模拟10步游戏
for (int i = 0; i < 10 && !game.isGameOver(); i++) {
System.out.println("=== 第" + (i + 1) + "步 ===");
game.step();
game.printBoard();
}
}
public boolean isGameOver() {
return gameOver;
}
}
算法复杂度分析
| 操作 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| DFS连通检测 | O(ROWS × COLS) | O(ROWS × COLS) |
| 重力下落 | O(ROWS × COLS) | O(ROWS) |
| 级联消除(每轮) | O(k × ROWS × COLS) | O(ROWS × COLS) |
| 整体步操作 | O(k × ROWS × COLS) | O(ROWS × COLS) |
其中 k 为连锁消除的轮数。由于 ROWS=13、COLS=6,实际运行非常快。
扩展方向
- 启发式AI玩家:使用蒙特卡洛模拟评估每种落地方案的期望得分,选择最优列位置和旋转顺序。
- 死局检测:当没有任何落地方案能避免游戏结束时提前判定。
- 高级消除规则:支持4连、5连的额外奖励和特殊宝石(如炸弹、彩虹宝石)。
小结
Columns游戏的核心算法围绕DFS连通块检测和重力下落级联消除展开。通过8方向DFS可以准确识别所有可消除的宝石组合,递归的级联处理则模拟了真实的物理连锁反应。这种”检测-消除-下落-再检测”的模式是众多消除类游戏(如消消乐、宝石迷阵)的共同算法骨架。