三消算法

起因

大一上学期的C语言大作业我们编写了一个消消乐游戏,示例视频如下

效果不错,但代码惨不忍睹,尤其是消去判断部分(游戏主体逻辑)。
今天我在这里回顾一下编写的经历及一些改进。

编写代码的过程

构造了一个7行8列的数组存放数字0-5,其中,0代表的是被消去的状态,1-5分别对应着5种图案
而消去判断函数的作用就是接受这个数组,并判断此时是否有可以消去的地方
将其置为0,然后使0上方的图案下降,最上面的随机生成新图案
返回是否可以消去(true/false),并记录下本次消去的数目(作为分数)
重复调用这个函数,直到返回值为0,即不再有可以消去的地方,就完成了一次排查
我最开始写的代码如下

bool judgemove(int a[][8], int *score)
{
    judge = 0;
    for (i = 1; i < 6; i++)
        for (j = 0; j < 8; j++)
        {
            if (a[i][j] != 0&&a[i][j] == a[i - 1][j] && a[i][j] == a[i + 1][j])//逐列寻找
            {
                for (m = i - 1; m <= i + 1; m++)
                {
                    a[m][j] = 0;//找到了三个相连,就将其置为0
                    setpicture(m, j, a[m][j]);//打印消去状态(黑色方块)
                }
                judge = 1;//确实消去了
            }
        }
    for (i = 0; i < 7; i++)//同上,但是逐行寻找
        for (j = 1; j < 7; j++)
        {
            if (a[i][j] != 0 && a[i][j] == a[i][j - 1] && a[i][j] == a[i][j + 1])
            {
                for (m = j - 1; m <= j + 1; m++)
                {
                    a[i][m] = 0;
                    setpicture(i, m, a[i][m]);
                }
                judge = 1;
            }
        }
    Sleep(200);//停顿以显示哪些被消去了
    for (j = 0; j < 8; j++)
        for (i = 6; i >= 0; i--)//逐列从下向上遍历,实现移动
            while (a[i][j] == 0)
            {
                for (m = i; m > 0; m--)
                    a[m][j] = a[m - 1][j];
                if (m == 0)//如果到达最上面,最上面的变成随机颜色
                    a[m][j] = random_picture();
            }
    for (i = 0; i < 7; i++)//重新打印
        for (j = 0; j < 8; j++)
            setpicture(i, j, a[i][j]);
    return judge;
}  

这段代码的不足是明显的,即仅仅判断了连续三个消除的情况,对于连续四个或五个,只会消去前三个
同时,对于T字形,由于纵向遍历在前,只会消除一竖排,而横排不会被消去
这都是很明显的错误
基于此,不断尝试,修修补补,终于把所有情况都写进来了,得到如下代码

bool judgemove(int a[][8],int version ,int *score)//version代表不同位置使用的判断(是否掉落与替换)
{
    static int judge1=1, judge2=0, judge3=0, i, j, m, n,p,q;
    int countx = 0, county = 0;//countx,county用来判断一次消去的个数(有没有五连消)
    if(version==1)
        Sleep(200);//暂停来调试
    judge1 = 0;//judge1用来判断还有没有待消去的棋子
    //如果出现了可以消去的棋子,先赋成零(形象化变为白色)
    for (i = 0; i < 7; i++)//遍历方向是从上到下,一竖列一竖列遍历
        for (j = 0; j < 8; j++)
        {
            judge2 = 0;
            for (m = i + 1; m<=5 && a[i][j] != 0 && a[m][j] == a[i][j] && a[m + 1][j] == a[i][j]; m++)
            //从小到大遍历,m<=5是因为最多纵向遍历到倒数第三行
            //利用了逻辑运算符的短路特性,最后一个相同点要在循环外面赋值
            {
                if (a[m][j - 1] == a[m][j] && a[m][j + 1] == a[m][j]&&j<7&&j>0)//t字形
                {
                    if (a[m][j + 2] == a[m][j])
                        a[m][j + 2] = 0;
                    if (a[m][j - 2] == a[m][j])
                        a[m][j - 2] = 0;
                    if ((!a[m][j + 2]) && (!a[m][j - 2]))//形成炸弹
                        bomb++;
                    a[m][j - 1] = 0;
                    a[m][j + 1] = 0;
                }
                judge3 = 0;
                for (n = j - 1; n > 0 && a[m][j] != 0 && a[m][n] == a[m][j] && a[m][n - 1] == a[m][j]; n--)//反向探索
                //每消去一个棋子前,都要看看它的横向能不能消去
                {
                    a[m][n] = 0;
                    judge3 = 1;
                    countx++;
                }
                if (judge3&&n>=0&&n<8)
                    a[m][n] = 0;
                judge3 = 0;
                for (n = j + 1; j < 6 && a[m][j] != 0 && a[m][n] == a[m][j] && a[m][n + 1] == a[m][j]; n++)//正向探索
                {
                    a[m][n] = 0;
                    judge3 = 1;
                    countx++;
                }
                if (judge3 && n < 8 && n >= 0)
                {
                    a[m][n] = 0;
                    countx++;
                    if (countx >= 5)
                        bomb++;
                    countx = 0;
                }
                a[m][j] = 0;
                county++;
                judge2 = 1;
                judge1 = 1;
            }
            if (judge2 == 1)//judge2=1代表纵向消去,为最后一个相同点单独赋值
            {
                if (a[m][j - 1] == a[m][j] && a[m][j + 1] == a[m][j]&&j<7&&j>0)//t字形
                {
                    if (a[m][j + 2] == a[m][j])
                        a[m][j + 2] = 0;
                    if (a[m][j - 2] == a[m][j])
                        a[m][j - 2] = 0;
                    if ((!a[m][j + 2]) && (!a[m][j - 2]))//形成炸弹
                        bomb++;
                    a[m][j - 1] = 0;
                    a[m][j + 1] = 0;
                }
                judge3 = 0;
                for (n = j - 1; n > 0 && a[m][j] != 0 && a[m][n] == a[m][j] && a[m][n - 1] == a[m][j]; n--)//反向探索
                {
                    a[m][n] = 0;
                    judge3 = 1;
                    countx++;
                }
                if (judge3&&n>=0&&n<8)
                    a[m][n] = 0;
                judge3 = 0;
                for (n = j + 1; j < 6 && a[m][j] != 0 && a[m][n] == a[m][j] && a[m][n + 1] == a[m][j]; n++)//正向探索
                {
                    a[m][n] = 0;
                    judge3 = 1;
                    countx++;
                }
                if (judge3&&n<8&&n>=0)
                {
                    a[m][n] = 0;
                    countx++;
                    if (countx >= 5)
                        bomb++;
                    countx = 0;
                }
                a[m][j] = 0;
                county+=2;
                if (county >= 5)
                    bomb++;
                county = 0;
            }
            for (n = j + 1; n <= 6 && a[i][j] != 0 && a[i][n] == a[i][j] && a[i][n + 1] == a[i][j]; n++)
                //n<=6是因为最多横向遍历到倒数第三列,逻辑运算符利用了短路特性
            {
                judge3 = 0;
                for (m = i - 1; m>1 && a[i][n] != 0 && a[m][n] == a[i][n] && a[m - 1][n] == a[i][n]; m--)//反向探索
                {
                    a[m][n] = 0;
                    judge3 = 1;
                    county++;
                }
                if (judge3&&m>=0&&n<8)
                    a[m][n] = 0;
                judge3 = 0;
                for (m = i + 1; i < 5 && a[i][n] != 0 && a[m][n] == a[i][n] && a[m + 1][n] == a[i][n]; m++)//正向探索
                {
                    a[m][n] = 0;
                    judge3 = 1;
                    county++;
                }
                if (judge3 && m < 7 && n < 8)
                {
                    a[m][n] = 0;
                    county++;
                    if (county >= 5)
                        bomb++;
                    county = 0;
                }
                a[i][n] = 0;
                countx++;
                judge2 = 2;
                judge1 = 1;
            }
            if (judge2 == 2)//judge2=2代表横向消去,为最后一个相同点单独赋值
            {
                judge3 = 0;
                for (m = i - 1; m > 1 && a[i][n] != 0 && a[m][n] == a[i][n] && a[m - 1][n] == a[i][n]; m--)
                {
                    a[m][n] = 0;
                    judge3 = 1;
                    county++;
                }
                if (judge3&&m>=0 && n < 8)
                    a[m][n] = 0;
                judge3 = 0;
                for (m = i + 1; i < 5 && a[i][n] != 0 && a[m][n] == a[i][n] && a[m + 1][n] == a[i][n]; m++)
                {
                    a[m][n] = 0;
                    judge3 = 1;
                    county++;
                }
                if (judge3 && m < 7 && n < 8)
                {
                    a[m][n] = 0;
                    county++;
                    if (county >= 5)
                        bomb++;
                    county = 0;
                }
                a[i][n] = 0;
                countx += 2;
                if (countx >= 5)
                    bomb++;
                countx = 0;
            }
            if (judge2)//只要有消去,就将起始点消去(前面要保留,否则不能判断既横向又纵向的情况)
                a[i][j] = 0;
        }
    if (version != 2&&version!=3)//如果是判断能不能移动或初次进入,不打印	
    {
        for (p = 0; p < 7; p++)//重新打印,显示出黑色矩形
            for (q = 0; q < 8; q++)
            {
                setpicture(p, q, a[p][q]);
                if (a[p][q] == 0)
                    (*score)++;//分数增加
            }
    }
    if (version == 2)
        return judge1;
    if(version==1)
        Sleep(200);
    for (j = 0; j < 8; j++)
        for (i = 6; i >= 0; i--)
            while (a[i][j] == 0)
            {
                for (m = i; m > 0; m--)
                    a[m][j] = a[m - 1][j];
                if (m == 0)//如果到达最上面,最上面的变成随机图像
                    a[m][j] = random_picture();
            }
    if(version==1)
        for (i = 0; i < 7; i++)//重新打印
            for (j = 0; j < 8; j++)
                setpicture(i, j, a[i][j]);
    return judge1;
}  

实在太长了,我自己都懒得看,而且有太多重复的部分
值得一提的是,该函数又引入了一个参数version
version代表该函数被调用的位置
version1:游戏过程中,正常地判断,返回,消去,打印
version2:反馈给鼠标控制函数移动是否合法,只需要是否能消去这个结果(true/false)
version3:刚进入游戏,此时不会把中间过程打印在屏幕上
并且会记录每次消去的个数,到达五个会给予奖励
这个函数成功地实现了所需的功能,并作为了提交版本
而今天再看时,发现如此复杂的原因是每次都是找到时就将它们置零
这就代表当时就要考虑所有情况,十分麻烦
今天我做了修改,仅仅将要消去的元素记录下来,到最后再一并消去
代码如下

bool judgemove(int a[][8], int version, int* score)//version代表不同位置使用的判断(是否掉落与替换)
{
    static int judge1 = 1, judge2 = 0, i, j, m, n, p, q;
    int countx = 0, county = 0;//countx,county用来判断一次消去的个数(有没有五连消)
    int count, change[2][100], t=0;//change数组记录下要消去的坐标,第一行记录行数,第二行记录列数
    if (version == 1)
        Sleep(200);//暂停来调试
    judge1 = 0;//judge1用来判断还有没有待消去的棋子
    //如果出现了可以消去的棋子,先赋成零(形象化变为白色)
    for (i = 0; i < 7; i++)//遍历方向是从上到下,一竖列一竖列遍历
        for (j = 0; j < 8; j++)
        {
            judge2 = 0;
            for (m = i + 1; m <= 5 && a[i][j] != 0 && a[m][j] == a[i][j] && a[m + 1][j] == a[i][j]; m++)
            //从小到大遍历,m<=5是因为最多纵向遍历到倒数第三行
            //利用了逻辑运算符的短路特性,最后一个相同点要在循环外面赋值
            {
                change[0][t] = m;
                change[1][t] = j;
                ++t;
                ++county;
                judge2 = 1;
                judge1 = 1;
            }
            if (judge2 == 1)//judge2=1代表纵向消去,为最后一个相同点单独赋值
            {
                change[0][t] = m;
                change[1][t] = j;
                ++t;
                county += 2;
                if (county >= 5)
                    bomb++;
                county = 0;
            }
            for (n = j + 1; n <= 6 && a[i][j] != 0 && a[i][n] == a[i][j] && a[i][n + 1] == a[i][j]; n++)
                //n<=6是因为最多横向遍历到倒数第三列,逻辑运算符利用了短路特性
            {
                change[0][t] = i;
                change[1][t] = n;
                ++t;
                ++countx;
                judge2 = 2;
                judge1 = 1;
            }
            if (judge2 == 2)//judge2=2代表横向消去,为最后一个相同点单独赋值
            {
                
                change[0][t] = i;
                change[1][t] = n;
                ++t;
                countx += 2;
                if (countx >= 5)
                    bomb++;
                countx = 0;
            }
            if (judge2)//只要有消去,就将起始点消去
                a[i][j] = 0;
        }
    for (i = 0; i < t; ++i)
        a[change[0][i]][change[1][i]] = 0;
    if (version != 2 && version != 3)//如果是判断能不能移动或初次进入,不打印	
    {
        for (p = 0; p < 7; p++)//重新打印,显示出黑色矩形
            for (q = 0; q < 8; q++)
            {
                setpicture(p, q, a[p][q]);
                if (a[p][q] == 0)
                    (*score)++;//分数增加
            }
    }
    if (version == 2)
        return judge1;
    if (version == 1)
        Sleep(200);
    for (j = 0; j < 8; j++)
        for (i = 6; i >= 0; i--)
            while (a[i][j] == 0)
            {
                for (m = i; m > 0; m--)
                    a[m][j] = a[m - 1][j];
                if (m == 0)//如果到达最上面,最上面的变成随机图像
                    a[m][j] = random_picture();
            }
    if (version == 1)
        for (i = 0; i < 7; i++)//重新打印
            for (j = 0; j < 8; j++)
                setpicture(i, j, a[i][j]);
    return judge1;
}  

至此,该函数已足够简单了(还是有一些重复的部分)
或许以后还会修改呢😂