C语言实现入门级小游戏——扫雷

235 阅读8分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路

上一期咱们用C语言实现了三子棋的小游戏 C语言实现三子棋

 今天我们再来写个扫雷的游戏,说起扫雷,相信大家都不陌生,可能许多朋友还是玩扫雷的高手。

其实扫雷和三子棋有许多相似之处,都需要用到数组的知识。

今天的扫雷也是相当有趣的

由于博主是一个编程学习的小白,所以这只是扫雷的初级版,此版本有一些功能还无法实现,在后续的学习中,我会持续更新,不断对这个小游戏进行优化,期待大家的关注,还有,这篇文章中若有错误或不当的地方,欢迎大家指正!

关于这个扫雷项目的代码,欢迎访问我的gitee代码仓库:

Living_Amethyst/code2021 - Gitee.com

让我们开始吧!

目录

一.游戏的整体思路

二.创建游戏菜单

三.游戏主体的实现

1.创建棋盘(数组)

2.初始化棋盘

3.打印棋盘

4.布置雷

4.排查雷

四.全部的代码

五.关于游戏的改进的思考

一.游戏的整体思路 为了让代码的可读性更高,思维性更强,我们需要创建三个文件来完成这个项目

test.c —— 测试游戏 game.h—— 游戏函数的声明 game.c—— 游戏函数的实现 然后我们需要建立两个棋盘,为什么呢?

我们先想一下扫雷的游戏规则,如图,如果我选中的方块不是雷,那么它上面就会显示一个数字,这个数字代表它以它为中心的这个3*3区域内(红色方框)地雷的个数,它的范围是0~8,我们初步的设想是:无雷的放数字0,有雷的放数字1

 但是如果我这个方块不是雷,显示它周围的雷的格式时,如果它的周围有一个雷,需要显示

数字1时 ,我们就会分不清这个1 是雷?还是排查出的周围雷的个数?所以我们需要建立两个棋盘

一个棋盘用来存放布置好的雷的信息(这是游戏结束前不让玩家看到的)

另一个棋盘存放排查好的雷的信息(这是游戏过程中玩家看到的)

还有一个问题

如果我们布置的棋盘是9*9的

当我们选择了排查这个位置的周围的雷的个数时,它的旁边就没有了,所以我们要将棋盘扩展为11*11

(但是打印时只打印9*9的棋盘 )

  如图,这样我们就能排查这格周围的地雷的个数了

我们具体这样操作

二.创建游戏菜单 与之前的三子棋游戏一样,我们首先需要设置一个游戏菜单

由于一进入游戏,我们首先就要看到菜单,所以这里我们采用do...while循环

我们的菜单需要实现的功能有:

游戏的进入 游戏的退出 非法输入的返回提示和说明 void menu() { printf("********************\n"); printf("* 1.play **\n"); printf(" 0.exit *****\n"); printf("**********************\n");

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

这里我们创建了test函数和menu函数,为的是让逻辑更清晰,现在我们的菜单部分就完成了

对于这个游戏菜单,我们能实现的游戏功能有:

输入1进入游戏 输入0退出游戏 输入其他数字提示选择错错误,将重新输入

                                         这是菜单的运行效果

三.游戏主体的实现 1.创建棋盘(数组) //创建数组 char mine[ROWS][COLS] = { 0 };//存放布置好的雷的信息 char show[ROWS][COLS] = { 0 };//存放排查好的雷的信息 2.初始化棋盘 我们把mine数组作为存放布置好的雷的信息的棋盘

show数组作为存放排查好的雷的信息棋盘 

我们用一个InitBoard函数来初始化这两个棋盘

看代码:

//函数的声明 void InitBoard(char board[ROWS][COLS], int rows,int cols,char set);

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

}

//函数的调用 //初始化mine数组为全字符'0' InitBoard(mine,ROWS,COLS,'0'); //初始化show数组为全'' InitBoard(show,ROWS,COLS,'');

我们把mine数组全部初始化为字符 ' 0 '

show数组全部初识化为字符 ' * ' 

3.打印棋盘 我们与两个棋盘

其中 mine数组代表的棋盘时游戏结束后或者游戏测试人员才能看到的

另一个show数组代表的棋盘是玩家玩游戏时能看到的

我们上代码

//函数的定义 void DisplayBoard(char board[ROWS][COLS], int row, int col);

//函数的定义 void DisplayBoard(char board[ROWS][COLS], int row, int col) { //1-9 int i = 0; int j = 0; printf("\n"); printf("---扫雷游戏---\n"); //打印列号 for (i = 0;i <= col;i++) { printf("%d ", i); } printf("\n"); for (i = 1;i <= row;i++) { printf("%d ", i); for (j = 1;j <= col;j++) {

		printf("%c ", board[i][j]);
	}
	printf("\n");
}

}

//函数的调用 DisplayBoard(show, ROW, COL);//只打印9*9的内容

4.布置雷 下面我们就可以来布置雷啦,由于雷是随机布置的,于是我们就要用到rand函数

srand((unsigned int)time(NULL)); //进行初始化 x = rand() % row+1; //x的范围是1~9 y = rand() % col+1; //y的范围是1~9

下面看代码:

//函数的声明 void SetMine(char mine[ROWS][COLS],int row,int col);

//函数的定义 void SetMine(char mine[ROWS][COLS], int row, int col) { //布置10个雷 int count = EASY_COUNT; while (count) { //生产随机的下标 int x = rand() % row + 1;//范围1到9 int y = rand() % col + 1; if (mine[x][y] == '0') { mine[x][y] = '1'; count--; } } }

//函数的调用 SetMine(mine, ROW, COL);

我们加上mine棋盘的打印,并将它注释掉,这是为了方便我们测试游戏或这检查游戏出现的问题的

这里的EASY_COUNT是雷的个数,我们用define定义它

4.排查雷 关于排查雷,我们是这样实现的:

输入排查雷的坐标 检查该坐标是不是雷 (1)是雷 --> 很遗憾炸死了 (0)不是雷 --> 统计坐标周围有几个雷-->存储排查雷的信息到show数组,游戏继续 这里我们用了 get_mine_count 和 FindMine 两个函数

get_mine_count函数用来统计坐标周围有几个雷

看代码

//声明 void FindMine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col);//从mine中排查放到show中

//排查雷 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+1] + mine[x + 1][y] + 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) { //1.输入排查雷的坐标 //2.检查该坐标是不是雷 //(1)是雷 --> 很遗憾炸死了 //(0)不是雷 --> 统计坐标周围有几个雷-->存储排查雷的信息到show数组,游戏继续 int x = 0; int y = 0; int win = 0; while (win<row*col- EASY_COUNT) { printf("请输入要排查的坐标: "); scanf("%d %d", &x, &y);//x的范围是1~9,y的范围是1~9 //判断坐标的合法性 if (x >= 1 && x <= col && y >= 1 && y <= row) { if (mine[x][y] == '1') { printf("很遗憾,你被炸死了\n"); DisplayBoard(mine, row, col);//把怎么被炸死的显现出来 break; } else { //不是雷的话统计(x,y)坐标周围有几个雷 int count = get_mine_count(mine, x, y); show[x][y] = count+'0'; //显示排查出的信息 DisplayBoard(show, row, col); win++; } } else { printf("坐标不合法,请重新输入\n"); } } if (win == row * col - EASY_COUNT) { printf("恭喜你,排雷成功!"); DisplayBoard(mine, row, col); }

}

//调用 FindMine(mine,show,ROW,COL);

这里有几个需要说明的点:

这里函数有两个形参,是为了从mine中排查然后放到show中

这些代表查找的坐标的周围其他格子的坐标

3.由于我们在棋盘中存放的是字符 '0 '和字符 '1'

我们先看一下 ASCII码 表

我们可以发现'0' '1' '2'这些字符的ASCII码值都是连续的,我们想显示出排查格子周围雷的个数,需要先将字符'0','1','2'...转化为数字 0,1 ,2,3...

我们只需要把每个字符减去一个'0',它的ASCII码值是48就可以了,看看我们是如何操作的吧:

之后再转化成字符的形式存到数组中

4.我们还要判断玩家输入坐标的合法性:

四.全部的代码 game.h—— 游戏函数的声明

#pragma once #include<stdio.h> #include<time.h> #include<stdlib.h> #define ROW 9 #define COL 9 #define EASY_COUNT 10

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

//初始化棋盘 void InitBoard(char board[ROWS][COLS], int rows,int cols,char set); //打印棋盘 void DisplayBoard(char board[ROWS][COLS], int row, int col); //布置雷 void SetMine(char mine[ROWS][COLS],int row,int col); //排查雷 void FindMine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col);

game.c—— 游戏函数的实现

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

} //打印棋盘 void DisplayBoard(char board[ROWS][COLS], int row, int col) { //1-9 int i = 0; int j = 0; printf("\n"); printf("---扫雷游戏---\n"); //打印列号 for (i = 0;i <= col;i++) { printf("%d ", i); } printf("\n"); for (i = 1;i <= row;i++) { printf("%d ", i); for (j = 1;j <= col;j++) {

		printf("%c ", board[i][j]);
	}
	printf("\n");
}

}

//布置雷 void SetMine(char mine[ROWS][COLS], int row, int col) { //布置10个雷 int count = EASY_COUNT; while (count) { //生产随机的下标 int x = rand() % row + 1;//范围1到9 int y = rand() % col + 1; if (mine[x][y] == '0') { mine[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+1] + mine[x + 1][y] + 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) { //1.输入排查雷的坐标 //2.检查该坐标是不是雷 //(1)是雷 --> 很遗憾炸死了 //(0)不是雷 --> 统计坐标周围有几个雷-->存储排查雷的信息到show数组,游戏继续 int x = 0; int y = 0; int win = 0; while (win<row*col- EASY_COUNT) { printf("请输入要排查的坐标: "); scanf("%d %d", &x, &y);//x的范围是1~9,y的范围是1~9 //判断坐标的合法性 if (x >= 1 && x <= col && y >= 1 && y <= row) { if (mine[x][y] == '1') { printf("很遗憾,你被炸死了\n"); DisplayBoard(mine, row, col);//把怎么被炸死的显现出来 break; } else { //不是雷的话统计(x,y)坐标周围有几个雷 int count = get_mine_count(mine, x, y); show[x][y] = count+'0'; //显示排查出的信息 DisplayBoard(show, row, col); win++; } } else { printf("坐标不合法,请重新输入\n"); } } if (win == row * col - EASY_COUNT) { printf("恭喜你,排雷成功!"); DisplayBoard(mine, row, col); }

}

test.c —— 测试游戏

#define _CRT_SECURE_NO_WARNINGS

#include"game.h" 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 };//存放排查好的雷的信息

//初始化mine数组为全字符'0'
InitBoard(mine,ROWS,COLS,'0');
//初始化show数组为全'*'
InitBoard(show,ROWS,COLS,'*');
//打印棋盘

DisplayBoard(show, ROW, COL);//只打印9*9的内容

//布置雷
SetMine(mine, ROW, COL);
/*DisplayBoard(mine, ROW, COL);*/
//这是不给玩家看到的

//排查雷
FindMine(mine,show,ROW,COL);//从mine中排查放到show中

}

void test() { int input = 0; srand((unsigned int)time(NULL)); do { menu(); printf("请选择:\n"); scanf("%d", &input); switch (input) { case 1: //扫雷 game(); break; case 0: break; default: printf("选择错误"); break;

		}
} while (input);

}

int main() { test(); return 0; }

好了,这样我们就把扫雷游戏编写完成了

五.关于游戏的改进的思考 如果你玩过正宗的扫雷游戏,那你肯定知道,扫雷游戏还有两个功能:

如果不是雷并且周围没有雷-->展开一片 如果我们确定哪个位置我们可以标记雷

这就是我们以后给这个游戏的优化方案

其中第一个功能我们需要使用递归实现

这些都会再以后更新,欢迎持续关注 !

更新内容:

实现了递归展开一片的功能 实现了标记地雷的功能 源代码

game.h

#pragma once

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

extern int row; extern int col; extern int mine_num;

typedef struct Rank { char name[20]; int time; }Rank;

//初始化棋盘 void InitBoard(char** board, char set); //打印棋盘 void DisplayBoard(char** board); //布置雷 void SetMine(charmine); //标记雷 void sign_mine(char show); //排查雷 int FindMine(char** mine, char** show);//从mine中排查放到show中

game.c

#define _CRT_SECURE_NO_WARNINGS #include"game.h" //初始化 void InitBoard(char**board, char set) { int i = 0; int j = 0; for (i = 0;i < row+2;i++) { for (j = 0;j < col+2;j++) { board[i][j] = set; } }

} //打印棋盘 void DisplayBoard(char** board) { //1-9 int i = 0; int j = 0; printf("\n"); printf("---扫雷游戏---\n"); //打印列号 for (i = 0;i <= col;i++) { printf("%d ", i); } printf("\n"); for (i = 1;i <= row;i++) { printf("%d ", i); for (j = 1;j <= col;j++) {

		printf("%c ", board[i][j]);
	}
	printf("\n");
}

} //设置雷 void SetMine(char** mine) {

int count = mine_num;
while (count)
{
	//生产随机的下标
	int x = rand() % row + 1;
	int y = rand() % col + 1;
	if (mine[x][y] == '0')
	{
		mine[x][y] = '1';
		count--;
	}
}

} //标记雷 void sign_mine(char** show) {

while (1)
{
	int input = 0;
	printf("-----------------------\n");
	printf("******你想标记地雷吗******\n");
	printf("*****  1.标记    *******\n");
	printf("*****  0.不标记  *******\n");
	printf("-----------------------\n");
	scanf("%d", &input);
	if (0 == input)
	{
		break;
	}
	else
	{
		int x = 0;
		int y = 0;
		printf("请输入你想标记的坐标: ");
		scanf("%d %d", &x, &y);
		//坐标合法性检验
		if (x >= 1 && x <= col && y >= 1 && y <= row)
		{
			show[x][y] = '@';//标记你认为的雷的位置为@
			DisplayBoard(show);
		}
		else
		{
			printf("非法的坐标,请重新标记\n");
		}

	}
}

}

int get_mine_count(char** mine, 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 + 1] + mine[x + 1][y] + mine[x][y + 1] + mine[x - 1][y + 1] - 8 * '0'; } //递归展开一片 void board(char** mine, char** show, int x, int y) { //判断坐标是否越界 if (x >= 1 && x <= col && y >= 1 && y <= row) { //判断是否已经被排除 if (show[x][y] != '' && show[x][y] != '@') { return; } int count = get_mine_count(mine, x, y); if (count > 0) { show[x][y] = count + '0'; return; } //递归拓展地图 else if (count == 0) { show[x][y] = '0'; board(mine, show, x - 1, y); board(mine, show, x - 1, y - 1); board(mine, show, x, y - 1); board(mine, show, x + 1, y - 1); board(mine, show, x + 1, y); board(mine, show, x + 1, y + 1); board(mine, show, x, y + 1); board(mine, show, x - 1, y + 1); } } } //排查雷 int FindMine(char** mine, char** show) { //1.输入排查雷的坐标 //2.检查该坐标是不是雷 //(1)是雷 --> 很遗憾炸死了 //(0)不是雷 --> 统计坐标周围有几个雷-->存储排查雷的信息到show数组,游戏继续 int x = 0; int y = 0; int win = 0; while (win < rowcol - mine_num) { sign_mine(show);//标记雷 printf("请输入要排查的坐标: "); scanf("%d %d", &x, &y);//x的范围是1~9,y的范围是1~9 //判断坐标的合法性 if (x >= 1 && x <= col && y >= 1 && y <= row) { if (mine[x][y] == '1') { printf("很遗憾,你被炸死了\n"); DisplayBoard(mine);//把怎么被炸死的显现出来 break; } else { //不是雷的话统计(x,y)坐标周围有几个雷 board(mine, show, x, y); //显示排查出的信息 DisplayBoard(show); win++; } } else { printf("坐标不合法,请重新输入\n"); } } if (win == row * col - mine_num) {

	printf("恭喜你,排雷成功!\n");
	return 1;
	DisplayBoard(mine);
	
}
return 0;

}

test.c

#define _CRT_SECURE_NO_WARNINGS #include"game.h" int row = 0; int col = 0; int mine_num = 0; int cmp(const void* a, const void* b) { Rank* aa = (Rank*)a; Rank* bb = (Rank*)b; return aa->time > bb->time; } //排行榜 void Update_Rank(Rank info) { Rank arr[6] = { 0 }; for (int i = 0; i < 6; i++) { arr[i].time = INT_MAX; } FILE* fp1 = fopen("rank.bin", "ab+"); //防止打开失败 if (!fp1) { printf("open failed"); return; } fseek(fp1, 0, SEEK_SET); int num = fread(arr, sizeof(Rank), 5, fp1); arr[num] = info; qsort(arr, num + 1, sizeof(Rank), cmp);

for (int i = 0; i <= num; i++)
{
	printf("%-20s %-20d   您的排名是:%d\n", arr[i].name, arr[i].time,i+1);
}
FILE* fp2 = fopen("rank.bin", "wb"); //不能用ab+
if (!fp2)
{
	printf("open failed");
	return;
}
num = num < 5 ? num + 1 : 5;
fwrite(arr, sizeof(Rank), num, fp2);
fclose(fp1);
fclose(fp2);

}

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

void game() { int input = 0; char name[20] = { 0 }; printf("请输入用户名:\n"); scanf("%s", name);

printf("请选择游戏难度:\n");
printf("***** 1.easy   *****\n");
printf("***** 2.normal *****\n");
printf("***** 3.hard   *****\n");

scanf("%d", &input);

switch (input)
{
case 1:row = 4, col = 4, mine_num = 1; break;
case 2:row = 9, col = 9, mine_num = 10; break;
case 3:row = 11, col = 11, mine_num = 15; break;
default: printf("选择错误");
}

//创建数组
char** mine = (char**)malloc(sizeof(char*) *(row + 2));
char** show = (char**)malloc(sizeof(char*) *(row + 2));
for (int i = 0; i < row+2; i++)
{
	mine[i] = (char*)malloc(sizeof(char) * (col + 2));
	show[i] = (char*)malloc(sizeof(char) * (col + 2));
}


//初始化mine数组为全字符'0'
InitBoard(mine, '0');
//初始化show数组为全'*'
InitBoard(show, '*');
//打印棋盘
DisplayBoard(show);//只打印9*9的内容

//布置雷
SetMine(mine);
DisplayBoard(mine);
//这是不给玩家看到的



//排查雷
int start = (int)clock();
int ret = FindMine(mine, show);//从mine中排查放到show中
Rank info;
strncpy(info.name, name, 20);
int end = (int)clock();
info.time = end - start;
if (ret)
	Update_Rank(info);

}

void test() { int input = 0; srand((unsigned int)time(NULL)); do { menu(); printf("请选择:\n"); scanf("%d", &input); switch (input) { case 1: //扫雷 game(); break; case 0: break; default: printf("选择错误"); break;

	}
} while (input);

} int main() { test(); return 0; }