C语言函数实现扫雷游戏

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 一:游戏规则
  • 二:代码实现
  • 三:分布讲解


历史背景:
扫雷在科技历史上也扮演了相似的角色。这个基于数字的逻辑谜题最早来自20世纪六七十年代,当时Jerimac Ratliff推出的名为“Cube”的游戏已经非常受人欢迎。几十年后的1992年,扫雷游戏被加入了Windows3.1,这并不是为了展示Windows是游戏操作系统专家,而是为了训练用户的鼠标左右键操作能力,让这些动作变得非常自然,并培养鼠标移动的速度和准确性


一、游戏规则

1.输入(0)开始游戏,在输入一个坐标(X,Y),如果这个坐标不是雷则会显示该坐标周围有几个雷。

二、代码实现

# include "game.h"
void InitBoard(char chessboard[LINES][LISTS], int lines, int lists,char set)//初始化棋盘
{
	int i = 0;//行
	for (i = 0; i < lines; i++)
	{
		int j = 0;
		for (j = 0; j < lists; j++)//列
		{
			chessboard[i][j] = set;
		}
	}
}

void DisplayBoard(char chessBoard [LINES][LISTS], int line, int list)//打印
{
	int z = 0;//打印列号
	for (z = 0; z <= list; z++)
	{
		printf("%d ", z);
    }
	printf("\n");
	for ( int i = 1; i <= line; i++)
	{
		printf("%d ", i);//打印行号
		for (int j = 1; j <= list; j++)//列
		{
			printf("%c ", chessBoard[i][j]);
		}
		printf("\n");
	}
	//printf("\n");
}
void set_mine(char chessboard[LINES][LISTS], int line, int list)
{
	int count = EASY_COUNT;
	while (count)
	{
		int x = rand() % line + 1;
		int y = rand() % list + 1;

		if (chessboard[x][y] == '0')
		{
			chessboard[x][y] = '1';
			count = count - 1;
		}
	}
}

void Display_set_mine(char chessBoard[LINES][LISTS], int line, int list)
{
	for (int i = 0; i <= line; i++)
	{
		for (int j = 0; j <= list; j++)
		{
			printf("%c ", chessBoard[i][j]);
		}
		printf("\n");
	}
}
int Get_minefind(char mine[LINES][LISTS], 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 find_mine(char mine[LINES][LISTS], char show[LINES][LISTS], int line, int list)
{
	int x = 0;
	int y = 0;
	int win = 0;
	while (win < line * list - EASY_COUNT)
	{
		printf("请输入要排查的坐标\n");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= line && y >= 1 && y <= list)
		{
			if (show[x][y] == '*')
			{
				if (mine[x][y] == '1')
				{
					printf("你被炸死了\n");
					DisplayBoard(mine, LINE, LIST);
					break;
				}
				else
				{
					int count = Get_minefind(mine, x, y);
					show[x][y] = count + '0';
					DisplayBoard(show, LINE, LIST);
					win++;

				}
			}

			else
			{
				printf("该坐标已被排查\n");
			}
		}
		    else
		    {
			  printf("坐标越界,请重新输入\n");
		    }
		if (win == line * list - EASY_COUNT)
		{
			printf("恭喜你,排雷成功\n");
			DisplayBoard(mine, LINE, LIST);
		}
		
	}
}
# define _CRT_SECURE_NO_WARNINGS
# include "game.h"
void menu()
{
	printf("********************\n");
	printf("******1.play********\n");
	printf("******0.exit********\n");
	printf("********************\n");
}

void game()
{
	char mine[LINES][LISTS] = { 0 };
	char show[LINES][LISTS] = { 0 };
	InitBoard( mine, LINES, LISTS, '0');//初始化
	InitBoard(show, LINES, LISTS, '*');
    //DisplayBoard(mine,LINE,LIST);//打印
	DisplayBoard(show, LINE, LIST);
	set_mine(mine, LINE, LIST);
	//Display_set_mine(mine,LINE,LIST);
	 find_mine (mine,show, LINES, LISTS);
}
int main()
{
	int input = 0;
	srand((unsigned int)time(NULL));
	do
	{
		menu();
			printf("请选择\n");
			scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("玩游戏\n");
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
	} while (input);

	return 0;
}
# define _CRT_SECURE_NO_WARNINGS
#pragma once
#define LINE 9//行
#define LIST 9//列
#define LINES LINE + 2//行初始化棋盘
#define LISTS LIST + 2//列初始化棋盘
#define EASY_COUNT 10
# include<stdio.h>
# include<stdlib.h>
# include<time.h>
//初始化棋盘
void InitBoard(char chessborad[LINES][LISTS],int lines, int lists, char set);
//打印棋盘
void DisplayBoard(char chessboard[LINES][LISTS], int line, int list);
//布置地雷
void set_mine(char chessboard[LINES][LISTS],int line,int list );
//void Display_set_mine(char chessBoard[LINE][LIST], int line, int list);
int Get_minefind(char mine[LINES][LISTS], int x, int y);
void find_mine(char mine[LINES][LISTS], char show[LINES][LISTS], int line, int list);

三:分布讲解

这个扫雷游戏设计的总体思路是分成三个部分来写;

首先定义一个game.h的头文件在定义两个game.c,test.c的两个.c文件这样做的目的是为了在把各个功能分开,这样写代码的书写更规范,也更容易查出问题。test.c是用来测试程序的功能,game.c是为了实现每个函数的功能,game,h是用来包含整个程序需要的头文件以及声明的函数。

下面先从主函数说起。

首先书写主函数;

int main()
{
	int input = 0;
	srand((unsigned int)time(NULL));
	do
	{
		menu();
			printf("请选择\n");
			scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("玩游戏\n");
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
	} while (input);

	return 0;
}

这一段代码表示了程序运行的总体逻辑,首先input输入1就是进行游戏输入0就是退出,进行游戏之后就执行menu()菜单函数;这个函数就只是显示选项没有什么特殊意义,也不用返回什么值,所以就定义的是void

void menu()
{
	printf("********************\n");
	printf("******1.play********\n");
	printf("******0.exit********\n");
	printf("********************\n");
}

在接下来就如果选择1就进行游戏就执行game()函数选0就直接退出。

下面来讲一讲game()函数和game.c;

void game()
{
	char mine[LINES][LISTS] = { 0 };
	char show[LINES][LISTS] = { 0 };
	InitBoard( mine, LINES, LISTS, '0');//初始化
	InitBoard(show, LINES, LISTS, '*');
    //DisplayBoard(mine,LINE,LIST);//打印
	DisplayBoard(show, LINE, LIST);
	set_mine(mine, LINE, LIST);
	//Display_set_mine(mine,LINE,LIST);
	 find_mine (mine,show, LINES, LISTS);
}

这里game函数代表整个游戏的运行逻辑,他与game.c是配套使用的下面将把两个和在一起说

这个是game.c

# include "game.h"
void InitBoard(char chessboard[LINES][LISTS], int lines, int lists,char set)//初始化棋盘
{
	int i = 0;//行
	for (i = 0; i < lines; i++)
	{
		int j = 0;
		for (j = 0; j < lists; j++)//列
		{
			chessboard[i][j] = set;
		}
	}
}

void DisplayBoard(char chessBoard [LINES][LISTS], int line, int list)//打印
{
	int z = 0;//打印列号
	for (z = 0; z <= list; z++)
	{
		printf("%d ", z);
    }
	printf("\n");
	for ( int i = 1; i <= line; i++)
	{
		printf("%d ", i);//打印行号
		for (int j = 1; j <= list; j++)//列
		{
			printf("%c ", chessBoard[i][j]);
		}
		printf("\n");
	}
	//printf("\n");
}
void set_mine(char chessboard[LINES][LISTS], int line, int list)
{
	int count = EASY_COUNT;
	while (count)
	{
		int x = rand() % line + 1;
		int y = rand() % list + 1;

		if (chessboard[x][y] == '0')
		{
			chessboard[x][y] = '1';
			count = count - 1;
		}
	}
}

void Display_set_mine(char chessBoard[LINES][LISTS], int line, int list)
{
	for (int i = 0; i <= line; i++)
	{
		for (int j = 0; j <= list; j++)
		{
			printf("%c ", chessBoard[i][j]);
		}
		printf("\n");
	}
}
int Get_minefind(char mine[LINES][LISTS], 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 find_mine(char mine[LINES][LISTS], char show[LINES][LISTS], int line, int list)
{
	int x = 0;
	int y = 0;
	int win = 0;
	while (win < line * list - EASY_COUNT)
	{
		printf("请输入要排查的坐标\n");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= line && y >= 1 && y <= list)
		{
			if (show[x][y] == '*')
			{
				if (mine[x][y] == '1')
				{
					printf("你被炸死了\n");
					DisplayBoard(mine, LINE, LIST);
					break;
				}
				else
				{
					int count = Get_minefind(mine, x, y);
					show[x][y] = count + '0';
					DisplayBoard(show, LINE, LIST);
					win++;

				}
			}

			else
			{
				printf("该坐标已被排查\n");
			}
		}
		    else
		    {
			  printf("坐标越界,请重新输入\n");
		    }
		if (win == line * list - EASY_COUNT)
		{
			printf("恭喜你,排雷成功\n");
			DisplayBoard(mine, LINE, LIST);
		}
		
	}
}

讲之前先要看看game.h这个头文件

# define _CRT_SECURE_NO_WARNINGS
#pragma once
#define LINE 9//行
#define LIST 9//列
#define LINES LINE + 2//行初始化棋盘
#define LISTS LIST + 2//列初始化棋盘
#define EASY_COUNT 10
# include<stdio.h>
# include<stdlib.h>
# include<time.h>
//初始化棋盘
void InitBoard(char chessborad[LINES][LISTS],int lines, int lists, char set);
//打印棋盘
void DisplayBoard(char chessboard[LINES][LISTS], int line, int list);
//布置地雷
void set_mine(char chessboard[LINES][LISTS],int line,int list );
//void Display_set_mine(char chessBoard[LINE][LIST], int line, int list);
int Get_minefind(char mine[LINES][LISTS], int x, int y);
void find_mine(char mine[LINES][LISTS], char show[LINES][LISTS], int line, int list);

这里包含了整个程序运行的函数,先看看第一个 define LINE 9 和 define LIST 9,这里定义的是九行九列的棋盘,那为什么后面又要定义一个定义一个LINE + 2,和LIST+2呢,先来看看这张图;


如果定义9行9列那选择棋盘中间区域的格子进行排雷,那没问题,那如果选择最边上的呢,那数组不就越界了,要知道扫雷游戏的规则是如果你选的这个格子不是雷那就要统计他周围的一圈里面一共有多少个雷,所以说我们需要创建一个11*11的格子来防止数组越界所以说要定义define LINES LINE +2 define LISTS LIST + 2,LINE 表示行,LIST表示列。为什么要使用宏定义而不直接使用数字?那是因为如果要修改行和列,那么如果纯用数字来表示的话,那就非常麻烦就要修改整个函数里面要用到行和列的地方,那如果直接用宏定义就直接在宏定义里面修改就好了,非常的方便。

在下来就是define east count 10这个表示的是游戏模式是简单,在整个棋盘里面布置10个雷

下面就是包含要用到的库函数

C语⾔标准中规定了C语⾔的各种语法规则,C语⾔并不提供库函数;C语⾔的国际标准ANSI C规定了些常⽤的函数的标准,被称为标准库,那不同的编译器⼚商根据ANSI提供的C语⾔标准就给出了⼀系列 函数的实现。这些函数就被称为库函数。

这里就不多做什么解释了。

在下来就是这个初始化棋盘函数(这个函数在game.c里)

void InitBoard(char chessboard[LINES][LISTS], int lines, int lists,char set)//初始化棋盘
{
	int i = 0;//行
	for (i = 0; i < lines; i++)
	{
		int j = 0;
		for (j = 0; j < lists; j++)//列
		{
			chessboard[i][j] = set;
		}
	}
}

从这里开始就要就要用到函数传参了,这个初始化棋盘函数用到了从test.c里面的参数

InitBoard( mine, LINES, LISTS, '0')

test.c里面的InitBoard的参数称为实参而game.c里面的称为形参,什么意思呢 

在上⾯代码中,在game.c中IntBoard是   函数的定义,有了函数后,再调用test.c里面的InitBoard。
我们把test.c里面的InitBoard(mine,LINES,LISTS,'0'),传递给函数的参数char            chessboard[LINES][LISTS], int lines, int lists,char set,称为实际参数,简称实参。
实际参数就是真实传递给函数的参数。
那什么是形参呢
实际上,如果只是定义了InitBoard函数,⽽不去调⽤的话,game.c里面的InitBoard 函数的参数 
  只是形式上存在的,不会向内存申请空间,不会真实存在的,所以叫形式参数。形式参数只有在
函数被调⽤的过程中为了存放实参传递过来的值,才向内存申请空间,这个过程就是形式的实例化。
这一段代码表示的意思是创建一个11*11的二维数组,用来初始化棋盘,后面的char set表示传递*或者字符0
InitBoard( mine, LINES, LISTS, '0');//初始化
InitBoard(show, LINES, LISTS, '*');

那么为什么要这样做呢,因为我们需要创建两个棋盘一个用来展示给玩家看,一个用来布置地雷就像这样

这个是用来布置地雷的
这个是用来展示给玩家看的
这里 为了保持两个数组的类型⼀致,可以使⽤同⼀ 套函数处理,用了一个char set 来接受来自两个棋盘里的字符0和字符*。
有了棋盘下一步就是随机埋地雷了,这里可以用到随机数来处理埋地雷,就像这样
void set_mine(char chessboard[LINES][LISTS], int line, int list)
{
	int count = EASY_COUNT;
	while (count)
	{
		int x = rand() % line + 1;
		int y = rand() % list + 1;

		if (chessboard[x][y] == '0')
		{
			chessboard[x][y] = '1';
			count = count - 1;
		}
	}
}

EAST_COUNT就是难度为简单,布置10个雷,下面就进入while循环每布置一个地雷就减少一个直到while等于0时跳出这个循环,在下来就是产生随机数了

C语⾔提供了⼀个函数叫 rand ,这函数是可以⽣成随机数的,函数原型如下所⽰:
int rand (void);
rand函数会返回⼀个伪随机数,这个随机数的范围是在0~ RAND_MAX 之间,这个RAND_MAX的⼤⼩是
依赖编译器上实现的,但是⼤部分编译器上是32767。
rand函数的使⽤需要包含⼀个头⽂件是:stdlib.h
但rand函数生成的随机数是伪随机的 ,伪随机数不是真正 的随机数,是通过某种算法⽣成的随机数。真正的随机数的是⽆法预测下⼀个值是多少rand函数是对⼀个叫“种⼦”的基准值进⾏运算⽣成的随机数。 之所以前⾯每次运⾏程序产⽣的随机数序列是⼀样的,那是因为rand函数⽣成随机数的默认种⼦是1。 如果要⽣成不同的随机数,就要让种⼦是变化的。
C语⾔中⼜提供了⼀个函数叫 srand ,⽤来初始化随机数的⽣成器的,srand的原型如下:
void srand (unsigned int seed);
程序中在调⽤ rand 函数之前先调⽤ srand 函数,通过 srand 函数的参数seed来设置rand函数⽣随
机数的时候的种⼦,只要种⼦在变化,每次⽣成的随机数序列也就变化起来了。 那也就是说给srand的种⼦是如果是随机的,rand就能⽣成随机数;在⽣成随机数的时候⼜需要⼀个随 机数,这就⽭盾了。
在程序中我们⼀般是使⽤程序运⾏的时间作为种⼦的,因为时间时刻在发⽣变化的。 在C语⾔中有⼀个函数叫 time ,就可以获得这个时间,time函数原型如下:
time_t time (time_t* timer);
time 函数会返回当前的⽇历时间,其实返回的是1970年1⽉1⽇0时0分0秒到现在程序运⾏时间之间的 差值,单位是秒。返回的类型是time_t类型的,time_t 类型本质上其实就是32位或者64位的整型类 型。 time函数的参数 timer 如果是⾮NULL的指针的话,函数也会将这个返timer中带回去。
如果 timer 是NULL,就只返回这个时间的差值。time函数返回的这个时间差也被叫做: 时间戳
time函数的时候需要包含头⽂件:time.h
如果只是让time函数返回时间戳,我们就可以这样写:
 time(NULL);
那我们就可以让⽣成随机数的代码改写成如下:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
	//使⽤time函数的返回值设置种⼦
	//因为srand的参数是unsigned int类型,我们将time函数的返回值强制类型转换
	srand((unsigned int)time(NULL));
	printf("%d\n", rand());
	printf("%d\n", rand());
	printf("%d\n", rand());
	printf("%d\n", rand());
	printf("%d\n", rand());
	return 0;
}

运行结果为;

 如果想生成在一个指定范围内的数的话,可以这样写;

 a + rand()%(b-a+1)

那所以说,要生成10个以内的地雷就可以这样写; 

int x = rand() % line + 1;
int y = rand() % list + 1;

让程序能够生成随机地雷了以后然后就要布置雷了,那怎么判断里面到底有没有地雷呢,就可以用if判断语句用x,yx来代表横纵坐标如果某一个坐标里面没有雷就设置一个雷,雷用字符1来表示,每布置完一个地雷,地雷数量就减去一个。这就是整个布雷的思路

if (chessboard[x][y] == '0')
{
	chessboard[x][y] = '1';
	count = count - 1;
}

在接下来就是打印布置好的雷了,代码如下

void Display_set_mine(char chessBoard[LINES][LISTS], int line, int list)
{
	for (int i = 0; i <= line; i++)
	{
		for (int j = 0; j <= list; j++)
		{
			printf("%c ", chessBoard[i][j]);
		}
		printf("\n");
	}
}

 

打印出来就长这样,

在接下来最后一步就是如何排查地雷了

首先来说一下这个函数

int Get_minefind(char mine[LINES][LISTS], 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');
}

这一段表示 统计你所选的一块格子周围有多少个地雷(会用到函数嵌套,这个函数会嵌套到 find_mine这个函数里去使用),如下图黄色的区域;

 因为在这个雷盘里面存放的是字符而不是数字,字符0表示没有地雷字符1表示有地雷,而0的ascii编码代号为48,字符1为49,那所以说如果要统计周围的地雷有几个就要让周围的坐标里面的字符全部加起来然后在减去8*0最后得到的结果就是有几个雷的数量。

下面再来看看这个代码,

void find_mine(char mine[LINES][LISTS], char show[LINES][LISTS], int line, int list)
{
	int x = 0;
	int y = 0;
	int win = 0;
	while (win < line * list - EASY_COUNT)
	{
		printf("请输入要排查的坐标\n");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= line && y >= 1 && y <= list)
		{
			if (show[x][y] == '*')
			{
				if (mine[x][y] == '1')
				{
					printf("你被炸死了\n");
					DisplayBoard(mine, LINE, LIST);
					break;
				}
				else
				{
					int count = Get_minefind(mine, x, y);
					show[x][y] = count + '0';
					DisplayBoard(show, LINE, LIST);
					win++;

				}
			}

			else
			{
				printf("该坐标已被排查\n");
			}
		}
		    else
		    {
			  printf("坐标越界,请重新输入\n");
		    }
		if (win == line * list - EASY_COUNT)
		{
			printf("恭喜你,排雷成功\n");
			DisplayBoard(mine, LINE, LIST);
		}
		
	}
}

这一段代码表示排雷的整体逻辑,首先要接收两个mine 和 show函数,还有行和列,定义整形x和y坐标还有win,当win等于0时就是所有坐标排查完了,就代表游戏胜利了,游戏内总共不埋地雷的个数为行*列在减去布雷数量,如果选择的每一个格子不是地雷的话win就加1,当布雷数量不在大于win时就代表游戏胜利。

在下来就进入while循环当win小于所有没布置雷的方格时就执行循环,输入要查找的坐标如下所示

if (x >= 1 && x <= line && y >= 1 && y <= list)
{
	if (show[x][y] == '*')
	{
		if (mine[x][y] == '1')
		{
			printf("你被炸死了\n");
			DisplayBoard(mine, LINE, LIST);
			break;

如果输入的坐标刚好是字符1所在的位置,就代表你被炸死了直接退出游戏,并且打印出埋雷的棋盘,如果没有就进行下一步,如下图所示 

else
{
				int count = Get_minefind(mine, x, y);
				show[x][y] = count + '0';
				DisplayBoard(show, LINE, LIST);
				win++;

}

如果输入的坐标没有地雷,那么就统计以此为中心周围雷的个数如这段的第一行代码表示 ,执行Get_minefind函数(上面已经讲过),然后第二行代码就是表示统计并显示出周围雷的个数,为什么要用count +字符0来表示呢?因为这个count表示周围雷的个数,是一个整形数字,而show数组是一个字符形数组,字符类型里面存的是ascii码,假如count是2,那么字符2的ascii码就是等于字符0加3. 执行完然后win就自增1.

	else
	{
		printf("该坐标已被排查\n");
	}
}
    else
    {
	  printf("坐标越界,请重新输入\n");
    }

下面这几行就是判断你输入的坐标对不对越没越界啥的 。

if (win == line * list - EASY_COUNT)
{
	printf("恭喜你,排雷成功\n");
	DisplayBoard(mine, LINE, LIST);
}
		

最后这一行就是判断你雷扫没扫完,如果当win等于所有的格子数量减去布置的雷的个数,那么就排雷成果打印棋盘,至此扫雷游戏结束!、

下面就是展示游戏运行框图了

 

也可以加入一些dos命令比如清屏啥的,这里就不多做描述了。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值