C语言从入门到放弃之数组实现游戏——扫雷(近万字解说、实现展开、插旗)

扫雷,经典休闲游戏,大部分人都会在微机课上偷偷来上一两局,是我们当时一段美好的记忆,那今天就让我带领大家用C语言实现扫雷。

有了上次实现三子棋的经验,这次扫雷也是一样,项目分成三部分。

 一.功能实现

1.打印开始菜单

和三子棋一样,我们首先需要一个开始菜单来决定我们是开始游戏还是退出游戏、

 

void is_start()
{
	printf("**********************\n");
	printf("*****   1:开始  *****\n");
	printf("*****   0:退出  *****\n");
	printf("**********************\n");
}

2.实现选择

实现选择和我们的三子棋一样,有不懂的可以去我的三子棋博客里面看看。

链接:http://t.csdn.cn/IJpDY

int i = 0;
do
{
	is_start();
	printf("请选择:");
	scanf_s("%d", &i);
	if (i == 1)
	{
		game();
		printf("开始游戏\n");
	}
	else if (i == 0)
	{
		printf("退出游戏\n");
	}
	else
	{
		printf("选择错误,重新选择\n");
	}
} while (i);

3.游戏实现

(1)游戏棋盘该如何埋雷并且让雷不被看见?

我们知道,在三子棋里面我们只需要一个棋盘,直接能看到里面的“棋子”是什么,但是我们扫雷是不能看到”雷”在哪,那我们是不是可以创建两个二维数组,一个用于给我们的玩家看,一个用于我们的埋“雷”。

//两个数组一个给玩家看,一个用来埋雷
	char mine[ROWS][LINS];//雷
	char board[ROWS][LINS];//玩家

这里用的ROWS和LINS与三子棋用法一致,不过我们扫雷的棋盘是不是有边角,但是在我们后面要对一圈进行排查的时候,我们排查边角就会造成数组越界,所以我们这个ROWS和LINS比我们真正的棋盘的行列都长出2,比如我们扫雷正常是9×9的,在这里我们的ROWS和LINS就是11。

#define ROWS 11
#define LINS 11
#define ROW 9
#define LIN 9

(2)初始化棋盘

对于扫雷来说,我们两个进行初始化的时候,一个玩家看到的棋盘和真正的棋盘最好符号是不一样的,这里我选择将我们看到的棋盘初始化成“*”,真正棋盘初始成“0”,注意此处是字符0,因为我们的数组是char类型的,当我们不带''时候,会将我们这个0默认成在ASCII码里面对应的字符,也就是NULL,那初始化也就是将每一行每一列都过一遍,将每一个元素都替换成我们想要替换的。

void init_board(char mb[ROWS][LINS], int row, int lin, char str)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{		
		int j = 0;
		for (j = 0; j < lin; j++)
		{
			mb[i][j] = str;
		}
	}
}

(3)打印棋盘

我们扫雷的棋盘就很简单了,但是因为是9×9,总不能每次扫雷输入坐标的时候都数一数,很麻烦,不方便,所以我们可以横着标一行数字,竖着标一行数字,都是从1~9,并且我们打印棋盘也打印下标1~9的,这样我们输入的坐标和坐标对应元素的下标就是一致的了。

void print_board(char mb[ROWS][LINS], int row, int lin)
{
	int j = 0;
	for (j = 0; j <=lin; j++)
	{
		printf("%d ",j);
	}
	printf("\n");
	int i = 0;
	for (i = 1; i <= row; i++)
	{
		printf("%d ", i);
		j = 0;
		for (j = 1; j <= lin; j++)
		{
			printf("%c ", mb[i][j]);
		}
		printf("\n");
	}
}

 


(4)埋雷

埋雷与我们三子棋的电脑下棋优化之前是一样的,都是利用时间戳一直在变的特性,利用rand和srand生成随机数,然后用ROW和LIN进行取模,注意这里取模结果要+1,因为我们的的雷需要在下标1~9之间,如果不加这个1的话,那取模的范围就是0~8,同时我们还需要判断这个随机的下标有没有被埋过雷了,写完这些之后我们可以看看埋雷之后我们真正的棋盘上有没有埋雷。

 

void bury_mine(char mine[ROWS][LINS], int row, int lin)
{
	int count = 0;
	while (1)
	{
		int i = rand() % row + 1;
		int j = rand() % lin + 1;
		if (mine[i][j] == '0')
		{
			mine[i][j] = '1';
			count++;
		}
		if (count == MINE)
		{
			break;
		}
	}
}
//srand
int i = 0;
	srand((unsigned int)time(NULL));
	do
	{
		is_start();
		printf("请选择:");
		scanf_s("%d", &i);
		if (i == 1)
		{
			game();
			printf("开始游戏\n");
		}
		else if (i == 0)
		{
			printf("退出游戏\n");
		}
		else
		{
			printf("选择错误,重新选择\n");
		}
	} while (i);

(5)判断所排雷坐标是否合法

我们埋雷之后就开始排雷了,那排雷之前,我们需要确定我们所输入的坐标是否合法,是否在我们的1~9范围之内,是否被排查过,合法与不合法都返回去一个值,在往外面写一个选择语句,如果不合法告诉我们,坐标不合法,然后我们重新输入一个坐标,再次判断,如果合法,那就进行下一步动作。

int brush_legal(char board[ROWS][LINS], int row, int lin)
{
	if (row < 10 && row>0&& lin < 10 && lin>0&& board[row][lin] == '*')
	{
		return 1;
	}
	else
	{
		return 0;
	}
}
//
int i = 0;
int j = 0;
scanf("%d%d", &i, &j);
int legal = brush_legal(board, i, j);
if (legal == 1)
{
    //进行下一步动作;
}

else
{
	printf("该坐标已经被排查或者超出排查范围,请重新选择。\n");
}

(6)检查坐标是否为雷

我们坐标合法之后,我们需要判断坐标是否就是我们的“雷”,如果是雷直接游戏结束,如果不是雷才能进行下一步的动作,那这里就和我们上面判断坐标是否合法就一样了,也是外面一个选择语句,然后函数去返回一个值。

int is_or_no(char mine[ROWS][LINS], int row, int lin)
{
	if (mine[row][lin] == '1')
	{
		return 0;
	}
	else
	{
		return 1;
	}	
}
//
int n = is_or_no(mine, i, j);
if (n == 1)
{
    //下一步动作;
}
else
{
	printf("你被炸死了,游戏结束\n");
	break;
}

(7)检查周围雷的个数

如果我们没有踩到“雷”,那下一步就是检查我们选择坐标周围一圈里面雷的个数,只需要将一圈里面的元素都检查一下,是不是雷,如果不是雷,那计数器加一,最后将我们的计数器个数返回去。

int num_mine(char mine[ROWS][LINS], int row, int lin)
{
	int count = 0;
	int i = 0;
	for (i = row + 1; i > row - 2; i--)
	{
		int j = 0;
		for (j = lin - 1; j < lin + 2; j++)
		{
			if (mine[i][j] == '1')
			{
				count++;
			}
		}
	}
	return count;
}

(8)递归展开

展开其实不难,当我们周围没有雷的时候,也就是计数器为0,返回值也为0,那我们就在以周围着八个坐标为中心坐标开始展开,遇到雷停下来,然后将我们的计数器加上一个“0”,然后替换掉我们棋盘棋盘上本来的“*”,用于表示周围有多少雷,其他周围没有雷的地方拿“ ”代替。

void brush_mine(char board[ROWS][LINS], char mine[ROWS][LINS], int row, int lin)
{
	int count = num_mine(mine, row, lin);
	if (row > 0 && row <= ROW && lin>0 && lin <= LIN)
	{
		if (count == 0)
		{
			board[row][lin] = ' ';
			int i = 0;
			for (i = row + 1; i > row - 2; i--)
			{
				int j = 0;
				for (j = lin - 1; j < lin + 2; j++)
				{
					if (board[i][j] == '*')
					{
						brush_mine(board, mine, row, lin);
					}
				}

			}
		}
		else
		{
			board[row][lin] = '0' + count;
		}
	}
}

(9)判断是否胜利

判断是否胜利的时候只需要我们将我们能看到的数组整个游历一遍,每遇到一个“*”计数器就加一,当游历完整个数组计数器等于十的时候那就证明我们赢了,如果没有那就接着排查。

int win_mine(char board[ROWS][LINS], int row, int lin)
{
	int count = 0;
	int i = 0;
	for (i = 1; i < row - 1; i++)
	{
		int j = 0;
		for (j = 1; j < lin - 1; j++)
		{
			if (board[i][j] == '*')
			{
				count++;
			}
		}
	}
	if (count == 10)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}
//
if (win == 1)
{
	printf("排雷成功\n");
	break;
}

(10)插旗

因为加了插旗的功能,所以在我们选择排查之前需要加一个选项,就是我们是要插旗还是排查,插旗的时候因为我们已经判断过坐标了,先将我们要插旗的地方换一个图标,表示插旗,然后检查一下插旗的中这个坐标是否是我们的雷,如果是,那就返回1,然后我们外面接收到之后,也加一,到我们插到十个雷之后,我们也胜利了。

int flag(char board[ROWS][LINS],char mine[ROWS][LINS], int row, int lin)
{
	int count = 0;
	board[row][lin] = "|>";
	if (mine[row][lin] == '1')
	{
		return 1;
	}
	return 0;
}
//
int win = 0;
int i = 0;
int j = 0;			
scanf("%d%d", &i, &j);
int legal = brush_legal(board, i, j);
if (legal == 1)
{
	int n = flag(board, mine, i, j);
	if (n == 1)
	{
		win++;
	}
	if (win == 10)
	{
		printf("排雷成功\n");
		break;
	}
}
else
{
	printf("该坐标已经被排查或者超出排查范围,请重新选择。\n");
}

二.程序模块调用

我们将功能都实现了,那我们应该按照上面顺序去调用?在打游戏之前肯定会先打印开始菜单,并且一定会打印以从,那这里我们就使用do while 循环,然后调用我们的开始菜单打印,打印完菜单选择是否开始游戏或者退出,进入游戏之后,我们首先需要的也只需要一次的就是我们的初始化棋盘,只有初始化棋盘之后才能去进行后面一系列操作,初始化结束,我们需要的是一个初始化好的棋盘打印出来,让我们去选择坐标,使用接下来是打印棋盘,打印棋盘完之后,就开始选择是排查还是插旗,不管选择这两个哪个,都会去判断坐标是否合法,这些动作包括之后的动作是需要一直重复去做的,所以这里我们将这些东西写进死循环里面,当我们胜利或者失败,跳出,那检查完坐标,并且坐标合法之后需要判断的就是我们这个坐标下面埋的是不是“雷”,如果不是,那就开始我们的找雷个数和递归展开,每次排查过或者插旗之后,我们都需要去检查一下我们现在是不是赢了,那大体思路就是只有,我们把他们穿起来就是:

//游戏实现模块          
void game()
{
	//两个数组一个给玩家看,一个用来埋雷
	char mine[ROWS][LINS];//雷
	char board[ROWS][LINS];//玩家
	//初始化棋盘
	init_board(mine, ROWS, LINS, '0');
	init_board(board, ROWS, LINS, '*');
	//打印棋盘
	print_board(board, ROW, LIN);
	//埋雷
	bury_mine(mine, ROW, LIN);
	//print_board(mine, ROWS, LINS);
	//排查坐标是否合法
	while (1)
	{
		int x = 0;
		printf("选择排查(1)还是插旗(2):>");
		scanf("%d", &x);
		if (x == 1)
		{
			int win = 0;
			int i = 0;
			int j = 0;
			scanf("%d%d", &i, &j);
			int legal = brush_legal(board, i, j);
			if (legal == 1)
			{
				
				//排雷
				int n = is_or_no(mine, i, j);
				if (n == 1)
				{
					brush_mine(board, mine, i, j);
					print_board(board, ROW, LIN);
					win = win_mine(board, ROWS, LINS);
					if (win == 1)
					{
						printf("排雷成功\n");
						break;
					}
				}
				else
				{
					printf("你被炸死了,游戏结束\n");
					break;
				}
			}
			else
			{
				printf("该坐标已经被排查或者超出排查范围,请重新选择。\n");
			}
		}
		else if (x == 2)
		{
			int win = 0;
			int i = 0;
			int j = 0;
			scanf("%d%d", &i, &j);
			int legal = brush_legal(board, i, j);
			if (legal == 1)
			{
				int n = flag(board, mine, i, j);
				if (n == 1)
				{
					win++;
				}
				if (win == 10)
				{
					printf("排雷成功\n");
					break;
				}
			}
			else
			{
				printf("该坐标已经被排查或者超出排查范围,请重新选择。\n");
			}
		}
		else
		{
			printf("选择错误,重新选择\n");
		}
	}
}

源码:

game.h

#pragma once

//库函数的头文件
#include<stdio.h>
#include<time.h>
#include<stdlib.h>

//#define定义的宏变量
#define ROWS 11
#define LINS 11
#define ROW 9
#define LIN 9
#define MINE 10

//为实现游戏功能所使用的自定义函数
void init_board(char mb[ROWS][LINS], int row, int lin, char str);//初始化棋盘
void print_board(char mb[ROWS][LINS], int row, int lin);//打印棋盘
void bury_mine(char mine[ROWS][LINS], int row, int lin);//埋雷
int brush_legal(char board[ROWS][LINS], int row, int lin);//判断排查坐标是是否合法
int is_or_no(char mine[ROWS][LINS], int row, int lin);//检查坐标是否为雷
int num_mine( char mine[ROWS][LINS], int row, int lin);//获取坐标周围雷的个数
void brush_mine(char board[ROWS][LINS], char mine[ROWS][LINS], int row, int lin);//递归展开排雷
int win_mine(char board[ROWS][LINS], int row, int lin);//判断是否胜利
int flag(char board[ROWS][LINS], char mine[ROWS][LINS], int row, int lin);//插旗

game.c

#include "game.h"

//初始化棋盘
void init_board(char mb[ROWS][LINS], int row, int lin, char str)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{		
		int j = 0;
		for (j = 0; j < lin; j++)
		{
			mb[i][j] = str;
		}
	}
}
//打印棋盘
void print_board(char mb[ROWS][LINS], int row, int lin)
{
	int j = 0;
	for (j = 0; j <=lin; j++)
	{
		printf("%d ",j);
	}
	printf("\n");
	int i = 0;
	for (i = 1; i <= row; i++)
	{
		printf("%d ", i);
		j = 0;
		for (j = 1; j <= lin; j++)
		{
			printf("%c ", mb[i][j]);
		}
		printf("\n");
	}
}
//埋雷
void bury_mine(char mine[ROWS][LINS], int row, int lin)
{
	int count = 0;
	while (1)
	{
		int i = rand() % row + 1;
		int j = rand() % lin + 1;
		if (mine[i][j] == '0')
		{
			mine[i][j] = '1';
			count++;
		}
		if (count == MINE)
		{
			break;
		}
	}
}
//判断排查坐标是否合法
int brush_legal(char board[ROWS][LINS], int row, int lin)
{
	if (row < 10 && row>0&& lin < 10 && lin>0&& board[row][lin] == '*')
	{
		return 1;
	}
	else
	{
		return 0;
	}
}
//检查所选坐标是否为雷
int is_or_no(char mine[ROWS][LINS], int row, int lin)
{
	if (mine[row][lin] == '1')
	{
		return 0;
	}
	else
	{
		return 1;
	}
}
//获取坐标周围雷的个数
int num_mine(char mine[ROWS][LINS], int row, int lin)
{
	int count = 0;
	int i = 0;
	for (i = row + 1; i > row - 2; i--)
	{
		int j = 0;
		for (j = lin - 1; j < lin + 2; j++)
		{
			if (mine[i][j] == '1')
			{
				count++;
			}
		}
	}
	return count;
}
//递归展开排雷
void brush_mine(char board[ROWS][LINS], char mine[ROWS][LINS], int row, int lin)
{
	int count = num_mine(mine, row, lin);
	if (row > 0 && row <= ROW && lin>0 && lin <= LIN)
	{
		if (count == 0)
		{
			board[row][lin] = ' ';
			int i = 0;
			for (i = row + 1; i > row - 2; i--)
			{
				int j = 0;
				for (j = lin - 1; j < lin + 2; j++)
				{
					if (board[i][j] == '*')
					{
						brush_mine(board, mine, row, lin);
					}
				}

			}
		}
		else
		{
			board[row][lin] = '0' + count;
		}
	}
}
//判断是否胜利
int win_mine(char board[ROWS][LINS], int row, int lin)
{
	int count = 0;
	int i = 0;
	for (i = 1; i < row - 1; i++)
	{
		int j = 0;
		for (j = 1; j < lin - 1; j++)
		{
			if (board[i][j] == '*')
			{
				count++;
			}
		}
	}
	if (count == 10)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}
//插旗
int flag(char board[ROWS][LINS],char mine[ROWS][LINS], int row, int lin)
{
	int count = 0;
	board[row][lin] = "|>";
	if (mine[row][lin] == '1')
	{
		return 1;
	}
	return 0;
}

test.h

#include"game.h"

//开始菜单
void is_start()
{
	printf("**********************\n");
	printf("*****   1:开始  *****\n");
	printf("*****   0:退出  *****\n");
	printf("**********************\n");
}
//游戏实现模块          
void game()
{
	//两个数组一个给玩家看,一个用来埋雷
	char mine[ROWS][LINS];//雷
	char board[ROWS][LINS];//玩家
	//初始化棋盘
	init_board(mine, ROWS, LINS, '0');
	init_board(board, ROWS, LINS, '*');
	//打印棋盘
	print_board(board, ROW, LIN);
	//埋雷
	bury_mine(mine, ROW, LIN);
	//print_board(mine, ROWS, LINS);
	//排查坐标是否合法
	while (1)
	{
		int x = 0;
		printf("选择排查(1)还是插旗(2):>");
		scanf("%d", &x);
		if (x == 1)
		{
			int win = 0;
			int i = 0;
			int j = 0;
			scanf("%d%d", &i, &j);
			int legal = brush_legal(board, i, j);
			if (legal == 1)
			{
				
				//排雷
				int n = is_or_no(mine, i, j);
				if (n == 1)
				{
					brush_mine(board, mine, i, j);
					print_board(board, ROW, LIN);
					win = win_mine(board, ROWS, LINS);
					if (win == 1)
					{
						printf("排雷成功\n");
						break;
					}
				}
				else
				{
					printf("你被炸死了,游戏结束\n");
					break;
				}
			}
			else
			{
				printf("该坐标已经被排查或者超出排查范围,请重新选择。\n");
			}
		}
		else if (x == 2)
		{
			int win = 0;
			int i = 0;
			int j = 0;
			scanf("%d%d", &i, &j);
			int legal = brush_legal(board, i, j);
			if (legal == 1)
			{
				int n = flag(board, mine, i, j);
				if (n == 1)
				{
					win++;
				}
				if (win == 10)
				{
					printf("排雷成功\n");
					break;
				}
			}
			else
			{
				printf("该坐标已经被排查或者超出排查范围,请重新选择。\n");
			}
		}
		else
		{
			printf("选择错误,重新选择\n");
		}
	}
}
void test()
{
	int i = 0;
	srand((unsigned int)time(NULL));
	do
	{
		is_start();
		printf("请选择:");
		scanf_s("%d", &i);
		if (i == 1)
		{
			game();
			printf("开始游戏\n");
		}
		else if (i == 0)
		{
			printf("退出游戏\n");
		}
		else
		{
			printf("选择错误,重新选择\n");
		}
	} while (i);
}
int main()
{
	test();
	return 0;
}

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

封心锁爱的前夫哥

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

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

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

打赏作者

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

抵扣说明:

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

余额充值