扫雷(自定义棋盘及雷数,可自动消去明显地块,可标?!)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 一、框架
  • 二、各项功能
    • 1.选择难度

    • 2.输出游戏界面

    • 3.布置雷

    • 4.选择挖开地面,标?,标!或者取消标记

    • 5.挖开地面

    • 6.标!与标?与取消标记

    • 7.点击时自行消去明显地块

    • 8.判断输赢

  • 总结

一、框架

我们小时候玩的经典扫雷有以下基本功能(可自行跳转到想看功能)

1.选择难度

2.布置雷及初始化游戏界面

3.输出游戏界面

4.选择挖开地面,标?,标!或者取消标记

5.挖开地面

6.标!与标?与取消标记

7.点击时自行消去明显地块

8.判断输赢

基础模式的运行流程:

   首先是对输出棋盘的初始化,地面的初始化(即放置雷),关于雷数数组的初始化,然后展示棋盘。而后正式游戏,由于我们是循环进行游戏,所以我们选择int来调控是否进行下一局游戏

//初级模式
int beginnermode() {
	char num[ROWS][COLS]; //存放周围雷的信息
	char ground[ROWS][COLS];//存放雷的位置 
	char show[ROWS][COLS]; //展示的数组
	initshow(show);
	initground(ground);
	initshowout(ground);
	initnum(num,ground);
	display(show);
	game(num, ground, show);
}

二、各项功能具体实现

1.选择难度

由于我们在一次游戏中只选择一次,所以我们可以在此处一同输出规则

//打印菜单
int menu() {
	int temp;
	printf("输入0结束游戏,1为初级模式,2为中级模式,为高级模式\n");
	scanf_s("%d", &temp);
	return temp;
}

而具体的难度选择可以通过switch语句实现(此处只写了基础难度,其他可通过更改define实现)同时我们应让玩家可以随时结束游戏,也可以在玩一局之后自行选择是否继续游玩,所以加上一个while循环(此处的srand是用于后面放置雷时使用)

int temp = menu();
if (temp == 0) printf("游戏结束\n");
srand((unsigned int)time(NULL));
while (temp!=0) {
	printf("游戏规则:#表示未探测地,*表示雷\n");
	switch (temp)
	{
		//初级模式
	case 1:
		if(beginnermode())
		break;
		//中级模式
	/*case 2:
		mediummode();
		break;
		//高级模式
	case 3:
		advancedmode();
		break;*/
	default:
		printf("输入错误请重新输入\n");
	}
}

2.布置雷及初始化游戏界面

布置雷时应该是随机的,所以使用rand函数。rand函数的原型在stdlib.h头文件中,形式为int rand(void)。它不需要传入参数,会返回一个范围在0RAND_MAX之间的伪随机整数。(可自行跳过讲解)

//基本用法示例
#include <stdio.h>
#include <stdlib.h>

int main() {
    // 生成一个随机数
    int randomNumber = rand();
    printf("随机数:%d\n", randomNumber);

    return 0;
}

通常我们需要生成在某个特定范围内的随机数,可以通过取余和加法运算来实现。例如,要生成一个在[a, b]范围内的随机数,可以使用以下公式:rand() % (b - a + 1) + a。示例代码如下:

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

int main() {
    // 使用当前时间作为随机数种子
    srand((unsigned int)time(NULL));

    // 生成1到100之间的随机数
    int a = 1;
    int b = 100;
    int randomNumberInRange = rand() % (b - a + 1) + a;
    printf("1到100之间的随机数:%d\n", randomNumberInRange);

    return 0;
}

rand函数生成的是伪随机数,每次程序运行时,如果不设置随机数种子,生成的随机数序列是固定的。为了让每次运行生成的随机数序列不同,通常会使用time函数来设置随机数种子,time函数在time.h头文件中。示例如下

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

int main() {
    // 使用当前时间作为随机数种子
    srand((unsigned int)time(NULL));

    // 生成1到100之间的随机数
    int a = 1;
    int b = 100;
    int randomNumberInRange = rand() % (b - a + 1) + a;
    printf("1到100之间的随机数:%d\n", randomNumberInRange);

    return 0;
}

然后使用rand放置雷,使用WINNUM来调控雷的数量(使用宏可增强代码可读性与健壮性)。在定义ground时为了在初始化num数组时防止溢出定义为ROWS*COLS(ROWS=ROW+2)

//初始化ground
void initground(char ground[ROWS][COLS]) {
	int count = 0, x, y;
	for (int i = 1; i <= ROW; i++) {
		for (int j = 1; j <= COL; j++) {
			ground[i][j] = ' ';
		}
	}
	for (; count < WINNUM;) {
			x = rand() % ROW + 1;
			y = rand() % COL + 1;
			if (ground[x][y] == ' ') {
				ground[x][y] = '*';
				++count;
			}
	}
}

现在进行打印数组的初始化。由于是通过输入坐标行动,所以应输出行列以便选择,将show数组边单独初始化,然后再初始化棋盘,使功能更独立。而在定义show数组时为了方便也定义为ROWS*COLS

//初始化show数组
void initshow(char show[ROWS][COLS]) {
	initshowout(show);
	initshowin(show);
}
//初始化show边界
void initshowout(char show[ROWS][COLS]) {
	for (int i = 0; i <= COL; i++) {
		show[i][0] = i + '0';
		show[0][i] = i + '0';
	}
}
//初始化show内部
void initshowin(char show[ROWS][COLS]) {
	for (int i = 1; i <= ROW; ++i) {
		for (int j = 1; j <= COL + 1; ++j) {
			show[i][j] = '#';
		}
	}
}

对于num,需要根据ground初始化,而得到的等于0的可以赋值为’ ‘,同时定义num为char便于赋值给show

//初始化num
void initnum(char num[ROWS][COLS], char ground[ROWS][COLS]) {
	for (int i = 1; i <= ROW; i++)
	{
		for (int j = 1; j <= COL; j++) {
			num[i][j] = '0';
			for (int I = i - 1; I <= i + 1; I++)
			{
				for (int J = j - 1; J <= j + 1; J++) {
					if (ground[I][J] == '*') num[i][j] += 1;
				}
			}
		}
	}
	for (int i = 1; i <= ROW; i++)
	{
		for (int j = 1; j <= COL; j++) {
			if (num[i][j] == '0') num[i][j] = ' ';
		}
	}
}

3.输出游戏界面

//展示游戏界面
void display(char show[ROWS][COLS]) {
	for (int i = 0; i <= ROW; i++)
	{
		for (int j = 0; j <= COL; j++) {
			if (show[i][j] == '0') show[i][j] = ' ';
		}
	}
		for (int i = 0; i <= ROW; i++)
		{
			for (int j = 0; j <= COL; j++) {
				printf("%c ", show[i][j]);
			}
			printf("\n");
		}
}

这里为了方便可以顺便把查看雷的程序写了

//展示雷
void displaymine(char ground[ROWS][COLS]) {
	for (int i = 0; i <= ROW; i++)
	{
		for (int j = 0; j <= COL; j++) {
			printf("%c ", ground[i][j]);
		}
		printf("\n");
	}
}

4.选择结束游戏,挖开地面,标?,标!或者取消标记

玩家应可以随时结束游戏,所以添加了结束游戏的选项,同1.选择难度时使用switch与while

,同样使用int型反映玩家选择对地面进行怎样的操作。每次操作后检查输赢(通过remain与check函数)

//选择结束游戏或挖或标记!或标记?或取消标记
int chose(void) {
	int num;
	printf("请选择0结束游戏,1挖掘,2标记!,3标记?,4取消标记\n");
	scanf_s("%d", &num);
	return num;
}
void game(char num[ROWS][COLS], char ground[ROWS][COLS], char show[ROWS][COLS]){
	int temp, count = 0, remain = ROW * COL;
	while (1) {
		temp = chose();
		switch (temp)
		{
		case 0:
			return 1;
		case 1:
			if (mine(show, ground, num, &remain)) {
				display(show);
				//displaymine(ground);  用于检查
				return 1;
			}
			display(show);
			displaymine(ground);
			break;
		case 2:
			excalmationmark(show, ground, &count);
			display(show);
			//displaymine(ground);
			break;
		case 3:
			questionmark(show);
			display(show);
			//displaymine(ground);
			break;
		case 4:
			cancelmark(show);
			display(show);
			break;
		default:
			printf("输入错误,请重新输入\n");
			break;

		}
		if (remain == WINNUM || check(show, ground, count)) {
			printf("你赢了\n");
			return 1;
		}

	}
}

5.挖开地面

在挖掘后有以下情况

(1)雷 (2)空,但是周围仍有雷(3)空,且周围没有雷

如果为(1)就直接游戏结束,而游戏结束通过返回1表示,故mine函数为int型

如果为(2)那么该位置就应该显示周围的雷数目,所以把show替换为num

如果为(3)那么就应把周围的地块清除,直到旁边有雷的地块,这时要替换show为’ ‘,而初始化num时已经把对应num赋值为’ ‘了,所以和(2)操作统一,但是还要调用expand函数

//挖掘
int mine(char show[ROWS][COLS], char ground[ROWS][COLS], char num[ROWS][COLS],int *remain) {
	int x, y;
	printf("请输入要挖的坐标,用空格隔开\n");
	scanf_s("%d %d", &x, &y);
	if (x > 0 && y > 0 && x <= ROW && y <= COL) {

		if (ground[x][y] == '*') {
			show[x][y] = '*';
			display(show);
			printf("你输了\n");
			return 1;
		}
		else {
			show[x][y] = num[x][y];
			expand(show, num);
			initshowout(show);
			(*remain)--;
		}
		return 0;
	}
	else {
		printf("输入错误,请重新输入\n");
		return mine(show, ground,num,remain);
	}
}

6.标!与标?与取消标记

标记!时不可以超过WINNUM个(在基础模式为10),所以要加上判断,使用count来传递是否已满(一定用指针,否则count无效)。并且已经挖开的地块不能标记。所以有

//标记!
void excalmationmark(char show[ROWS][COLS], char ground[ROWS][COLS], int *count) {
	if (*count == WINNUM) {
		printf("已标%d个!,请重新选择\n",WINNUM);
	}
	else {
		int x, y;
		printf("请输入要标记!的坐标,用空格隔开\n");
		scanf_s("%d %d", &x, &y);
		if (x > 0 && y > 0 && x <= ROW && y <= COL) {
			if (show[x][y] == '#' || show[x][y] == '?' || show[x][y] == '!')
			{
				show[x][y] = '!';
				(*count)++;
			}
			else printf("该位置已挖开,不能标记\n");
		}
		else {
			printf("输入错误,请重新输入\n");
		}
	}
}

而另外两个函数与!函数基本相同,甚至更简单,简单复制删除即可

//标记?
void questionmark(char show[ROWS][COLS]) {
	int x, y;
	printf("请输入要标记?坐标,用空格隔开\n");
	scanf_s("%d %d", &x, &y);
	if (x > 0 && y > 0 && x <= ROW && y <= COL) {
		if (show[x][y] == '#' || show[x][y] == '?' || show[x][y] == '!')
		{
			show[x][y] = '?';
		}
		else printf("该位置已挖开,不能标记\n");
	}
	else {
		printf("输入错误,请重新输入\n");
	}
}

//取消标记
void cancelmark(char show[ROWS][COLS]) {
	int x, y;
	printf("请输入要取消标记的坐标,用空格隔开\n");
	scanf_s("%d %d", &x, &y);
	if (x > 0 && y > 0 && x <= ROW && y <= COL) {
		if (show[x][y] == '#' || show[x][y] == '?' || show[x][y] == '!')
		{
			show[x][y] = '#';
		}
		else printf("该位置已挖开,不能取消标记\n");
	}
	else {
		printf("输入错误,请重新输入\n");
	}
}

7.点击时自行消去明显地块

当玩家选择情况(3)时show中出现了’ ‘,那么就把该地块周围show全部赋值为num,递归调用即可(狗头)。其实这样会溢出。把递归改为遍历所有的’ ‘,当第一次过后出现新的’ ‘时就可以显示其周围的地块,实现功能。为防止溢出,把遍历和赋值两个功能分为两个函数

//拓展雷区
void expand(char show[ROWS][COLS], char num[ROWS][COLS]) {
	for (int i = 1; i <= ROW; i++) {
		for (int j = 1; j <= COL; j++)
		{
			if (show[i][j] == ' ') {
				for (int I = i - 1; I <= i + 1 && I<ROW + 1; I++)
				{
					for (int J = j - 1; J <= j + 1&&J<COL+1; J++) {
						 show[I][J] = num[I][J];
						 if (show[I][J] == ' ') EXPAND(show, num,I,J);
					}
				}
			}
		}
	}
}
void EXPAND(char show[ROWS][COLS], char num[ROWS][COLS], int i, int j) {
	for (int I = i - 1; I <= i + 1 && I < ROW + 1; I++)
	{
		for (int J = j - 1; J <= j + 1 && J < COL + 1; J++) {
			show[I][J] = num[I][J];
		}
	}
}

8.判断输赢

判断输已在前面实现,而不通过标!获胜的判断通过剩余的地块可以轻易实现(在4中已有简单描述),所以只需写出通过标!获胜的判断函数。当标!的地块均为雷时获胜,而只有当标了WINNUM个!时才有可能获胜,所以前面count还可以在此作用

//判断赢
int check(char show[ROWS][COLS], char ground[ROWS][COLS], int count) {
	if (count == WINNUM) {
		for (int i = 1; i <= ROW; i++) {
			for (int j = 0; j <= COL; j++)
			{
				if (show[i][j] == '!') {
					if (ground[i][j]!= '*') {
						return 0;
					}
				}
			}
		}
		return 1;
	}
	else return 0;
}

总结

以下是全部代码的整合,读者可自行获取。本人初学,水平有限,望指正或建议。

game.h

#pragma once
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#define _CRT_SECURE_NO_WARNINGS
#define ROW 9
#define COL 9
#define ROWS ROW+2  //防止越界
#define COLS COL+2
#define WINNUM 10

//打印菜单
int menu();

//初级模式
int beginnermode();

//正式游戏
void game(char num[ROWS][COLS], char ground[ROWS][COLS], char show[ROWS][COLS]);

//初始化show
void initshow(char show[ROWS][COLS]);
//初始化show边界
void initshowout(char show[ROWS][COLS]);
//初始化show内部
void initshowin(char show[ROWS][COLS]);

//初始化ground
void initground(char ground[ROWS][COLS]);

//初始化num
void initnum(char num[ROWS][COLS], char ground[ROWS][COLS]);

//展示游戏界面
void display(char show[ROWS][COLS]);


//展示雷
void displaymine(char ground[ROWS][COLS]);

//选择挖或标记或不确定
int chose(void);

//挖掘
int mine(char show[ROWS][COLS], char ground[ROWS][COLS], char num[ROWS][COLS],int *remain);

//标记!
void excalmationmark(char show[ROWS][COLS], char ground[ROWS][COLS],int *count);

//标记?
void questionmark(char show[ROWS][COLS]);

//取消标记
void cancelmark(char show[ROWS][COLS]);

//拓展雷区
void expand(char show[ROWS][COLS], char num[ROWS][COLS]);
void EXPAND(char show[ROWS][COLS], char num[ROWS][COLS],int i,int j);

//判断赢
int check(char show[ROWS][COLS], char ground[ROWS][COLS], int count);

test.c

#include"game。h.h"
#define _CRT_SECURE_NO_WARNINGS

int main() {
	int temp = menu();
	if (temp == 0) printf("游戏结束\n");
	srand((unsigned int)time(NULL));
	while (temp!=0) {
		printf("游戏规则:#表示未探测地,*表示雷\n");
		switch (temp)
		{
			//初级模式
		case 1:
			if(beginnermode())
			break;
			//中级模式
		/*case 2:
			mediummode();
			break;
			//高级模式
		case 3:
			advancedmode();
			break;*/
		default:
			printf("输入错误请重新输入\n");
		}
	}
	return 0;
}

game.c

#include"game。h.h"
#define _CRT_SECURE_NO_WARNINGS

//打印菜单
int menu() {
	int temp;
	printf("输入0结束游戏,1为初级模式,2为中级模式,为高级模式\n");
	scanf_s("%d", &temp);
	return temp;
}

//初级模式
int beginnermode() {
	char num[ROWS][COLS]; //存放周围雷的信息
	char ground[ROWS][COLS];//存放雷的位置 
	char show[ROWS][COLS]; //展示的数组
	initshow(show);
	initground(ground);
	initshowout(ground);
	initnum(num,ground);
	display(show);
	game(num, ground, show);
}

//
void game(char num[ROWS][COLS], char ground[ROWS][COLS], char show[ROWS][COLS]){
	int temp, count = 0, remain = ROW * COL;
	while (1) {
		temp = chose();
		switch (temp)
		{
		case 0:
			return 1;
		case 1:
			if (mine(show, ground, num, &remain)) {
				display(show);
				//displaymine(ground);  用于检查
				return 1;
			}
			display(show);
			displaymine(ground);
			break;
		case 2:
			excalmationmark(show, ground, &count);
			display(show);
			//displaymine(ground);
			break;
		case 3:
			questionmark(show);
			display(show);
			//displaymine(ground);
			break;
		case 4:
			cancelmark(show);
			display(show);
			break;
		default:
			printf("输入错误,请重新输入\n");
			break;

		}
		if (remain == WINNUM || check(show, ground, count)) {
			printf("你赢了\n");
			return 1;
		}

	}
}


//初始化show数组
void initshow(char show[ROWS][COLS]) {
	initshowout(show);
	initshowin(show);
}
//初始化show边界
void initshowout(char show[ROWS][COLS]) {
	for (int i = 0; i <= COL; i++) {
		show[i][0] = i + '0';
		show[0][i] = i + '0';
	}
}
//初始化show内部
void initshowin(char show[ROWS][COLS]) {
	for (int i = 1; i <= ROW; ++i) {
		for (int j = 1; j <= COL + 1; ++j) {
			show[i][j] = '#';
		}
	}
}

//初始化ground
void initground(char ground[ROWS][COLS]) {
	int count = 0, x, y;
	for (int i = 1; i <= ROW; i++) {
		for (int j = 1; j <= COL; j++) {
			ground[i][j] = ' ';
		}
	}
	for (; count < WINNUM;) {
			x = rand() % ROW + 1;
			y = rand() % COL + 1;
			if (ground[x][y] == ' ') {
				ground[x][y] = '*';
				++count;
			}
	}
}

//初始化num
void initnum(char num[ROWS][COLS], char ground[ROWS][COLS]) {
	for (int i = 1; i <= ROW; i++)
	{
		for (int j = 1; j <= COL; j++) {
			num[i][j] = '0';
			for (int I = i - 1; I <= i + 1; I++)
			{
				for (int J = j - 1; J <= j + 1; J++) {
					if (ground[I][J] == '*') num[i][j] += 1;
				}
			}
		}
	}
	for (int i = 1; i <= ROW; i++)
	{
		for (int j = 1; j <= COL; j++) {
			if (num[i][j] == '0') num[i][j] = ' ';
		}
	}
}

//展示游戏界面
void display(char show[ROWS][COLS]) {
	for (int i = 0; i <= ROW; i++)
	{
		for (int j = 0; j <= COL; j++) {
			if (show[i][j] == '0') show[i][j] = ' ';
		}
	}
		for (int i = 0; i <= ROW; i++)
		{
			for (int j = 0; j <= COL; j++) {
				printf("%c ", show[i][j]);
			}
			printf("\n");
		}
}
//展示雷
void displaymine(char ground[ROWS][COLS]) {
	for (int i = 0; i <= ROW; i++)
	{
		for (int j = 0; j <= COL; j++) {
			printf("%c ", ground[i][j]);
		}
		printf("\n");
	}
}

//选择结束或挖或标记!或标记?或取消标记
int chose(void) {
	int num;
	printf("请选择0结束游戏,1挖掘,2标记!,3标记?,4取消标记\n");
	scanf_s("%d", &num);
	return num;
}

//挖掘
int mine(char show[ROWS][COLS], char ground[ROWS][COLS], char num[ROWS][COLS],int *remain) {
	int x, y;
	printf("请输入要挖的坐标,用空格隔开\n");
	scanf_s("%d %d", &x, &y);
	if (x > 0 && y > 0 && x <= ROW && y <= COL) {

		if (ground[x][y] == '*') {
			show[x][y] = '*';
			display(show);
			printf("你输了\n");
			return 1;
		}
		else {
			show[x][y] = num[x][y];
			expand(show, num);
			initshowout(show);
			(*remain)--;
		}
		return 0;
	}
	else {
		printf("输入错误,请重新输入\n");
		return mine(show, ground,num,remain);
	}
}

//标记!
void excalmationmark(char show[ROWS][COLS], char ground[ROWS][COLS], int *count) {
	if (*count == WINNUM) {
		printf("已标%d个!,请重新选择\n",WINNUM);
	}
	else {
		int x, y;
		printf("请输入要标记!的坐标,用空格隔开\n");
		scanf_s("%d %d", &x, &y);
		if (x > 0 && y > 0 && x <= ROW && y <= COL) {
			if (show[x][y] == '#' || show[x][y] == '?' || show[x][y] == '!')
			{
				show[x][y] = '!';
				(*count)++;
			}
			else printf("该位置已挖开,不能标记\n");
		}
		else {
			printf("输入错误,请重新输入\n");
		}
	}
}

//标记?
void questionmark(char show[ROWS][COLS]) {
	int x, y;
	printf("请输入要标记?坐标,用空格隔开\n");
	scanf_s("%d %d", &x, &y);
	if (x > 0 && y > 0 && x <= ROW && y <= COL) {
		if (show[x][y] == '#' || show[x][y] == '?' || show[x][y] == '!')
		{
			show[x][y] = '?';
		}
		else printf("该位置已挖开,不能标记\n");
	}
	else {
		printf("输入错误,请重新输入\n");
	}
}

//取消标记
void cancelmark(char show[ROWS][COLS]) {
	int x, y;
	printf("请输入要取消标记的坐标,用空格隔开\n");
	scanf_s("%d %d", &x, &y);
	if (x > 0 && y > 0 && x <= ROW && y <= COL) {
		if (show[x][y] == '#' || show[x][y] == '?' || show[x][y] == '!')
		{
			show[x][y] = '#';
		}
		else printf("该位置已挖开,不能取消标记\n");
	}
	else {
		printf("输入错误,请重新输入\n");
	}
}

//拓展雷区
void expand(char show[ROWS][COLS], char num[ROWS][COLS]) {
	for (int i = 1; i <= ROW; i++) {
		for (int j = 1; j <= COL; j++)
		{
			if (show[i][j] == ' ') {
				for (int I = i - 1; I <= i + 1 && I<ROW + 1; I++)
				{
					for (int J = j - 1; J <= j + 1&&J<COL+1; J++) {
						 show[I][J] = num[I][J];
						 if (show[I][J] == ' ') EXPAND(show, num,I,J);
					}
				}
			}
		}
	}
}
void EXPAND(char show[ROWS][COLS], char num[ROWS][COLS], int i, int j) {
	for (int I = i - 1; I <= i + 1 && I < ROW + 1; I++)
	{
		for (int J = j - 1; J <= j + 1 && J < COL + 1; J++) {
			show[I][J] = num[I][J];
		}
	}
}

//判断赢
int check(char show[ROWS][COLS], char ground[ROWS][COLS], int count) {
	if (count == WINNUM) {
		for (int i = 1; i <= ROW; i++) {
			for (int j = 0; j <= COL; j++)
			{
				if (show[i][j] == '!') {
					if (ground[i][j]!= '*') {
						return 0;
					}
				}
			}
		}
		return 1;
	}
	else return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值