扫雷-C语言实现

目录

//初始化

//打印棋盘

//布置雷

//排查雷

//展开周围区域

//完整代码


今天我来带大家来写扫雷的小游戏

 我们还是用工程化的方法来写,首先创建一个.h文件和两个.文件(一个实现函数接口,一个用来测试)。

 同样的,为了避免main函数内过于冗杂,我们代码将写在test函数里,我们首先来实现游戏菜单,只需使用自己喜欢的符号用printf输出即可:

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

大家按照自己的喜好来写即可,接下来,我们实现用户的选择逻辑,使用do-while和switch语句即可完成

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

此时,我们先不要着急,先看看我们的代码能不能正常运行,要记住写一步测一步,否则后期出现几十个错误,心态的一下子就崩了。

 很好,我们接着来实现game函数。

这里首先,我们要确定扫雷棋盘的大小,我们选取9*9的棋盘,为了以后修改棋盘大小方便,我们一劳永逸的使用#define来定义。

这里先给不知道扫雷规则的同学科普一下,以上边的棋盘为例:数字1,代表这个格子以自身为中心,周围3*3(除掉自己)的八个格子里有一颗雷,同样的,数字3就代表周围八个格子里有三颗雷,我们需要根据数字来判断哪里有雷,哪里没有雷,我们要把所有没有雷的格子点开,这样就算游戏成功,如果在排雷的过程中点到了雷,那么游戏失败。

我们选用char数组来存放棋盘中的元素,我们先规定,棋盘里边的1为雷,0为非雷,也就是说,这个棋盘里边的元素只有0和1,同时,我们再定义一个char数组,用来存放我们游戏里的周围雷数,比如上面的1,2,3等等,定义两个数组可以使我们写代码的难度降低许多。

我们定义mine数组进行存放雷和非雷的数据,定义show数组存放我们排雷信息。用户在选择要排查的格子后,会像游戏里一样显示出数字,这个数字的计算思路就是把它周围的8个格子里的数据加起来,我们把这个数字存放到show数组里。我们会发现,在我们计算边缘格子的时候,比如第一行第一列这个格子,它的周围不够8个格子,如果我们要加的话会出现数组越界,同时,我们再写一个函数又非常麻烦,这里我们就可以把棋盘扩大一圈,原本是9*9的棋盘,现在就变成了11*11,同时为了存储数据方便,我们把show数组也变成11*11,这样两边的坐标就可以通用了。

所以我们使用#define定义如下:

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

这样我们以后要修改棋盘大小使也非常方便。

//初始化

接着,我们来实现棋盘的初始化,我们要把mine数组里的元素全部初始化为0,当然,我们在给用户展示时,只会打印show数组,为了保持神秘感,我们把show数组里的元素全部初始化为 ” * “ 号,就如同游戏开始全是空白一样。

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

因为要初始化的是整个数组(11*11)而不只是我们要打印的部分(9*9),所以传的参数是rows和cols,同时,为了提高代码的复用性,我们要初始化两个数组,两个数组初始化的元素不同,但我们不可能写两个一样的函数,所以我们给定最后一个参数为char类型,传入我们想要初始化的结果。

//打印棋盘

我们再来写打印棋盘的函数,这样就可以打印出来我们的棋盘,看看我们写的对不对

void DisplayBoard(char board[ROWS][COLS], int row, int col)//打印棋盘
{
	int i = 0;
	for (i = 1; i <= row; i++) {
		int j = 0;
		for (j = 1; j <= col; j++) {
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
}

因为打印棋盘只需要打印我们需要打印的内容(9*9),所以参数是row和col,但是我们的数组创建时候的类型是board[ROWS][COLS],所以数组传参的括号里的数字不要写错哦。

我们来看看我们的棋盘是什么样子的

 很好,上面的0是我们的mine数组,下边的*是我们的show数组

//布置雷

接下来,我们就该在mine数组里边布置雷了,我们还是选取#define的方法来控制雷的个数,这样以后在优化时也更方便,我们还可以设置难度选择,根据用户选择难度的不同,对棋盘的大小和雷数量的多少进行变化,我选取的雷的数量为EASY_COUNT=10,大家根据自己的难度自行定义即可

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

我们在布置雷的时候,要使用rand函数生成随机坐标,rand()%row的结果是0~8(因为row是9),加一的话就是1~9,刚好是我们的棋盘,同时,我们要对生成的坐标进行判断,如果该坐标已经有雷了,就不能再放了,所以我们要把count--写到if语句里边,不然坐标重复的话雷的数量会少于我们规定的数量。使用rand函数前还需要使用srand函数,这个函数我在猜数游戏里进行过详细的介绍,这里就不再叙述

(1条消息) 猜数游戏(详细讲解)_KLZUQ的博客-CSDN博客_猜数游戏

我们再来测试一下代码吧,看看是不是符合我们的需求

可以看到,没有问题,写到这里,大家会发现我们在观察棋盘时非常难受,也不利于用户进行选择格子,我们对打印棋盘的函数进行一下优化,棋盘上方和左边打印出行和列数。

void DisplayBoard(char board[ROWS][COLS], int row, int col)//打印棋盘
{
	int i = 0;
	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 FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)//排查雷
{
	int x = 0, y = 0;
	int win = 0;
	while (win < (row * col - EASY_COUNT)) {
		printf("请选择排查坐标:\n");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col) {
			if (show[x][y] != '*') {
				printf("这个坐标被排查过了,请重新选择!\n");
				continue;
			}
			if (mine[x][y] == '1') {
				printf("很遗憾,你被炸死了!\n");
				DisplayBoard(mine, ROW, COL);
				break;
			}
			else {
				int n = get_mine_count(mine, x, y);
				show[x][y] = n + '0';
				DisplayBoard(show, ROW, COL);
				win++;
			}
		}
		else {
			printf("排查坐标错误,请重新选择!\n");
		}
	}
	if (win == (row * col - EASY_COUNT)) {
		printf("恭喜你!排雷成功!!!\n");
		DisplayBoard(mine, ROW, COL);
	}
}

我们让用户输入要排查的坐标,我们先对坐标进行检验,看输入坐标是否合法,如果输入坐标合法,那我们进一步对坐标进行检验,如果该坐标已经被排查过了,我们就告诉用户并且直接continue,如果没被排查过,我们先判断坐标在mine数组里是否为1,如果为1,说明这个坐标是雷,我们告诉用户游戏失败,并且打印mine数组,让他死得瞑目,如果不是1,那么我们对该坐标周围8格进行相加并储存在show数组里,因为show和mine数组都是char类型的,字符数字和数字是相差48的,所以我们需要在结果上加上字符0,同时,我们把这个相加8个格子的功能封装为一个函数,这个函数不需要在.h文件中声明,因为这只是一个辅助函数

int get_mine_count(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');
}

同样,因为mine是char类型数组,所以我们在相加8个格子后,与我们需要的整数相差了8*48,所以我们最后需要减去8*字符0。

为了让用户不断的排查,我们使用while语句,我们创建一个变量win,初始化为0,代表用户已经排查了多少个格子,用户每排查一个格子,我们让win++,所以我们的循环条件就是win < (row * col - EASY_COUNT),棋盘的总格子数减去雷数,就是用户需要排查格子的数量,win小于这个数字的话,说明用户还没有排查完,就让用户继续排查,相等,说明排查完毕,同时,用户在排查雷的过程中,可以会失败,这时候也会break,所以我们要在最后加上判断win是否等于(row * col - EASY_COUNT),如果相等说明用户是通过排查完所有格子break的,这时候告诉用户游戏胜利,如果不相等,说明用户是通过游戏失败break的,这时候直接结束即可。

现在我们再来测试一下代码,当然,我们可不要傻乎乎的去排查所有格子,我们直接把雷的数量调到79,这样我们只需要排查两个格子,如果程序没问题,我们再改回来就行,同时,我们在布置完雷后就直接打印mine数组,照着排查。

 可以看到,我们的程序没有问题。

//展开周围区域

接着我们来完成最后一步,我们游玩扫雷时,点一个格子会展开一片,而我们现在的程序只能显示一个格子,我们要让我们的扫雷也可以展开一片。

void OpenShow(char mine[ROWS][COLS], char show[ROWS][COLS],int x,int y)//展开周围的区域
{
	if (x == 0 || y == 0 || x == ROWS - 1 || y == COLS - 1) {
		return;
	}
	 if (show[x][y] != '*'){
	   	return;
  	}
		int n = get_mine_count(mine, x, y);
		show[x][y] = n + '0';
		if (n>0) {
			show[x][y] = n+'0';
			return;
		}
		if (n==0) {
			OpenShow(mine, show, x - 1, y);
			OpenShow(mine, show, x - 1, y - 1);
			OpenShow(mine, show, x, y - 1);
			OpenShow(mine, show, x + 1, y - 1);
			OpenShow(mine, show, x + 1, y);
			OpenShow(mine, show,  x + 1, y + 1);
			OpenShow(mine, show,  x, y + 1);
			OpenShow(mine, show,x - 1, y + 1);
		}
}

展开函数我们要使用递归,我们传入mine和show数组、x,y坐标,我们先对x和y坐标进行判断,因为我们棋盘虽然打印出来是9*9的,但实际上却是11*11的,多余的两行的两行里的元素并不我们正常的元素,所以我们要判断x和y,不能让他们等于外边的那一圈,然后我们要再次进行判断,我们不能对show数组里已经打印出来的再进行打印,所以这里不等于*的也直接return,紧着着,我们要用到排查雷里的东西,我们判断该坐标周围一圈是否有雷,如果有,那么就将雷数赋值给该坐标然后直接返回,否则我们进行递归,对周围八个格子进行判断,这样写的话,我们要对排查雷里边的一部分逻辑进行改变,否则进到展开周围的函数会直接结束(第二个if),因为我们传入的坐标是玩家已经选择的坐标。

我们只需修改一部分即可

else {
				OpenShow(mine, show, x, y);
				int n = get_mine_count(mine, x, y);
				show[x][y] = n + '0';
				DisplayBoard(show, ROW, COL);
				win++;
			}

这样,我们的扫雷就完成了,我们来看看实际效果吧

写完了扫雷游戏,大家也自己玩一把放松一下吧,希望大家可以有所收获

最后附上全部代码

//完整代码

#pragma once
//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
#define EASY_COUNT 10
void InitBoard(char board[ROWS][COLS], int rows, int cols,char x);//初始化

void DisplayBoard(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);//排查雷

void OpenShow(char mine[ROWS][COLS], char show[ROWS][COLS],int x,int y);//展开周围的区域

 

#include "game.h"
//test.c
void menu() {
	printf("*********************\n");
	printf("******* 1. play *****\n");
	printf("******* 0. exit *****\n");
	printf("*********************\n");
}

void game() {
	char mine[ROWS][COLS] = { 0 };
	char show[ROWS][COLS] = { 0 };
	//初始化
	InitBoard(mine, ROWS, COLS, '0');
	InitBoard(show, ROWS, COLS, '*');
	//打印棋盘
	//DisplayBoard(mine, ROW, COL);
	DisplayBoard(show, ROW, COL);
	//布置雷
	SetMine(mine, ROW, COL);
	DisplayBoard(mine, ROW, COL);
	//排查雷
	FindMine(mine, show, ROW, COL);
}
void test() {
	srand((unsigned int)time(NULL));
	int input = 0;
	do {
		menu();
		printf("请选择:\n");
		scanf("%d", &input);
		switch (input) {
		case 1:
			game();
			break;
		case 0:
			printf("游戏退出!\n");
			break;
		default:
			printf("选择错误,请重新选择!\n");
			break;
		}
	} while (input);
}

int main() {
	test();
	return;
}
#include"game.h"
//game.c
void InitBoard(char board[ROWS][COLS], int rows, int cols, char x) {//初始化
	int i = 0;
	for (i = 0; i < rows; i++) {
		int j = 0;
		for (j = 0; j < cols; j++) {
			board[i][j] = x;
		}
	}
}

void DisplayBoard(char board[ROWS][COLS], int row, int col)//打印棋盘
{
	int i = 0;
	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;
		int y = rand() % col + 1;
		if (board[x][y] == '0') {
			board[x][y] = '1';
			count--;
		}
	}
}
int get_mine_count(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 OpenShow(char mine[ROWS][COLS], char show[ROWS][COLS],int x,int y)//展开周围的区域
{
	if (x == 0 || y == 0 || x == ROWS - 1 || y == COLS - 1) {
		return;
	}
	 if (show[x][y] != '*'){
	   	return;
  	}
		int n = get_mine_count(mine, x, y);
		show[x][y] = n + '0';
		if (n>0) {
			show[x][y] = n+'0';
			return;
		}
		if (n==0) {
			OpenShow(mine, show, x - 1, y);
			OpenShow(mine, show, x - 1, y - 1);
			OpenShow(mine, show, x, y - 1);
			OpenShow(mine, show, x + 1, y - 1);
			OpenShow(mine, show, x + 1, y);
			OpenShow(mine, show,  x + 1, y + 1);
			OpenShow(mine, show,  x, y + 1);
			OpenShow(mine, show,x - 1, y + 1);
		}
}

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)//排查雷
{
	int x = 0, y = 0;
	int win = 0;
	while (win < (row * col - EASY_COUNT)) {
		printf("请选择排查坐标:\n");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col) {
			if (show[x][y] != '*') {
				printf("这个坐标被排查过了,请重新选择!\n");
				continue;
			}
			if (mine[x][y] == '1') {
				printf("很遗憾,你被炸死了!\n");
				DisplayBoard(mine, ROW, COL);
				break;
			}
			else {
				OpenShow(mine, show, x, y);
				int n = get_mine_count(mine, x, y);
				show[x][y] = n + '0';
				DisplayBoard(show, ROW, COL);
				win++;
			}
		}
		else {
			printf("排查坐标错误,请重新选择!\n");
		}
	}
	if (win == (row * col - EASY_COUNT)) {
		printf("恭喜你!排雷成功!!!\n");
		DisplayBoard(mine, ROW, COL);
	}
}
  • 8
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值