这是一篇介绍扫雷游戏的博客,采用C语言实现,主要作为数组的简单实践,包括有游戏实现的简单思路、游戏代码,以及笔者的一些小经验,希望可以给同为小白的同志一些参考,如有错误,还望大佬们不吝赐教。 此博客与上一篇三子棋有不少相似处,各位也可一看:三子棋C语言实现

一、实现思路

1、布置雷

  • 定义二维字符数组mine,用以存放雷,以‘1’表示雷,‘0’表示无雷;
  • 定义二维字符数组show,用以保存扫雷需要的信息;

2、排雷

以输入坐标的形式排雷,可能出现四种结果:

  • 坐标非法;
  • 踩到雷,游戏结束;
  • 踩到处周边有雷,显示周边雷的数量;
  • 踩到处周边无雷,将周边无雷的区域展开;

3、检查雷是否被排完

排完则游戏成功,否则游戏继续。

二、函数代码实现

1、“雷”数组的初始化以及打印“雷区”

  • 初始化: 将数组中的元素全部初始化为set;
//初始化数组
void Initarr(char arr[ROWS][COLS], int row, int col, char set)
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			arr[i][j] = set;
		}
	}
}
  • 打印雷区: 利用分割线分开每一次排雷,打印行标与列标以便选择位置。
//打印“雷区”,由于第一行、最后一行、第一列与最后一列作为辅助作用,
//因此不予打印,只打印中间9行。
void Display(char arr[ROWS][COLS], int row, int col)
{
	printf("------- 扫雷 ------\n");
	printf("0 1 2 3 4 5 6 7 8 9\n");//打印列标
	for (int i = 1; i < row + 1; i++)
	{
		printf("%d ", i);//打印行标
		for (int j = 1; j < col + 1; j++)
		{
			printf("%c ", arr[i][j]);
		}
		printf("\n");
	}
	printf("------- 扫雷 ------\n");
}

2、布置雷与扫雷

  • 布置雷 以count表示游戏难度,即布置雷的数量,通过传参保证函数脱离全局变量后依旧可用;
//随机布置雷
void Setmine(char mine[ROWS][COLS], int row, int col, int count)
{
	int x = 0;
	int y = 0;
	while (count)
	{
		//mine为11×11大小,首行末行与首列末列均不布置雷
		x = rand() % row + 1;
		y = rand() % col + 1;
		if (mine[x][y] == '0')
		{
			mine[x][y] = '1';
			count--;
		}
	}
}
  • 扫雷,根据出现的情况进行分类 在判断输赢时采用了循环检查雷是否排查完成,效率较低;
//扫雷可能出现的情况:
// 1、坐标非法->重新输入
// 2、坐标合法->A、踩雷->游戏结束
//              B、未踩雷->a、周边有雷->显示雷的数量
//                         b、周边无雷->大面积展开
//              C、该位置已经被扫过->坐标非法
void Sweep(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int count)
{
	int x = 0, y = 0;
	while (1)
	{
		printf("请输入坐标:\n");
		scanf("%d %d", &x, &y);
		if (x < 1 || x > row || y < 1 || y > col)
		{
			printf("坐标非法,请重新输入:\n");
		}
		else if (mine[x][y] == '1')
		{
			printf("此处有雷,游戏结束\n");
			Display(mine, row, col);
			break;
		}
		else if (show[x][y] != '*')
		{
			printf("该位置已被扫过,请重新输入:\n");
		}
		else
		{
			//周边无雷
			if (get_mine(mine, x, y) == 0)
			{
				unfold(mine, show, x, y);
				Display(show, row, col);
			}
			//周边有雷
			else
			{
				show[x][y] = get_mine(mine, x, y) + '0';
				Display(show, row, col);
			}
		}
		//判断是否完成
		int win = 0;
		for (int i = 1; i < row + 1; i++)
		{
			for (int j = 1; j < col + 1; j++)
			{
				if (show[i][j] == '*')
					win++;
			}
		}
		if (win == EASY)
		{
			printf("扫雷成功!\n");
			Display(mine, row, col);
			break;
		}
	}
		
}
  • 递归展开无雷处 采用递归,依次判断周边的八个位置,达到展开效果;
void unfold(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
	if (x == 0 || x == ROW + 1 || y == 0 || y == COL + 1)
		return;
	if (show[x][y] == '*')
	{
		int ret = get_mine(mine, x, y);
		if (ret == 0)
		{
			show[x][y] = ' ';
			unfold(mine, show, x -1, y -1);
			unfold(mine, show, x - 1, y);
			unfold(mine, show, x - 1, y + 1);
			unfold(mine, show, x, y - 1);
			unfold(mine, show, x, y + 1);
			unfold(mine, show, x + 1, y - 1);
			unfold(mine, show, x + 1, y);
			unfold(mine, show, x + 1, y + 1);
		}
		else
		{
			show[x][y] = ret + '0';
		}
	}
}
  • 获取周边雷的数量 检查周边8个位置的雷个数,利用两层嵌套循环检查;
//获取周边的雷的个数
int get_mine(char mine[ROWS][COLS], int x, int y)
{
	int count = 0;
	for (int i = -1; i < 2; i++)
	{
		for(int j = -1; j < 2; j++)
			if (mine[x + i][y + j] == '1')
			{
				count++;
			}
	}
	return count;
}

三、小tips

  • 定义二维数组存放雷时,将数组定义大两个,9×9的棋盘采用11×11的数组,如此,即便是判断边缘格子时也可以采用一样的函数,不用担心越界;
  • 定义游戏难度时,可将相关内容都放在头文件中,以#define定义的方式,方便以后修改;
  • 定义函数时,可将只会在本.c文件中用到的函数前加上static,防止其他文件调用,同时也让在其他.c文件中定义函数时更为方便;
  • 书写代码时,须首先严谨思考,考虑到各种结果,先思考再敲代码,会更有效率。

四、代码汇总

头文件game.h

#pragma once

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

#define ROW 9
#define COL 9

#define ROWS 11
#define COLS 11

//游戏难度
#define EASY 10


//初始化数组,初始arr数组中的值为set
void Initarr(char arr[ROWS][COLS], int row, int col, char set);

//打印“雷区”
void Display(char arr[ROWS][COLS], int row, int col);

//随机布置雷
void Setmine(char mine[ROWS][COLS], int row, int col, int count);

//扫雷
void Sweep(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int count);

函数文件game.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"game.h"
//具体函数的实现

//选择菜单
void menu()
{
	printf("******************\n");
	printf("***** 1、开始 ****\n");
	printf("***** 0、退出 ****\n");
	printf("******************\n");
}

//初始化数组
void Initarr(char arr[ROWS][COLS], int row, int col, char set)
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			arr[i][j] = set;
		}
	}
}

//打印“雷区”,由于第一行、最后一行、第一列与最后一列作为辅助作用,因此不予打印,只打印中间9行
void Display(char arr[ROWS][COLS], int row, int col)
{
	printf("------- 扫雷 ------\n");
	printf("0 1 2 3 4 5 6 7 8 9\n");//打印列标
	for (int i = 1; i < row + 1; i++)
	{
		printf("%d ", i);//打印行标
		for (int j = 1; j < col + 1; j++)
		{
			printf("%c ", arr[i][j]);
		}
		printf("\n");
	}
	printf("------- 扫雷 ------\n");
}

//随机布置雷
void Setmine(char mine[ROWS][COLS], int row, int col, int count)
{
	int x = 0;
	int y = 0;
	while (count)
	{
		//mine为11×11大小,首行末行与首列末列均不布置雷
		x = rand() % row + 1;
		y = rand() % col + 1;
		if (mine[x][y] == '0')
		{
			mine[x][y] = '1';
			count--;
		}
	}
}

//获取周边的雷的个数
static int get_mine(char mine[ROWS][COLS], int x, int y)
{
	int count = 0;
	for (int i = -1; i < 2; i++)
	{
		for(int j = -1; j < 2; j++)
			if (mine[x + i][y + j] == '1')
			{
				count++;
			}
	}
	return count;
}

//展开函数
static void unfold(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
	if (x == 0 || x == ROW + 1 || y == 0 || y == COL + 1)
		return;
	if (show[x][y] == '*')
	{
		int ret = get_mine(mine, x, y);
		if (ret == 0)
		{
			show[x][y] = ' ';
			unfold(mine, show, x -1, y -1);
			unfold(mine, show, x - 1, y);
			unfold(mine, show, x - 1, y + 1);
			unfold(mine, show, x, y - 1);
			unfold(mine, show, x, y + 1);
			unfold(mine, show, x + 1, y - 1);
			unfold(mine, show, x + 1, y);
			unfold(mine, show, x + 1, y + 1);

			
		}
		else
		{
			show[x][y] = ret + '0';
		}
	}
}
//扫雷可能出现的情况:
// 1、坐标非法->重新输入
// 2、坐标合法->A、踩雷->游戏结束
//              B、未踩雷->a、周边有雷->显示雷的数量
//                         b、周边无雷->大面积展开
//              C、该位置已经被扫过->坐标非法
void Sweep(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int count)
{
	int x = 0, y = 0;
	while (1)
	{
		printf("请输入坐标:\n");
		scanf("%d %d", &x, &y);
		if (x < 1 || x > row || y < 1 || y > col)
		{
			printf("坐标非法,请重新输入:\n");
		}
		else if (mine[x][y] == '1')
		{
			printf("此处有雷,游戏结束\n");
			Display(mine, row, col);
			break;
		}
		else if (show[x][y] != '*')
		{
			printf("该位置已被扫过,请重新输入:\n");
		}
		else
		{
			//周边无雷
			if (get_mine(mine, x, y) == 0)
			{
				unfold(mine, show, x, y);
				Display(show, row, col);
			}
			//周边有雷
			else
			{
				show[x][y] = get_mine(mine, x, y) + '0';
				Display(show, row, col);
			}
		}
		//判断是否完成
		int win = 0;
		for (int i = 1; i < row + 1; i++)
		{
			for (int j = 1; j < col + 1; j++)
			{
				if (show[i][j] == '*')
					win++;
			}
		}
		if (win == EASY)
		{
			printf("扫雷成功!\n");
			Display(mine, row, col);
			break;
		}
	}	
}

void game()
{
	// mine数组用以存放雷
	char mine[ROWS][COLS] = { 0 };
	// show数组用以存放扫出的信息
	char show[ROWS][COLS] = { 0 };
	//初始化mine数组为字符0,show数组为字符'*'
	Initarr(mine, ROWS, COLS, '0');
	//Display(mine, ROW, COL);//检测初始化效果
	Initarr(show, ROWS, COLS, '*');
	//Display(show, ROW, COL);//检测初始化效果
	Setmine(mine, ROW, COL, EASY);
	//Display(mine, ROW, COL);//查看雷的布置位置
	Display(show, ROW, COL);
	Sweep(mine, show, ROW, COL, EASY);
}
  • 测试文件test.h
#define _CRT_SECURE_NO_WARNINGS 1

#include"game.h"

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