手记

贝蒂快扫雷(C语言)

​ ✨欢迎大家来到贝蒂大讲堂✨✨

​ 🎈🎈养成好习惯,先赞后看哦~🎈🎈

​ 所属专栏:贝蒂的游戏

​ 贝蒂的主页:Betty‘s blog

引言:

​ 扫雷相信大家小时候到玩过吧,那我们通过目前已学的知识,自己实现一个扫雷小游戏呢,答案自然是肯定的。

​ 本章你可能会用到的知识:

​ 1. 数组的使用:小小数组,给贝蒂坐下

​ 2. 随机数的生成:贝蒂的捣蛋小游戏

1. 游戏要求

  1. 玩家可以通过菜单选择玩游戏和退出游戏。
  2. 默认棋盘为9×9的格子。
  3. 默认雷的个数为10。
  4. 可以排查雷
    • 如果位置不是雷,就显⽰周围有⼏个雷,并且循环展开。
    • 如果位置是雷,就炸死游戏结束
    • 把除10个雷之外的所有⾮雷都找出来,排雷成功,游戏结束
    • 玩家可以对认为是雷的位置进行可能是雷或肯定是雷的标记。

2. 游戏分析

  1. 我们要在9×9的棋盘上操作,自然使用数组模拟,并且我们先假设0代表无雷,1代表雷

  2. 玩家选择一个格子排查,如果没雷,将显示周围八个格子雷的个数。

  • 为了解决可能得数组越界问题,所以尝试在外围增加“一圈”。
  1. 但是我们不可能把这个棋盘布置的信息给玩家看,所以要创建两个数组,一个数组(mine)负责布置雷的信息,一个数组(show)负责展现给玩家看。

  2. 如果玩家排查的位置没有雷,我们将显示周围雷的个数,为了不与布置雷的信息冲突,所以将无雷改为‘0’,有雷改为‘1’。

  3. 玩家棋盘初始全为‘*’,代表未排查。

3. 多文件操作

​ 为了方便代码的管理和保证游戏实现逻辑的清晰性,我们将采用多文件管理的模式。

  1. 创建头文件game.c,包含所有头文件(其他源文件只需引用它即可),以及所有游戏功能的展现。

  2. 创建源文件game.c,负责所有功能的具体代码实现。

  3. 创建源文件main.c,负责展现游戏实现的总体逻辑。

4. 简易菜单的实现

4.1功能

  1. 玩家可以通过选择1进入游戏,0退出游戏。

  2. 选错的话提醒玩家,重新选择。

4.2代码实现

为了完成这些目标,我们可以简单的do-while,switch结构实现。

代码如下:

void menu()
{
	printf("****************************\n");
	printf("*******    1. play    ******\n");
	printf("*******    0. exit    ******\n");
	printf("****************************\n");
}
int main()
{
	int input = 0;
	srand((unsigned int)time(NULL));//设置时间种子
	do
	{
		menu();//简易菜单的实现
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			system("cls");//清空屏幕,头文件<stdlib.h>
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误,重新选择\n");
			break;
		}
	} while (input);//选0结束游戏
	return 0;
}

5. 游戏功能实现

5.1 预定义信息

#define ROW 9//棋盘的行
#define COL 9//棋盘的列
#define ROWS ROW+2//扩展后的行
#define COLS COL+2//扩展后的列
#define MINES 10//雷的个数
#include<stdio.h>
#include<stdlib.h>
#include<time.h>

5.2 初始化棋盘

(1)要求

  1. mine棋盘初始化为全‘0’。

  2. show棋盘初始化为全‘1’。

(2)实现

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;
		}
	}
}

贝蒂说:“因为在game.c中实现游戏的具体功能,所以千万不要忘了在game.h中声明哦~”

5.3 打印棋盘

(1)要求

  1. 打印出棋盘中的元素。

  2. 利用—,|模拟出棋盘框。

  3. 显示出每行,每列的序号。

(2)实现

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
    printf("-----------------扫雷游戏----------------\n");
    for (int i = 0; i <= col; i++)
    {
        if (i == 0)
        {
            printf("    ");//四个空格
        }
        else
        {
            printf(" %d  ", i);//两个空格一个数字
        }

    }
    printf("\n");
    for (int i = 0; i <= col; i++)
    {
        if (i == 0)
        {
            printf("   |");//三个空格
        }
        else
        {
            printf("---|");
        }
    }
    printf("\n");
    for (int i = 1; i <= row; i++)
    {
        printf(" %d |", i);
        for (int j = 1; j <= col; j++)
        {
            printf(" %c |", board[i][j]);
        }
        printf("\n");
        for (int j = 1; j <= col; j++)
        {
            if (j == 1)
            {
                printf("   ");//三个空格
                printf("|---|");
            }
            else
            {
                printf("---|");
            }
        }
        printf("\n");
    }
    printf("-------------------结束线----------------\n");
}

(3)效果展示

5.4 埋雷

(1)要求

  1. 在mine棋盘中随机布置10个雷,雷为‘0’。

  2. 重复位置不能布置。

(2)实现

void SetMine(char board[ROWS][COLS], int row, int col)
{
    int num = MINES;
    while (num)
    {
        int x = rand() % row + 1;
        int y = rand() % col + 1;
        if (board[x][y] == '0')
        {
            board[x][y] = '1';
            num--;
        }
    }
}

(3)效果展示

5.5 排雷和标记

(1)要求·

  1. 玩家可以选择排雷或者标记。

  2. 保证玩家输入的值合法。

  3. 对于同一个坐标,第一次标记为!代表肯定是雷;第二次标记为?代表可能是雷;第三次 标记恢复‘*’。(’!'的数量最多等于雷的数量)

  4. 标记时,统计正确标记雷的个数。

  5. 排雷时,玩家输入要排除的坐标,如果是雷,则游戏结束;如果不是雷,则统计周围雷的个数。

  6. 如果周围没有雷,则向周围展开至有雷或者有标志位置为止。

贝蒂说:“因为布置的雷为‘1’,字符在内存中是以ASCII码值存储,所以统计方法为将周围八个格子值加起来减去8*‘0’。”

(2)排雷与递归排雷的实现

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');
}
void SpreadBoard(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)//递归排雷
{
    if (x < 1 || x>ROW || y < 1 || y>COL)//坐标不合理不递归
    {
        return;
    }
    if (show[x][y] != '*')//递归结束条件
    {
        return;
    }
    int count = GetMineCount(mine, x, y);
    if (count == 0)//无雷继续扩展
    {
        show[x][y] = ' ';//扩建为' '
        SpreadBoard(mine,show, x, y + 1);
        SpreadBoard(mine,show, x, y - 1);
        SpreadBoard(mine, show, x + 1, y - 1);
        SpreadBoard(mine, show, x + 1, y +1);
        SpreadBoard(mine, show, x + 1, y);
        SpreadBoard(mine, show, x - 1, y + 1);
        SpreadBoard(mine, show, x - 1, y - 1);
        SpreadBoard(mine, show, x - 1, y);
    }
    else
    {
        show[x][y] = '0' + count;//有雷输出个数
    }
}
int FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)//排查雷
{
    int x = 0;
    int y = 0;
    printf("请输入你要排查的坐标:>");
    scanf("%d%d", &x, &y);
    if (x >= 1 && x <= row && y >= 1 && y <= col)//坐标要合法
    {
        if (show[x][y] != '*')
        {
            if (show[x][y] == '!' || show[x][y] == '?')
            {
                printf("该位置已被标记,暂时不可排雷!\n");
            }
            else
            {
                printf("该位置已排雷,不可重复排雷!\n");
            }
        }
        else
        {
            if (mine[x][y] == '1')
            {
                return 0;//被炸死
            }
            else
            {
                SpreadBoard(mine, show, x, y);//递归扩展
            }
        }
    }
    else
    {
        printf("坐标非法!\n");
    }
    return 1;
}

效果展示:

(3)标记的实现

void MineMark(char mine[ROWS][COLS],char show[ROWS][COLS],int row, int col,int *p)
{
    static int sum = 0;//判断!的个数
    if (sum == MINES)
    {
        printf("!已满,请减少!的使用\n");
    }
    int x = 0, y = 0;
    printf("请输入标记的坐标:>");
    scanf("%d%d", &x, &y);
    if (x >= 1 && x <= row && y >= 1 && y <= col)//输入的坐标数值合法
    {
        if (show[x][y] == '*')//第一次标记
        {
            if (sum < MINES)
            {
                show[x][y] = '!';
                if (mine[x][y] == '1')
                {
                    (*p)++;//统计标记正确雷的个数
                }
                sum++;//!的个数+1
            }
        }
        else if (show[x][y] == '!')//第二次标记
        {
            show[x][y] = '?';
            if (mine[x][y] == '1')
            {
                (*p)--;//标记正确雷的个数减1
            }
            sum--;
        }
        else if(show[x][y]=='?')//第三次标记
        {
            show[x][y] = '*';
        }
        else
        {
            printf("该位置已经排查过,不能标记!\n");
        }
    }
    else
    {
        printf("输入坐标不合法,请重新输入!\n");
    }
}

5.6 胜利条件

(1)要求

  1. 成功用‘!’标记所有雷的正确位置,游戏胜利。

  2. 或者成功排查完除雷外所有格子,游戏胜利。

(2)实现

int IsWin(char board[ROWS][COLS],int row,int col, int* p)
{
    int count = 0;
    for (int i = 1; i <= row; i++)//遍历所有格子,看看是否已经排查完
    {
        for (int j = 1; j <= col; j++)
        {
            if (board[i][j] != '*' && board[i][j] != '!' && board[i][j] != '?')
            {
                count++;
            }
        }
    }
    if (count == row * col - MINES||*p==MINES)//如果排查完,或者成功标记完雷的个数
    {
        return 1;//游戏胜利
    }

    return 0;
}

6. 游戏主逻辑的搭建

6.1搭建顺序

  1. 初始化棋盘。

  2. 埋雷。

  3. 玩家选择标记还是埋雷。

  4. 判断玩家是否被炸死。

  5. 判断玩家是否胜利。

  6. 如果步骤4或5未成功执行,就重复执行步骤3,4,5。

6.2 实现

void game()
{
	char mine[ROWS][COLS]; //存放布置好的雷
	char show[ROWS][COLS]; //展示棋盘
	InitBoard(mine, ROWS, COLS, '0');
	InitBoard(show, ROWS, COLS, '*');
	//打印棋盘
	//DisplayBoard(mine, ROW, COL);
	//DisplayBoard(show, ROW, COL);
	//1. 布置雷
	SetMine(mine, ROW, COL);
	//DisplayBoard(mine, ROW, COL);
	//2. 排查雷
	//FindMine(mine, show, ROW, COL);
	int input = 0;
	int flag = 0;
	int num = -1;
	while (1)
	{
		DisplayBoard(show, ROW, COL);
		choose();
		printf("请选择排雷或者标记\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			num=FindMine(mine, show, ROW, COL);
			Sleep(900);//停顿0.9秒
			system("cls");//刷新棋盘
			break;
		case 2:
		    MineMark(mine, show, ROW, COL, &flag);
			Sleep(900);
			system("cls");//刷新棋盘
			break;
		default:
			printf("输入非法情重新输入\n");
			Sleep(900);
			system("cls");//刷新棋盘
			break;
		}
		if (num == 0)//判断是否被炸死
		{
			DisplayBoard(show, ROW, COL);
			DisplayBoard(mine, ROW, COL);
			printf("很遗憾,你被炸死了\n");
			break;
		}
		int ret=IsWin(show,ROW,COL,&flag);
		if (ret)//判断是否胜利
		{
			DisplayBoard(show, ROW, COL);
			DisplayBoard(mine, ROW, COL);
			printf("恭喜你,扫雷成功\n");
			break;
		}
	}
}

6.3 补充

为了让玩家正确理解如何进行游戏,我们应该提前告诉玩家游戏规则。

void rules()
{
	printf("游戏规则如下:\n");
	printf("1.你可以通过排雷和标记逐渐逼近胜利条件\n");
	printf("2.对同一坐标标记1次是!,标记2次是?,第3次恢复*\n");
	printf("3.标记!代表肯定是雷,标记?代表可能是雷\n");
	printf("4.标记!的次数最多为雷的个数\n");
	printf("5.如果选中雷,则游戏失败\n");
	printf("6.当成功标记所有雷,或者排查完所有格子,游戏胜利\n");
}

7. 源码

7.1 main.c

#include"game.h"
void menu()
{
	printf("****************************\n");
	printf("*******    1. play    ******\n");
	printf("*******    0. exit    ******\n");
	printf("****************************\n");
}
void choose()
{
	printf("********************************************\n");
	printf("***************    1. 排雷     *************\n");
	printf("***************    2. 标记     *************\n");
	printf("********************************************\n");
}
void rules()
{
	printf("游戏规则如下:\n");
	printf("1.你可以通过排雷和标记逐渐逼近胜利条件\n");
	printf("2.对同一坐标标记1次是!,标记2次是?,第3次恢复*\n");
	printf("3.标记!代表肯定是雷,标记?代表可能是雷\n");
	printf("4.标记!的次数最多为雷的个数\n");
	printf("5.如果选中雷,则游戏失败\n");
	printf("6.当成功标记所有雷,或者排查完所有格子,游戏胜利\n");
}
void game()
{
	char mine[ROWS][COLS]; //存放布置好的雷
	char show[ROWS][COLS]; //展示棋盘
	InitBoard(mine, ROWS, COLS, '0');
	InitBoard(show, ROWS, COLS, '*');
	//打印棋盘
	//DisplayBoard(mine, ROW, COL);
	//DisplayBoard(show, ROW, COL);
	//1. 布置雷
	SetMine(mine, ROW, COL);
	//DisplayBoard(mine, ROW, COL);
	//2. 排查雷
	//FindMine(mine, show, ROW, COL);
	int input = 0;
	int flag = 0;
	int num = -1;
	while (1)
	{
		DisplayBoard(show, ROW, COL);
		choose();
		printf("请选择排雷或者标记\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			num=FindMine(mine, show, ROW, COL);
			Sleep(900);//停顿0.9秒
			system("cls");//刷新棋盘
			break;
		case 2:
		    MineMark(mine, show, ROW, COL, &flag);
			Sleep(900);
			system("cls");//刷新棋盘
			break;
		default:
			printf("输入非法情重新输入\n");
			Sleep(900);
			system("cls");//刷新棋盘
			break;
		}
		if (num == 0)//判断是否被砸死
		{
			DisplayBoard(show, ROW, COL);
			DisplayBoard(mine, ROW, COL);
			printf("很遗憾,你被炸死了\n");
			break;
		}
		int ret=IsWin(show,ROW,COL,&flag);
		if (ret)
		{
			DisplayBoard(show, ROW, COL);
			DisplayBoard(mine, ROW, COL);
			printf("恭喜你,扫雷成功\n");
			break;
		}
	}
}
int main()
{
	int input = 0;
	srand((unsigned int)time(NULL));//设置时间种子
	printf("   欢迎来到贝蒂的扫雷游戏\n");
	do
	{
		menu();//简易菜单的实现
		rules();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			system("cls");//清空屏幕,头文件<stdlib.h>
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误,重新选择\n");
			break;
		}
	} while (input);//选0结束游戏
	return 0;
}

7.1 game.h

#define ROW 9//棋盘的行
#define COL 9//棋盘的列
#define ROWS ROW+2//扩展后的行
#define COLS COL+2//扩展后的列
#define MINES 10//雷的个数
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<windows.h>
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);//初始化棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col);//打印棋盘
void SetMine(char board[ROWS][COLS], int row, int col);//布置雷
void MineMark(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col,int*p);//标记雷
int FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);//排雷
int IsWin(char board[ROWS][COLS],int row,int col,int *p);//判断是否胜利

7.3 game.c

#include"game.h"
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;
		}
	}
}
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
    printf("-----------------扫雷游戏----------------\n");
    for (int i = 0; i <= col; i++)
    {
        if (i == 0)
        {
            printf("    ");//四个空格
        }
        else
        {
            printf(" %d  ", i);//两个空格一个数字
        }

    }
    printf("\n");
    for (int i = 0; i <= col; i++)
    {
        if (i == 0)
        {
            printf("   |");//三个空格
        }
        else
        {
            printf("---|");
        }
    }
    printf("\n");
    for (int i = 1; i <= row; i++)
    {
        printf(" %d |", i);
        for (int j = 1; j <= col; j++)
        {
            printf(" %c |", board[i][j]);
        }
        printf("\n");
        for (int j = 1; j <= col; j++)
        {
            if (j == 1)
            {
                printf("   |");//三个空格
                printf("---|");
            }
            else
            {
                printf("---|");
            }
        }
        printf("\n");
    }
    printf("-------------------结束线----------------\n");
}
void SetMine(char board[ROWS][COLS], int row, int col)
{
    int num = MINES;
    while (num)
    {
        int x = rand() % row + 1;
        int y = rand() % col + 1;
        if (board[x][y] == '0')
        {
            board[x][y] = '1';
            num--;
        }
    }
}
void MineMark(char mine[ROWS][COLS],char show[ROWS][COLS],int row, int col,int *p)
{
    static int sum = 0;//判断!的个数
    if (sum == MINES)
    {
        printf("!已满,请减少!的使用\n");
    }
    int x = 0, y = 0;
    printf("请输入标记的坐标:>");
    scanf("%d%d", &x, &y);
    if (x >= 1 && x <= row && y >= 1 && y <= col)//输入的坐标数值合法
    {
        if (show[x][y] == '*')//第一次标记
        {
            if (sum < MINES)
            {
                show[x][y] = '!';
                if (mine[x][y] == '1')
                {
                    (*p)++;//统计正确雷的个数
                }
                sum++;//!的个数+1
            }
        }
        else if (show[x][y] == '!')//第二次标记
        {
            show[x][y] = '?';
            if (mine[x][y] == '1')
            {
                (*p)--;
            }
            sum--;
        }
        else if(show[x][y]=='?')//第三次标记
        {
            show[x][y] = '*';
        }
        else
        {
            printf("该位置已经排查过,不能标记!\n");
        }
    }
    else
    {
        printf("输入坐标不合法,请重新输入!\n");
    }
}
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');
}
void SpreadBoard(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)//递归排雷
{
    if (x < 1 || x>ROW || y < 1 || y>COL)//坐标不合理不递归
    {
        return;
    }
    if (show[x][y] != '*')//递归结束条件
    {
        return;
    }
    int count = GetMineCount(mine, x, y);
    if (count == 0)//无雷继续扩展
    {
        show[x][y] = ' ';//扩建为' '
        SpreadBoard(mine,show, x, y + 1);
        SpreadBoard(mine,show, x, y - 1);
        SpreadBoard(mine, show, x + 1, y - 1);
        SpreadBoard(mine, show, x + 1, y +1);
        SpreadBoard(mine, show, x + 1, y);
        SpreadBoard(mine, show, x - 1, y + 1);
        SpreadBoard(mine, show, x - 1, y - 1);
        SpreadBoard(mine, show, x - 1, y);
    }
    else
    {
        show[x][y] = '0' + count;//有雷输出个数
    }
}
int FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)//排查雷
{
    int x = 0;
    int y = 0;
    printf("请输入你要排查的坐标:>");
    scanf("%d%d", &x, &y);
    if (x >= 1 && x <= row && y >= 1 && y <= col)//坐标要合法
    {
        if (show[x][y] != '*')
        {
            if (show[x][y] == '!' || show[x][y] == '?')
            {
                printf("该位置已被标记,暂时不可排雷!\n");
            }
            else
            {
                printf("该位置已排雷,不可重复排雷!\n");
            }
        }
        else
        {
            if (mine[x][y] == '1')
            {
                return 0;//被炸死
            }
            else
            {
                SpreadBoard(mine, show, x, y);//递归扩展
            }
        }
    }
    else
    {
        printf("坐标非法!\n");
    }
    return 1;
}
int IsWin(char board[ROWS][COLS],int row,int col, int* p)
{
    int count = 0;
    for (int i = 1; i <= row; i++)//遍历所有格子,看看是否已经排查完
    {
        for (int j = 1; j <= col; j++)
        {
            if (board[i][j] != '*' && board[i][j] != '!' && board[i][j] != '?')
            {
                count++;
            }
        }
    }
    if (count == row * col - MINES||*p==MINES)//如果排查完,或者成功标记完雷的个数
    {
        return 1;//游戏胜利
    }

    return 0;
}

​ 🎈🎈 **完结撒花,完结撒花 !!**🎈🎈

0人推荐
随时随地看视频
慕课网APP