C语言实现扫雷游戏(递归展开+标记功能)

0.扫雷游戏

扫雷网页版这是具体的扫雷游戏。

我们可以在这个游戏中借鉴参考,一步步地将其中的功能用C语言构思出来。

1.扫雷游戏的思路

1.我们可以定义两个二维字符数组,分别是hid和sur,hid字符数组存储着'0'和'1',其中'0'代表不是地雷,'1'代表是地雷。sur字符数组最开始存储'#',当我们扫某个坐标的雷时,如果这个坐标是雷,就会结束游戏,否则会调用一个函数计算它周围8个坐标雷的数量,然后把这个数量存储到这个坐标中。

2.初始化数组(棋盘),把hid全部赋值为'0',sur全部赋值为'#'。

3.布置雷,随机在hid棋盘上赋值为'1',而这个'1'的数量就是雷的数量。

4.打印棋盘,给玩家看到。

5.计算雷的数量,玩家输入要扫雷的坐标,如果是雷,游戏结束,如果不是雷,计算它周围雷的数量,这里我们可以知道如果我们输入的坐标是棋盘的边界的话,就要分情况计算周围雷的数量,例如一个棋盘长宽都是9,如果坐标在(0,0),我们要计算周围3个坐标雷的数量,如果坐标在(0,1),我们要计算周围5个坐标雷的数量,这样要判断坐标的位置,十分麻烦,我们可以定义一个11x11大小的棋盘,最外一圈都是'0',然后取居中的9x9就好,这样就算坐在边界,也是一个方法计算,我们定义中间那块棋盘为居中棋盘。

2.具体的代码

代码是分成了3个文件,main.c是选择开始或者退出的函数,game.h则保存着宏定义和函数的定义,game.c是函数的具体实现。

1.主函数选择是否进行游戏

#define _CRT_SECURE_NO_WARNINGS 1

#include "game.h"

int main()
{
	int input = 0;
	srand((unsigned int)time(NULL));
	do
	{
		system("cls");
		menu1(); //菜单1
		printf("请选择(0/1): ");
		scanf("%d", &input);
		system("cls");
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("\n退出成功!");
			Sleep(3000);
			break;
		default:
			printf("选择非法,请选择(0/1): ");
			Sleep(1000);
			break;
		}
	} while (input);

	return 0;
}

其中Sleep()函数可以让计算机程序休眠,不至于有些信息一闪而过,其中单位是ms,那么Sleep(1000)就是睡眠1s,头文件是windows.h。

system("cls")函数是清屏的意思,执行完这行代码就会将控制台的信息清空,头文件也是windows.h。

2.game.h和game.c

#pragma once

#include 
#include 
#include 
#include 

//基础模式
#define BaseR 11 //真实的棋盘长宽
#define BaseC 11
#define DisBaseR BaseR-2 //展示的棋盘长宽
#define DisBaseC BaseC-2
#define BaseThunder 10

//中级模式
#define Mid_R 16 //真实的棋盘长宽
#define Mid_C 16
#define DisMidR  Mid_R-2 //展示的棋盘长宽
#define DisMidC Mid_C-2
#define MidThunder 40

//专家模式
#define expert_R 18 //真实的棋盘长宽
#define expert_C 32
#define Dis_expert_R expert_R-2 //展示的棋盘长宽
#define Dis_expert_C expert_C-2
#define expertThunder 99



//游戏开始界面
void menu1();

//难度选择界面
void menu2();

//游戏函数
void game();

//扫雷游戏基础难度
void mine_sweeper();
//中级难度
void mine_sweeper2();
//专家难度
void mine_sweeper3();

//初始化棋盘
void Init_board(char *board, int r, int c, char ch);

//打印棋盘
void Disply_board(char* board, int r, int c, int C);

//设置雷
void Set_Bomb(char* board,int r,int c, int C,int num);

//排雷
int de_Bomb(char* board, char* board2, int r, int c,int *st,int R,int C,int num);

//计算周围雷的数量
void Num_Bomb(char* board, char* board2, int x, int y, int C);

//判断是否排完了全部雷
int Ju_Win(char* board, char* board2,int r,int c, int C);
#define _CRT_SECURE_NO_WARNINGS 1

#include "game.h"

void menu1()
{
    printf("*******扫雷********\n");
    printf("*****1.开始游戏****\n");
    printf("*****0.退出********\n");
    printf("*******************\n");
    printf("格子里的数字表示这个格子的周围一圈(8格)有几个雷\n\n");
}
void menu2()
{

    printf("*****1.基础*****\n");
    printf("*****2.中级*****\n");
    printf("*****3.专家*****\n\n");
}

void game()
{
    
    int input = 0;
    while (1)
    {
        system("cls");
        menu2();
        printf("请选择游戏难度(1/2/3): ");
        scanf("%d", &input);
        system("cls");
        switch (input)
        {
        case 1:
            mine_sweeper();
            goto end;
            break;
        case 2:
            mine_sweeper2();
            goto end;
            break;
        case 3:
            mine_sweeper3();
            goto end;
            break;
        default:
            printf("请输入合法的选项(1/2/3)");
            Sleep(1000);
            break;
        }
    }
end:
    printf("游戏已经退出!\n");
    Sleep(3000);
}

void mine_sweeper()
{
    char hid[BaseR][BaseC] = {'0'};
    char sur[BaseR][BaseC] = {'0'};

 
    Init_board(hid, BaseR, BaseC, '0');
    Init_board(sur, BaseR, BaseC, '#'); 

    for (int i = 0; i 9)
            printf("%d ", i);
        else
        printf("%d  ",i);
    }
    printf("\n");
    for (int i = 1; i <= r; i++)
    {
        if (i > 9)
            printf("%d ", i);
        else
            printf("%d  ", i);
        for (int j = 1; j <=c; j++)
        {
            printf("%c  ", *(board + i * C + j));
        }
        printf("\n");
    }
}

void Set_Bomb(char* board, int r, int c, int C,int num)
{
    while (num)
    {
        int x = rand() % r + 1;
        int y = rand() % c + 1;

        if (*(board + x* C + y) != '1')
        {
            *(board + x * C + y) = '1';
            num--;
        }
        else
            continue;
    }
}

int de_Bomb(char* board, char* board2,int r, int c,int *st, int R,int C,int num)
{
    int x, y;
    int flag = 0;
    while (1)
    {
        printf("标雷是1,排雷是0,请输入(0/1): ");
        scanf("%d", &flag);
        switch (flag)
        {
        case 0:
            goto de_bomb;
            break;
        case 1:
            goto mark;
            break;
        default:
            printf("请选择(0/1):");
            Sleep(1000);
            system("cls");
        }
    }
    de_bomb:
    while (1)
    {
        printf("选择你排雷的坐标: ");
        scanf("%d %d", &x, &y);
        while (1)
        {
            if (*(board + x * C + y) == '1' && *st > 0)
            {
                Init_board(board, R, C, '0');
                Set_Bomb(board, r, c,C,num);
            }
            else
                break;
        }
        *st = -2;
        if (x >= 1 && x <= r && y >= 1 && y <= c)
        {
            if (*(board2 + x * C + y) != '#'&& *(board2 + x * C + y) != '*')
            {
                printf("排雷重复,请选择其他的坐标: ");
                continue;
            }
            if (*(board + x * C + y) == '0')
            {
                
                Num_Bomb(board, board2, x, y,C);
                return 0;
            }
            else
            {
                printf("炸了,游戏失败\n");
                return 1;
            }
        }
        printf("请输入正确的坐标x:(1~9)y:(1~9): ");
    }
    mark:
    while (1)
    {
        printf("选择你标记雷的坐标: ");
        scanf("%d %d", &x, &y);
        if (*(board2 + x * C + y) == '#')
        {
            *(board2 + x * C + y) = '*';
            return 0;
        }
        else
            printf("请选择未排过的坐标来标记! ");
    }
}


void  Num_Bomb(char* board, char* board2, int x, int y, int C)
{
    
    *(board2 + x * C + y) ='0'+(
        *(board + x * C + y-1) +
        *(board + x * C + y+1) +
        *(board + (x-1) * C + y) +
        *(board + (x-1) * C + y-1) +
        *(board + (x-1) * C + y+1) +
        *(board + (x+1) * C + y-1) +
        *(board + (x+1) * C + y+1) +
        *(board + (x+1) * C + y) -8*'0');
    if (*(board2 + x * C + y) == '0')
    {
        if(*(board2 + (x - 1) * C + y) =='#')
        Num_Bomb(board, board2, x - 1, y,C);
        if (*(board2 + (x + 1) * C + y) == '#')
        Num_Bomb(board, board2, x + 1, y, C);
        if (*(board2 + x * C + y + 1) == '#')
        Num_Bomb(board, board2, x, y + 1, C);
        if (*(board2 + (x - 1) * C + y + 1) == '#')
        Num_Bomb(board, board2, x - 1, y + 1, C);
        if (*(board2 + (x + 1) * C + y + 1) == '#')
        Num_Bomb(board, board2, x + 1, y + 1, C);
        if (*(board2 + x * C + y - 1) == '#')
        Num_Bomb(board, board2, x, y - 1, C);
        if (*(board2 + (x - 1) * C + y - 1) == '#')
        Num_Bomb(board, board2, x - 1, y - 1, C);
        if (*(board2 + (x + 1) * C + y - 1) == '#')
        Num_Bomb(board, board2, x + 1, y - 1, C);
    }
    if (*(board2 + x * C + y) == '0')
        *(board2 + x * C + y) = ' ';
        
}

int Ju_Win(char* board, char* board2,int r,int c,int C)
{
    for (int i = 1; i < r; i++)
    {

        for (int j = 1; j < c; j++)
        {
            if (*(board + i * C + j) == '0' && *(board2 + i * C + j) == '#')
                return 0;
        }
    }
    return 1;
}


void mine_sweeper2()
{
    char hid[Mid_R][Mid_C] = { '0' };
    char sur[Mid_R][Mid_C] = { '0' };


    Init_board(hid, Mid_R, Mid_C, '0');
    Init_board(sur, Mid_R, Mid_C, '#');

    for (int i = 0; i < Mid_R; i++)
    {
        if (i == 0 || i == Mid_C - 1)
        {
            for (int j = 0; j < Mid_R; j++)
            {
                sur[i][j] = '-';
            }
        }
        else
        {
            sur[i][0] = '-';
            sur[i][Mid_C - 1] = '-';
        }
    }

    Set_Bomb(hid, DisMidR, DisMidC, Mid_C, MidThunder);
    int win = 0;
    int st = 1;
    while (1)
    {
        Disply_board(sur, DisMidR, DisMidC, Mid_C);
        int res = de_Bomb(hid, sur, DisMidR, DisMidC, &st, Mid_R,Mid_C, MidThunder);
        if (res)
            break;
        win = Ju_Win(hid, sur, DisMidR, DisMidC, Mid_C);
        if (win)
            break;
        system("cls");
    }
    if (win)
        printf("你赢了,排完了全部雷!\n");
}


void mine_sweeper3()
{
    char hid[expert_R][expert_C] = { '0' };
    char sur[expert_R][expert_C] = { '0' };


    Init_board(hid, expert_R, expert_C, '0');
    Init_board(sur, expert_R, expert_C, '#');

    
    for (int i = 0; i < expert_R; i++)
    {
        if (i == 0 || i == expert_R - 1)
        {
            for (int j = 0; j < expert_C; j++)
            {
                sur[i][j] = '-';
            }
        }
        else
        {
            sur[i][0] = '-';
            sur[i][expert_C - 1] = '-';
        }
    }

    Set_Bomb(hid, Dis_expert_R, Dis_expert_C, expert_C, expertThunder);
    int win = 0;
    int st = 1;
    while (1)
    {
        Disply_board(sur, Dis_expert_R, Dis_expert_C, expert_C);
        int res = de_Bomb(hid, sur, Dis_expert_R, Dis_expert_C, &st, expert_R, expert_C, expertThunder);
        if (res)
            break;
        win = Ju_Win(hid, sur, Dis_expert_R, Dis_expert_C, expert_C);
        if (win)
            break;
        system("cls");
    }
    if (win)
        printf("你赢了,排完了全部雷!\n");
}

3.菜单1和菜单2

void menu1()
{
    printf("*******扫雷********\n");
    printf("*****1.开始游戏****\n");
    printf("*****0.退出********\n");
    printf("*******************\n");
}
void menu2()
{

    printf("*****1.基础*****\n");
    printf("*****2.中级*****\n");
    printf("*****3.专家*****\n\n");
}

4.选择游戏难度的函数

void game()
{
    
    int input = 0;
    while (1)
    {
        system("cls");
        menu2(); //菜单2
        printf("请选择游戏难度(1/2/3): ");
        scanf("%d", &input);
        system("cls");
        switch (input)
        {
        case 1:
            mine_sweeper(); //基础难度
            goto end;
            break;
        case 2:
            mine_sweeper2(); //中级难度
            goto end;
            break;
        case 3:
            mine_sweeper3(); //专家难度
            goto end;
            break;
        default:
            printf("请输入合法的选项(1/2/3)");
            Sleep(1000);
            break;
        }
    }
end:
    printf("游戏已经退出!\n");
    Sleep(3000);
}

goto语句可以去到标志的地方,例如end:  是一个标志,只要玩了一把过后就会退出游戏,回到主函数选择是否游戏。

5.基础难度的扫雷游戏

三种难度就只有棋盘大小不同,所以我就只讲基础难度,以下是基础难度的函数。

void mine_sweeper()
{
    char hid[BaseR][BaseC] = {'0'}; //先创建两个字符数组
    char sur[BaseR][BaseC] = {'0'};

 
    Init_board(hid, BaseR, BaseC, '0'); //这里是初始化两个字符数组
    Init_board(sur, BaseR, BaseC, '#'); 

    for (int i = 0; i 

1.实现初始化棋盘函数

void Init_board(char* board, int r, int c, char ch) 
{
//r 和 c是真实棋盘的长和宽,其他函数的r和c都是居中棋盘的长和宽,居中棋盘在思路里有定义
    for (int i = 0; i < r; i++)
    {
        for (int j = 0; j < c; j++)
        {
           *( board+i*c+j) = ch;
        }
    }
}

因为二维数组在内存中是连续存放的,所以二维数组用一级指针接收,c是列数,所以i*c是跳过i行的意思,所以*(board+i*c+j)和数组[i][j]是等价的,char ch存放我们要用什么字符来初始化。

2.打印棋盘函数

void Disply_board(char* board, int r, int c, int C)
{

    这里的r 和 c 是居中棋盘的长和宽,C是真实的列数
    for (int i = 0; i <= c; i++)
    {
        if(i>9)
            printf("%d ", i);
        else
        printf("%d  ",i);
    }
    printf("\n");
    for (int i = 1; i <= r; i++)
    {
        if (i > 9)
            printf("%d ", i);
        else
            printf("%d  ", i);
        for (int j = 1; j <=c; j++)
        {
            printf("%c  ", *(board + i * C + j));
        }
        printf("\n");
    }
}

一位数和两位数要分情况打印,不然会不整齐。

3.布置雷

void Set_Bomb(char* board, int r, int c, int C,int num)
{
    //num是雷的个数,C是真实的列数
    while (num)
    {
        int x = rand() % r + 1;
        int y = rand() % c + 1;
        //x,y的随机范围是1~9
        if (*(board + x* C + y) != '1') //如果不是雷的话就布置雷,同时雷的数量-1
        {
            *(board + x * C + y) = '1';
            num--;
        }
        else
            continue;
    }
}

4.扫雷的过程

int de_Bomb(char* board, char* board2,int r, int c,int *st, int R,int C,int num)
{
    int x, y;
    int flag = 0; //决定这步操作是标记雷还是排雷
    while (1)
    {
        printf("标雷是1,排雷是0,请输入(0/1): ");
        scanf("%d", &flag);
        switch (flag)
        {
        case 0:
            goto de_bomb; //去排雷
            break;
        case 1: 
            goto mark; //去标记雷
            break;
        default:
            printf("请选择(0/1):");
            Sleep(1000);
            system("cls");
        }
    }
    de_bomb:
    while (1)
    {
        printf("选择你排雷的坐标: ");
        scanf("%d %d", &x, &y);
        while (1)
        {
            if (*(board + x * C + y) == '1' && *st > 0) 
            {
                //如果这一次排到雷并且是第一次
                //那么就会初始化棋盘并且重新布置雷的位置
                //直到玩家选择的排雷坐标不是雷
                Init_board(board, R, C, '0');
                Set_Bomb(board, r, c,C,num);
            }
            else
                break;
        }
        *st = -2;  //当第一次不是雷或者已经不是第一次了,就将*st赋值为-2,*st就是之前的st
        //这样以后就再也不会用到,上面的代码,确保了第一次操作不是雷
        if (x >= 1 && x <= r && y >= 1 && y <= c) //坐标合法
        {
            if (*(board2 + x * C + y) != '#'&& *(board2 + x * C + y) != '*')
            {
                //如果棋盘已经排了或者标记过了,就提示重复了
                printf("排雷重复,请选择其他的坐标: ");
                continue;
            }
            if (*(board + x * C + y) == '0') //棋盘1如果是'0',就计算它周围雷的数量
            {
                
                Num_Bomb(board, board2, x, y,C);
                return 0;
            }
            else  //代表棋盘1是'1',是雷
            {
                printf("炸了,游戏失败\n");
                return 1;
            }
        }
        printf("请输入正确的坐标x:(1~9)y:(1~9): ");
    }
    mark:
    while (1)
    {
        printf("选择你标记雷的坐标: ");
        scanf("%d %d", &x, &y);
        if (*(board2 + x * C + y) == '#') //只有这个棋盘2还是未知才可以标记
        {
            *(board2 + x * C + y) = '*';  //标记过后就变成'*' 
            return 0;
        }
        else
            printf("请选择未排过的坐标来标记! ");
    }
}

5.计算雷的数量(递归)

void  Num_Bomb(char* board, char* board2, int x, int y, int C)
{
    
    *(board2 + x * C + y) ='0'+(
        *(board + x * C + y-1) +
        *(board + x * C + y+1) +
        *(board + (x-1) * C + y) +
        *(board + (x-1) * C + y-1) +
        *(board + (x-1) * C + y+1) +
        *(board + (x+1) * C + y-1) +
        *(board + (x+1) * C + y+1) +
        *(board + (x+1) * C + y) -8*'0');
    if (*(board2 + x * C + y) == '0') //周围8个坐标的递归
    {
        if(*(board2 + (x - 1) * C + y) =='#')
        Num_Bomb(board, board2, x - 1, y,C);
        if (*(board2 + (x + 1) * C + y) == '#')
        Num_Bomb(board, board2, x + 1, y, C);
        if (*(board2 + x * C + y + 1) == '#')
        Num_Bomb(board, board2, x, y + 1, C);
        if (*(board2 + (x - 1) * C + y + 1) == '#')
        Num_Bomb(board, board2, x - 1, y + 1, C);
        if (*(board2 + (x + 1) * C + y + 1) == '#')
        Num_Bomb(board, board2, x + 1, y + 1, C);
        if (*(board2 + x * C + y - 1) == '#')
        Num_Bomb(board, board2, x, y - 1, C);
        if (*(board2 + (x - 1) * C + y - 1) == '#')
        Num_Bomb(board, board2, x - 1, y - 1, C);
        if (*(board2 + (x + 1) * C + y - 1) == '#')
        Num_Bomb(board, board2, x + 1, y - 1, C);
    }
    if (*(board2 + x * C + y) == '0') //如果这个坐标雷的数量是0个,将它赋值为空格
        *(board2 + x * C + y) = ' ';
        
}

首先将棋盘1中这个坐标周围8个坐标的雷加起来,然后存放到棋盘2中。

那么什么情况下要递归呢

1.这个坐标雷的数量是'0'

2.周围某个坐标还没有被排过

如果没有条件2,因为如果两个相邻的坐标周围雷的数量都是'0',那么他们两个会无限递归

6.判断是否胜利

int Ju_Win(char* board, char* board2,int r,int c,int C)
{
    for (int i = 1; i < r; i++)
    {

        for (int j = 1; j < c; j++)
        {
            if (*(board + i * C + j) == '0' && *(board2 + i * C + j) == '#')
                return 0;
        }
    }
    return 1;
}

如果遍历两个数组,如果某个坐标不是雷并且还没被排过,那就还没胜利,就返回0。如果都不满足,就返回1。

写得比较粗糙,因为有些知识我还不太了解,比如二维数组传参,我这里每个函数都是用一级指针接收,并且加了一个变量(列数)C,模拟了二维数组来使用

你可能感兴趣的:(游戏,c语言)