C语言-简易的扫雷游戏

1. 扫雷游戏分析和设计

1.1 扫雷游戏的功能说明

  • 体现菜单功能
  • 实现棋盘功能
  • 实现展示棋盘功能
  • 实现布雷功能
  • 使用控制台呈现扫雷游戏

游戏界面:

开始菜单

棋盘展示

1.2 游戏的功能分析

扫雷过程中,存储的雷以及玩家搜查的雷的信息都是需要存储的,对于一个9x9的棋盘,我们能够轻松的想到用一个9x9的二维数组来储存。

雷的信息我们便用'1'来存储,无雷则用'0'来存储。

而如果我们下达到像扫雷游戏中,点击一次,便告知周围九宫格内的雷的数量,我们只需将九宫格内的字符相加-'0',得到的便是雷的数量。

但问题随之而来,在检测棋盘周边时,会出现数组越界的情况,为了防止这样的情况,我们可以将9x9的棋盘变成11x11存储进数组内,但在显示时只展示中间的9x9的棋盘。

2. 代码的实现

2.1 菜单的实现

void menu() {
	printf("*********************\n");
	printf("******	1.game	*****\n");
	printf("******	0.exit	*****\n");
	printf("*********************\n");
	printf("注:只有扫出所有非雷处才可胜利!\n");
	printf("\n");
}

对于以上的简易菜单,我们需要实现的功能如下:

  1. 不断的循环,我们在游戏结束后如果我们还想继续游戏,我们希望接着弹出这个游戏菜单。
  2. 功能的实现,我们输入1便开始游戏,输入0便退出游戏,但我们也有可能输入别的,这就需要我们对错误信息进行提示。
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");
			printf("\n");
			break;
		}

	} while (input);
}

在这里,我们利用do...while循环便能够实现功能,若输入1则进入game()函数中进行游戏;若输入0便退出switch,同时因为while条件为input,所以一旦输入0,循环也退出,便达到了退出游戏的效果;若输入其他的,便会进入default中。

2.2 game函数的实现

在上文中,我们输入了1,进入了game()函数,我们便可以开始游戏了。首先我们需要创建数组来存储棋盘,我们需要两个数组,一个用来放雷,一个用来显示,如上图中的棋盘一样。

#define ROW 9
#define COL 9
#define ROWS 11
#define COLS 11
	char mine[ROWS][COLS] = { 0 };//存放雷区
	char show[ROWS][COLS] = { 0 };//用来显示

而为了实现雷区中1有雷0无雷,玩家眼中数字为雷的数量,'*'为未排查区域的效果,我们首先需要对棋盘初始化,其次需要将棋盘打印出来。

2.2.1 棋盘初始化

在这里我们创建一个Initboard函数以对棋盘初始化,对于这样的函数,我们首先要将数组传过去,其次需要告诉他我们的行列个数,最后需要告诉他我们希望棋盘变成什么样子。

	Initboard(mine, ROWS, COLS, '0');//初始化雷区,'0'为无雷,'1'为有雷
	Initboard(show, ROWS, COLS, '*');//初始化显示

//初始化函数
void Initboard(char board[ROWS][COLS], int rows, int cols, char set) {
	for (int i = 0; i < rows; i++) {
		for (int j = 0; j < cols; j++) {
			board[i][j] = set;
		}
	}
}

2.2.2 棋盘展示

对于这样一个Display函数,我们也需要将数组,棋盘的行列进行传参,随后就像初始化函数一样,利用两层循环嵌套实现对棋盘的打印。

	Display(show, ROW, COL);//展示

//显示函数
void Display(char board[ROWS][COLS], int row, int col) {
	printf("-----扫雷游戏-----\n");
	//打印列标
	for (int j = 0; j <= col; j++) {
		if (j == 0) {
			printf("  ");//让第一个不显示0,而是空格
			continue;
		}
		printf("%d ", j);
	}
	printf("\n");
	for (int i = 1; i <= row; i++) {
		printf("%d ", i);//打印行标
		for (int j = 1; j <= col; j++) {
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
	printf("-----扫雷游戏-----\n");
	printf("\n");
}

在这里为了让我们的游戏更为直观,我们打印了行标与列标,还打印了扫雷游戏的title,但本质还是对循环的嵌套运用。

2.2.3 棋盘安雷

我们使用rand实现对雷区安雷的随机性,同时我们引入时间戳以实现其初值的随机性。但要注意的是:srand需要的是unsigned int类型的值,所以我们需要对时间戳进行强制类型转换。

#define Easy_Count 10
srand((unsigned int)time(NULL));

//设置雷区函数
void SetMine(char board[ROWS][COLS], int row, int col) {
	int count = Easy_Count;
	while (count) {
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		if (board[x][y] == '0') {
			board[x][y] = '1';
			count--;
		}
	}
}

在这里我们设置难度为Easy—Count,也就是10个雷。将rand得出的随机值%列与行再+1,得到的便是x与y的准确坐标。

2.2.4 周边雷的数量的显示

在玩家输入坐标后,如果不是雷,我们需要对其周边雷的数量进行统计并展示。

//数出周边雷的函数
int get_mine_count(char board[ROWS][COLS], int x, int y) {
	int num = 0;
	for (int i = -1; i <= 1; i++) {
		for (int j = -1; j <= 1; j++) {
			num += board[x + i][y + j];//字符相加
		}
	}
	int ret = 9 * '0';//字符相加=他们的ascall码值相加,再减去9个'0'的ascall码,化为数字
	num -= ret;
	return num;
}

2.2.5 扫雷与胜负判定

在这里我们实现一个简单的扫雷,玩家需要找出所有没有雷的地方,才能判定胜利。

但对于扫雷函数的实现,我们仍要有注意的地方,玩家是否重复查找,玩家查找时是否踩雷,玩家是否输错坐标等。

我们引入x,y作为坐标;win用来观察是否胜利。

如果win==row*col-Easy-Count,也就是win等于棋盘位置的总数减去雷数,即玩家胜利,除此则进入循环扫雷环节。

首先我们需要对玩家输入的坐标进行判定,如果输错坐标,我们要提示玩家;而如果输对了坐标,我们则需对坐标是否重复排查进行判定。

我们知道在我们的show棋盘,也就是显示棋盘中,如果玩家没有搜查,则全为'*',如果有玩家搜查的地方则为其周边雷的数量,所以如果玩家输入的坐标此时不是'*',则玩家重复排查。

如果玩家没有重复排查,此时我们则只需要对此处坐标是否有雷进行判定。如果有雷,也就是mine棋盘上此处为'1',则我们在提示玩家比赛结束的同时还可以展示棋盘雷的位置。如果无雷则win++,同时展示此处周围雷的数量。

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) {
	int x = 0;
	int y = 0;
	int win = 0;
	//如果win<有雷数
	while (win < row * col - Easy_Count) {
		printf("公主请扫雷(请输入x y):");
		scanf("%d %d", &x, &y);
		//如果没有输错坐标
		if (x >= 1 && x <= row && y >= 1 && y <= col) {
			//如果重复排查
			if (show[x][y] != '*') {
				printf("\n");
				printf("这不整过了吗,重整!\n");
				printf("\n");

			}
			//如果没有重复排查
			else {
				//如果是雷
				if (mine[x][y] == '1') {
					printf("\n");
					printf("被炸死了笨比\n");
					printf("\n");
					Display(mine, ROW, COL);
					break;
				}
				//如果不是雷
				else {
					win++;
					int count = get_mine_count(mine, x, y);
					show[x][y] = count + '0';//转换成数字字符
					Display(show, ROW, COL);

				}
			}
		}
		//如果输错坐标
		else {
			printf("输错了笨比!给我重输!\n");
			printf("\n");
		}
		//如果win==无雷数
		if (win == row * col - Easy_Count) {
			printf("\n");
			printf("恭喜你胜利!\n");
			printf("\n");
			Display(mine, ROW, COL);
		}
	}
}

游戏展示:

3. 代码展示

如上,我们的游戏便完成了。要注意不要忘记对函数进行声明,虽然在编译器里不声明不一定报错,但为了代码的逻辑性与规范性,我们还是应该要养成这样良好的习惯。

我们可以使用两个文件,一个头文件用来函数声明,一个源文件用以功能实现。

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_Count 10

void menu();
void game();
void Initboard(char board[ROWS][COLS], int rows, int cols, char set);
void Display(char board[ROWS][COLS], int row, int 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);
int get_mine_count(char board[ROWS][COLS], int x, int y);

game.c:

#define _CRT_SECURE_NO_WARNINGS
#include "game.h"

void menu() {
	printf("*********************\n");
	printf("******	1.game	*****\n");
	printf("******	0.exit	*****\n");
	printf("*********************\n");
	printf("注:只有扫出所有非雷处才可胜利!\n");
	printf("\n");
}

void game() {
	char mine[ROWS][COLS] = { 0 };//存放雷区
	char show[ROWS][COLS] = { 0 };//用来显示
	Initboard(mine, ROWS, COLS, '0');//初始化雷区,'0'为无雷,'1'为有雷
	Initboard(show, ROWS, COLS, '*');//初始化显示
	Display(show, ROW, COL);//展示
	SetMine(mine, ROW, COL);//随机安排雷
	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");
			printf("\n");
			break;
		}

	} while (input);
}
//初始化函数
void Initboard(char board[ROWS][COLS], int rows, int cols, char set) {
	for (int i = 0; i < rows; i++) {
		for (int j = 0; j < cols; j++) {
			board[i][j] = set;
		}
	}
}
//显示函数
void Display(char board[ROWS][COLS], int row, int col) {
	printf("-----扫雷游戏-----\n");
	//打印列标
	for (int j = 0; j <= col; j++) {
		if (j == 0) {
			printf("  ");//让第一个不显示0,而是空格
			continue;
		}
		printf("%d ", j);
	}
	printf("\n");
	for (int i = 1; i <= row; i++) {
		printf("%d ", i);//打印行标
		for (int j = 1; j <= col; j++) {
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
	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;
		int y = rand() % col + 1;
		if (board[x][y] == '0') {
			board[x][y] = '1';
			count--;
		}
	}
}
//扫雷函数
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) {
	int x = 0;
	int y = 0;
	int win = 0;
	//如果win<有雷数
	while (win < row * col - Easy_Count) {
		printf("公主请扫雷(请输入x y):");
		scanf("%d %d", &x, &y);
		//如果没有输错坐标
		if (x >= 1 && x <= row && y >= 1 && y <= col) {
			//如果重复排查
			if (show[x][y] != '*') {
				printf("\n");
				printf("这不整过了吗,重整!\n");
				printf("\n");

			}
			//如果没有重复排查
			else {
				//如果是雷
				if (mine[x][y] == '1') {
					printf("\n");
					printf("被炸死了笨比\n");
					printf("\n");
					Display(mine, ROW, COL);
					break;
				}
				//如果不是雷
				else {
					win++;
					int count = get_mine_count(mine, x, y);
					show[x][y] = count + '0';//转换成数字字符
					Display(show, ROW, COL);

				}
			}
		}
		//如果输错坐标
		else {
			printf("输错了笨比!给我重输!\n");
			printf("\n");
		}
		//如果win==无雷数
		if (win == row * col - Easy_Count) {
			printf("\n");
			printf("恭喜你胜利!\n");
			printf("\n");
			Display(mine, ROW, COL);
		}
	}
}
//数出周边雷的函数
int get_mine_count(char board[ROWS][COLS], int x, int y) {
	int num = 0;
	for (int i = -1; i <= 1; i++) {
		for (int j = -1; j <= 1; j++) {
			num += board[x + i][y + j];//字符相加
		}
	}
	int ret = 9 * '0';//字符相加=他们的ascall码值相加,再减去9个'0'的ascall码,化为数字
	num -= ret;
	return num;
}

  • 28
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Squirrel-Htzsl

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值