C语言—实现扫雷游戏

目录

一、准备工作

二、步骤分解

2.1 数组初始化

2.2 设置雷

2.3 打印棋盘

2.4 扫雷

2.5 菜单

三、汇总


一、准备工作

  • 我们用一个头文件game.h,来放置函数的声明,使用库函数需要包含的头文件,以及宏定义
  • 用一个源文件test.c来实现游戏的整个逻辑运行,
  • 再用另一个源文件game.c来进行游戏相关函数的实现  

我们将整个游戏分成大致两部分:

1.布置雷

2.扫雷

二、步骤分解

2.1 数组初始化

首先我们需要创建两个二维数组,一个数组mine用来存放好布置的雷的信息,也就是后台看不到的,另一个数组show用来存放好排查出来的雷的信息,也就是一次次展现给用户看的

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

关于二维数组的大小,我们想要一个9*9的棋盘来进行游戏,埋进去10个雷,但是因为在扫雷的过程中,每一个格子对雷的数目的统计是通过计算其周围相邻的8个方格的雷的个数,即:

                                              

但是如果仅仅把二维数组设置成9*9的大小时,当遇到周围边界的格子,在统计数目时就会发生数组越界的问题,如图所示:

                                               

所以,我们在设置二维数组的大小时,就会把周围全部扩大一格,来防止统计时数组越界,即设置为11*11的大小,只不过在打印时打印9*9的大小就可以了

所以采用define定义的常量,这样的好处是以后可以轻松修改棋盘的大小,代码放于game.h中

同时,我们想要把数组初始化没有雷为0,把雷表示成为1,但是这样做当周围8个格子的雷的个数为1时,就会出现歧义,所以我们在设置雷时用字符‘0’表示没有雷,用字符‘1’表示雷,就可以避免歧义,所以是char类型的二维数组

初始化棋盘的函数(InitBoard):

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

随后我们在test.c的main函数中连续调用两次该函数,第一次将mine数组全部设置为字符‘0’,为接下来的埋雷做准备,第二次是在开始排雷前展现给用户的界面,我们用 来表示还未进行排雷的地方

2.2 设置雷

先来看设置雷的函数(SetMine):

void SetMine(char board[ROWS][COLS], int row, int col)
{
	int count = EASY_COUNT;
	while (count)
	{
		int x = rand() % row + 1;//0~8+1-->1~9
		int y = rand() % col + 1;

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

其中,EASY_COUNT是指要设置的雷的个数,也用define定义常量,方便以后修改

紧接着,因为我们要在x,y坐标范围都是1~9之间的随机位置埋雷,这里我们就使用随机数生成函数rand函数

因为rand函数会生成0~32767之间的数,%9之后为0~8的范围,+1就变成了我们想要的范围1~9

在调用rand函数前我们要先调用srand函数,这是规定死的,而时间的不断流逝所生成的时间戳就是真正随机的,这里用time函数来生成时间戳,而srand函数的接收值为unsigned int类型

代码如下:

(关于这几个函数的用法和时间戳的概念如果有不清楚的地方请自行百度)

在设置好坐标随机不断在1~9范围内生成之后,我们用while循环进行埋雷,总共要埋10次

但是为了避免在同一个坐标上重复埋雷,我们使用count作为计数器,通过if语句,在不重复的地方埋了雷之后,count-1,直到count为0结束循环

2.3 打印棋盘

打印棋盘的函数(DisPlayBoard)

void DisPlayBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	printf("-------- 扫雷 --------\n");
	for (i = 0; i <= row; i++)
	{
		//打印列号
		printf("%d ", i);
	}
	printf("\n");
	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");
	}
}

函数中第一个for循环打印的是列号,从0开始

下面则是每一排先打印行号,再打印棋盘上的字符内容

每一次扫雷后都会重复调用该函数,第一次的打印结果如下:

2.4 扫雷

扫雷实现的函数如下(FindMine):

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)
	{
		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);
				break;
			}
			else
			{
				if (show[x][y] != '*')
				{
					printf("该坐标已经被排查过了,无需再排查\n");
				}
				else
				{
					//统计mine数组的x,y坐标周围8个坐标中有几个雷
					int count = GetMineCount(mine, x, y);
					show[x][y] = count + '0';//把数字3换成字符3放进去
					DisPlayBoard(show, ROW, COL);
					win++;
				}
			}
		}
		else
		{
			printf("输入的坐标非法,请重新输入\n");
		}
	}
	if (win == row * col - EASY_COUNT)
	{
		printf("恭喜你,排雷成功\n");
		DisPlayBoard(mine, ROW, COL);
	}
}

首先,我们要探讨一个问题,就是怎样统计输入的坐标周围8个格子的雷的个数

我们知道,字符‘0’的ASCII码值是48,字符‘1’的ASCII码值是49,字符‘2’的ASCII码值是50

发现了吗?字符‘1’-‘0’的结果不正好就是1吗,字符‘2’-‘0’的结果不正好就是2

所以我们可以将输入的坐标的8个格子里的字符相加,再减去8个‘0’,算出来的值不就是坐标周围雷的个数吗,再将这个数字加上一个字符‘0’,就变为字符数字,可以放入show中展现在用户的面前

而负责将周围8个格子里的字符相加的函数,我们命名为GetMineCount:

int GetMineCount(char mine[ROWS][COLS], 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';
}

                             

再看代码中的win,win是指我们扫雷的次数,每执行输入一次坐标排查一个格子之后,win就执行+1,当win的值等于总共的格子数减去雷的数后,就代表扫雷已经结束,全部扫完,打印排雷成功

2.5 菜单

菜单函数(menu):

放置在程序的最前端,用户输入1后即可开始游戏

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

三、汇总

我们将上面零散的各个函数统一汇总到一个名为game的函数中,执行逻辑为

创建棋盘 —> 初始化棋盘 —> 布置雷 ——> 打印棋盘 ——> 排查雷

如下所示:

void game()
{
	char mine[ROWS][COLS] = {0};//存放好布置的雷的信息
	char show[ROWS][COLS] = {0};//存放好排查出来的雷的信息
	InitBoard(mine, ROWS, COLS, '0');
	InitBoard(show, ROWS, COLS, '*');
	SetMine(mine, ROW, COL);
    DisPlayBoard(show, ROW, COL);
	FindMine(mine, show, ROW, COL);
}

最终我们在main函数里执行所有程序,写法如下:

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

			}
	} while (input);
	return 0;
}

这里我们使用do while循环,确保用户一开始就能够直接选择进行游戏,输入1执行函数game,游戏开始,0则退出

所有代码汇总:

game.h:

#pragma once
#define ROW 9
#define COL 9
#define ROWS 11
#define COLS 11
#define _CRT_SECURE_NO_WARNINGS
#define EASY_COUNT 10
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);//棋盘初始化
void DisPlayBoard(char board[ROWS][COLS], int rows, int cols);//打印棋盘
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

#include "game.h"
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
	int i = 0;
	for (i = 0; i < rows; i++)
	{
		int j = 0;
		for (j = 0; j < cols; j++)
		{
			board[i][j] = set;
		}
	}
}

void DisPlayBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	printf("-------- 扫雷 --------\n");
	for (i = 0; i <= row; i++)
	{
		//打印列号
		printf("%d ", i);
	}
	printf("\n");
	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");
	}
}

void SetMine(char board[ROWS][COLS], int row, int col)
{
	int count = EASY_COUNT;
	while (count)
	{
		int x = rand() % row + 1;//0~8+1-->1~9
		int y = rand() % col + 1;

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

int GetMineCount(char mine[ROWS][COLS], 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 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)
	{
		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);
				break;
			}
			else
			{
				if (show[x][y] != '*')
				{
					printf("该坐标已经被排查过了,无需再排查\n");
				}
				else
				{
					//统计mine数组的x,y坐标周围8个坐标中有几个雷
					int count = GetMineCount(mine, x, y);
					show[x][y] = count + '0';//把数字3换成字符3放进去
					DisPlayBoard(show, ROW, COL);
					win++;
				}
			}
		}
		else
		{
			printf("输入的坐标非法,请重新输入\n");
		}
	}
	if (win == row * col - EASY_COUNT)
	{
		printf("恭喜你,排雷成功\n");
		DisPlayBoard(mine, ROW, COL);
	}
}

test.c

#include "game.h"

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

void game()
{
	char mine[ROWS][COLS] = {0};//存放好布置的雷的信息
	char show[ROWS][COLS] = {0};//存放好排查出来的雷的信息
	InitBoard(mine, ROWS, COLS, '0');
	InitBoard(show, ROWS, COLS, '*');
	SetMine(mine, ROW, COL);
    DisPlayBoard(show, ROW, COL);
	FindMine(mine, show, ROW, COL);
}

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

			}
	} while (input);
	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值