每日算法 — 使用java实现宝石方块:贪心匹配与级联消除算法

一、背景介绍

宝石方块(Columns)是一款经典的三消类益智游戏,1990年由世嘉发行。玩家通过控制下落的宝石组合,使三个或以上同色宝石横、竖或斜线连成一线即可消除,得分并触发上方宝石下落填补空位,可能引发连锁消除反应。

本游戏的核心算法包括:

  • 下落控制:根据用户输入旋转或加速宝石组合的下落。
  • 贪心匹配检测:每次宝石稳定后扫描全盘寻找三连以上同色宝石。
  • 级联消除:消除后上方宝石在重力作用下下落,可能形成新的匹配,需要反复迭代。
  • 连锁加分:连续触发的级联消除会获得递增的额外奖励。

二、整体设计

游戏采用 MVC 模式,将界面、逻辑和数据分离。核心数据结构是一个二维字符数组,0 表示空位,1-7 表示不同颜色的宝石。

  • Cell:表示宝石的单元格。
  • GameBoard:封装了棋盘状态、下落、匹配检测与消除等核心逻辑。
  • GameEngine:游戏引擎,负责游戏循环与状态推进。
  • GameMain:带 main 函数的入口,演示整局游戏。

三、完整代码实现

3.1 宝石单元格 Cell.java

/**
 * 宝石单元格
 * 颜色为 0 表示空位,1-7 表示 7 种宝石
 */
public class Cell {
    public int color; // 颜色
    public Cell(int color) { this.color = color; }
    public boolean isEmpty() { return color == 0; }
}

3.2 游戏棋盘 GameBoard.java

import java.util.*;

/**
 * 宝石方块游戏棋盘
 * 提供下落、匹配检测、消除与重力下落等核心算法
 */
public class GameBoard {
    public static final int ROWS = 12;     // 行数
    public static final int COLS = 6;      // 列数
    public static final int COLOR_COUNT = 7; // 宝石颜色数
    private final int[][] grid = new int[ROWS][COLS];

    /** 清空棋盘 */
    public void clear() {
        for (int[] row : grid) Arrays.fill(row, 0);
    }

    /**
     * 检查位置是否合法
     */
    private boolean inBounds(int r, int c) {
        return r >= 0 && r < ROWS && c >= 0 && c < COLS;
    }

    /**
     * 贪心检测所有可消除的宝石
     * 返回需要消除的坐标集合
     * 核心思路:
     *  1) 水平方向:对每一行,从左到右扫描连续同色段,长度 >=3 标记
     *  2) 垂直方向:对每一列,从下到上扫描连续同色段,长度 >=3 标记
     *  3) 斜线方向(对角线):从左上到右下、从右上到左下两个方向扫描
     * 时间复杂度 O(R*C)
     */
    public Set<String> findMatches() {
        Set<String> matched = new HashSet<>();
        // 水平扫描
        for (int r = 0; r < ROWS; r++) {
            int c = 0;
            while (c < COLS) {
                if (grid[r][c] == 0) { c++; continue; }
                int start = c;
                while (c < COLS && grid[r][c] == grid[r][start]) c++;
                if (c - start >= 3) {
                    for (int k = start; k < c; k++) matched.add(r + "," + k);
                }
            }
        }
        // 垂直扫描
        for (int c = 0; c < COLS; c++) {
            int r = 0;
            while (r < ROWS) {
                if (grid[r][c] == 0) { r++; continue; }
                int start = r;
                while (r < ROWS && grid[r][c] == grid[start][c]) r++;
                if (r - start >= 3) {
                    for (int k = start; k < r; k++) matched.add(k + "," + c);
                }
            }
        }
        // 对角线扫描(左上到右下)
        for (int sr = 0; sr < ROWS; sr++) {
            for (int sc = 0; sc < COLS; sc++) {
                if (grid[sr][sc] == 0) continue;
                int r = sr, c = sc;
                while (inBounds(r + 1, c + 1) && grid[r + 1][c + 1] == grid[sr][sc]) {
                    r++; c++;
                }
                if (r - sr >= 2 && c - sc >= 2) {
                    for (int dr = sr; dr <= r; dr++)
                        for (int dc = sc; dc <= c; dc++)
                            matched.add(dr + "," + dc);
                }
            }
        }
        // 对角线扫描(右上到左下)
        for (int sr = 0; sr < ROWS; sr++) {
            for (int sc = 0; sc < COLS; sc++) {
                if (grid[sr][sc] == 0) continue;
                int r = sr, c = sc;
                while (inBounds(r + 1, c - 1) && grid[r + 1][c - 1] == grid[sr][sc]) {
                    r++; c--;
                }
                if (r - sr >= 2 && sc - c >= 2) {
                    for (int dr = sr; dr <= r; dr++)
                        for (int dc = sc; dc >= c; dc--)
                            matched.add(dr + "," + dc);
                }
            }
        }
        return matched;
    }

    /**
     * 消除指定坐标的宝石
     */
    public int eliminate(Set<String> cells) {
        for (String s : cells) {
            String[] parts = s.split(",");
            grid[Integer.parseInt(parts[0])][Integer.parseInt(parts[1])] = 0;
        }
        return cells.size();
    }

    /**
     * 模拟重力,让宝石自然下落填补空位
     * 对每一列从下往上扫描,遇到空格就把上方非空元素下移
     * 时间复杂度 O(R*C)
     */
    public void applyGravity() {
        for (int c = 0; c < COLS; c++) {
            int writeRow = ROWS - 1;
            for (int r = ROWS - 1; r >= 0; r--) {
                if (grid[r][c] != 0) {
                    grid[writeRow--][c] = grid[r][c];
                }
            }
            // 顶部剩余位置清零
            for (int r = writeRow; r >= 0; r--) grid[r][c] = 0;
        }
    }

    /**
     * 级联消除算法
     * 反复执行:检测匹配 -> 消除 -> 重力 -> 再次检测
     * 直到没有新的匹配出现
     * 返回每级消除的宝石数(用于计算连锁奖励)
     */
    public List<Integer> cascade() {
        List<Integer> chain = new ArrayList<>();
        while (true) {
            Set<String> matched = findMatches();
            if (matched.isEmpty()) break;
            int count = eliminate(matched);
            chain.add(count);
            applyGravity();
        }
        return chain;
    }

    /**
     * 在指定位置放置一列宝石(自顶向下)
     * 模拟玩家释放组合后的稳定
     */
    public boolean placeFallingPiece(int col, int[] colors) {
        if (col < 0 || col >= COLS) return false;
        // 找到最低可放置位置
        int writeRow = ROWS - 1;
        for (int i = colors.length - 1; i >= 0; i--) {
            if (writeRow < 0) return false;
            grid[writeRow--][col] = colors[i];
        }
        return true;
    }

    /** 打印棋盘(调试用) */
    public void printBoard() {
        for (int[] row : grid) {
            StringBuilder sb = new StringBuilder();
            for (int v : row) sb.append(v == 0 ? " . " : " " + v + " ");
            System.out.println(sb);
        }
    }

    public int[][] snapshot() {
        int[][] copy = new int[ROWS][COLS];
        for (int i = 0; i < ROWS; i++) copy[i] = grid[i].clone();
        return copy;
    }
}

3.3 游戏引擎 GameEngine.java

import java.util.*;

/**
 * 游戏引擎
 * 负责:随机生成下落组合、模拟玩家操作、计分与连锁奖励
 */
public class GameEngine {
    private final GameBoard board = new GameBoard();
    private final Random rand = new Random();
    private int score = 0;
    private int chainLevel = 0;

    public GameBoard getBoard() { return board; }
    public int getScore() { return score; }

    /** 随机生成一个三格宝石组合 */
    public int[] generateFallingColors() {
        int[] colors = new int[3];
        for (int i = 0; i < 3; i++)
            colors[i] = rand.nextInt(GameBoard.COLOR_COUNT) + 1;
        return colors;
    }

    /**
     * 模拟玩家释放宝石组合
     * 触发级联消除与连锁奖励
     * 连锁奖励:第 N 级消除获得 基础分 * 2^(N-1)
     */
    public int dropPiece(int col) {
        int[] colors = generateFallingColors();
        if (!board.placeFallingPiece(col, colors)) return -1;
        int gained = 0;
        chainLevel = 0;
        for (int count : board.cascade()) {
            chainLevel++;
            gained += (int) (count * 10 * Math.pow(2, chainLevel - 1));
        }
        score += gained;
        return gained;
    }
}

3.4 入口 GameMain.java

import java.util.*;

/**
 * 宝石方块主程序
 * 模拟 20 步随机操作,输出棋盘演化与最终得分
 */
public class GameMain {
    public static void main(String[] args) {
        GameEngine engine = new GameEngine();
        Random rand = new Random();
        System.out.println("=== 宝石方块游戏开始 ===");
        for (int step = 1; step <= 20; step++) {
            int col = rand.nextInt(GameBoard.COLS);
            int gained = engine.dropPiece(col);
            System.out.printf("Step %2d: drop at col %d, gained %d, total %d%n",
                    step, col, gained, engine.getScore());
            engine.getBoard().printBoard();
            System.out.println();
        }
        System.out.println("=== 最终得分: " + engine.getScore() + " ===");
    }
}

四、算法分析

4.1 时间复杂度

  • 匹配检测 findMatches:扫描整盘四次(水平、垂直、两条对角线),每次扫描 O(RC)。因此单次匹配检测时间复杂度为 O(R·C)*。
  • 级联消除 cascade:每次级联至少消除 3 个宝石,最多消除 R·C 个,因此最坏情况下需要执行 O(R·C) 次级联。每次级联包括匹配 + 消除 + 重力各 O(R·C),总时间复杂度为 O((R·C)²)。
  • 重力应用 applyGravity:单列扫描 O(R),共 C 列,总时间复杂度 O(R·C)。

4.2 空间复杂度

  • 棋盘存储使用二维数组,空间复杂度 O(R·C)。
  • 匹配集合使用 HashSet 存储最坏 O(R·C) 个坐标。

五、可扩展方向

  1. AI 玩家:基于棋盘评估函数(高度差、匹配机会数、未来消除潜力)使用 Minimax 或蒙特卡洛搜索。
  2. 特殊宝石:炸弹宝石(消除同色所有)、彩虹宝石(万能色)。
  3. 连击特效:动画呈现连锁消除,增强视觉体验。
  4. 联机对战:双玩家同时操作同一棋盘,奖励与惩罚相互影响。

六、运行说明

将 Cell.java、GameBoard.java、GameEngine.java、GameMain.java 放入同一目录,使用 javac *.java && java GameMain 即可运行。每一步会打印棋盘状态与得分变化。