@TOC
本游戏程序和上一篇三子棋小程序的制作思路相仿,实现基础都是二维数组的运用

游戏主函数

首先,我们设计三子棋时,要设计选择进入游戏退出游戏的选项,以及输入错误内容时,重新选择的程序。
将游戏标题函数设置为

menu()

将游戏主题函数设置为

game()

若在选择时,输入内容不在程序设定范围内,则要求使用者重新输入。所以程序此处应有一个循环。
因此主函数程序设置如下:

int main()
{
    int input = 0;
    do
    {
        menu();
        printf("请选择:");
        scanf("%d", &input);
        switch (input)
        {
        case 1:
            game();
            break;
        case 2:
            printf("退出游戏\n");
            input -= 2;
            break;
        default:
            printf("输入错误,请重新选择\n");
            break;
        }
    } while (input);
    return 0;
}

当输入1时,进入case 1:运行game()函数;
当输入2时,进入case 2:打印“退出游戏”并跳出switch循环,在dowhile结尾进行检测时,检测结果为0,跳出循环,退出程序;
当输入其他字符时,进入default,打印‘“输入错误,请重新选择”。因为input非零,所以继续进行dowhile循环。

代码实现的初步工作

首先我们创建游戏程序文件==game.c==,和头文件==game.h==
为了提高系统的可操作性,我们先将程序中的行与列定义为==ROW==和==COL==
所以头文件中预处理指令如下:

#define _CRT_SECURE_NO_WARNINGS 1   //scanf函数防止报错
#include <stdio.h>                  //printf函数       
#include <time.h>                   //time函数,产生随机值(后面讲)
//例如9×9的棋盘,周边多一圈,这样便于进行周围雷数量的判断。周边一圈不进行打印,不设置雷
#define ROW 9   //行
#define COL 9   //列
#define ROWS ROW+2   //棋盘外围拓展一圈,便于后续的程序制作,这里后面再讲
#define COLS COL+2
//设定雷的数量
#define NUM 10
//这里将游戏中用到的棋盘尺寸,雷的数量提前设置好,提高程序整体的可变性,便于后续修改

然后在==game.c==和主函数所在文件==TigerMinesweeper.c==中引用头文件:

#include "game.h"

接着设计主函数中的==menu()==函数。因为打印游戏菜单不需要返回值,所以函数类型void即可:

void menu()
{
    printf("********扫雷游戏*********\n");
    printf("******** 1.play ********\n");
    printf("******** 2.exit ********\n");
    printf("************************\n");
}

根据主函数的设计,我们继续设计game()函数:
同理不需要返回值,所以函数类型为void:

void game()
{
    char mine[ROWS][COLS] = { 0 };    //创建雷的位置的 数组
    char show[ROWS][COLS] = { 0 };    //创建展示给玩家的页面 的数组
    //初始化棋盘
    InitBoard(mine, ROWS, COLS, '0');
    InitBoard(show, ROWS, COLS, '*');

    //布置雷
    SetMine(mine, ROW, COL);
    //打印棋盘
    DisplayBoard(show, ROW, COL);
    //开始逐步排查雷
    FindMine(mine, show, ROW, COL);
}

如上文所示,我们先整理出了一个大致的代码设计方向。

游戏实现原理与程序优化

初始化棋盘

首先我们需要将数组初始化
mine数组为雷组,“1”为有雷,“0”为无雷
show数组展示给玩家,“*”为初始展示界面,数字代表周围八个格子中雷的数量,0则说明无雷

void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
    int i = 0;
    for (i = 0; i < rows; i++)
    {
        int j = 0;
        for (j = 0; j < cols; j++)
        {
            board[i][j] = set;     //set对应传进来的参数,这样两个数组可以共用一个函数
        }
    }
} 

然后再在头文件==game.h==中声明,并做好注释:

//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);

打印棋盘

因为我们周围的一圈不算入游戏,只是为了程序进行时便于判断,故打印棋盘时,只打印1~9,0和10不打印(在行列都为9时)

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
    printf("-----扫雷 游戏-----\n");
    int i = 0;
    for (i = 0; i <= row; i++)
    {
        printf("%d ", i);
    }
    printf("\n");
    for (i = 1; i <= row; i++)
    {
        printf("%d ", i);
        int j = 0;
        for (j = 1; j <= col; j++)
        {
            printf("%c ", board[i][j]);
        }
        printf("\n");
    }
}

然后我们同样在头文件==game.h==中声明此函数,并做好注释:

//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col);

这样此函数既可用于打印显示给玩家的棋盘,也可以打印雷的分布图,便于后续程序的调试

布置雷

此处依然采用time函数,讲随机值取模后再+1后获得1~9的随机数。
依此方法获取雷的随机坐标,将二维数组中的0赋值为1(0为无雷,1为有雷)

void SetMine(char mine[ROWS][COLS], int row, int col)
{
    int count = NUM;
    while (count)
    {
        int x = rand() % row + 1;
        int y = rand() % col + 1;
        if (mine[x][y] != '1')
        {
            mine[x][y] = '1';
            count--;
        }
    }
}

同样,在头文件==game.h==中声明此函数,并做好注释:

//布置雷
void SetMine(char board[ROWS][COLS], int row, int col);

排查雷

排查雷的时候,是游戏的最重要部分,即玩家的操作部分。
所以我们要先理清游戏流程,以此着手制作

//1输入坐标
//2检查是否为雷
//2.1若是雷,GG
//2.2若不是雷,统计坐标周围有几个雷,存储数据到二维数组show中,游戏继续

所以我们可以这样制作出一串代码:

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
    int x = 0;
    int y = 0;
    while (1)
    {
        printf("请输入排查雷的坐标:\n");
        scanf("%d %d", &x, &y);
        if (x >= 1 && x <= row && y >= 1 && y <= col)
        {
            if (mine[x][y] == '1')         //是雷,GG
            {
                printf("你被炸死了,GG\n");
                DisplayBoard(mine, ROW, COL);
                break;
            }
            else                          
            {
                //不是雷,获取坐标周边的累个数
            }
        }
        else
        {
            printf("坐标不存在,请重新输入");
        }
    }
}

然后我们再思考如何获取玩家输入坐标周围的雷个数,这里设定一个函数来获取周围雷的数量:

//获取周边雷数量
int GetMineCount(char mine[ROWS][COLS], int x, int y)
{

}

此处我有两种思路:
①因为ASCII表的0~9的是连续排列的,所以:

//获取周边雷数量
int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
    return (mine[x - 1][y] +
        mine[x - 1][y - 1] +
        mine[x][y - 1] +
        mine[x + 1][y - 1] +
        mine[x + 1][y] +
        mine[x + 1][y + 1] +
        mine[x][y + 1] +
        mine[x - 1][y + 1] - 8 * '0');
}

②既然要获取周边的雷数量,那么我们首先知道的是,玩家所选坐标是没有雷的。
这么说,我们可以统计玩家所选坐标周围,总共九格中雷的数量。所以我们采用两重嵌套的for循环:

//获取周边雷数量
int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
    int i = 0;
    int j = 0;
    int count = 0;
    for (i = x - 1; i <= x + 1; i++)
    {
        for (j = y - 1; j <= y + 1; j++)
        {
            if (mine[i][j] == '1')
            {
                count++;
            }
        }
    }
    return count;
}

然后我们就可以完善先前的==排查雷==的函数了。
当游戏中所有雷被排除,记所有非雷的位置被玩家选择后,剩下未选择的都是雷,那么玩家胜利。
以此我们可以设置变量win,当玩家正确输入次数达到要求时,游戏结束,玩家胜利
如下:

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
    int x = 0;
    int y = 0;
    int win = 0;
    while (win < row*col - NUM)
    {
        printf("请输入排查雷的坐标:\n");
        scanf("%d %d", &x, &y);
        if (x >= 1 && x <= row && y >= 1 && y <= col)
        {
            if (mine[x][y] == '1')         //是雷,GG
            {
                printf("你被炸死了,GG\n");
                DisplayBoard(mine, ROW, COL);
                break;
            }
            else                          //不是雷,获取坐标周边的累个数
            {
                int count = GetMineCount(mine, x, y);
                show[x][y] = count + '0';
                DisplayBoard(show, ROW, COL);
                win++;
            }
        }
        else
        {
            printf("坐标不存在,请重新输入");
        }
    }
    if (win == row * col - NUM)
    {
        printf("恭喜你,排雷成功\n");
        DisplayBoard(mine, ROW, COL);
    }
}

但是你以为结束了吗?并没有
我发现,如果玩家输入同一坐标多次,那win会不停地增长,在win达到目标后玩家胜利。而事实上,玩家并没有将所有非雷的空选择(并没有把雷排除完)

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
    //1输入坐标
    //2检查是否为雷
    //2.1若是雷,GG
    //2.2若不是雷,统计坐标周围有几个雷,存储数据到二维数组show中,游戏继续
    int x = 0;
    int y = 0;
    int win = 0;
    while (win < row*col - NUM)
    {
        printf("请输入排查雷的坐标:\n");
        scanf("%d %d", &x, &y);
        if (x >= 1 && x <= row && y >= 1 && y <= col)
        {
            if (mine[x][y] == '1')         //是雷,GG
            {
                printf("你被炸死了,GG\n");
                DisplayBoard(mine, ROW, COL);
                break;
            }
            else                          //不是雷,获取坐标周边的累个数
            {
                int count = GetMineCount(mine, x, y);
                if (show[x][y] == '*')    //这里增加一次判断,若玩家已经排过雷,则不计入总次数
                {
                    win++;
                }
                show[x][y] = count + '0';
                DisplayBoard(show, ROW, COL);
            }
        }
        else
        {
            printf("坐标不存在,请重新输入");
        }
    }
    if (win == row * col - NUM)
    {
        printf("恭喜你,排雷成功\n");
        DisplayBoard(mine, ROW, COL);
    }
}

下面是传统艺能,挖坑。
以上程序,如果执行,可以很明显地发现,和以前玩电脑时的扫雷有很大区别。
至少两个坑吧
1.在周围都没有雷的时候,自动将周围的空白摊开,直到数字不为0(优化玩家体验)
2.不是简单的输入坐标,而是能使用鼠标光标进行游戏