当前位置: 首页 > 工具软件 > 扫雷放置 > 使用案例 >

扫雷程序详解

夏祯
2023-12-01

目录

一、文件编排

二、实现游戏的进入和退出

三、实现扫雷运行的主体函数

四、基本函数详解

1.初始化函数

2.打印棋盘的函数

3.设置地雷

4.判断胜利

5.查找坐标

五、递归展开优化

六、标记与取消标记

七、整个游戏程序(白嫖直接看这里)


一、文件编排

1.需要创建的文件:

一个头文件(game.h)和两个源文件(game.c和function.c)

2.文件的作用:

(1)game.h:负责放置函数的声明,头文件引用和标识符常量的定义。

(2)game.c:负责放置主程序的实现,包括进入、退出游戏的程序和游戏的整体实现的函数

(3)function.c:负责放置游戏整体实现函数的内部细节函数

二、实现游戏的进入和退出

1.整体逻辑

(1)首先我们需要打印选择界面,让玩家可以直到如何选择,此时就需要一个menu()函数。

(2)定义一个变量,允许玩家输入值,用switch语句输入1开始游戏,0退出游戏,输入其他数值就重新输入

(3)这个程序需要一个循环,不管玩家想玩还是不想玩,我们都应该先让他可以选择,此时我们使用do while循环

(4)判断条件为a,a输入值为0时,判断为假,跳出循环

(5)我们可以把需要使用的头文件的引用放在game.h这个文件中,在每个源文件中再引用这个头文件即可

2.代码

game.c

#include"game.h"

 

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


int main()
{
	srand((unsigned int)time(NULL));
	int a = 0;
	do 
	{
		menu();
		printf("请输入:");
		scanf("%d", &a);
		switch (a)
		{
		    case 0:
		    {
			    printf("退出游戏\n");
			    break;
		    }
		    case 1:
		    {
		    	printf("开始游戏\n");
			    game();//游戏主程序,此时还未编写
			    break;
		    }
		    default:
		    {
		    	printf("请重新输入\n");
			    break;
		    }
		}
	} while (a);
	return 0;
}

(2)game.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1//这句话在VS中使用scanf函数时需要,其他开发程序不需要


#include<stdio.h>

三、实现扫雷运行的主体函数

1、整体逻辑

(1)建立两个数组,一个数组mine用于存放地雷,另一个show用于玩家观察,数组的行数和列数可以定义为标识符常量。

(2)这两个数组应该在棋盘的基础上扩大一圈,这样在判断棋盘最外层的格子的数字的时候我们就不需要单独判断位置进行计算,这个扩大一圈的常量也定义为标识符常量。

(3)初始化两个数组,mine数组所有元素初始化为0,show数组所有元素初始化为*

(4)设置一定数目的地雷在除去最外圈的方块内,即将mine数组中的部分0改为1表示这个地方有雷

(5)打印show数组,供玩家观察

(6)写一个用于检查的函数,当这个函数跳出时本局游戏结束

2、代码

void game()
{
	char mine[ROWS][COLS] = { 0 };
	char show[ROWS][COLS] = { 0 };
	
	initialboard(mine,ROWS,COLS,'0');
	initialboard(show, ROWS, COLS, '*');
	//初始化数组,将mine的每一个元素赋值为字符0
	//初始化数组,将show的每一个元素赋值为字符*,保持神秘感
	
	setmine(mine,ROWS,COLS,MINE);
	//放置地雷,没有地雷的地方为0,放地雷的地方为1
 	

        printboard(show, ROW, COL);
	//打印ROW×COL的方格,打印n×n数组的中间n-1×n-1项元素

	check(mine, show, ROW, COL);
}

四、基本函数详解

1.初始化函数

(1)整体逻辑

就是把数组所有的元素赋值为一个特定的字符,在参数设置上我们需要传递需要初始化的数组,数组的行数于列数,需要赋值的符号,通过循环即可实现。

(2)代码

void initialboard(char board[ROWS][COLS], int rows, int cols, char a)
{
	int i = 0;
	for (i = 0; i < rows; i++)
	{
		int j = 0;
		for (j = 0; j < cols; j++)
		{
			board[i][j] = a;
		}
	}
}

2.打印棋盘的函数

(1)整体逻辑

  • 我们只需要打印棋盘的内部n-1×n-1的部分即可,所以根据数组的表表位置来打印即可(例如:需要打印9行9列,只需要内部变量从1到9即可)。
  • 为了便于辨认行数与列数,我们可以先在前面从0到列数(比如0-9)打印,然后再在每一行的开头打印行数。
  • 还可以加入----扫雷游戏----进行装饰和区分。

(2)代码

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

3.设置地雷

(1)整体逻辑

  • 在n×n的棋盘内部将内部n-1×n-1的部分字符0转化为字符1视作布雷。
  • 通过生成1-9的随机数来设置坐标,这里还需要使用到srand与time函数,放置在程序的开始处。
  • 在布雷的同时需要判断随机生成的位置是否已经布雷,在没有布好的雷数等于0时退出程序,地雷的数量为标识符常量。

(2)代码

void setmine(char mine[ROWS][COLS], int rows, int cols, int m)
{
    int count = m;
    while (count)
    {
        int x = rand() % ROW + 1;
        int y = rand() % COL + 1;
	if (mine[x][y]=='0')
	{
	    mine[x][y] = '1';
	    count--;
	}
    }
}

4.判断胜利

(1)整体逻辑

  • 将show数组中n-1×n-1的部分已检查的元素相加,若已检查的元素等于=棋盘的行数×棋盘的列数-地雷总数,则此时玩家胜利。
  • 因为后面还要设置标记的功能所以把判断胜利的条件写成一个函数会更方便。
  • *代表未检查的元素,#表示已标志为地雷的元素,这个在以后的程序中是有用的。

(2)代码

int iswin(char show[ROWS][COLS],int row,int col)
{
	int win = 0;
	int i = 1;
	for (i = 1; i <= row; i++)
	{
		int j = 0;
		for (j = 1; j <= col; j++)
		{
			if (show[i][j] != '*' && show[i][j] != '#')
			{
				win++;
			}
		}
	}
	return win;
}

5.查找坐标

(1)整体逻辑

  • 玩家对整个游戏的操作都在这个函数的内部完成,内部细节我会详细讲,此时我们先实现基础的程序,然后再讲附加的内容
  • 代码中被注释的函数是附加功能,下面会补充

(2)代码

void check(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
    int x = 0;//横坐标
    int y = 0;//纵坐标
    while (1)//循环在输或赢的时候跳出
    {
        printf("请输入需要查找的坐标:");
        scanf("%d %d", &x, &y);
        if (x>=1 && x<=row && y>=1 && y<=col)//坐标合法进入,不合法跳到最下面
        {
            if (show[x][y] != '*')//检查合法的坐标是否已经被检查,被检查需要重新输入
            {
		printf("该地址已经被检查,请重新输入\n");
	    }
	    else//该地址没有被检查
	    {
		if (mine[x][y] == '1')//判断踩雷
		{
		    printf("你被炸死了\n");
		    printboard(mine, ROW, COL);//输了打印mine,让你知道怎么输的
		    break;
		}
		else 
		{
		    int i = -1;
		    int num = 0;
		    for (i = -1; i <= 1; i++)
		    {
		    	int j = -1;
		        for (j = -1; j <= 1; j++)
		        {
		            num += mine[x + i][y + j];
                            //把mine数组一个元素周围的每一个元素相加
			    num -= '0';
                            //字符储存为ASCII码,我们需要减去字符0的ASCII码来得到整形
		        }
		    }//统计该位置周围雷的个数,周围没有雷赋为空格,有雷赋为周围雷的数字
		    if (num == 0)//周围没有雷
		    {
                        show[x][y] = ' ';                           
			//spread(mine,show,x,y);
			if (iswin(show, ROW, COL) == ROW * COL - MINE)
			{
			    printboard(mine, ROW, COL);
			    printf("你赢了\n");
			    break;
			}
		    }
		    else//周围有雷
		    {
			show[x][y] = num+'0';
			if (iswin(show, ROW, COL) == ROW * COL - MINE)
			{
			    printboard(mine, ROW, COL);
			    printf("你赢了\n");
			    break;
			}
		    }
		    printboard(show, ROW, COL);//打印show数组让玩家看到信息
		   // mark(mine, show, x, y);

		}
	    }
	}
	else//坐标不合法,
	{
	    printf("坐标不合法请重新输入\n");
	}
    }
}

五、递归展开优化

1、整体逻辑

(1)我么在玩windows自带的扫雷游戏时,当我们点到周围没有雷的元素时是会展开一大片的,这个功能可以通过递归实现,节省了我们的时间。

(2)递归函数的思路为只要找到满足

  • 该地址合法
  • 该地址不是雷
  • 该地址周围没有雷
  • 该地址没有被排查过

在这样的条件下,我们就需要对周围3×3的数组元素中没有被递归的元素继续进行递归,排除的递归就可以向下进行,否则直接跳出递归,将该地址赋值为这个地址周围的类的个数即可。

(3)在递的过程中,程序可以找到所有符合我们要求的方格;在归的过程中,程序找到的的元素会被赋值为周围的地雷总数。

2、代码

int isempty(char mine[ROWS][COLS], int x, int y)//检验周围有没有雷
{
	int i = -1;
	int num = 0;
	for (i = -1; i <= 1; i++)
	{
		int j = -1;
		for (j = -1; j <= 1; j++)
		{
			num += mine[x + i][y + j];
			num -= '0';
		}
	}
	return num;
}

 
     

void spread(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y) 
{
	if (x >= 1 && x <= ROW && y >= 1 && y <= COL)//首先最重要的是元素坐标需要合法
	{
		if (mine[x][y] != '1' && isempty(mine, x, y) == 0 &&
			(show[x][y] != ' ' && show[x][y] == '*'))
                //该地址不是雷,该地址周围没有雷,该地址没有被排查过
		{
			show[x][y] = ' ';//已被排查的元素赋为空格,避免再次排查
			int i = -1;
			for (i = -1; i <= 1; i++)
			{
				int j = -1;
				for (j = -1; j <= 1; j++)
				{
					spread(mine, show, x + i, y + j);
				}
			}
		}
		else
		{
			if (isempty(mine, x, y) == 0)
			{
				show[x][y] = ' ';//周围没有雷的各自赋为空格
			}
			else
			{
				show[x][y] = isempty(mine, x, y) + '0';//有了的赋为数字字符
			}
		}
	}
}

六、标记与取消标记

1.整体逻辑

(1)标记就是把你还没有展示但是你已经知道有雷的位置进行标记,这里标记为#,相当于把*转为#;取消标记同理,把#转为*

(2)标记与取消标记需要条件,取消标记的条件是棋盘上必须有标记,标记的条件是标记数小于地雷数。

2.代码

void mark(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
    static int c = 0;//避免影响到其他程序,不必要
    do//先问问你标不标
    {
        printf("请选择以下操作\n");
        printf("0.不标记 1.标记地雷 2.取消做出的标记\n请输入:");
        scanf("%d", &c);
        static int num = 0;//已标记的坐标个数,放在静态区内随程序更新
        switch (c)
        {
	        case 1:
	        {
	            if(num<MINE)//在你用完全部的小旗之后,不被允许继续标记
		        {
		            while (1)//当你输错的时候可以重来
		            {
		                printf("请输入坐标:");
                        int x = 0;
                        int y = 0;
                        scanf("%d %d", &x, &y);
                        if (show[x][y] == '*')       
                        {
		                    show[x][y] = '#';
			                printboard(show, ROW, COL);
			                printf("标记成功\n");
			                num++;//标记数加一
		                    break;
			            }
			            else
			            {
			                printf("坐标非法重新输入\n");
			            }
                    }
		        }
		        else
		        {
		            printf("操作非法,退出\n");
		        }
		        printf("是否继续进行标记或取消标记操作:\n1.继续 0.放弃\n");
		        printf("请输入:");
		        scanf("%d", &c);//继续改变c选择继续这些操作还是继续游戏,下面的取消标注原理相同
		        break;
		    }
		    case 2:
		    {
		        if (num > 0)//首先得有已标记得位置才能取消
		        {
			        while (1)
			        {
			            printf("请输入坐标:");
			            int x = 0;
			            int y = 0;
			            scanf("%d %d", &x, &y);
			            if (show[x][y] == '#')
			            {
			            	show[x][y] = '*';
			    	        printboard(show, ROW, COL);
			              	printf("取消标记成功\n");
                            num--;
			    	        break;
			            }
			            else
			            {
			                printf("坐标非法重新输入\n");
			            }
		            }
		        }
		        else
		        {
			        printf("操作非法,退出\n");
		        }
		        printf("是否继续进行标记或取消标记操作:\n1.继续 0.放弃\n");
		        printf("请输入:");
		        scanf("%d", &c);
		        break;
		    }
		    case 0:
		    {
		        printf("继续游戏\n");
		        break;
		    }
	        default: 
		    {
		        printf("请重新输入\n");
                break;
	        }
         
        }
    } while (c);
}

七、整个游戏程序(白嫖直接看这里)

1.game.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1


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


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


void initialboard(char show[ROWS][COLS], int rows, int cols, char a);
//初始化数组
void printboard(char board[ROWS][COLS], int row, int col);
//打印数组
void setmine(char mine[ROWS][COLS], int rows, int cols,int m);
//设置地雷
void check(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
//检查地雷
void spread(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
//相邻零元素展开
void mark(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y);
//标记与取消标记地雷
int isempty(char mine[ROWS][COLS], int x, int y);
//判断周围是否有地雷
int iswin(char show[ROWS][COLS], int row, int col);
//判断胜利

2.game.c

#include"game.h"

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



void game()
{
	char mine[ROWS][COLS] = { 0 };
	char show[ROWS][COLS] = { 0 };
	
	initialboard(mine,ROWS,COLS,'0');
	initialboard(show, ROWS, COLS, '*');
	//初始化数组,将mine的每一个元素赋值为字符0
	//初始化数组,将show的每一个元素赋值为字符*,保持神秘感
	
	setmine(mine,ROWS,COLS,MINE);
	//放置地雷,没有地雷的地方为0,放地雷的地方为1
	
	//printboard(mine, ROW, COL);
	//printboard(show, ROW, COL);
	//打印n×n数组的中间n-1×n-1项元素
	
	printboard(show, ROW, COL);
	//打印ROW×COL的方格

	check(mine, show, ROW, COL);
}



int main()
{
	srand((unsigned int)time(NULL));
	int a = 0;
	do 
	{
		menu();
		printf("请输入:");
		scanf("%d", &a);
		switch (a)
		{
		    case 0:
		    {
			    printf("退出游戏\n");
			    break;
		    }
		    case 1:
		    {
		    	printf("开始游戏\n");
			    game();
			    break;
		    }
		    default:
		    {
		    	printf("请重新输入\n");
			    break;
		    }
		}
	} while (a);
	return 0;
}

3.function.c

#include"game.h"


void initialboard(char board[ROWS][COLS], int rows, int cols, char a)
{
	int i = 0;
	for (i = 0; i < rows; i++)
	{
		int j = 0;
		for (j = 0; j < cols; j++)
		{
			board[i][j] = a;
		}
	}
}



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



void setmine(char mine[ROWS][COLS], int rows, int cols, int m)
{
	int count = m;
	while (count)
	{
	    int x = rand() % ROW + 1;
	    int y = rand() % COL + 1;
		if (mine[x][y]=='0')
		{
			mine[x][y] = '1';
			count--;
		}
	}
}



void check(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0;
    int y = 0;
	while (1)
	{
		printf("请输入需要查找的坐标:");
		scanf("%d %d", &x, &y);
		if (x>=1 && x<=row && y>=1 && y<=col)
		{
			if (show[x][y] != '*')
			{
				printf("该地址已经被检查,请重新输入\n");
			}
			else
			{
				if (mine[x][y] == '1')
				{
					printf("你被炸死了\n");
					printboard(mine, ROW, COL);
					break;
				}
				else 
				{
				    int i = -1;
				    int num = 0;
				    for (i = -1; i <= 1; i++)
				    {
				    	int j = -1;
					    for (j = -1; j <= 1; j++)
					    {
					    	num += mine[x + i][y + j];
					    	num -= '0';
					    }
				    }
					if (num == 0)
					{
						spread(mine,show,x,y);
						if (iswin(show, ROW, COL) == ROW * COL - MINE)
						{
							printboard(mine, ROW, COL);
							printf("你赢了\n");
							break;
						}
					}
					else
					{
						show[x][y] = num+'0';
						if (iswin(show, ROW, COL) == ROW * COL - MINE)
						{
							printboard(mine, ROW, COL);
							printf("你赢了\n");
							break;
						}
					}
					printboard(show, ROW, COL);
					mark(mine, show, x, y);

				}
			}
		}
		else
		{
			printf("坐标不合法请重新输入\n");
		}
	}
}
	


void mark(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
	static int c = 0;
	do
	{
		printf("请选择以下操作\n");
		printf("0.不标记 1.标记地雷 2.取消做出的标记\n请输入:");
		scanf("%d", &c);
		static int num = 0;//已标记的坐标个数,放在静态区内随程序更新
        switch (c)
	    {
		    case 1:
			{
				if(num<MINE)//在你用完全部的小旗之后,不被允许继续标记
				{
					while (1)
					{
						printf("请输入坐标:");
						int x = 0;
						int y = 0;
						scanf("%d %d", &x, &y);
						if (show[x][y] == '*')
						{
							show[x][y] = '#';
							printboard(show, ROW, COL);
							printf("标记成功\n");
							num++;
							break;
						}
						else
						{
							printf("坐标非法重新输入\n");
						}
					}
				}
				else
				{
					printf("操作非法,退出\n");
				}
				printf("是否继续进行标记或取消标记操作:\n1.继续 0.放弃\n");
				printf("请输入:");
				scanf("%d", &c);
				break;
			}
			case 2:
			{
				if (num > 0)//首先得有已标记得位置才能取消
				{
					while (1)
					{
						printf("请输入坐标:");
						int x = 0;
						int y = 0;
						scanf("%d %d", &x, &y);
						if (show[x][y] == '#')
						{
							show[x][y] = '*';
							printboard(show, ROW, COL);
							printf("取消标记成功\n");
							break;
						}
						else
						{
							printf("坐标非法重新输入\n");
						}
					}
				}
				else
				{
					printf("操作非法,退出\n");
				}
				printf("是否继续进行标记或取消标记操作:\n1.继续 0.放弃\n");
				printf("请输入:");
				scanf("%d", &c);
				break;
			}
		    case 0:
		    {
				printf("继续游戏\n");
				break;
		    }
		    default: 
		    {
				printf("请重新输入\n");
				break;
		    }
	    }
	} while (c);
}



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



void spread(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y) 
{
	if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
	{
		if (mine[x][y] != '1' && isempty(mine, x, y) == 0 &&
			(show[x][y] != ' ' && show[x][y] == '*'))
		{
			show[x][y] = ' ';
			int i = -1;
			for (i = -1; i <= 1; i++)
			{
				int j = -1;
				for (j = -1; j <= 1; j++)
				{
					spread(mine, show, x + i, y + j);
				}
			}
		}
		else
		{
			if (isempty(mine, x, y) == 0)
			{
				show[x][y] = ' ';
			}
			else
			{
				show[x][y] = isempty(mine, x, y) + '0';
			}
		}
	}
}



//1.该坐标不是雷
//2.该坐标周围没有雷
//3.该坐标没有被排查过
int iswin(char show[ROWS][COLS],int row,int col)
{
	int win = 0;
	int i = 1;
	for (i = 1; i <= row; i++)
	{
		int j = 0;
		for (j = 1; j <= col; j++)
		{
			if (show[i][j] != '*' && show[i][j] != '#')
			{
				win++;
			}
		}
	}
	return win;
}

代码的缩进可能有问题,不影响使用。

 类似资料: