C语言实现基础版扫雷

1、扫雷游戏简介

在这里插入图片描述

如上所示是一个九成九的初阶版扫雷棋盘,棋盘上八十一个格子共有十处埋有地雷。当在(x,y)处有地雷,玩家被炸死,游戏结束,(x,y)处没有地雷,(x,y)处会显示其周围八个坐标雷的信息,假设周围八处共有三处埋有地雷,则在(x,y)处显示3。

2、游戏实现流程

  1. 扫雷游戏棋盘的设计
  2. 棋盘的初始化
  3. 生成雷埋于棋盘中
  4. 打印棋盘
  5. 玩家找雷且输赢判定

3、游戏棋盘的设计

1、在九成九的棋盘中下棋,无非就是在二维数组中进行存放元素。char board [9][9] = { 0 };
2、设定将一块棋盘上埋雷的信息存放字符1,无雷的信息存放字符0(就是在字符数组中存放字符1与字符0)。
3、根据游戏规则,当一个坐标处无雷时,会在该坐标上显示周围八个坐标的信息,假设当周围有一个雷时,该坐标显示1,如何区分这个1是此坐标埋的雷的信息,还是此处坐标无雷,显示了周围八个坐标雷的信息。为了解决此问题,我们可以创建两个数组,一个数组用于存放埋雷的信息,一个用于存放展示给玩家看某坐标周围八个坐标的信息。
char mine[9][9] = { 0 };此数组用于存放埋雷的信息。
char show[9][9] = { 0 };此数组用于存放排查出雷的信息。
4、当我们遍历棋盘最外围处坐标周围坐标的信息时,棋盘最外围坐标的周围没有八个坐标,如四个对角坐标,他们的周围只有三个坐标。对于数组的遍历来说,就会造成越界。为了避免这种情况,我们给最边缘处再加上一行一列,此时二维数组的大小由[9][9]变成了[11][11]。不过我们使用的部分的大小依然是九成九,数组行和列在下标上的范围是1~9。如下图所示:
在这里插入图片描述

4、游戏棋盘的初始化

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;
		}
	}
}

1、将mine数组全部初始化为字符0,show数组初始化为字符*。
2、为了将数组初始化函数的普遍性提高,能将我们传过去的任意字符数组初始化为我们想要的结果,在设计初始化函数的时候,创建一个形参将要初始化的内容也一并接收。这样将要初始化的内容一并传过去,传的是什么,初始化的就是什么。
3、ROWS与COLS是我们设定的行与列,已经在一个专门的头文件(.h文件)中用#define的标识符常量定义好为11行11列,如下:

#define ROW 9
#define COL 9

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

5、设置雷的信息

我们将mine数组中存放雷的信息

SetMine(mine, ROW, COL);//将mine数组传过去布置雷

void SetMine(char board[ROWS][COLS], int row, int col)
{
	int count = EASY_COUNT;  //布置雷的个数
	while (count)
	{
		int x = rand() % row + 1; //随机数模9,得到0~8的随机数,加上1,得到1~9的随机数
		int y = rand() % col + 1;

		if (board[x][y] == '0')
		{
			board[x][y] = '1';//将雷的信息布置为1。
			count--;
		}
	}
}

1、行和列只在1~9的范围上布置雷,所以传参传的是ROW与COL。
2、EASY_COUNT是我们布置雷的个数,已经在头文件(.h文件)中用#define的标识符常量定义为10,#define EASY_COUNT 10。
3、利用srand与rand函数生成1 ~ 9的随机数,随机数组成棋盘行和列的坐标,在棋盘1 ~ 9的行和列上存放字符1,即埋雷。
4、埋雷的时候,为了不再一个地方重复埋雷,导致埋雷的个数不足十个,先判断次数坐标是否已经有雷,有雷即这个坐标已经存放了字符1,没有雷,即这个坐标依然是初始化的字符0。
5、循环条件count,布置好一个雷,count - 1,直到十个雷全部埋好,循环条件不满足,循环结束。

6、打印棋盘

我们展示的棋盘是show数组,所以将show数组打印出来。当然后面遇到雷炸了,或者玩家赢了等游戏结束的情形,我们也要通过打印函数,将mine数组打印出来,告诉玩家为什么输或者为什么赢。

DisplayBoard(show, ROW, COL);//打印的时候只打印我们要展示的数组下标1~9的范围,所以传参ROW与COL

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	printf("-------扫雷--------\n");
	for (j = 0; j <= col; j++)
	{
		printf("%d ", j);//打印列号,为了用户能够在棋盘上识别出棋子的坐标
	}
	printf("\n");
	for (i = 1; i <= row; i++)//我们需要用的部分的是ROW与COL这么大的空间。在这里为1~9。
	{
		printf("%d ", i);//打印行号,为了用户能够在棋盘上识别出棋子的坐标
		for (j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
	printf("-------扫雷--------\n");
}

打印结果
在这里插入图片描述

  1. 虽然我们要打印数组行和列的范围是1 ~ 9,不过函数形参接收的数组设计依然要写成[ROWS][COLS],因为我们设计的数组本身就是这么大,必须要用相应大的数组来作为形参来接收,只不过我们只是用其中1 ~ 9的范围,而不是全部的范围0 ~ 10。
  2. 为了玩家便于知道棋盘上的坐标,我们在棋盘周围将行号与列号同时打印出来,为了对仗工整,列号的打印为0 ~ 9,行号的打印为1 ~ 9。

7、玩家找雷且输赢判定

排查雷,在mine数组中找雷,找出雷的信息显示在show数组中,所以要将两个数组都作为参数传过去。

FindMine(mine, show, ROW, COL);

int GetMine_count(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'); //字符1的ASCII值是49,字符的ASCII值是48,'1'-'0'=49-48=1,所以将board[x][y]
	                                    //周围八个坐标相加减去八个字符0,就能得到周围board[x][y]八个坐标有几个雷的信息
	                                    //不能妄想将周围八个坐标相加得到几就有几个雷,因为这里是字符1,不是数字1。
}

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int win = 0;//找到非雷的个数
	while (win < row * col - EASY_COUNT)//9*9-10=71
	{
		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')//等于1,说明踩到雷了
				{
					printf("很遗憾,你被炸死了\n");
					DisplayBoard(mine, ROW, COL);//告诉玩家为什么被炸死了,即这里雷的信息打印出来
					break;
				}
				else//如果不是雷,显示这个坐标周围雷的信息(周围有几个雷)
				{
					win++;
					int count = GetMine_count(mine, x, y);
					show[x][y] = count + '0';//将周围雷的信息传递个这个坐标,因为传递回来的是数字,例如将数字3转化为字符3,加上字符0
											 //3+'0'=3+48=51,数字3对应的ASCII值就是51,将51存进字符数组,存进去的就是字符3。

					DisplayBoard(show, ROW, COL);//此时将棋盘上的信息打印一遍
				}
			}
			
		}
		else
		{
			printf("输入坐标非法,请重新输入");
		}
	}
	if (win == row * col - EASY_COUNT)
	{
		printf("恭喜你,排雷成功\n");
		DisplayBoard(mine, ROW, COL);
	}
	
}
  1. GetMine_count(mine, x, y); 函数是为了探寻坐标(x,y)处周围八个坐标的雷的信息。
  2. 字符1的ASCII值是49,字符的ASCII值是48,‘1’-‘0’=49-48=1,所以将board[x][y]周围八个坐标相加减去八个字符0,就能得到周围board[x][y]八个坐标有几个雷的信息。不能妄想将周围八个坐标相加得到几就有几个雷,因为这里是字符1,不是数字1。这个案例提示我们要注意内存的含义,即1. 内存中存的是什么,2. 怎么理解这块内存
  3. 玩家在输入某个坐标(x,y)后,我们会将此坐标雷的信息标记在数组show中,但是我们是在mine数组中找的雷,并且对坐标(x,y)找寻后,没有在数组mine中进行标记,所以在下次重复输入相同坐标(x,y)后,会使win++重复,导致判断赢的循环条件在没有排除雷的情况下进行了修正。使得游戏出现bug,为了避免这种情况,对输入的坐标判断其是否已经被排查过了,排查过的坐标在数组show上是已经被标记过周围雷的信息的,即show数组上该坐标的元素不是初始化的 ’ * '。------> if (show[x][y] != ‘*’)
  4. 玩家什么时候赢?
    在给一个9×9的棋盘上布置10个雷,当无雷的9×9-10=71个坐标数都被排查了,说明已经取得胜利。所以给循环条件设计为 win<71,没成功排查一个坐标数win+1。循环中止后,再判断是否 win=71(因为被炸死也会中止循环),等于,说明赢了。
  5. 被炸死,即玩家输入的该坐标,对应的mine数组中存放的是字符1。

8、游戏完整代码

game.h 头文件:包含了库函数的头文件以及自定义函数的声明

#pragma once

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

#define ROW 9
#define COL 9

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

#define EASY_COUNT 10

void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
void DisplayBoard(char board[ROWS][COLS], int row, int col);//数组部分依然是ROWS与COLS,因为我们定义的数组就是这么大。虽然我们只用ROW与COL部分的空间。
void SetMine(char board[ROWS][COLS], int row, int col);
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);

game.c文件:实现扫雷的主要功能,如:初始化棋盘、设计雷的信息、打印棋盘、玩家找雷、判断输赢

#define _CRT_SECURE_NO_WARNINGS 1

#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)
{
	int i = 0;
	int j = 0;
	printf("-------扫雷--------\n");
	for (j = 0; j <= col; j++)
	{
		printf("%d ", j);//打印列号,为了用户能够在棋盘上识别出棋子的坐标
	}
	printf("\n");
	for (i = 1; i <= row; i++)//我们需要用的部分的是ROW与COL这么大的空间。在这里为1~9。
	{
		printf("%d ", i);//打印行号,为了用户能够在棋盘上识别出棋子的坐标
		for (j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
	printf("-------扫雷--------\n");
}

void SetMine(char board[ROWS][COLS], int row, int col)
{
	int count = EASY_COUNT;  //布置雷的个数
	while (count)
	{
		int x = rand() % row + 1; //随机数模9,得到0~8的随机数,加上1,得到1~9的随机数
		int y = rand() % col + 1;

		if (board[x][y] == '0')
		{
			board[x][y] = '1';//将雷的信息布置为1。
			count--;
		}
	}
}

int GetMine_count(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'); //字符1的ASCII值是49,字符的ASCII值是48,'1'-'0'=49-48=1,所以将board[x][y]
	                                    //周围八个坐标相加减去八个字符0,就能得到周围board[x][y]八个坐标有几个雷的信息
	                                    //不能妄想将周围八个坐标相加得到几就有几个雷,因为这里是字符1,不是数字1。
}

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int win = 0;//找到非雷的个数
	while (win < row * col - EASY_COUNT)//9*9-10=71
	{
		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')//等于1,说明踩到雷了
				{
					printf("很遗憾,你被炸死了\n");
					DisplayBoard(mine, ROW, COL);//告诉玩家为什么被炸死了,即这里雷的信息打印出来
					break;
				}
				else//如果不是雷,显示这个坐标周围雷的信息(周围有几个雷)
				{
					win++;
					int count = GetMine_count(mine, x, y);
					show[x][y] = count + '0';//将周围雷的信息传递个这个坐标,因为传递回来的是数字,例如将数字3转化为字符3,加上字符0
											 //3+'0'=3+48=51,数字3对应的ASCII值就是51,将51存进字符数组,存进去的就是字符3。

					DisplayBoard(show, ROW, COL);//此时将棋盘上的信息打印一遍
				}
			}
			
		}
		else
		{
			printf("输入坐标非法,请重新输入");
		}
	}
	if (win == row * col - EASY_COUNT)
	{
		printf("恭喜你,排雷成功\n");
		DisplayBoard(mine, ROW, COL);
	}
	
}

test.c文件:将主函数放在此文件中,整合实现扫雷游戏功能的函数的顺序,什么时候实现什么函数的功能。

#define _CRT_SECURE_NO_WARNINGS 1

#include"game.h"

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

void game()
{
	char mine[ROWS][COLS] = { 0 };//存放布置好的的雷的信息
	char show[ROWS][COLS] = { 0 };//存放排查出的雷的信息

	//初始化数组的内容为指定的内容
	//mine 数组在没有布置雷的时候,都是'0'
	InitBoard(mine, ROWS, COLS,'0'); //将要初始化的内容一并传过去,传的是什么,初始化的就是什么
	//show 数组在没有排查雷的时候,都是'*'
	InitBoard(show, ROWS, COLS,'*');

	//DisplayBoard(mine, ROW, COL);//一般不打印埋雷的信息,不能让人看见埋雷的信息

	//设置雷
	SetMine(mine, ROW, COL);//将mine数组传过去布置雷
	DisplayBoard(show, ROW, COL);//打印的时候只打印我们要展示的内容,所以传参ROW与COL
	
	//排查雷,在mine数组中找雷,找出雷的信息显示在show数组中,所以要将两个数组都作为参数传过去。
	FindMine(mine, show, ROW, COL);
}
int main()
{
	srand((unsigned int)time(NULL));
	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;
}
  1. 选择do while循环结构,程序一运行先打印游戏菜单,选择是否玩游戏。将输入决定玩游戏的 input来作为循环条件,input = 1,玩游戏 ;input =0 ,游戏结束,循环结束;input = 其他数字,循环条件非 0 循环依然在继续,重新选择是否玩游戏。
  2. 如何测试这个游戏代码的正确性?
    我们先在头文件中将雷的个数改为80个,在游戏开始时将show数组与mine数组同时打印出来,这样就能知道剩下的唯一那个不是雷的具体坐标。此时想测试玩家被炸死或则玩家赢了游戏都是十分方便快捷的。
  • 16
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 13
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值