从零开始设计并运行扫雷游戏(c语言)

1. 游戏概述

1.1 扫雷游戏的基本规则与目标

在扫雷游戏中,玩家要在格子中找雷,可以根据提示标记雷的位置。当排查的位置是雷,则玩家被炸死,玩家失败;当玩家排查完所有不是雷的位置,则玩家获胜。

 网页扫雷游戏

1.2 历史背景与流行版本

《扫雷》于1992年由微软发行的小游戏,在大多版本的Windows有预装,其Windows XP的扫雷最为经典。

2. 游戏设计和分析

2.1文件封装

2.1.1 game.h

游戏需要的数据类型和头文件等,如stdio,define...在这声明,在其他文件声明该头文件,减少重复的声明

2.1.2 game.c

游戏的函数实现等

2.1.3 test.c

游戏的测试逻辑

2.2 设计游戏

2.2.1 游戏菜单

首先,我们要为玩家提供一个菜单(printf 打印),要让玩家做出选择是否玩游戏。 给玩家选择  1. 玩游戏   0.退出

该过程至少有一次,采用do-while循环。

我们要记录玩家的选择(scanf 记录),并且要对选择做出判断(比如输入2(不合规))。

进入不同的事件。使用swtich,case和default的组合。

代码如下

game.h

#include <stdio.h> //使用printf , scanf

test.c

#include "game.h"


void game()
{
	;
}


void menu()//菜单
{
	printf("###########扫雷游戏##########\n");
	printf("#############################\n");
	printf("########## 1. play ##########\n");
	printf("########## 0. exit ##########\n");
	printf("#############################\n");
}


int main()
{
	int input = 0;
	do
	{
		menu();//菜单

		printf("请选择\n");

		scanf("%d", &input);//记录玩家输入


		switch (input)
		{
		case 1://玩游戏

			game();
			break;

		case 0://退出

			printf("退出\n");

			break;//跳出do-while循环

		default://无效输入

			printf("无效输入,请重新输入\n");//提示
			break;
		
		}


	} while (input);//input == 0 循环结束 


	return 0;
}

2.2.2 测试1.0

2.2.3 棋盘初始化

棋盘选择11行11列,最后打印9行9列,行和列用 define 定义,用二维数组(arr[][])。要注意的是初始化棋盘要创建雷棋盘和展示棋盘,它们的行列最好相同,方便使用同一个函数。

代码如下

game.h

#define ROW 9 //用宏定义行
#define COL 9 //用宏定义列
#define ROWS ROW + 2
#define COLS COL + 2


void initboard(char board[ROWS][COLS], int rows, int cols);//初始化棋盘函数声明

game.c

void initboard(char board[ROWS][COLS], int rows, int cols)//初始化棋盘函数
{
	int i = 0;

	for (i = 0; i < rows; i++)//行
	{
		int j = 0;

		for (j = 0; j < cols; j++)//列
		{
			board[i][j] = '0';
		}
	}
}

test.c

void game()
{
	char board[ROWS][COLS] = { 0 };
	char mine[ROWS][COLS] = { 0 };

	initboard(board, ROWS, COLS);//初始化展示棋盘
	initboard(mine, ROWS, COLS);//初始化雷棋盘
}

###为什么棋盘选择11行11列,最后却打印9行9列?

当玩家选择一个坐标,就要统计周围8个坐标雷的个数。如果,选择边缘的坐标,就会越界统计,导致程序出bug。

2.2.4 打印棋盘

打印棋盘用双for循环结构;注意打印不是从下标0开始,而是1;要打印坐标,方便玩家操作。

当打印大于10行或10列,会产生不对齐的情况,我们可这样解决

printf("%-nd",...);//向左对齐n个位置,同理%nd向右对齐...

game.h

void display(char arr[ROWS][COLS], int row, int col);//打印棋盘函数声明

game.c

void game()
{
	char board[ROWS][COLS] = { 0 };
	char mine[ROWS][COLS] = { 0 };

	initboard(board, ROWS, COLS, '*');//初始化展示棋盘
	initboard(mine, ROWS, COLS, '0');//初始化雷棋盘

	display(board, ROW, COL);//打印展示棋盘
	display(mine, ROW, COL);//打印雷棋盘
}

test.c

void display(char board[ROWS][COLS], int row, int col)//打印棋盘函数
{
	int i = 0;
	int j = 0;

	for (i = 0; i <= row; i++)//打印列下标
		printf("%d ", i);
	
	printf("\n");

	for (i = 1; i <= row; i++)//行
	{
		printf("%d ", i);//打印行下标

		for (j = 1; j <= col; j++)//列
		{

			printf("%c ", board[i][j]);

		}
		printf("\n");//末尾换行
	}
	printf("\n");
}

2.2.5 测试2.0

2.2.6 设置雷

2.2.6.1 分析

在设置雷时要注意以下情况

(1)将要设置雷的位置是否已有雷,有则重新设置

(2)设置雷的坐标要在符合范围

2.2.6.2 如何设置随机数

用 rand 函数,该函数的的使用要声明头文件 stdlib.h 。但 rand 函数生成的是伪随机数单独使用,每次生成的随机数的值不会改变,因为它的种子(srand()函数,括号内初始值为1)不变,如以下代码

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

int main()
{

	printf("%d\n", rand());
	printf("%d\n", rand());
	printf("%d\n", rand());
	printf("%d\n", rand());
	printf("%d\n", rand());

	return 0;
}

值始终为

1804289383

846930886

1681692777

1714636915

1957747793

要实现随机数我们要使用 time 函数,该函数的的使用要声明头文件 time.h 。time() 的值是1970年1月1日00:00:00至今的秒数(也称为时间戳),传入NULL,则返回时间值类型为 time_t 使用时要强转为 unsigined int 类型,该值是随机的,但是如果 srand 和 rand 的位置近,快速运行两次代码时,会出现生成随机数的不变的情况

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

int main()
{
	srand((unsigned int)time(NULL));
	printf("%d\n", rand());
	printf("%d\n", rand());

	return 0;
}

既然随机数有,但对于随机数的取值是有要求的范围是1-9

我们可以这样实现

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

int main()
{
	srand((unsigned int)time(NULL));

	int i = rand() % 9 + 1;
	int j = rand() % 9 + 1;

	printf("%d %d", i, j);
	return 0;
}

我们可以这样写

game.h

#include <stdlib.h> //使用srand, rand函数
#include <time.h> //使用time函数

#define MINE_COUNT 10 //雷的个数

void putmine(char mine[ROWS][COLS], int row, int col, int n);//设置雷函数声明

game.h

void putmine(char mine[ROWS][COLS], int row, int col, int n)//设置雷函数
{

	while (n)
	{
		int i = rand() % row + 1;//设置行的随机值 1-9
		int j = rand() % col + 1;//设置列的随机值 1-9

		if (mine[i][j] == '0')//判断该坐标是否有雷
		{
			mine[i][j] = '1';//设置为雷
			n--;
		}
	}

}

test.c

void game()
{
	char board[ROWS][COLS] = { 0 };
	char mine[ROWS][COLS] = { 0 };

	initboard(board, ROWS, COLS, '*');//初始化展示棋盘
	initboard(mine, ROWS, COLS, '0');//初始化雷棋盘

	display(board, ROW, COL);//打印展示棋盘
	display(mine, ROW, COL);//打印雷棋盘

	putmine(mine, ROW, COL, MINE_COUNT);//设置雷

}

2.2.7 测试3.0

注意!要想看到雷的分布,就要在 putmine(mine, ROW, COL, MINE_COUNT); 后再加上 display(mine, ROW, COL); 打印雷棋盘

2.2.8 排查雷

2.2.8.1 分析

当我们写排查雷函数时要对玩家输入的坐标做出以下判断

(1) 坐标是否合规(1. 是否已排查过,2. 是否超出范围)

否,则提醒并要求重新输入;是则考虑(2)

(2)在雷棋盘中对应的该坐标是否有雷

否,则考虑(3);是,则宣布玩家失败,展示雷棋盘并跳出排查雷函数

(3)是否达到胜利条件(变量 win == 0)

否,则,输出周围雷的个数并使win自减1;是,则宣布胜利

通过以上分析我们可以用下结构设计函数

while (win)
{
    if (1)//坐标是否合规
    {

        if (2)//在雷棋盘中对应的该坐标是否有雷
        {
            ...
        }
        else
        {

            if (3)//是否达到胜利条件(变量 win == 0)
            {
                ...
            }
        }
    }
    else
    {
        ...
    }

}
...//win==0 或 win > 0
2.2.8.2 统计雷的个数

如图

当玩家排查坐标(x,y)合规且未踩雷,我们要在雷棋盘统计(x,y)周围雷的个数,并把个数放入展示棋盘(x,y)中。

那么,如何统计,在雷棋盘中,我们放入的都是字符,它们都有对应的ASCII码值,且字符0和字符1的ASCII码值相差1,因此我们可以遍历图中所有坐标的字符,并都减去字符0,在最后加上字符0,还原为对应的字符。

void mine_count(char board[ROWS][COLS], char mine[ROWS][COLS], int x, int y)
{
	int i = 0;
	int j = 0;
	for (i = x - 1; i <= x + 1; i++)
	{
		for (j = y - 1; j <= y + 1; j++)
		{
			board[x][y] = mine[i][j] - '0';
		}
	}

	board[x][y] = board[x][y] + '0';
}

game.h

void findmine(char board[ROWS][COLS], char mine[ROWS][COLS], int row, int col, int le);//排查雷函数声明

game.c

void mine_count(char board[ROWS][COLS], 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++)
		{
			count = mine[i][j] - '0';
		}
	}

	board[x][y] = count + '0';//转换为对应字符

}


void findmine(char board[ROWS][COLS], char mine[ROWS][COLS], int row, int col, int le)//排查雷函数
{
	int x = 0;
	int y = 0;
	int win = row * col - le;//统计无雷坐标的个数

	while (win)
	{
		printf("请输入你要排查的坐标>");//提示

		scanf("%d %d", &x, &y);//获取玩家要排查的坐标

		if (0 < x && x <= row && 0 < y && y <= col && board[x][y] == '*')//判断坐标是否规
		{
			if (mine[x][y] == '1')//排查坐标是雷
			{
				printf("你踩雷了,游戏结束\n");//失败

				display(mine, ROW, COL);//打印雷棋盘

				break;//跳出while循环
			}
			else//不是雷
			{
				if (win)
				{
					mine_count(board, mine, x, y);//统计周围雷的个数

					win--;

					display(board, ROW, COL);//打印展示棋盘
				}
			}
		}
		else//不合规
		{
			printf("输入非法,请重新输入\n");//坐标非法
		}
	}

	printf("恭喜!排雷成功\n");//获胜
}

test.c

void game()
{
	char board[ROWS][COLS] = { 0 };
	char mine[ROWS][COLS] = { 0 };

	initboard(board, ROWS, COLS, '*');//初始化展示棋盘
	initboard(mine, ROWS, COLS, '0');//初始化雷棋盘

	display(board, ROW, COL);//打印展示棋盘
	//display(mine, ROW, COL);//打印雷棋盘

	putmine(mine, ROW, COL, MINE_COUNT);//设置雷
	//display(mine, ROW, COL);//打印雷棋盘

	findmine(board, mine, ROW, COL, MINE_COUNT);//排查雷
}

2.2.9 最终测试

非法输入

踩雷

成功

\

3. 源代码

game.h

#include <stdio.h> //使用printf , scanf函数
#include <stdlib.h> //使用srand, rand函数
#include <time.h> //使用time函数

#define ROW 9 //用宏定义行
#define COL 9 //用宏定义列
#define ROWS ROW + 2
#define COLS COL + 2

#define MINE_COUNT 10 //雷的个数

void initboard(char board[ROWS][COLS], int rows, int cols, char flag);//初始化棋盘函数声明


void display(char arr[ROWS][COLS], int row, int col);//打印棋盘函数声明


void putmine(char mine[ROWS][COLS], int row, int col, int n);//设置雷函数声明


void findmine(char board[ROWS][COLS], char mine[ROWS][COLS], int row, int col, int le);//排查雷函数声明

game.c

#include "game.h"


void initboard(char board[ROWS][COLS], int rows, int cols, char flag)//初始化棋盘函数
{
	int i = 0;

	for (i = 0; i < rows; i++)//行
	{
		int j = 0;

		for (j = 0; j < cols; j++)//列
		{
			board[i][j] = flag;
		}
	}
}


void display(char board[ROWS][COLS], int row, int col)//打印棋盘函数
{
	int i = 0;
	int j = 0;

	for (i = 0; i <= row; i++)//打印列下标
		printf("%d ", i);
	
	printf("\n");

	for (i = 1; i <= row; i++)//行
	{
		printf("%d ", i);//打印行下标

		for (j = 1; j <= col; j++)//列
		{

			printf("%c ", board[i][j]);

		}
		printf("\n");//末尾换行
	}
	printf("\n");
}


void putmine(char mine[ROWS][COLS], int row, int col, int n)//设置雷函数
{

	while (n)
	{
		int i = rand() % row + 1;//设置行的随机值 1-9
		int j = rand() % col + 1;//设置列的随机值 1-9

		if (mine[i][j] == '0')//判断该坐标是否有雷
		{
			mine[i][j] = '1';//设置为雷
			n--;
		}
	}

}


void mine_count(char board[ROWS][COLS], 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++)
		{
			count += mine[i][j] - '0';
		}
	}

	board[x][y] = count + '0';//转换为对应字符

}


void findmine(char board[ROWS][COLS], char mine[ROWS][COLS], int row, int col, int le)//排查雷函数
{
	int x = 0;
	int y = 0;
	int win = row * col - le;//统计无雷坐标的个数

	while (win)
	{
		printf("请输入你要排查的坐标>");//提示

		scanf("%d %d", &x, &y);//获取玩家要排查的坐标

		if (0 < x && x <= row && 0 < y && y <= col && board[x][y] == '*')//判断坐标是否规
		{
			if (mine[x][y] == '1')//排查坐标是雷
			{
				printf("你踩雷了,游戏结束\n");//失败

				display(mine, ROW, COL);//打印雷棋盘

				break;//跳出while循环
			}
			else//不是雷
			{
				if (win)
				{
					mine_count(board, mine, x, y);//统计周围雷的个数

					win--;

					display(board, ROW, COL);//打印展示棋盘
				}
			}
		}
		else//不合规
		{
			printf("输入非法,请重新输入\n");//坐标非法
		}
	}

	if(win == 0)
	printf("恭喜!排雷成功\n");//获胜
}

test.c

#include "game.h"


void game()
{
	char board[ROWS][COLS] = { 0 };
	char mine[ROWS][COLS] = { 0 };

	initboard(board, ROWS, COLS, '*');//初始化展示棋盘
	initboard(mine, ROWS, COLS, '0');//初始化雷棋盘

	display(board, ROW, COL);//打印展示棋盘
	//display(mine, ROW, COL);//打印雷棋盘

	putmine(mine, ROW, COL, MINE_COUNT);//设置雷
	display(mine, ROW, COL);//打印雷棋盘

	findmine(board, mine, ROW, COL, MINE_COUNT);//排查雷
}


void menu()//菜单
{
	printf("###########扫雷游戏##########\n");
	printf("#############################\n");
	printf("########## 1. play ##########\n");
	printf("########## 0. exit ##########\n");
	printf("#############################\n");
}


int main()
{
	int input = 0;
	do
	{
		srand((unsigned int)time(NULL)); //设置种子的随机值

		menu();//菜单

		printf("请选择\n");

		scanf("%d", &input);//记录玩家输入


		switch (input)
		{
		case 1://玩游戏

			game();
			break;

		case 0://退出

			printf("退出\n");

			break;//跳出do-while循环

		default://无效输入

			printf("无效输入,请重新输入\n");//提示
			break;
		
		}


	} while (input);//input == 0 循环结束 


	return 0;
}

4. 扫雷扩展建议

扫雷游戏的基本功能已实现,但与真正的扫雷游戏还有差距,我们在可以源代码的基础上减小它们的差距,来提高玩家体验。有以下方面可以扩展:

1. 难度选择      2. 优化坐标轴      3. 改变雷的字符     4. 计时

 5. 标记            6. 自动清屏         7. 递归展开             8. 字符颜色

大家可以自行完善扫雷,如有错误欢迎大家在评论区指正,谢谢!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值