c语言编写简单的扫雷游戏(附源码)

        今天还是同样的,在c语言课上学习了扫雷这个游戏,所以我又根据上课学到的东西,编写了自己的扫雷程序,也加入了很多新的东西,当然就算加了很多新的东西,也比不过大佬们的程序,这里所编写的就是一个简单的小游戏,希望对大家有帮助。

一、大纲

1.头文件(game.h)

2.测试程序文件(test.c)

2.1游戏目录

2.2主函数

2.3游戏程序

3.游戏程序文件(game.c)

3.1初始化棋盘

3.2打印棋盘

3.3设置地雷

3.4排查地雷

3.4.1整体内容

3.4.2地雷标记

3.4.3地雷计算

3.3.4周边排查

4.源码

4.1game.h

4.2test.c

4.3game.c

5.附 


1.头文件

#include <stdio.h>
#include <time.h>
#include <stdlib.h>

#define ROW 9
#define COL 9

#define ROWS ROW + 2
#define COLS COL + 2

#define easy_count 20

//初始化棋盘
//初始化地雷棋盘
void init_board_mine(char board[ROWS][COLS], int rows,int cols);
//初始化展示棋盘
void init_board_show(char board[ROWS][COLS], int rows,int cols);
//打印棋盘
void display_board(char board[ROWS][COLS],int row,int col);
//设置地雷
void set_mine(char board[ROWS][COLS],int row,int col);
//排查地雷
void find_mine(char mine[ROWS][COLS],char show[ROWS][COLS],int row,int col);

        本来想这一部分放在最后的,但是我后来又想了一下,还是放在了前面,因为里面包含了一些,整个程序都在使用的东西,比如定义的ROWCOL,以及ROWSCOLS,还有游戏的难易度easy_count(表示地雷的个数) ,并且还有一点,谁叫它是头文件呢~

        至于定义的ROWCOL,以及ROWSCOLS,为什么这样定义呢?是因为要照顾后续的函数,比如计算你选择的坐标周围有多少地雷,如果只定义9*9的话,可能有的时候会超出数组范围,所以设计横竖分别多了两行。

2.测试程序文件

        说完了头文件,接下来就是测试程序文件,他是整个游戏的大纲部分,内容还是很简单的,跟我上一个游戏--五子棋里面的一致。

2.1游戏目录

void menu()
{
    printf("------------------------------------\n");
    printf("--------------1.play----------------\n");
    printf("--------------2.exit----------------\n");
    printf("------------------------------------\n");

}

        这个地方就没有介绍的必要了,就是printf函数打印的东西。

2.2主函数

int main()
{
    //设置随机数的生成起点
    srand((unsigned int)time(NULL));
    int i = 0;
    do {
        menu();
        printf("请输入数字:");
        scanf("%d",&i);
        switch (i)
        {
            case 1:
                game();
                printf("是否想要继续游戏。\n");
                break;
            case 2:
                printf("程序已退出。\n");
                break;
            default:
                printf("输入错误,请重新输入!\n");
                break;
        }
    } while (i != 2);
    return 0;
}

        这里的东西和我上一次发的五子棋是一样的原理,主要有一个随机数生成起点,是为了后续随机埋地雷所设计的,其余的部分应该仔细看一下就明白了。

2.3游戏程序

void game()
{
    char mine[ROWS][COLS] = {0};//存放布置好的雷的信息
    char show[ROWS][COLS] = {0};//存放排查的信息
    //初始化棋盘
    //初始化mine棋盘
    init_board_mine(mine,ROWS,COLS);
    //初始化show棋盘
    init_board_show(show,ROWS,COLS);

    //设置地雷
    set_mine(mine,ROW,COL);
    //打印棋盘
    display_board(show,ROW,COL);
    //排查地雷
    find_mine(mine,show,ROW,COL);

}

        首先是创建两个字符数组,一个用于埋放地雷,一个用于给用户展示,因为毕竟不可能直接展示给用户地雷的埋放情况,也需要这部分信息,所以需要2个字符数组

        接下来的部分就是游戏主体部分,包括初始化棋盘、设置地雷、打印棋盘、排查地雷。这四个部分,在接下来我将一一介绍一下。

3.游戏程序文件(game.c)

3.1初始化棋盘

//初始化mine棋盘
void init_board_mine(char board[ROWS][COLS], int rows,int cols)
{
    int i = 0;
    int j = 0;
    for(i = 0;i < rows;i++)
        for(j = 0;j < cols;j++)
        {
            board[i][j] = '0';
        }

}

//初始化show棋盘
void init_board_show(char board[ROWS][COLS], int rows,int cols)
{
    int i = 0;
    int j = 0;
    for(i = 0;i < rows;i++)
        for(j = 0;j < cols;j++)
        {
            board[i][j] = '#';
        }

}

        这一部分也很好理解,就是单纯的数组遍历赋值,我们对mine棋盘,赋值为'0',对show棋盘赋值为'#',如果各位有觉得有什么比较好看的字符,也可以放上去。只不过要记住一点,在后续计算中使用的是这些字符的ascii码,不是对应的数字,因为这是字符变量

3.2打印棋盘

//打印棋盘
void display_board(char board[ROWS][COLS],int row,int col)
{
    int i = 0;
    int j = 0;
    //对竖行打印造成的空缺,用空格补齐
    printf("     ");
    //打印横行数字
    for(j = 1;j<=col;j++)
    {
        printf(" %d ",j);
    }
    printf("\n");//第一个仅仅是为了换行
    printf("\n");//第二个是为了和竖行的空格保持对齐
    for(i = 1;i<=row;i++)
    {
        printf(" %d ",i);//打印数行数字
        printf("  ");//打印竖行空格
        for(j = 1;j<=col;j++)
        {
            printf(" %c ",board[i][j]);
        }
        printf("\n");
    }

}

        这一部分打印棋盘就和之前的五子棋有所不同了,这也是为当时在五子棋上没有做的(太懒了,平时课有点多,就没有进一步优化,呜呜呜~)

        首先第一个打印函数,是为了补齐竖行造成的空缺,可以先看后续的步骤,可以更好的理解。接下来打印横行的数字,对应的是棋盘的横向坐标(1-9)。接下来的两个打印函数的用处都标在了注释里。第二个换行是因为强迫症,因为竖行与棋盘的间隔和横行对棋盘的间隔不一样,所以单独又写了一个。

        接下来到竖行坐标,在每一次打印竖行棋盘之前,先打印一下对应坐标,然后打印一个空格(这就是为什么上面打印横行时要打印一些空格),本来是想打印一个(|)竖线的,但是观感不好就换成空格了。接下来就是打印棋盘字符,记住我们打印的是show棋盘,不要打印错棋盘了

3.3设置地雷

//设置地雷
void set_mine(char board[ROWS][COLS],int row,int col)
{
    int count = easy_count;//可以在game.h中设置难度
    while(count)
    {
        //生成随机数(1~9)对应坐标
        int x = rand() % row + 1;
        int y = rand() % col + 1;

        if(board[x][y] == '0')
        {
            board[x][y] = '1';
            count--;//当count == 0时,while循环结束
        }
    }
}

        这一部分就是设置地雷的部分,首先我们要让系统知道,我们要设置多少地雷,也就是easy_count的值,我们可以在头文件里面写上normal_count和hard_count对应不同的难度和地雷个数。这里建立了一个整形变量count接收这个值。我用while循环来进行程序的循环执行,当count减到0时表示地雷埋完了,循环条件为false,循环结束

        接下来就是生成随机数,用于埋下地雷。毕竟不能所有的对局,地雷都是在同一个位置吧。

rand()函数就是用于生成随机数的,前面srand()就是用来给它生成一个随机数起点的,具体的使用方法可以参考别的大佬的关于这个函数的介绍。我们随机生成的数字和我们对应的row以及col取余数,得到的结果肯定在(0-8)之间,再加上一,结果即是(1-9)对应了我们的坐标。

        接下来使用一个if语句进行判断,假如说mine数组对应的位置上没有雷('0')我们则埋下地雷('1'),记住这里传的变量是mine数组,不是show数组。

3.4排查地雷

        这一部分就是整体游戏最难、最复杂的地方,我将拆成四分来数,具体的内容,在大纲中也介绍到了。

3.4.1整体内容
//排查地雷
void find_mine(char mine[ROWS][COLS],char show[ROWS][COLS],int row,int col)
{
    int x = 0;
    int y = 0;
    while (1)//构建死循环
    {
        printf("是否要进行地雷标记(y/n):");
        char mark = 0;
        scanf("\n%c",&mark);//不要惊奇为什么会有\n,因为当你进入菜单,准备游戏,输入1然后回车时,回车的\n会被这个scanf函数收取,所以我先他一步,写了一个\n
        if(mark == 'y')
        {
            mark_mine(show,mine);//地雷标记,再打印
            display_board(show,ROW,COL);
        }
        printf("请输入要排查的坐标(格式1 1):");
        scanf("\n%d %d", &x, &y);//此\n同上
        printf("\n");
        if(x>=1 && x<=row && y >=1 && y <= col)//对输入的合法性进行检查
        {
            if(mine[x][y] == '1')//如果是雷
            {
                int i = 0;
                int j = 0;
                for(i = 1 ;i<=row;i++)
                    for(j = 1;j<=col;j++)
                    {
                        if(mine[i][j] == '1')
                        {
                            show[i][j] = '@';//遍历然后展示出所有的地雷,为了与1做区分,所以采用了@
                        }
                    }
                display_board(show,ROW,COL);
                printf("很遗憾,你被炸死了.\n");
                printf("\n\n");//为了好看才写的~
                break;
            }
            else if(show[x][y] != '#')//防止重复输入
            {
                printf("已经检测,请重新输入。\n");
            }
            else//如果不是雷
            {
                int count = mine_count(mine,x,y);
                show[x][y] = count + '0';
                perimeter_search(mine,show,x,y);//进行周边查找
                display_board(show,ROW,COL);
            }
        }
        else
        {
            printf("输入非法,请重新输入。\n");
        }
    }
}

        可以看到这一部分还是很长的,我们一步一步来说明。

        首先我们先构成一个死循环,用于用户循环操作。接下来就是第一个功能--地雷标记。但是用户并不可能每一次都需要地雷标记,就比如游戏一开始也没人会去标记。所以在这里,我们先询问用户是否需要标记,不需要则进行正常的游戏,如果选择需要,我们则进行地雷标记功能,这个功能我后续会讲到,在标记后不要忘记打印一次棋盘哦~

注:至于为什么第一个scanf内有一个\n,我在注释里有说明:“不要惊奇为什么会有\n,因为当你进入菜单,准备游戏,输入1然后回车时,回车的\n会被这个scanf函数收取,所以我先他一步,写了一个\n”下面也有类似的代码,原理都是一样的。

        接下来进入地雷的排查(惊心动魄的时刻),首先我们对坐标进行合法化判断,如果不合法,则进行重新输入,如果合法则进行下一步判断。

        假如你输入的坐标是地雷的话,很遗憾,你被炸死了!当踩到地雷时,我们先对mine棋盘进行遍历,找到地雷所对应的坐标打印在展示棋盘上,然后打印show棋盘,告诉你所有雷的地址。接下来的两个换行是为了好看才写的。

        如果你输入的坐标不是地雷,恭喜你,你暂时安全了!接着进行第二个功能--计算地雷程序会计算你输入的坐标周围有几个地雷(为什么会写show[x][y] = count + '0';,这里先不用好奇,在下面会介绍)然后就是第三个功能--周边查找,这个就是第二个功能的升级版程序会查找你输入的坐标周围的八个地方是否是地雷,如果不是就输入它对应的周围有几个地雷。然后打印棋盘给用户,让你仔细斟酌一下接下来怎么走。

3.4.2地雷标记
//地雷标记
void mark_mine(char show[ROWS][COLS],char mine[ROWS][COLS])
{
    printf("请输入要标记的坐标:");
    int x = 0;
    int y = 0;
    scanf("%d %d",&x,&y);
    show[x][y] = '!';
    int count = 0;
    int i = 0;
    int j = 0;
    for(i = 1;i <=ROW;i++)//做这一部分的时候思考过一个问题,假如没有进入这个函数,不就不会提示标记完了吗,但是你都没有进入,你怎么标记呢
        for(j = 1;j <=COL;j++)
        {
            if(mine[i][j] == '1' && x == i && y == j )//循环遍历mine数组,如果标记正确则count++
            {
                count++;
                if(count == easy_count)//当count达到了总地雷的值时,便结束游戏
                {
                    printf("恭喜你,找到了所有的地雷!\n");
                    break;
                }
            }
        }
}

        当你在排查地雷之前选择了地雷标记,则会进入上图所示的程序。首先我们获取所需要标记的坐标,直接在show棋盘上进行字符替换然后进行排查,如果你找到了所有的地雷,则游戏胜利。(做这一部分的时候思考过一个问题,假如没有进入这个函数,不就不会提示标记完了吗。但是你都没有进入,你怎么标记呢~)

3.4.3地雷计算
//计算雷数
int mine_count(char board[ROWS][COLS],int x,int y)
{
    //简单的计算,记住对应的'1'和'0'是相对的ascii码值,不是数字1和2
    return (board[x-1][y] + board[x -1][y -1] + board[x][y -1] +
    board[x + 1][y - 1] + board[x + 1][y] + board[x + 1][y + 1] +
    board[x][y + 1] + board[x - 1][y + 1] - 8*'0');
}

        这里就是简单的计算,记住一点:对应的'1'和'0'是相应的ascii码值,不是数字的1和2。所以在计算上和一般的数值计算有微小的不同。然后我们返回的是int值。所以刚刚才会有这行代码:show[x][y] = count + '0';我们对得到的数字加上'0'即可得到计算的数字所对应的字符值。

3.3.4周边排查
//周边排查(只排查了一圈)
void perimeter_search(char mine[ROWS][COLS],char show[ROWS][COLS],int x,int y)
{
    //防止超过了数组的范围,因为是到对相应坐标的四周,8个坐标值进行判断
        if(x>=1 && y >=1 && x <= 9 && y <= 9)
        {
            int i = 0;
            int j = 0;
            for (i = -1; i < 2; i++)
                for (j = -1; j < 2; j++)
                {
                    if (mine[x + i][y + j] != '1')
                    {
                        int count = mine_count(mine, x + i, y + j);
                        show[x + i][y + j] = count + '0';
                        //这个递归操作,超内存了,所以给注释掉了,后续再想想能不能优化一下
                        //perimeter_search(mine,show,x + i,y + j);
                    }
                }
        }
}

        首先为了防止超过数组范围,我们会对相应的x和y的值进行判断。如果在范围内,我们则对相应坐标四周的数值进行判断,如果不是地雷,我们就计算它对应周围有多少地雷,并打印到show棋盘上。

注:本来是想设计成和一般的扫雷一样的功能,设计了一个递归,但是电脑提示内存超了,看一下后续能不能优化一下,或者各位大佬能不能给提一点意见。

4.源码

4.1game.h
#include <stdio.h>
#include <time.h>
#include <stdlib.h>

#define ROW 9
#define COL 9

#define ROWS ROW + 2
#define COLS COL + 2

#define easy_count 20

//初始化棋盘
//初始化地雷棋盘
void init_board_mine(char board[ROWS][COLS], int rows,int cols);
//初始化展示棋盘
void init_board_show(char board[ROWS][COLS], int rows,int cols);
//打印棋盘
void display_board(char board[ROWS][COLS],int row,int col);
//设置地雷
void set_mine(char board[ROWS][COLS],int row,int col);
//排查地雷
void find_mine(char mine[ROWS][COLS],char show[ROWS][COLS],int row,int col);
4.2test.c
#include"game.h"
#include "game.c"

void menu()
{
    printf("------------------------------------\n");
    printf("--------------1.play----------------\n");
    printf("--------------2.exit----------------\n");
    printf("------------------------------------\n");

}
void game()
{
    char mine[ROWS][COLS] = {0};//存放布置好的雷的信息
    char show[ROWS][COLS] = {0};//存放排查的信息
    //初始化棋盘
    //初始化mine棋盘
    init_board_mine(mine,ROWS,COLS);
    //初始化show棋盘
    init_board_show(show,ROWS,COLS);

    //设置地雷
    set_mine(mine,ROW,COL);
    //打印棋盘
    display_board(show,ROW,COL);
    //排查地雷
    find_mine(mine,show,ROW,COL);

}

int main()
{
    //设置随机数的生成起点
    srand((unsigned int)time(NULL));
    int i = 0;
    do {
        menu();
        printf("请输入数字:");
        scanf("%d",&i);
        switch (i)
        {
            case 1:
                game();
                printf("是否想要继续游戏。\n");
                break;
            case 2:
                printf("程序已退出。\n");
                break;
            default:
                printf("输入错误,请重新输入!\n");
                break;
        }
    } while (i != 2);
    return 0;
}
4.3game.c
//
// Created by 石丹阳 on 2023/9/15.
//

#include "game.h"

//初始化mine棋盘
void init_board_mine(char board[ROWS][COLS], int rows,int cols)
{
    int i = 0;
    int j = 0;
    for(i = 0;i < rows;i++)
        for(j = 0;j < cols;j++)
        {
            board[i][j] = '0';
        }

}

//初始化show棋盘
void init_board_show(char board[ROWS][COLS], int rows,int cols)
{
    int i = 0;
    int j = 0;
    for(i = 0;i < rows;i++)
        for(j = 0;j < cols;j++)
        {
            board[i][j] = '#';
        }

}

//打印棋盘
void display_board(char board[ROWS][COLS],int row,int col)
{
    int i = 0;
    int j = 0;
    //对竖行打印造成的空缺,用空格补齐
    printf("     ");
    //打印横行数字
    for(j = 1;j<=col;j++)
    {
        printf(" %d ",j);
    }
    printf("\n");//第一个仅仅是为了换行
    printf("\n");//第二个是为了和竖行的空格保持对齐
    for(i = 1;i<=row;i++)
    {
        printf(" %d ",i);//打印数行数字
        printf("  ");//打印竖行空格
        for(j = 1;j<=col;j++)
        {
            printf(" %c ",board[i][j]);
        }
        printf("\n");
    }

}

//设置地雷
void set_mine(char board[ROWS][COLS],int row,int col)
{
    int count = easy_count;//可以在game.h中设置难度
    while(count)
    {
        //生成随机数(1~9)对应坐标
        int x = rand() % row + 1;
        int y = rand() % col + 1;

        if(board[x][y] == '0')
        {
            board[x][y] = '1';
            count--;//当count == 0时,while循环结束
        }
    }
}

//计算雷数
int mine_count(char board[ROWS][COLS],int x,int y)
{
    //简单的计算,记住对应的'1'和'0'是相对的ascii码值,不是数字1和2
    return (board[x-1][y] + board[x -1][y -1] + board[x][y -1] +
    board[x + 1][y - 1] + board[x + 1][y] + board[x + 1][y + 1] +
    board[x][y + 1] + board[x - 1][y + 1] - 8*'0');
}

//周边排查(只排查了一圈)
void perimeter_search(char mine[ROWS][COLS],char show[ROWS][COLS],int x,int y)
{
    //防止超过了数组的范围,因为是到对相应坐标的四周,8个坐标值进行判断
        if(x>=1 && y >=1 && x <= 9 && y <= 9)
        {
            int i = 0;
            int j = 0;
            for (i = -1; i < 2; i++)
                for (j = -1; j < 2; j++)
                {
                    if (mine[x + i][y + j] != '1')
                    {
                        int count = mine_count(mine, x + i, y + j);
                        show[x + i][y + j] = count + '0';
                        //这个递归操作,超内存了,所以给注释掉了,后续再想想能不能优化一下
                        //perimeter_search(mine,show,x + i,y + j);
                    }
                }
        }
}

//地雷标记
void mark_mine(char show[ROWS][COLS],char mine[ROWS][COLS])
{
    printf("请输入要标记的坐标:");
    int x = 0;
    int y = 0;
    scanf("%d %d",&x,&y);
    show[x][y] = '!';
    int count = 0;
    int i = 0;
    int j = 0;
    for(i = 1;i <=ROW;i++)//做这一部分的时候思考过一个问题,假如没有进入这个函数,不就不会提示标记完了吗,但是你都没有进入,你怎么标记呢
        for(j = 1;j <=COL;j++)
        {
            if(mine[i][j] == '1' && x == i && y == j )//循环遍历mine数组,如果标记正确则count++
            {
                count++;
                if(count == easy_count)//当count达到了总地雷的值时,便结束游戏
                {
                    printf("恭喜你,找到了所有的地雷!\n");
                    break;
                }
            }
        }
}

//排查地雷
void find_mine(char mine[ROWS][COLS],char show[ROWS][COLS],int row,int col)
{
    int x = 0;
    int y = 0;
    while (1)//构建死循环
    {
        printf("是否要进行地雷标记(y/n):");
        char mark = 0;
        scanf("\n%c",&mark);//不要惊奇为什么会有\n,因为当你进入菜单,准备游戏,输入1然后回车时,回车的\n会被这个scanf函数收取,所以我先他一步,写了一个\n
        if(mark == 'y')
        {
            mark_mine(show,mine);//地雷标记,再打印
            display_board(show,ROW,COL);
        }
        printf("请输入要排查的坐标(格式1 1):");
        scanf("\n%d %d", &x, &y);//此\n同上
        printf("\n");
        if(x>=1 && x<=row && y >=1 && y <= col)//对输入的合法性进行检查
        {
            if(mine[x][y] == '1')//如果是雷
            {
                int i = 0;
                int j = 0;
                for(i = 1 ;i<=row;i++)
                    for(j = 1;j<=col;j++)
                    {
                        if(mine[i][j] == '1')
                        {
                            show[i][j] = '@';//遍历然后展示出所有的地雷,为了与1做区分,所以采用了@
                        }
                    }
                display_board(show,ROW,COL);
                printf("很遗憾,你被炸死了.\n");
                printf("\n\n");//为了好看才写的~
                break;
            }
            else if(show[x][y] != '#')//防止重复输入
            {
                printf("已经检测,请重新输入。\n");
            }
            else//如果不是雷
            {
                int count = mine_count(mine,x,y);
                show[x][y] = count + '0';
                perimeter_search(mine,show,x,y);//进行周边查找
                display_board(show,ROW,COL);
            }
        }
        else
        {
            printf("输入非法,请重新输入。\n");
        }
    }
}

5.附

        感谢各位的阅读,有问题的地方或者建议可以留言告诉我~感谢各位的支持。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shishishishi~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值