我们这次要实现的是一个 Linux 控制台版 V1 原型,功能包括:
固定大小二维棋盘
随机生成初始棋盘,且没有初始三连
控制台打印棋盘
给每个格子标记索引,用户通过索引交换方块
仅允许交换相邻方块
支持横向三连及以上检测
支持纵向三连及以上检测
支持同时多处消除
支持消除后清空格子
支持重力下落
支持顶部随机补块
支持自动连锁消除
支持多轮连续操作
支持输入 q 退出程序
这篇文章讲解的是“核心玩法逻辑”,不包含:
图形界面
鼠标操作
动画特效
音效
特殊方块
分数系统
关卡系统
这个项目最核心的数据结构就是棋盘。
我们使用一个二维数组表示它:
int board[ROWS][COLS];比如定义一个 8x8 棋盘:
#define ROWS 8#define COLS 8
每个格子里存一个整数:
1 ~ 5:表示 5 种不同类型的方块
0:表示空格子
因为我们现在还没有鼠标点击操作,所以让用户直接输入“行列坐标”会稍微麻烦。 更简单的方法是:
给每个格子分配一个一维索引。
例如 8x8 棋盘,共有 64 个格子,索引范围就是:
0 ~ 63索引和二维坐标的转换公式是:
row = index / COLS;col = index % COLS;
例如:
index = 10
row = 10 / 8 = 1
col = 10 % 8 = 2
所以索引 10 对应的是第 1 行第 2 列。
我们还会在控制台打印出类似这样的内容:
[12:3]它的含义是:
这个格子的索引是 12
它当前的方块类型是 3
这样玩家就可以输入:
12 13表示交换索引 12 和索引 13 的方块。
玩家输入两个索引之后,我们先要判断两件事:
第一,这两个索引是否合法。 第二,这两个格子是否相邻。
合法索引的判断很简单:
index >= 0 && index < ROWS * COLS相邻则只允许两种情况:
同一行,列差为 1
同一列,行差为 1
也就是说,只有上下左右可以交换,斜着不行。
我们的目标是找出所有满足以下条件的格子:
横向连续 3 个及以上相同
纵向连续 3 个及以上相同
注意,不是只找一处,而是:
一次扫描要找出整个棋盘上所有可消除的位置。
为此,我们引入另一个二维数组:
int marks[ROWS][COLS];它的作用是“标记哪些位置应该被消除”。
规则是:
marks[r][c] = 1:这个格子要消除
marks[r][c] = 0:这个格子不消除
以每一行为单位,从左往右扫描。 找到一段连续相同数字的区间。
例如某一行:
2 2 2 4 5 5 5 5那么:
前面 2 2 2 是一段长度为 3 的连续区间
后面 5 5 5 5 是一段长度为 4 的连续区间
只要长度大于等于 3,就把整段都标记为 1。
纵向扫描和横向扫描完全类似,只是把“行”和“列”交换一下。
例如某一列:
33312
前 3 个格子组成纵向三连,那么把它们在 marks 中标记出来。
一边扫描,一边直接把匹配位置清空。
这样容易导致问题,因为一旦你提前把某个位置改成 0,后续扫描的结果就会受到影响。
正确做法是分两步:
第一步:只负责“找出所有该消除的位置”,写入 marks。 第二步:根据 marks 统一执行消除。
当 marks 标记完成后,消除就很简单了。
遍历整个棋盘:
如果 marks[r][c] == 1
就把 board[r][c] = EMPTY
这是整个项目里另一个很有代表性的算法。
当某些格子被消除后,棋盘里会出现空位。 这时每一列都要执行“方块向下掉落”。
例如一列数据可能原本是:
10302
下落之后应该变成:
00132
也就是说:
所有非空方块向下集中
空位集中到顶部
下落之后,棋盘顶部会留下空位。 为了让棋盘重新填满,我们只需要遍历整个棋盘:
如果某个位置是 EMPTY
就生成一个新的随机方块
三消游戏最有趣的地方之一,就是“连锁”。
意思是:
你交换一次方块,触发第一轮消除
第一轮消除后,方块下落
下落后又形成了新的三连
又触发第二轮消除
补块后可能还会继续形成第三轮……
这种连续自动触发的消除,就叫连锁。


