【C语言】扫雷(初阶+进阶)小项目实战

一.初阶版

1.菜单设置

进入游戏时,首先映入眼帘的应该是游戏菜单,提供用户选项以进行游戏。对应用户所选而执行相应的程序,首先想到使用switch语句。考虑到用户玩完一局后还会有再来一次的想法,于是可以将游戏整体放入一个循环do-while,而巧妙地使用用户输入来进行判断,输入0自然不进行循环,直接退出游戏。可以看见以下的效果。

#include"test.h"
void menu()
{
	printf("*************************\n");
	printf("*****    1.Play     *****\n");
	printf("*****    0.exit     *****\n");
	printf("*************************\n");
}
int main()
{
	int input = 0;
	do
	{
		menu();
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
	} while (input);

	return 0;
}

2.棋盘函数设置

扫雷可以看作是在一个大棋盘上进行的一系列操作,自然有关棋盘的函数必须设置好,其中包括:棋盘初始化,棋盘展示,埋雷,排查雷(这是游戏主体部分,在下一小节)等函数,先写好这些基础函数有利于设计游戏中的调试进行。

不过在设置之前,必须对该扫雷游戏的实现原理进行剖析。棋盘,方正周整,如同二维数组,故用二维数组实现棋盘最合适不过。在设置棋盘时可以用0,1分别表示无雷和有雷的状态,这样设置不仅方便埋雷,并且在排查雷的过程中便于统计,排查时若无雷则显示数字,数字代表周围含有雷的数量,最多为8,用周围的0,1直接相加便能得到数字,十分便捷。然而此时就遇到一个问题,0,1固然方便,可显示给用户的不可能是直接的0,1棋盘,在用户眼里必须全是未知状态,故此游戏需要用两个棋盘的实现。接下来就来一一实现。

棋盘的初始化,使用二维数组,在此基础版上,使用9*9的棋盘足矣,然而此时应当初始化9*9的棋盘吗?作者此时选择初始化原先行列分别+2的棋盘(即11*11),如此,不仅方便排查雷时直接使用1-9的坐标,更重要的是在排查边缘坐标时,不会出现数组越界的情况,使最边缘皆为0,也不会使统计雷数量出现错误,一举两得。

下面是初始化棋盘和打印棋盘的函数实现,在此,在初始化棋盘initboard函数时加入了参数char ch,便是由于需要有两个棋盘(两个char类型的二维数组mineboard和showboard)而设置的,mineboard需要用字符0初始化,表示有雷无雷,而showboard则需要用*初始化,是展示给用户的界面。另外,在实现printboard函数时可以考虑美观性,适当添加了一些如坐标等的修饰。

#define EASY_COUNT 10

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

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

//打印棋盘
void printboard(char arr[ROWS][COLS], int row, int col)
{
	printf("-------扫雷--------\n");
	for (int i = 0; i <= col; i++)
	{
		printf("%d ", i);
	}
	printf("\n");
	for (int i = 1; i <= row; i++)
	{
		printf("%d ", i);
		for (int j = 1; j <= col; j++)
		{
			printf("%c ", arr[i][j]);
		}
		printf("\n");
	}
}

3.埋雷,查雷函数设置

埋雷设置必然是随机数,所以在此使用srand,rand,time等库函数来解决,随机生成两个1-9的数字来做雷的坐标,在mineboard中用1替换0作为雷。在排查雷中,应重复循环执行,直到排查到雷(游戏失败)或者排查结束(游戏胜利)为止,用户指定输入坐标进行排查,若对应mineboard为1则是雷,直接结束游戏,若为0则显示周围雷数量到对应showboard上。这里得到雷的数量也可以作为一个函数进行书写。判定游戏胜利则需要一个计数器cnt,每次探查成功cnt都+1,直到cnt==棋盘总格数-雷数为止,游戏胜利。

Tips:mineboard初始化时是由字符'0''1'表示有雷与否,但在getmine得到目标周围雷数量需要进行相加,需要知道 字符数字减去字符‘0’得到整形数字 ,以下相加8个字符自然也是减去8个字符‘0’.

//埋雷
void setmine(char arr[ROWS][COLS],int row,int col)
{
	int count = 1;
	while (count <= EASY_COUNT)
	{
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		if (arr[x][y] == '0')
		{
			arr[x][y] = '1';
			count++;
		}
	}
}

//得到目标周围雷的数量
int getmine(char arr[ROWS][COLS], int x, int y)
{
	return arr[x - 1][y - 1]
		+ arr[x - 1][y]
		+ arr[x - 1][y + 1]
		+ arr[x][y - 1]
		+ arr[x][y + 1]
		+ arr[x + 1][y - 1]
		+ arr[x + 1][y]
		+ arr[x + 1][y + 1]
		- 8 * '0';
}

//排查雷
void searchmine(char mineboard[ROWS][COLS],char showboard[ROWS][COLS],int row,int col)
{
	int x, y;
	int cnt = 0;
	while (cnt < ROW * COL - EASY_COUNT)
	{
		printf("请输入你要排查的对象:");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (showboard[x][y] != '*')
			{
				printf("该点已排查,请重新选择\n");
			}
			else
			{
				if (mineboard[x][y] == '1')
				{
					printf("很遗憾,这是炸弹,游戏结束\n");
					printboard(mineboard, ROW, COL);
					break;
				}
				else
				{
					showboard[x][y] = getmine(mineboard, x, y) + '0';
					printboard(showboard, ROW, COL);
					cnt++;
				}
			}
		}
		else
			printf("输入不合法,请重新输入\n");
	}
	if (cnt == ROW * COL - EASY_COUNT)
		printf("恭喜你,排雷成功!\n");
}

二.进阶版

1.计时器

使用时间函数clock()进行计时功能。该函数需包含头文件<time.h>,上述埋雷的随机值的生成中也用到了该头文件里time函数。可以如下实现,如此便能在游戏结束后打印出本次游戏的用时。

//用start,end分别记录开始与结束的时刻
clock_t start, end;
double duration;

//在开始游戏选择case1的情况中加入时刻记录
case 1:
	start = clock();
	game();
	end = clock();
	printf("此次用时%lf秒\n", (double)((end - start) / 1000.0));
	break;

2.标记雷与取消标记

在此用#代替*来表示标记雷,用mark和unmark函数,标记的结果需展示在showboard上,当然也要考虑重复标记,无效标记(已经探查),错误输入等情况,实现函数如下。

void mark(char showboard[ROWS][COLS], int row, int col)
{
	while (1)
	{
		printf("请输入你要标记的对象:");
		int x, y;
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (showboard[x][y] == '*')
			{
				showboard[x][y] = '#';
				break;
			}
			else if (showboard[x][y] == '#')
			{
				printf("该对象已被标记,无需重复标记\n");
			}
			else
			{
				printf("此处已探查,无需标记\n");
			}
		}
		else
		{
			printf("输入错误,请重新输入\n");
		}
	}
}

void unmark(char showboard[ROWS][COLS], int row, int col)
{
	while (1)
	{
		printf("请输入你要取消标记的对象:");
		int x, y;
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (showboard[x][y] == '#')
			{
				showboard[x][y] = '*';
				break;
			}
			else if (showboard[x][y] == '*')
			{
				printf("该对象没有被标记,无需取消标记\n");
			}
			else
			{
				printf("此处已探查,无需取消标记\n");
			}
		}
		else
		{
			printf("输入错误,请重新输入\n");
		}
	}
}

函数写出固然容易,然而此处的改动后便需要调整之前的代码,由于用户可能执行侦察,标记,取消标记等操作,因此在此设置第二个菜单menu2,之前菜单重新设置为menu1,用户每次操作前得先从menu2中选择,因此前文的seachmine函数也需调整,首先要把searchmine里while循环排查取消,设置新循环在最外层以便循环选择menu2,此时排到雷便需要返回-1到外围循环来终结循环了。然后有一个小细节,之前的if(showboard[x][y]!='*'还要再加一个!=‘#’,否则标记过的雷就无法排查了。并且设置胜利条件也不能放在searchmine里了,同时为了方便前文返回-1终止循环,故作者将其设置为返回int类型(返回cnt),在函数外记录并判断,不过searchmine函数里的cnt要一直抱有计数功能,不能因为函数使用完后就又归零,因此用static修饰cnt延长其生命周期。作者特地保留注释掉修改前的代码,方便对比。

void menu2()
{
	printf("*************************\n");
	printf("*****    1.Check    *****\n");
	printf("*****    2.Mark     *****\n");
	printf("*****    3.Unmark   *****\n");
	printf("*************************\n");
}


int searchmine(char mineboard[ROWS][COLS],char showboard[ROWS][COLS],int row,int col)
{
	int x, y;
	static int cnt = 0;
	//while (cnt < ROW * COL - EASY_COUNT)
	//{
		printf("请输入你要排查的对象:");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (showboard[x][y] != '*' && showboard[x][y] != '#')
			{
				printf("该点已排查,请重新选择\n");
			}
			else
			{
				if (mineboard[x][y] == '1')
				{
					printf("很遗憾,这是炸弹,游戏结束\n");
					printboard(mineboard, ROW, COL);
					return -1;
					//break;
				}
				else
				{
					showboard[x][y] = getmine(mineboard, x, y) + '0';
					printboard(showboard, ROW, COL);
					cnt++;
					return cnt;
				}
			}
		}
		else
			printf("输入不合法,请重新输入\n");
	//}
	//if (cnt == ROW * COL - EASY_COUNT)
	//{
	//		printf("恭喜你,排雷成功!\n");
	//}
}

int cnt = 0;
while (cnt !=-1)
{
	int input = 0;
	menu2();
	printf("请选择你要执行的操作:");
	scanf("%d", &input);
	switch (input)
	{
	case 1:
		cnt = searchmine(mineboard, showboard, ROW, COL);
		break;
	case 2:
		mark(showboard,ROW,COL);
		printboard(showboard, ROW, COL);
		break;
	case 3:
		unmark(showboard, ROW, COL);
		printboard(showboard, ROW, COL);
		break;
	default:
		printf("选择错误,请重新选择\n");
		break;
	}
	if (cnt == ROW * COL - EASY_COUNT)
	{
		printf("恭喜你,排雷成功!\n");
		break;
	}
}

//searchmine(mineboard, showboard, ROW, COL);

3.空白展开,难度选择.......

更多扫雷的进阶拓展,敬请期待,欢迎关注持续更新......

三.完整代码

1.头文件test.h

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


#define EASY_COUNT 10

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

//棋盘的初始化
void initboard(char arr[ROWS][COLS], int rows, int cols, char ch);

//打印棋盘
void printboard(char arr[ROWS][COLS], int row, int col);
//埋雷
void setmine(char arr[ROWS][COLS],int row,int col);

//得到周围雷的数量
int getmine(char arr[ROWS][COLS], int x, int y);

//排查雷
int searchmine(char mineboard[ROWS][COLS], char showboard[ROWS][COLS], int row, int col);

void menu2();

//标记雷
void mark(char showboard[ROWS][COLS], int row, int col);

//取消标记雷
void unmark(char showboard[ROWS][COLS], int row, int col);

2.源文件test.c

#include"test.h"
void menu1()
{
	printf("*************************\n");
	printf("*****    1.Play     *****\n");
	printf("*****    0.exit     *****\n");
	printf("*************************\n");
}
void game()
{
	char mineboard[ROWS][COLS] = { 0 };
	char showboard[ROWS][COLS] = { 0 };
	
	initboard(mineboard, ROWS,COLS,'0');
	initboard(showboard, ROWS,COLS,'*');

	//printboard(mineboard, ROW, COL);
	setmine(mineboard,ROW,COL);
	//printboard(mineboard,ROW,COL);
	printboard(showboard,ROW,COL);
	//int x, y;
	//scanf("%d %d", &x, &y);
	//printf("%d\n", getmine(mineboard, x,y));
	int cnt = 0;
	while (cnt !=-1)
	{
		int input = 0;
		menu2();
		printf("请选择你要执行的操作:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			cnt = searchmine(mineboard, showboard, ROW, COL);
			break;
		case 2:
			mark(showboard,ROW,COL);
			printboard(showboard, ROW, COL);
			break;
		case 3:
			unmark(showboard, ROW, COL);
			printboard(showboard, ROW, COL);
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
		if (cnt == ROW * COL - EASY_COUNT)
		{
			printf("恭喜你,排雷成功!\n");
			break;
		}
	}

	//searchmine(mineboard, showboard, ROW, COL);
}
int main()
{
	srand((unsigned int)time(NULL));
	clock_t start, end;
	double duration;
	int input = 0;
	do
	{
		menu1();
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			start = clock();
			game();
			end = clock();
			printf("此次用时%lf秒\n", (double)((end - start) / 1000.0));
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
	} while (input);

	return 0;
}

3.源文件game.c

#include"test.h"

void initboard(char arr[ROWS][COLS], int rows, int cols, char ch)
{
	for (int i = 0; i < rows; i++)
	{
		for (int j = 0; j < cols; j++)
		{
			arr[i][j] = ch;
		}
	}
}

void printboard(char arr[ROWS][COLS], int row, int col)
{
	printf("-------扫雷--------\n");
	for (int i = 0; i <= col; i++)
	{
		printf("%d ", i);
	}
	printf("\n");
	for (int i = 1; i <= row; i++)
	{
		printf("%d ", i);
		for (int j = 1; j <= col; j++)
		{
			printf("%c ", arr[i][j]);
		}
		printf("\n");
	}
}

void setmine(char arr[ROWS][COLS],int row,int col)
{
	int count = 1;
	while (count <= EASY_COUNT)
	{
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		if (arr[x][y] == '0')
		{
			arr[x][y] = '1';
			count++;
		}
	}
}

int getmine(char arr[ROWS][COLS], int x, int y)
{
	return arr[x - 1][y - 1]
		+ arr[x - 1][y]
		+ arr[x - 1][y + 1]
		+ arr[x][y - 1]
		+ arr[x][y + 1]
		+ arr[x + 1][y - 1]
		+ arr[x + 1][y]
		+ arr[x + 1][y + 1]
		- 8 * '0';
}

int searchmine(char mineboard[ROWS][COLS],char showboard[ROWS][COLS],int row,int col)
{
	int x, y;
	static int cnt = 0;
	//while (cnt < ROW * COL - EASY_COUNT)
	//{
		printf("请输入你要排查的对象:");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (showboard[x][y] != '*' && showboard[x][y] != '#')
			{
				printf("该点已排查,请重新选择\n");
			}
			else
			{
				if (mineboard[x][y] == '1')
				{
					printf("很遗憾,这是炸弹,游戏结束\n");
					printboard(mineboard, ROW, COL);
					return -1;
					//break;
				}
				else
				{
					showboard[x][y] = getmine(mineboard, x, y) + '0';
					printboard(showboard, ROW, COL);
					cnt++;
					return cnt;
				}
			}
		}
		else
			printf("输入不合法,请重新输入\n");
	//}
	//if (cnt == ROW * COL - EASY_COUNT)
	//{
	//		printf("恭喜你,排雷成功!\n");
	//}
}


void menu2()
{
	printf("*************************\n");
	printf("*****    1.Check    *****\n");
	printf("*****    2.Mark     *****\n");
	printf("*****    3.Unmark   *****\n");
	printf("*************************\n");
}

void mark(char showboard[ROWS][COLS], int row, int col)
{
	while (1)
	{
		printf("请输入你要标记的对象:");
		int x, y;
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (showboard[x][y] == '*')
			{
				showboard[x][y] = '#';
				break;
			}
			else if (showboard[x][y] == '#')
			{
				printf("该对象已被标记,无需重复标记\n");
			}
			else
			{
				printf("此处已探查,无需标记\n");
			}
		}
		else
		{
			printf("输入错误,请重新输入\n");
		}
	}
}

void unmark(char showboard[ROWS][COLS], int row, int col)
{
	while (1)
	{
		printf("请输入你要取消标记的对象:");
		int x, y;
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (showboard[x][y] == '#')
			{
				showboard[x][y] = '*';
				break;
			}
			else if (showboard[x][y] == '*')
			{
				printf("该对象没有被标记,无需取消标记\n");
			}
			else
			{
				printf("此处已探查,无需取消标记\n");
			}
		}
		else
		{
			printf("输入错误,请重新输入\n");
		}
	}
}

  • 13
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值