用C语言实现简单版9*9扫雷小游戏

目录
  • 一.扫雷的规则
  • 二.代码实现前的一些问题
    • 1.棋盘尺寸=数组尺寸?
    • 2.一个数组足矣?
  • 三.代码实现
    • 0.初步完成头文件
    • 1.游戏的入口-菜单
    • 2.棋盘的初始化
    • 3.展示棋盘
    • 4.布置雷
    • 5.扫雷
  • 四.空格展开的实现
    • 五.源码展示

      一.扫雷的规则

      玩家通过点击棋盘上的格子来探雷,如果此处不是雷,则会显示出一个数字代表以此格为中心的3×3的区域的雷数;而如果此处是雷,玩家被炸死,游戏结束。当玩家点击完所有非雷区时,玩家胜利。


      二.代码实现前的一些问题

      1.棋盘尺寸=数组尺寸?

      显然我们会用数组来模拟棋盘。首先我们知道,当玩家点了非雷区时,该区域要反馈附近区域雷数的信息,这就意味着程序需要对以此格为中心的3×3区域的空间进行排查。而这个过程存在一个问题:当程序对以棋盘边角为中心的区域进行搜查时,会出现数组越界的情况。
      对于这个问题,有两种解决方法:
      (1)为搜查边角写另一种搜查函数
      (2)扩大数组的尺寸

      很明显,用方法(1)解决问题会比较麻烦。所以我选择方法(2)。方法(2)的实现非常简单。例如有一个55的棋盘,我们只需要用一个7*7的数组模拟它就行了。(实际多出来的部分只会在搜查雷数的时候会用到)

      同时方法(2)还有一个好处:数组下标与棋盘坐标可以一一对应,在后续代码实现,我们可以避免考虑数组下标与棋盘坐标的校正问题。我们都知道,数组的下标从0开始,但玩家真正需要访问的是数组下标1-5的部分。

      2.一个数组足矣?

      这里我们模拟一个棋盘。我们用1代表雷,0代表非雷。

      根据当玩家点击二行三列的格子时,这个格子会变成2,没有问题。但当玩家点击二行四列的格子时,这个会变成1。这里会出现歧义。
      而且如果只用一个数组,我们难以隐藏雷区信息,所以不妨使用两个数组:一个用来模拟雷区,一个用来模拟排查出雷的信息(提供给玩家)

      对于一个5×5的棋盘,我们可以创建两个char类型数组:
      char mine[7][7]
      char show[7][7]

      三.代码实现

      在本程序中,我会把代码写在三个文件中,分别是test.c(测试游戏),game.c(游戏相关函数的定义),game.h(库函数引用、函数声明以及#define定义常量)
      tips:若代码块第一行未标明所位于的文件,则按以下规定分辨:
      (1).函数的使用->test.c
      (2).函数的声明->game.h
      (3).函数的定义->game.c

      0.初步完成头文件

      //game.h
      #include <stdio.h>
      #include <stdlib.h>
      #include <time.h>
      #define ROW 9
      #define COL 9
      #define ROWS ROW+2
      #define COLS COL+2

      如果想更改行和列,直接在此处修改即可。这只是初始版的头文件,在后文会继续补充。
      Q:为什么有了ROW,还要定义ROWS?
      A:在后续既要用到11×11的数组,也要用到9×9的数组,方便后续使用。

      1.游戏的入口-菜单

      大致思路:由于菜单至少打印一次,所以在主函数里用do-while循环进行菜单打印,再用switch语句根据玩家的选择进行下一步

      //test.c
      #include "game.h"
      void menu()
      {
      	printf("**********************\n");
      	printf("****** 1.play ******\n");
      	printf("****** 0.exit ******\n");
      	printf("**********************\n");
      }
      
      void game()
      {
      
      }
      
      int main()
      {
      	int input = 0;
      	do{
      		menu();
      		printf("请选择:>");
      		scanf("%d", &input);
      		switch (input)//根据玩家选择进行不同操作
      		{
      		case 1:
      			game();
      			break;
      		case 0:
      			printf("退出游戏\n");
      			break;
      		default:
      			printf("输入错误,请重新输入\n");
      			break;
      			
      		}
      	} while (input);
      	return 0;
      }

      效果如下:

      2.棋盘的初始化

      大致思路:创建两个char类型的数组,再使用InitBoard()函数为其赋值。
      这里我们发现给两个数组初始化的内容不同,可以用两个函数来初始化。

      //test.c
      ...
      void game()
      {
      	char mine[ROWS][COLS];
      	char show[ROWS][COLS];
      	InitBoard1(mine, ROWS, COLS);
      	InitBoard2(show, ROWS, COLS);
      }
      ....
      //game.c
      #include "game.h"
      void InitBoard1(char board[ROWS][COLS], int rows, int cols)
      {
      	int i = 0;
      	int j = 0;
      	for (i = 0; i < rows; i++)
      	{
      		for (j = 0; j < cols; j++)
      		{
      			board[i][j] = '0';
      		}
      	}
      }
      void InitBoard2(char board[ROWS][COLS], int rows, int cols)
      {
      	int i = 0;
      	int j = 0;
      	for (i = 0; i < rows; i++)
      	{
      		for (j = 0; j < cols; j++)
      		{
      			board[i][j] = '*';
      		}
      	}
      }
      //game.h
      ...
      void InitBoard1(char board[ROWS][COLS], int rows, int cols);
      void InitBoard2(char board[ROWS][COLS], int rows, int cols);
      ...

      但其实只需要一个函数就能完成对两个数组赋不同值的操作,我们只需要将数组需要赋的值作为InitBoard()函数的参数,就能达成目的。

      InitBoard(mine, ROWS, COLS,'0');
      	InitBoard(show, ROWS, COLS,'*');
      void InitBoard(char board[ROWS][COLS], int rows, int cols,
      char set)
      {
      	int i = 0;
      	int j = 0;
      	for (i = 0; i < rows; i++)
      	{
      		for (j = 0; j < cols; j++)
      		{
      			board[i][j] = set;
      		}
      	}
      }
      void InitBoard(char board[ROWS][COLS], int rows, int cols,char set);

      3.展示棋盘

      大致思路,将数组show的内容打印出来呈现给玩家即可

      void game()
      {
      	char mine[ROWS][COLS];
      	char show[ROWS][COLS];
      	
      	InitBoard(mine, ROWS, COLS,'0');//初始化
      	InitBoard(show, ROWS, COLS,'*');//初始化
      	
      	DisplayBoard(show, ROW, COL);//展示棋盘
      }
      void DisplayBoard(char board[ROWS][COLS], int row, int col)
      {
      	int i = 0;
      	int j = 0;
      	for (i = 1; i <= row; i++)
      	{
      		for (j = 1; j <= col; j++)
      			printf("%c ", board[i][j]);
      		printf("\n");
      	}
      }
      void DisplayBoard(char board[ROWS][COLS], int row, int col);

      效果如下:

      便于提高棋盘可读性与美观性,我们可以在DisplayBoard()函数里为我们的棋盘添加行数、列数、分隔线。

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

      效果如下:

      4.布置雷

      大致思路:首先确定雷的个数。产生两个1-9的随机数x,y作为坐标,若mine[x][y]的值为'0',则将其改成'1',若mine[x][y]值为'1',则不修改值。将上述操作循环直至雷的个数达到需求量。在这里我们在BombSet()函数后面使用DisplayBoard()函数,查看一下效果(正式游戏时需删除DisplayBoard())。

      //game.h
      ...
      #define BOMB_COUNT 10
      ...
      void game()
      {
      	char mine[ROWS][COLS];
      	char show[ROWS][COLS];
      	
      	InitBoard(mine, ROWS, COLS,'0');//初始化
      	InitBoard(show, ROWS, COLS,'*');//初始化
      	
      	DisplayBoard(show, ROW, COL);//展示棋盘
      
      	BombSet(mine, ROW, COL, BOMB_COUNT);//布置雷
      	DisplayBoard(mine, ROW, COL);//展示棋盘
      }
      void BombSet(char board[ROWS][COLS], int row, int col, int count)
      {
      	while (count)
      	{
      		int x = rand() % row + 1;//产生1-9的随机数
      		int y = rand() % col + 1;
      		if (board[x][y] == '0')
      		{
      			board[x][y] = '1';
      			count--;
      		}
      	}
      }
      void BombSet(char board[ROWS][COLS], int row, int col, int count);

      但是光靠rand()函数是不能达到随机的效果的,所以我们需要在主函数中使用srand()函数保证其随机性。

      srand((unsigned int)time(NULL));

      5.扫雷

      大致思路:(1).首先我们要根据玩家输入的坐标来判断坐标是否合法,其次踩没踩雷。如果踩雷,游戏结束,打印数组mine(让玩家“死的瞑目”);没踩雷,进行下一步。
      (2).我们要对以此坐标为中心的3×3区域进行排查,将雷数(char类型)赋给此坐标对应的数组元素,再打印数组show,将信息反馈给玩家。
      (3).重复上述步骤。直至当非雷区被排查完时,玩家胜利。

      void game
      {
      ...
      	SearchBomb(mine, show, ROW, COL);//排雷
      }
      void SearchBomb(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
      {
      	int times = 0;//记录下子次数
      	while (times+BOMB_COUNT<row*col)//当下子次数+雷数<棋盘总格数时循环继续
      	{
      		int x = 0;
      		int y = 0;
      		printf("请输入您想排查的坐标:>");
      		scanf("%d%d", &x, &y);
      		if (x >= 1 && x <= row && y >= 1 && y <= col)//坐标是否合法
      		{
      			if (mine[x][y] == '1')//判断是否为雷
      			{
      				printf("游戏结束,您被炸死了。\n");
      				DisplayBoard(mine, ROW, COL);//打印mine
      				break;
      			}
      			else
      			{
      				show[x][y] = BombNum(mine, x, y);//获取雷数
      				DisplayBoard(show, ROW, COL);//打印show
      				times++;
      			}
      		}
      		else
      			printf("坐标非法,请重新输入!\n");
      	}
      	if (times + BOMB_COUNT == row*col)//判断是否胜利
      		printf("恭喜您,您成功了!\n");
      }
      void SearchBomb(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
      int BombNum(char board[ROWS][COLS], int x, int y)
      {
      	int i = 0;
      	int j = 0;
      	int sum = 0;
      	for (i = x - 1; i <= x + 1;i++)
      	{
      		for (j = y - 1; j <= y + 1;j++)
      			sum += board[i][j];
      	}
      	return sum - 9 * '0'+'0';//由于雷是'1',非雷是'0',所以直接相减最后再加'0'使其成为一个字符
      }

      BombNum()也可以这样定义

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

       


      四.空格展开的实现

      如上的设计有一个缺点:需要一个一个点格子,过于麻烦。一般的扫雷机制会有空格展开的机制:

      那么在C语言中可以怎样实现它呢?此处我想到的是递归:如果玩家点击的格子满足一定条件,那么程序就会以它为中心向四面八方继续排查,如此递归下去,最终就能达到我们想要的效果。

      那么递归条件是什么呢?
      (1).该格子不是雷。
      (2).该格子的附近8格都不是雷。
      (3).该格子没被排查过。(防止出现死递归)

      以下是代码部分:

      void SearchBomb(char mine[ROWS][COLS], char show[ROWS][COLS],
      int row, int col)
      {
      	int times = 0;//记录排查格子的数目
      	int* p = &times;//获取times的地址,方便后续在另一个函数对其进行修改
      	while (times+BOMB_COUNT<row*col)//循环条件
      	{
      		int arr[ROW][COL] = { 0 };//用一个数组记录一个格子是否被排查过,此处赋值为零,代表都未排查过   
      		int x = 0;
      		int y = 0;
      		printf("请输入您想排查的坐标:>");
      		scanf("%d%d", &x, &y);
      		if (x >= 1 && x <= 9 && y >= 1 && y <= 9)//坐标是否合法
      		{
      			if (mine[x][y] == '1')//如果是雷
      			{
      				printf("很遗憾,您被炸死了!\n");
      				break;
      			}
      			else
      			{
      				Unfold(mine, show, x, y, arr, p);
      				DisplayBoard(show, ROW, COL);
      			}
      		}
      		else
      			printf("坐标非法,请重新输入!\n");
      	}
      	if (times + BOMB_COUNT == row*col)//如果非雷区排查完
      		printf("恭喜您,胜利!\n");
      }
      void Unfold(char mine[ROWS][COLS] ,char show[ROWS][COLS], int x, int y,int arr[ROW][COL],int* pt)
      {
      	if (arr[x - 1][y - 1] == 0 && x >= 1
      	 && x <= 9 && y >= 1 && y <= 9)//判断此格是否被排查过以及坐标的合法性
      	{
      		(*pt)++;//排查次数+1
      		arr[x - 1][y - 1] = 1;//代表此格被排查
      		if (BombNum(mine, x, y) == '0')//如果附近八格没有雷
      		{
      			show[x][y] = ' ';
      			int i = 0;
      			int j = 0;
      			for (i = x - 1; i <= x + 1; i++)
      			{
      				for (j = y - 1; j <= y + 1; j++)
      				{
      						Unfold(mine, show, i, j, arr, pt);//递归
      				}
      			}
      		}
      		else
      		{
      			show[x][y] = BombNum(mine, x, y);//有雷则计算附近雷的数目,赋值给数组show对应的元素
      		}
      	}
      }
      void Unfold(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, int arr[ROW][COL], int* pt);

       

      五.源码展示

      game.h

      #include <stdlib.h>
      #include <time.h>
      #define ROW 9
      #define COL 9
      #define ROWS ROW+2
      #define COLS COL+2
      #define BOMB_COUNT 10
      
      void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
      void DisplayBoard(char board[ROWS][COLS], int row, int col);
      void BombSet(char board[ROWS][COLS], int row, int col, int count);
      void SearchBomb(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
      int BombNum(char board[ROWS][COLS], int x, int y);
      void Unfold(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, int arr[ROW][COL], int* pt);

      test.c

      #include "game.h"
      void menu()
      {
      	printf("**********************\n");
      	printf("****** 1.play ******\n");
      	printf("****** 0.exit ******\n");
      	printf("**********************\n");
      }
      
      void game()
      {
      	char mine[ROWS][COLS];
      	char show[ROWS][COLS];
      
      	InitBoard(mine, ROWS, COLS, '0');//初始化
      	InitBoard(show, ROWS, COLS, '*');//初始化
      
      	DisplayBoard(show, ROW, COL);//展示棋盘
      
      	BombSet(mine, ROW, COL, BOMB_COUNT);//布置雷
      
      	SearchBomb(mine, show, ROW, COL);//排雷
      }
      
      int main()
      {
      	int input = 0;
      	do{
      		menu();
      		printf("请选择:>");
      		scanf("%d", &input);
      		switch (input)//根据玩家选择进行不同操作
      		{
      		case 1:
      			game();
      			break;
      		case 0:
      			printf("退出游戏\n");
      			break;
      		default:
      			printf("输入错误,请重新输入\n");
      			break;
      
      		}
      	} while (input);
      	return 0;
      }

      game.c

      #include "game.h"
      
      void InitBoard(char board[ROWS][COLS], int rows, int cols,
      	char set)
      {
      	int i = 0;
      	int j = 0;
      	for (i = 0; i < rows; i++)
      	{
      		for (j = 0; j < cols; j++)
      		{
      			board[i][j] = set;
      		}
      	}
      }
      
      void DisplayBoard(char board[ROWS][COLS], int row, int col)
      {
      	printf("------扫雷游戏------\n");
      	int i = 0;
      	int j = 0;
      	for (i = 0; i <= row; i++)
      	{
      		printf("%d ", i);
      	}
      	printf("\n");
      	for (i = 1; i <= row; i++)
      	{
      		printf("%d ", i);
      		for (j = 1; j <= col; j++)
      			printf("%c ", board[i][j]);
      		printf("\n");
      	}
      	printf("------扫雷游戏------\n");
      }
      
      void BombSet(char board[ROWS][COLS], int row, int col, int count)
      {
      	while (count)
      	{
      		int x = rand() % row + 1;//产生1-9的随机数
      		int y = rand() % col + 1;
      		if (board[x][y] == '0')//不是雷
      		{
      			board[x][y] = '1';//布置雷
      			count--;
      		}
      	}
      }
      
      void SearchBomb(char mine[ROWS][COLS], char show[ROWS][COLS],
      	int row, int col)
      {
      	int times = 0;//记录排查格子的数目
      	int* p = &times;//获取times的地址,方便后续在另一个函数对其进行修改
      	while (times + BOMB_COUNT<row*col)//循环条件
      	{
      		int arr[ROW][COL] = { 0 };//用一个数组记录一个格子是否被排查过,此处赋值为零,代表都未排查过   
      		int x = 0;
      		int y = 0;
      		printf("请输入您想排查的坐标:>");
      		scanf("%d%d", &x, &y);
      		if (x >= 1 && x <= 9 && y >= 1 && y <= 9)//坐标是否合法
      		{
      			if (mine[x][y] == '1')//如果是雷
      			{
      				printf("很遗憾,您被炸死了!\n");
      				break;
      			}
      			else
      			{
      				Unfold(mine, show, x, y, arr, p);//展开
      				DisplayBoard(show, ROW, COL);//给玩家展示棋盘show
      			}
      		}
      		else
      			printf("坐标非法,请重新输入!\n");
      	}
      	if (times + BOMB_COUNT == row*col)//如果非雷区排查完
      		printf("恭喜您,胜利!\n");
      }
      
      int BombNum(char board[ROWS][COLS], int x, int y)
      {
      	return board[x - 1][y - 1] + board[x - 1][y] + board[x - 1][y + 1] +
      		board[x][y - 1] + board[x][y + 1] + board[x + 1][y - 1] + board[x + 1][y] +
      		board[x + 1][y + 1] - 8 * '0' + '0';
      }
      
      void Unfold(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, int arr[ROW][COL], int* pt)
      {
      	if (arr[x - 1][y - 1] == 0 && x >= 1
      		&& x <= 9 && y >= 1 && y <= 9)//判断此格是否被排查过以及坐标的合法性
      	{
      		(*pt)++;//排查次数+1
      		arr[x - 1][y - 1] = 1;//代表此格被排查
      		if (BombNum(mine, x, y) == '0')//如果附近八格没有雷
      		{
      			show[x][y] = ' ';
      			int i = 0;
      			int j = 0;
      			for (i = x - 1; i <= x + 1; i++)
      			{
      				for (j = y - 1; j <= y + 1; j++)
      				{
      					Unfold(mine, show, i, j, arr, pt);//递归
      				}
      			}
      		}
      		else
      		{
      			show[x][y] = BombNum(mine, x, y);//有雷则计算附近雷的数目,赋值给数组show对应的元素
      		}
      	}
      }

      本文转自网络,如有侵权请联系客服删除。