【C语言编程练习】手撕扫雷

【C语言编程练习】手撕扫雷

  • 一、目标
  • 二、具体实现步骤
    • 1、棋盘的设计思路
    • 2、选定模式
    • 3、创建及初始化棋盘
    • 4、布置雷到棋盘
    • 5、打印棋盘
    • 6、排查雷
    • 7、递归版统计雷数
    • 8、判断是否胜出的函数
  • 三、完整代码逻辑展示
    • 1、Minesweeping.h
    • 2、Minesweeping.c
    • 3、test.c

一、目标

之所以打算将扫雷游戏再次实现一次,主要是先检验一下自己的C语言是否学得扎实。因为我清晰的记得我的老师对我们说过:“如果你学完C语言之后,能够不作任何参考的写出扫雷游戏,那说明你的C语言是学的不错的”。
而且我本人对自己的要求也比较苛刻,我对自己是否熟练的掌握一门编程语言的衡量标准是:
在这里插入图片描述
当然啦🐶,我知道我现在还达不到这个水平,不过手撕个扫雷我觉得自己还是可以做到的,所以今天打算来挑战一下。
今天的扫雷要达成的目标:

1、有多种模式可选择:经典模式(棋盘规模8X8)、简单模式(棋盘规模9X9)、中等模式(棋盘规模16X16)、专家模式(棋盘规模20X20)、自定义模式(棋盘规模?X?)。而且自定义模式也可以自定义雷的个数。
2、如果出现一大片空间都没有雷,将会展开一大片坐标(就像网页上的一样)。
3、胜出或失败都会显示棋盘结果。

二、具体实现步骤

1、棋盘的设计思路

设计棋盘我用的是“整型二维数组”(其实是动态申请的空间)。原因是使用整型数据更加直观。
当一个坐标周围有1个雷的时候,我们就将此坐标的数据赋值为1,如果有3个雷就赋值为3,而如果这个坐标是雷,我们就用-2来表示:
在这里插入图片描述
而如果一个坐标为被排查过(就像上图蓝方框所示),我们用-1来表示。至于周围没有雷的坐标,我们同样将其赋值为0,但我们最终并不会显示个0出来,就像上图一样。

2、选定模式

先弄个简易的菜单:

void menu() {
	printf(" ________________________________________________________\n");
	printf("|                    欢迎来到扫雷游戏!                  |\n");
	printf("|--------------------------------------------------------|\n");
	printf("| 1.  |      经典模式(8X8)                               |\n");
	printf("|--------------------------------------------------------|\n");
	printf("| 2.  |      简单模式(9X9)                               |\n");
	printf("|--------------------------------------------------------|\n");
	printf("| 3.  |      中等模式(16X16)                             |\n");
	printf("|--------------------------------------------------------|\n");
	printf("| 4.  |      专家模式(20X20)                             |\n");
	printf("|--------------------------------------------------------|\n");
	printf("| 5.  |      自定义模式(?X?)                             |\n");
	printf("|--------------------------------------------------------|\n");
	printf("| 0.  |      退出游戏                                    |\n");
	printf("  ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄\n");
}

菜单弄好了,我们就可以通过接收用户输入来选择模式了,因为是动态的,所以我们在设计模式时候只需要根据情况初始化行数row和列数col即可,最后再统一用malloc函数申请空间:

int main() {
	int row = 0;
	int col = 0;
	int mines_count = 0;
	int input = 0;
	do {
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input) {
		case 1:
			row = 8;
			col = 8;
			mines_count = 9;
			break;
		case 2:
			row = 9;
			col = 9;
			mines_count = 10;
			break;
		case 3:
			row = 16;
			col = 16;
			mines_count = 40;
			break;
		case 4:
			row = 20;
			col = 20;
			mines_count = 80;
			break;
		case 5:
			printf("请输入行数(7 <= row <= 20):>");
			while (1) {
				scanf("%d", &row);
				if (row < 7 || row > 20) {
					printf("输入行数超出范围,请重新输入:");
				}
				else {
					break;
				}
			}
			printf("请输入列数(7 <= col <= 20):>");
			while (1) {
				scanf("%d", &col);
				if (col < 7 || col > 20) {
					printf("输入列数超出范围,请重新输入:");
				}
				else {
					break;
				}
			}
			int max_count = (int)((row * col) * 0.7);
			printf("请输入雷的个数(5 <= mines_count <= %d):>", max_count);
			while (1) {
				scanf("%d", &mines_count);
				if (mines_count < 5 || mines_count > max_count) {
					printf("输入雷数多少或过多,请重新输入:");
				}
				else {
					break;
				}
			}
			break;
		case 0:
			printf("已退出游戏……\n");
			break;
		default:
			printf("输入有误,请重新输入……\n");
			break;
		}
		if (input >= 1 && input <= 5) {
			// 创建棋盘
			int** board = (int**)malloc(row * sizeof(int*));
			if (NULL == board) {
				perror("main");
				return -1;
			}
			int i = 0;
			for (i = 0; i < row; i++) {
				board[i] = (int*)malloc(col * sizeof(int));
				if (NULL == board[i]) {
					perror("main");
					return -2;
				}
			}
		}
	} while (input);
}

至于雷数的限制,出于对难度的考虑,我们就让最低限度统一为最低5个,而关于上限,我电脑中的扫雷游戏的上限大概是最多到坐标总数的70%:
在这里插入图片描述
所以我们这也将上限设置为坐标总数的70%。
在这里插入图片描述

3、创建及初始化棋盘

模式选定后我们紧跟着就要来创建我们的棋盘了,我们的棋盘board其实是一个二级指针,我们只需要为board申请row个int*类型大小的空间,然后再为每个board[i]申请col个int类型大小的空间。就可以模拟出一个整型二维数组了:

if (input >= 1 && input <= 5) { // 先要判断,才能创建
			// 创建棋盘
			int** board = (int**)malloc(row * sizeof(int*));
			if (NULL == board) {
				perror("main");
				return -1;
			}
			int i = 0;
			for (i = 0; i < row; i++) {
				board[i] = (int*)malloc(col * sizeof(int));
				if (NULL == board[i]) {
					perror("main");
					return -2;
				}
			}
		}

初始化棋盘
我们将棋盘内的坐标全都初始化为-1,表示所有的坐标都未被访问(排查)过,这交给一个函数来完成即可:

// 初始化棋盘
void init_board(int** board, int row, int col) {
	assert(board);
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++) {
		for (j = 0; j < col; j++) {
			board[i][j] = -1;
		}
	}
}

4、布置雷到棋盘

布置雷的逻辑其实也并不难,我们只需要产生mines_count个有效的随机坐标,然后在该坐标中放入-2即可:

// 布置雷到棋盘,我们用-2表示雷
void set_mines(int** board, int row, int col, int mines_count) {
	assert(board);
	srand((unsigned int)time(NULL));
	int i = 0;
	int j = 0;
	while (mines_count) {
		i = (rand() % row);
		j = (rand() % col);
		if (board[i][j] == -1) {
			board[i][j] = -2;
			mines_count--;
		}
	}
}

5、打印棋盘

打印期盼的时候,我们只需要通过判断棋盘board具体坐标的值,选择性的打印出一些符号即可,如果一个坐标为未被访问过(即坐标值为-1)我们就在屏幕上打印一个“■”,例如在游戏的最开始,打印是这样的:
在这里插入图片描述
如一个坐标被拍查了,且它周围的雷数大于0,那就显示对应的数字即可,如果雷数是0,那就显示一个符号“□”,例如游戏玩到一半的时候打印是这样的:
在这里插入图片描述
如果点到雷,那就被炸死了,就将所有的雷显示出来,我们用符号“※”来表示雷,但为被排查过的坐标还是不显示,例如:
在这里插入图片描述
如果扫雷胜利了,我们就在雷的坐标上放一个符号“★”,表示胜利的标志,例如:
在这里插入图片描述
然后还有一件需要做的事就是在棋盘的周围打印上坐标,我在搜狗的符号库里找了很久,最后决定使用以下符号:
在这里插入图片描述
但有一挺坏的消息是,这些字符都不在ASCLL码的范围内,所以只能够用printf函数直接打印,而不能够将其转化成字符类型打印:
在这里插入图片描述
后来我思来想去,最终想出了一个比较挫的动态打印方式😓:

// 打印坐标的函数
void print_coord(int i) {
	switch (i) {
	case 0:
		printf("⑴");
		break;
	case 1:
		printf("⑵");
		break;
	case 2:
		printf("⑶");
		break;
	case 3:
		printf("⑷");
		break;
	case 4:
		printf("⑸");
		break;
	case 5:
		printf("⑹");
		break;
	case 6:
		printf("⑺");
		break;
	case 7:
		printf("⑻");
		break;
	case 8:
		printf("⑼");
		break;
	case 9:
		printf("⑽");
		break;
	case 10:
		printf("⑾");
		break;
	case 11:
		printf("⑿");
		break;
	case 12:
		printf("⒀");
		break;
	case 13:
		printf("⒁");
		break;
	case 14:
		printf("⒂");
		break;
	case 15:
		printf("⒃");
		break;
	case 16:
		printf("⒄");
		break;
	case 17:
		printf("⒅");
		break;
	case 18:
		printf("⒆");
		break;
	case 19:
		printf("⒇");
		break;
	default:
		break;
	}
}

我知道这很麻烦,但这也只仅仅是为了美观,如果不想这样,直接打印数字当坐标也可以。
所以最终的打印函数实现如下:

// 打印棋盘
// 参数flag表示的是打印的状态,0表示正常打印,1表示胜利时的打印,-1则表示被炸死时的打印
void print(int** board, int row, int col, int flag) {
	assert(board);
	// 坐标数组,用于打印
	int i = 0;
	int j = 0;
	// 打印边界(都是为了美观,其实可以忽略)
	for (i = 0; i < col + 2; i++) {
		if (0 == i) {
			printf(" ");
		}
		printf("__");
	}
	printf("\n");
	// 先打印列坐标
	for (i = 0; i < col; i++) {
		if (0 == i) {
			printf("|");
			printf("◤");
		}
		print_coord(i);
		if (col - 1 == i) {
			printf("◥");
			printf("|");
		}
	}
	printf("\n");
	// 打印棋盘内容与行坐标
	for (i = 0; i < row; i++) {
		printf("|");
		print_coord(i); // 首先打印上一个纵坐标
		for (j = 0; j < col; j++) {
			if (board[i][j] == -1) {
				printf("■");
			}
			else if (board[i][j] == -2) {
				if (0 == flag) {
					printf("■");
				}
				else if (1 == flag) {
					printf("★");
				}
				else {
					printf("※");
				}
			}
			else if (board[i][j] == 0) {
				printf("□");
			}
			else {
				printf("%2d", board[i][j]);
			}
		}
		print_coord(i); // 后面再跟上一个纵坐标
		printf("|");
		printf("\n");
	}
	// 最底端也打印一排坐标
	for (i = 0; i < col; i++) {
		if (0 == i) {
			printf("|");
			printf("◣");
		}
		print_coord(i);
		if (col - 1 == i) {
			printf("◢");
			printf("|");
		}
	}
	printf("\n");
	// 打印边界
	for (i = 0; i < col + 2; i++) {
		if (0 == i) {
			printf(" ");
		}
		printf(" ̄");
	}
	printf("\n");
}

最终的打印效果如下:
在这里插入图片描述

6、排查雷

做好了以上的工作,我们就可以开始排查雷了:

void sweep_mines(int** board, int row, int col) {
	assert(board);
	int i = 0;
	int j = 0;
	while (1) {
		printf("请输入你要排查的坐标(i,j) :>");
		scanf("%d %d", &i, &j);
		if ((i < 1 || i > row) || (j < 1 || j > col)) {
			printf("输入的坐标非法,请重新输入……\n");
		}
		else if (board[i - 1][j - 1] == -2) {
			printf("  ________________________\n");
			printf(" |**********(>_<)*********|\n");
			printf(" |***很遗憾,你被炸死了!***|\n");
			printf("   ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄\n");

			print(board, row, col, -1);
			system("pause"); // 让程序先暂停好查看结果
			printf("正在返回主菜单……\n");
			Sleep(3000);
			system("cls");
			return;
		}
		else if (board[i - 1][j - 1] == -1) {
			// 统计坐标周围有多少个雷
			count_mines(board, row, col, i - 1, j - 1);
			if (is_wins(board, row, col)) {
				printf("  ____________________________________\n");
				printf(" |★★★★★★★(*^O^*)★★★★★★★ |\n");
				printf(" |★★★恭喜你,排雷成功!太棒了!★★★ |\n");
				printf("   ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄\n");
				print(board, row, col, 1);
				system("pause");
				printf("正在返回主菜单……\n");
				Sleep(3000);
				system("cls");
				return;
			}
			else {
				print(board, row, col, 0);
			}
		}
		else {
			printf("该坐标已被排查过,请重新输入……\n");
		}
	}
}

既然是要通过输入的坐标来排查,就一定要对坐标的有效性做检查,只有当坐标有效是才执行后面的操作,如果被排查到的坐标是雷的话,游戏就直接结束。如果不是就统计该坐标周围有多少个雷,这个操作交给一个函数count_mines来完成。并在每一次count_mines调用完后都判断游戏是否胜出,这个操作交给一个函数is_wins来完成。

7、递归版统计雷数

如果我们想要向电脑上的扫雷游戏一样,排查一个坐标,有可能展开一片空白:
在这里插入图片描述
那就可以使用递归的方法实现。

统计雷数的主要思路是用一个特殊的循环来遍历一个坐标周围的坐标:

int count = 0;
for (i = -1; i <= 1; i++) {
	for (j = -1; j <= 1; j++) {
		if (0 == i && 0 == j) {
			continue;
		}
		if (-2 == board[x + i][y + j]) {
			count++;
		}
	}
}

置于为什么能做到,相信画个图大家就能够理解了:
在这里插入图片描述
而我们在遍历周围的这些坐标时,也一定不要忘了要对其合法性进行判断。这在后面进行递归时候也是一样的,所以我们完整的函数实现就如下所示:

// 统计坐标周围有多少个雷
void count_mines(int** board, int row, int col, int x, int y) {
	assert(board);
	int count = 0;
	int i = 0;
	int j = 0;
	// 开始计算雷数
	for (i = -1; i <= 1; i++) {
		for (j = -1; j <= 1; j++) {
			int X = x + i;
			int Y = y + j;
			if ((X < 0 || X > row - 1) || (Y < 0 || Y > col - 1)) { // 判断坐标的有效性
				continue;
			}
			if (i == 0 && j == 0) {
				continue;
			}
			if (-2 == board[X][Y]) {
				count++;
			}
		}
	}
	board[x][y] = count;
	if (0 == count) { // 当count等于0时,就要开始递归。
		// 开始递归
		for (i = -1; i <= 1; i++) {
			for (j = -1; j <= 1; j++) {
				int X = x + i;
				int Y = y + j;
				if ((X < 0 || X > row - 1) || (Y < 0 || Y > col - 1)) { // 判断坐标的有效性
					continue;
				}
				if (i == 0 && j == 0) {
					continue;
				}
				if (-1 == board[X][Y]) {
					count_mines(board, row, col, X, Y);
				}
			}
		}
	}
}

8、判断是否胜出的函数

最后剩下的就是判断是否胜出的函数了,其实这个函数也非常简单,我们只需要遍历整个棋盘,当在棋盘中再也找不到-1时,就表示所有非雷的坐标已经被我们排查完了,所以游戏胜出:

// 判断游戏是否胜利,判断是否胜利的思路很简单,棋盘中再也找不到值为-1的坐标时候,游戏就胜利
// 胜利返回1,否则返回0
int is_wins(int** board, int row, int col) {
	assert(board);
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++) {
		for (j = 0; j < col; j++) {
			if (-1 == board[i][j]) {
				return 0;
			}
		}
	}
	return 1;
}

三、完整代码逻辑展示

1、Minesweeping.h

该文件存放各种函数的声明和头文件的包含:

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

// 初始化棋盘
void init_board(int **board, int row, int col);

// 布置雷到棋盘
void set_mines(int **board, int row, int col, int mines_count);

// 打印棋盘
void print(int** board, int row, int col, int flag);

// 排查雷
void sweep_mines(int **board, int row, int col);

// 统计坐标周围有多少个雷
void count_mines(int **board, int row, int col, int x, int y);

// 判断游戏是否胜利
int is_wins(int** board, int row, int col);

2、Minesweeping.c

该文件存放各种函数的定义:

#include "Minesweeping.h"

// 初始化棋盘
void init_board(int** board, int row, int col) {
	assert(board);
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++) {
		for (j = 0; j < col; j++) {
			board[i][j] = -1;
		}
	}
}

// 布置雷到棋盘,我们用-2表示雷
void set_mines(int** board, int row, int col, int mines_count) {
	assert(board);
	srand((unsigned int)time(NULL));
	int i = 0;
	int j = 0;
	while (mines_count) {
		i = (rand() % row);
		j = (rand() % col);
		if (board[i][j] == -1) {
			board[i][j] = -2;
			mines_count--;
		}
	}
}

// 打印坐标的函数
void print_coord(int i) {
	switch (i) {
	case 0:
		printf("⑴");
		break;
	case 1:
		printf("⑵");
		break;
	case 2:
		printf("⑶");
		break;
	case 3:
		printf("⑷");
		break;
	case 4:
		printf("⑸");
		break;
	case 5:
		printf("⑹");
		break;
	case 6:
		printf("⑺");
		break;
	case 7:
		printf("⑻");
		break;
	case 8:
		printf("⑼");
		break;
	case 9:
		printf("⑽");
		break;
	case 10:
		printf("⑾");
		break;
	case 11:
		printf("⑿");
		break;
	case 12:
		printf("⒀");
		break;
	case 13:
		printf("⒁");
		break;
	case 14:
		printf("⒂");
		break;
	case 15:
		printf("⒃");
		break;
	case 16:
		printf("⒄");
		break;
	case 17:
		printf("⒅");
		break;
	case 18:
		printf("⒆");
		break;
	case 19:
		printf("⒇");
		break;
	default:
		break;
	}
}

// 打印棋盘
// 参数flag表示的是打印的状态,0表示正常打印,1表示胜利时的打印,-1则表示被炸死时的打印
void print(int** board, int row, int col, int flag) {
	assert(board);
	// 坐标数组,用于打印
	int i = 0;
	int j = 0;
	// 打印边界(都是为了美观,其实可以忽略)
	for (i = 0; i < col + 2; i++) {
		if (0 == i) {
			printf(" ");
		}
		printf("__");
	}
	printf("\n");
	// 先打印列坐标
	for (i = 0; i < col; i++) {
		if (0 == i) {
			printf("|");
			printf("◤");
		}
		print_coord(i);
		if (col - 1 == i) {
			printf("◥");
			printf("|");
		}
	}
	printf("\n");
	// 打印棋盘内容与行坐标
	for (i = 0; i < row; i++) {
		printf("|");
		print_coord(i); // 首先打印上一个纵坐标
		for (j = 0; j < col; j++) {
			if (board[i][j] == -1) {
				printf("■");
			}
			else if (board[i][j] == -2) {
				if (0 == flag) {
					printf("■");
				}
				else if (1 == flag) {
					printf("★");
				}
				else {
					printf("※");
				}
			}
			else if (board[i][j] == 0) {
				printf("□");
			}
			else {
				printf("%2d", board[i][j]);
			}
		}
		print_coord(i); // 后面再跟上一个纵坐标
		printf("|");
		printf("\n");
	}
	// 最底端也打印一排坐标
	for (i = 0; i < col; i++) {
		if (0 == i) {
			printf("|");
			printf("◣");
		}
		print_coord(i);
		if (col - 1 == i) {
			printf("◢");
			printf("|");
		}
	}
	printf("\n");
	// 打印边界
	for (i = 0; i < col + 2; i++) {
		if (0 == i) {
			printf(" ");
		}
		printf(" ̄");
	}
	printf("\n");
}

// 统计坐标周围有多少个雷
void count_mines(int** board, int row, int col, int x, int y) {
	assert(board);
	int count = 0;
	int i = 0;
	int j = 0;
	// 开始计算雷数
	for (i = -1; i <= 1; i++) {
		for (j = -1; j <= 1; j++) {
			int X = x + i;
			int Y = y + j;
			if ((X < 0 || X > row - 1) || (Y < 0 || Y > col - 1)) { // 判断坐标的有效性
				continue;
			}
			if (i == 0 && j == 0) {
				continue;
			}
			if (-2 == board[X][Y]) {
				count++;
			}
		}
	}
	board[x][y] = count;
	if (0 == count) { // 当count等于0时,就要开始递归。
		// 开始递归
		for (i = -1; i <= 1; i++) {
			for (j = -1; j <= 1; j++) {
				int X = x + i;
				int Y = y + j;
				if ((X < 0 || X > row - 1) || (Y < 0 || Y > col - 1)) { // 判断坐标的有效性
					continue;
				}
				if (i == 0 && j == 0) {
					continue;
				}
				if (-1 == board[X][Y]) {
					count_mines(board, row, col, X, Y);
				}
			}
		}
	}
}


// 排查雷
void sweep_mines(int** board, int row, int col) {
	assert(board);
	int i = 0;
	int j = 0;
	while (1) {
		printf("请输入你要排查的坐标(i,j) :>");
		scanf("%d %d", &i, &j);
		if ((i < 1 || i > row) || (j < 1 || j > col)) {
			printf("输入的坐标非法,请重新输入……\n");
		}
		else if (board[i - 1][j - 1] == -2) {
			printf("  ________________________\n");
			printf(" |**********(>_<)*********|\n");
			printf(" |***很遗憾,你被炸死了!***|\n");
			printf("   ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄\n");

			print(board, row, col, -1);
			system("pause"); // 让程序先暂停好查看结果
			printf("正在返回主菜单……\n");
			Sleep(3000);
			system("cls");
			return;
		}
		else if (board[i - 1][j - 1] == -1) {
			// 统计坐标周围有多少个雷
			count_mines(board, row, col, i - 1, j - 1);
			if (is_wins(board, row, col)) {
				printf("  ____________________________________\n");
				printf(" |★★★★★★★(*^O^*)★★★★★★★ |\n");
				printf(" |★★★恭喜你,排雷成功!太棒了!★★★ |\n");
				printf("   ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄\n");
				print(board, row, col, 1);
				system("pause");
				printf("正在返回主菜单……\n");
				Sleep(3000);
				system("cls");
				return;
			}
			else {
				print(board, row, col, 0);
			}
		}
		else {
			printf("该坐标已被排查过,请重新输入……\n");
		}
	}
}

// 判断游戏是否胜利,判断是否胜利的思路很简单,棋盘中再也找不到值为-1的坐标时候,游戏就胜利
// 胜利返回1,否则返回0
int is_wins(int** board, int row, int col) {
	assert(board);
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++) {
		for (j = 0; j < col; j++) {
			if (-1 == board[i][j]) {
				return 0;
			}
		}
	}
	return 1;
}

3、test.c

该文件用来真正运行扫雷游戏:

#include "Minesweeping.h"

// 先写一个简易的菜单
void menu() {
	printf(" ________________________________________________________\n");
	printf("|                    欢迎来到扫雷游戏!                  |\n");
	printf("|--------------------------------------------------------|\n");
	printf("| 1.  |      经典模式(8X8)                               |\n");
	printf("|--------------------------------------------------------|\n");
	printf("| 2.  |      简单模式(9X9)                               |\n");
	printf("|--------------------------------------------------------|\n");
	printf("| 3.  |      中等模式(16X16)                             |\n");
	printf("|--------------------------------------------------------|\n");
	printf("| 4.  |      专家模式(20X20)                             |\n");
	printf("|--------------------------------------------------------|\n");
	printf("| 5.  |      自定义模式(?X?)                             |\n");
	printf("|--------------------------------------------------------|\n");
	printf("| 0.  |      退出游戏                                    |\n");
	printf("  ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄\n");
}

int main() {
	int row = 0;
	int col = 0;
	int mines_count = 0;
	int input = 0;
	do {
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input) {
		case 1:
			row = 8;
			col = 8;
			mines_count = 9;
			break;
		case 2:
			row = 9;
			col = 9;
			mines_count = 10;
			break;
		case 3:
			row = 16;
			col = 16;
			mines_count = 40;
			break;
		case 4:
			row = 20;
			col = 20;
			mines_count = 80;
			break;
		case 5:
			printf("请输入行数(7 <= row <= 20):>");
			while (1) {
				scanf("%d", &row);
				if (row < 7 || row > 20) {
					printf("输入行数超出范围,请重新输入:");
				}
				else {
					break;
				}
			}
			printf("请输入列数(7 <= col <= 20):>");
			while (1) {
				scanf("%d", &col);
				if (col < 7 || col > 20) {
					printf("输入列数超出范围,请重新输入:");
				}
				else {
					break;
				}
			}
			int max_count = (int)((row * col) * 0.7);
			printf("请输入雷的个数(5 <= mines_count <= %d):>", max_count);
			while (1) {
				scanf("%d", &mines_count);
				if (mines_count < 5 || mines_count > max_count) {
					printf("输入雷数多少或过多,请重新输入:");
				}
				else {
					break;
				}
			}
			break;
		case 0:
			printf("已退出游戏……\n");
			break;
		default:
			printf("输入有误,请重新输入……\n");
			break;
		}
		if (input >= 1 && input <= 5) {
			// 创建棋盘
			int** board = (int**)malloc(row * sizeof(int*));
			if (NULL == board) {
				perror("main");
				return -1;
			}
			int i = 0;
			for (i = 0; i < row; i++) {
				board[i] = (int*)malloc(col * sizeof(int));
				if (NULL == board[i]) {
					perror("main");
					return -2;
				}
			}
			// 初始化棋盘,初始化全为-1,表示坐标全都未被访问过
			init_board(board, row, col);
			// 布置雷到棋盘
			set_mines(board, row, col, mines_count);
			printf("游戏开始!\n");
			// 打印棋盘
			print(board, row, col, 0);
			// 开始扫雷
			sweep_mines(board, row, col);
			// 游戏结束,释放空间
			for (int i = 0; i < row; i++) {
				free(board[i]);
				board[i] = NULL;
			}
			free(board);
			board = NULL;
		}
	} while (input);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/5505.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

板内盘中孔设计狂飙,细密间距线路中招

一博高速先生成员&#xff1a;王辉东大风起兮云飞扬&#xff0c;投板兮人心舒畅。赵理工打了哈欠&#xff0c;伸了个懒腰&#xff0c;看了看窗外&#xff0c;对林如烟说道&#xff1a;“春天虽美&#xff0c;但是容易让人沉醉。如烟&#xff0c;快女神节了&#xff0c;要不今晚…

AHP层次分析法分析流程

AHP层次分析法分析流程&#xff1a; 一、案例背景 当前有一项研究&#xff0c;想要构建公司绩效评价指标体系&#xff0c;将一级指标分为4个&#xff0c;分别是&#xff1a;服务质量、管理水平、运行成本、安全生产&#xff0c;现在想要确定4个指标的权重。 AHP层次分析法是一…

【MySQL】 SQL 执行顺序 OR 递增id用完了怎么办呢?哪个问题难回答

这里写目录标题写在前面基础概念SQL 执行顺序FROMONJOINWHEREGROUP BYHAVINGSELECTDISTINCTORDER BYMysql 自增 ID用完了1.有主键的情况解决方案2.没有主键解决方案&#xff1a;总结写在前面 三月已经结束了&#xff0c;不知道这个月你有没有被邀请面试&#xff0c;如果有面试…

【C++笔试强训】第二天

选择题 解析&#xff1a;考查printf&#xff0c;%后面-表示输出左对齐&#xff0c;输出左对齐30个字符格式为%-30f&#xff0c;.后面表示精度。%e字符以指数形势输出&#xff0c;可以认为是double类型&#xff08;也就是小数点后保留6位&#xff09;的指数。为%f字符表示输出格…

JVM问题(二) -- 内存泄漏

1. 什么是内存泄漏&#xff1a; 2. 内存泄漏的理解&#xff1a; 严格来说&#xff0c;只有对象不会再被程序用到了&#xff0c;但是GC又不能回收他们的情况&#xff0c;才叫内存泄漏。 但是实际情况很多时候一些不太好的实践&#xff08;或疏忽&#xff09;会导致对象的生命周…

2023年3月华为HCIA认证新增题库(H12-811)

850、 SNMP报文是通过 TCP来承载的。 A、对 B、错 试题答案&#xff1a;[["B"]] 试题解析&#xff1a; 851、 Trunk端口可以允许多个 VLAN通过,包括 VLAN4096。 A、对 B、错 试题答案&#xff1a;[["B"]] 试题解析&#xff1a; 852、 RADIUS是实…

【websocket消息推送】前端+后端实现websocket消息推送的整个生命周期(附源码详解)

【写在前面】写这篇文章的原因主要还是博主在工作的过程中遇到了一个困难&#xff0c;就是客户端开了两个一模一样的窗口&#xff08;A和B&#xff09;&#xff0c;然后A窗口触发一个请求&#xff0c;请求后是推送到前端的&#xff0c;但是推送的消息只推给了B&#xff0c;而A没…

【C++笔试强训】第三天

选择题 解析&#xff1a;字符数组里面的最后一个字符是0&#xff0c;说明里面本身就是一个字符串——"123456789"&#xff0c;数组名表示数组首元素的地址&#xff0c;那么p a i指向的就是字符数组中元素9&#xff0c;那么p - 3就是指向元素6的地址&#xff0c;%s打…

在VScode中配置Python开发环境----需要注意的一个点:settings.json

在VScode中配置Python开发环境&#xff08;可以参考这个博主的方法&#xff09;&#xff1a; http://t.csdn.cn/L1jux 1、安装python 官网下载地址&#xff1a;https://www.python.org/ftp/python/3.8.0/python-3.8.0-amd64.exe 双击打开.exe文件 勾选 Add Python 3.8 to Pat…

【计算机视觉 | 目标检测】DETR风格的目标检测框架解读

文章目录一、前言二、理解2.1 DETR的理解2.2 DETR的细致理解2.2.1 Backbone2.2.2 Transformer encoder2.2.3 Transformer decoder2.2.4 Prediction feed-forward networks (FFNs)2.2.5 Auxiliary decoding losses2.3 更具体的结构2.4 编码器的原理和作用2.5 解码器的原理和作用…

刚刚,Frontiers in Psychology 取消on hold状态,但这本期刊仍在评估中

3月28日时&#xff0c;Frontiers in Psychology仍处于on hold状态。 就在刚刚&#xff01;小编查询Frontiers in Psychology时&#xff0c;发现Master Journal List中&#xff0c;期刊Frontiers in Psychology的on hold标识没有了&#xff0c;这表示期刊目前正被SSCI数据库收录…

独立部署基于apiKey或accessToken的GPT聊天工具

最近chat-GPT的强大功能让人新潮澎湃,大家都在讨论,都想尝试一下。。。 奈何用不了!自己整整,内附具体步骤,如何用手机验证码注册,如何自己搭一个前端,nodejs后端,可以访问自己的GTP。 先上图: 自己搭的: 官网: 步骤一、用个代理 因为没这个无法访问GPT官网 忍…

类与对象,对象在内存的存在形式,java方法

类是抽象的&#xff0c;概念的&#xff0c;代表一类事物&#xff0c;比如人类&#xff0c;猫类..即它是数据类型对象是具体的实际的&#xff0c;代表一个具体事物&#xff0c;即实例。类是对象的模板&#xff0c;对象是类的一个个体&#xff0c;对应一个实例 public class Targ…

Jenkins入门

Jenkins是一个开源的、提供友好操作界面的持续集成(CI)工具 持续集成&#xff08;CI&#xff09;是在源代码变更后自动检测、拉取、构建和&#xff08;在大多数情况下&#xff09;进行单元测试的过程 可以简单将jenkins理解为一个代码部署工具。 在没有持续部署工具之前&#x…

【Redis进阶】Redis数据结构

文章目录1. 前言2. SDS2. 链表3. 压缩链表4. 哈希表5. 整数集合6. 跳表7. quicklist8. listpack1. 前言 Redis常用的数据结构为String&#xff0c;List&#xff0c;Hash&#xff0c;Set&#xff0c;Sorted Set。但这只是我们在用的时候键值对的表现形式&#xff0c;他们底层真…

《程序员面试金典(第6版)》面试题 08.05. 递归乘法

题目描述 递归乘法。 写一个递归函数&#xff0c;不使用 * 运算符&#xff0c; 实现两个正整数的相乘。可以使用加号、减号、位移&#xff0c;但要吝啬一些。 示例1: 输入&#xff1a;A 1, B 10 输出&#xff1a;10 示例2: 输入&#xff1a;A 3, B 4 输出&#xff1a;…

vue3使用useMouseInElement实现图片局部放大预览效果

1、首先要安装vueuse/core npm i vueuse/core2、实现过程如下&#xff1a; <template><div class"goods-image"><!-- 大图 --><div v-show"show" class"large" :style"[{backgroundImage:url(${images[currIndex]})…

Cursor,程序员的 AI 代码编辑助手

相信大家都或多或少地听说过、了解过 chatGPT &#xff0c;半个月前发布的 GPT-4 &#xff0c;可谓是 AI 赛道上的一个王炸 那么今天咸鱼给大家分享一个开源的 AI 代码编辑器——Cursor&#xff0c;让各位程序员在编程之路上一骑绝尘 &#x1f603; 介绍 Cursor 是一个人工智…

深度学习应用技巧4-模型融合:投票法、加权平均法、集成模型法

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下&#xff0c;深度学习中的模型融合。它是将多个深度学习模型或其预测结果结合起来&#xff0c;以提高模型整体性能的一种技术。 深度学习中的模型融合技术&#xff0c;也叫做集成学习&#xff0c;是指同时使用多个…

209. 长度最小的子数组

209. 长度最小的子数组 力扣题目链接(opens new window) 给定一个含有 n 个正整数的数组和一个正整数 s &#xff0c;找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组&#xff0c;并返回其长度。如果不存在符合条件的子数组&#xff0c;返回 0。 示例&#xff1a; 输入…