C语言学习——从零开始学编程(第六篇:实战1——三子棋,扫雷)

写在前面:
盼星星盼月亮,我们总算盼来了我们学习C语言的小小成果课,制作小游戏——三子棋,扫雷游戏,在本文,小颖会先给出自己的原始代码,读者可以先复制到自己的编译器上体验一下游戏是怎样的,然后在跟着讲解一步步把代码打出来,这样在学习的时候目标会更明确,也更容易理解;等把游戏自己独立写出来以后,可以对小颖的代码进行升级从而写出逻辑和趣味性更强的游戏,期待你的表现哦~~


前言

在学习完之后可以自己尝试写一写,当自己可以完全不参照资料而全部独立写出来之后,就说明你已经对这些知识掌握了


一、三子棋

1.效果展示

首先创建三个文件,一个头文件三子棋.h和两个源文件三子棋.ctest.c
在这里插入图片描述

头文件用于声明函数,而声明的函数定义在对应的源文件里,最后在检测函数里检测函数是否可以正常运行,在以后写比较大的代码时候,我们一般都是这样三个文件。
这样你对“C语言程序是由函数组成的”这句话又有了更深刻的理解。

代码:
三子棋.h:

#include<stdio.h>
#include<time.h>
#define ROW 3
#define COL 3
//初始化棋盘
void Board_initial(char arr[ROW][COL], int row, int col);
//打印棋盘
void Board_print(char arr[ROW][COL], int row, int col);
//玩家下棋
void Player_move(char arr[ROW][COL], int row, int col);
//电脑下棋
void Computer_move(char arr[ROW][COL], int row, int col);
//判断输赢
char is_win(char arr[ROW][COL], int row, int col);

三子棋.c:

#include"三子棋.h"
//初始化棋盘
void Board_initial(char arr[ROW][COL], int row, int col)
{
	int i, j;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			arr[i][j] = ' ';
		}
	}
}

//打印棋盘
void Board_print(char arr[ROW][COL], int row, int col)
{
	int i, j;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			printf(" %c ", arr[i][j]);
			if (j != col - 1)
				printf("|");
		}
		printf("\n");
		if (i != row - 1)
		{
			for (j = 0; j < col; j++)
			{
				printf("---");
				if (j != col - 1)
					printf("|");
			}
		}
		printf("\n");
	}
}

//玩家下棋
void Player_move(char arr[ROW][COL], int row, int col)
{
	printf("玩家回合,请输入下棋坐标:\n");
	int x, y;
	while(1)
	{
		scanf("%d %d", &y, &x);
		if (x<1 || x>ROW || y<1 || y>COL)
		{
			printf("你是准备把棋子下自己头顶上吗??重新输入:\n");
		}
		else if (arr[x - 1][y - 1] != ' ')
		{
			printf("该位置已被占用,请重新输入:\n");
		}
		else
		{
			break;
		}
	}
	arr[x - 1][y - 1] = '*';
}

//电脑下棋
void Computer_move(char arr[ROW][COL], int row, int col)
{
	int x, y;
	printf("电脑的回合:\n");
	while(1)
	{
		x = rand() % row;
		y = rand() % col;
		if (arr[x][y] == ' ')
		{
			arr[x][y] = '#';
			break;
		}
	}
}

//判断输赢
char is_win(char arr[ROW][COL], int row, int col)
{
	int i, j;
	//判断每一列
	for (i = 0; i < ROW - 2; i++)
	{
		for (j = 0; j < COL; j++)
		{
			if (arr[i][j] == arr[i + 1][j] && arr[i][j] == arr[i + 2][j] && arr[i][j] != ' ')
			{
				return arr[i][j];
			}
		}
	}

	//判断每一行
	for (j = 0; j < COL - 2; j++)
	{
		for (i = 0; i < ROW; i++)
		{
			if (arr[i][j] == arr[i][j + 1] && arr[i][j] == arr[i][j + 2] && arr[i][j] != ' ')
			{
				return arr[i][j];
			}
		}
	}

	//判断对角线
	for (j = 0; j < COL - 2; j++)
	{
		for (i = 0; i < ROW - 2; i++)
		{
			if (arr[i][j] == arr[i + 1][j + 1] && arr[i][j] == arr[i + 2][j + 2] && arr[i][j] != ' ')
			{
				return arr[i][j];
			}
		}
	}

	int flag = 0;
	for (i = 0; i < ROW; i++)
	{
		for (j = 0; j < COL; j++)
		{
			if (arr[i][j] == ' ')
				flag = 1;
		}
	}
	if (flag)
		return 'c';//c就是continue,表示游戏继续
	else
		return 'e';//e就是equal,表示平局
}

test.c:

#include"三子棋.h"
//打印菜单
void menu()
{
	printf("----------------------\n");
	printf("-----*	1. play	 *----\n");
	printf("-----*	0. exit	 *----\n");
	printf("----------------------\n");
}

void game()
{
	//创建二维数组
	char arr[ROW][COL];
	char ret;
	//初始化棋盘
	Board_initial(arr,ROW,COL);
	//打印棋盘
	Board_print(arr, ROW, COL);
	while(1)
	{
		//玩家下棋
		Player_move(arr, ROW, COL);
		Board_print(arr, ROW, COL);
		ret = is_win(arr, ROW, COL);
		if (ret != 'c')
		{
			break;
		}
		
		//电脑下棋
		Computer_move(arr, ROW, COL);
		Board_print(arr, ROW, COL);
		ret = is_win(arr, ROW, COL);
		if (ret != 'c')
		{
			break;
		}
	}
	if (ret == 'e')
	{
		printf("平局\n");
	}
	else if (ret == '*')
	{
		printf("你太强了\n");
	}
	else
	{
		printf("你好菜啊\n");
	}
}

int main()
{
	int input;
	srand((unsigned int)time(NULL));

	menu();
	
	do
	{
		printf("请选择: ");
		scanf("%d", &input);
		switch (input)
		{
			case 0:
				break;
			case 1:
				game();
				break;
			default:
				printf("你输入了个啥玩意???\n");
				break;
		}
	} while (input);
	return 0;
}

游戏效果大致是这样(你可以自己尝试看看):
1.选择是否进入游戏时:
在这里插入图片描述
2.下棋子时:
在这里插入图片描述
3.输赢阶段:
在这里插入图片描述

2.分解讲解

1)创建三个文件

从上面的代码复杂程度你可以发现,如果要把所有代码写在同一个文件里,不仅很长不容易翻阅,而且你会发现如果程序除了问题,你很难发现是哪里出了问题。就比如一辆汽车不能运转,你就很如果把汽车全体都检测一遍是不是很麻烦,但是如果我们这个汽车是很多零件拼接而成的,那么我们只需要相关部件拿出来检测该部件是否可以正常运转就可以。

在写大程序的时候也会这样,所以一般我们会单独拿一个源文件定义函数,在监测文件里只需要去调用即可,可以让检测环节大大简化。同时我们需要头文件去声明使用的函数和预处理。

所以先创建三个文件,一个头文件,两个源文件:
在这里插入图片描述

2)打印菜单并进行选择

我们玩不玩游戏,按什么按键进入或者退出游戏,都要有一个根据,我们在玩游戏的时候,就有这样一个东西——“菜单”所以我们在检测函数里打印出菜单:

#include<stdio.h>
void menu()
{
	printf("----------------------\n");
	printf("-----*	1. play	 *----\n");
	printf("-----*	0. exit	 *----\n");
	printf("----------------------\n");
}

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

那我们需要提醒玩家选择并且对不同的选择进行不同的操作,所以要创建变量input来储存玩家的选择,并根据这个变量的值进入不同的分支,我们使用switch分支语句:

	menu();
	do//我们的玩家万一想玩不止一次怎么办?所以提供循环,而用do-while循环至少要进行一次选择,比较合适
	{
		printf("请选择: ");
		scanf("%d", &input);
		switch (input)
		{
			case 0:
				break;
			case 1:
				game();//选择1进入游戏,我们用游戏函数来完成这个过程,在检测函数中这个过程是怎样完成的并不需要在意
				break;
			default://输入了没有提供的选择,记得提示一下
				printf("你输入了个啥玩意???\n");
				break;
		}
	}while(input);//input == 0时正好退出循环,退出游戏,其他的选择都会导致继续循环,和菜单一致

到这里,游戏进入的准备阶段就完成了,接下来要做的,就是补充game()函数的具体内容

3)game函数的具体实现

在刚刚的menu函数下面我们来定义game函数

#define ROW 3
#define COL 3//用宏定义行和列数,这样以后如果想扩大棋盘,可以直接改这里而不用到函数里一个个改了
void menu()
{...}
void game()
{
	char arr[ROW][COL];//创建二维数组,因为棋盘是二维的,我们上一节学的二维数组正好可以处理平面问题
	//根据你运行小颖的代码可以发现,游戏一开始就给你看到了棋盘的样子所以要初始化并打印出棋盘
	//初始化棋盘
	Board_initial(arr,ROW,COL);
	//打印棋盘
	Board_print(arr, ROW, COL);
	//接下来是玩家和电脑走棋,这是个循环,玩家走完电脑走,然后再玩家走,再电脑走
	while(1)
	{
		//玩家下棋
		Player_move(arr, ROW, COL);
		//电脑下棋
		Computer_move(arr, ROW, COL);
	}
}
int main()
{
	menu();
	...
	game();
	...
}

写完上面的代码发现我们每次下完棋是不是要看看棋盘的状况?我们下完这步棋有没有赢是不是需要判断一下?while循环根本没有尽头啊,那怎么办?
我们在while循环里要做一些改变:

while(1)
	{
		//玩家下棋
		Player_move(arr, ROW, COL);
		Board_print(arr, ROW, COL);
		is_win(arr, ROW, COL);
		
		//电脑下棋
		Computer_move(arr, ROW, COL);
		Board_print(arr, ROW, COL);
		is_win(arr, ROW, COL);
	}

但是问题来了,还是没法结束循环
也就是说,我们的判断输赢函数is_win(arr, ROW, COL);顶多只能返回一个代表输赢的数字或者字符,但是没办法自己跳出循环,所以我们可以去接受一下这个返回值,然后根据他来结束循环,我们暂时用c(continue的缩写)表示没有人赢输,如果返回的不是零就说明有输赢或者是平局了,跳出循环。

while(1)
	{
		//玩家下棋
		Player_move(arr, ROW, COL);
		Board_print(arr, ROW, COL);
		ret = is_win(arr, ROW, COL);
		if (ret != 'c')
		{
			break;
		}
		
		//电脑下棋
		Computer_move(arr, ROW, COL);
		Board_print(arr, ROW, COL);
		ret = is_win(arr, ROW, COL);
		if (ret != 'c')
		{
			break;
		}
	}

然后跳出循环之后是谁赢了?平局了没有?我们需要知道结果:

	if (ret == 'e')//e代表equal
	{
		printf("平局\n");
	}
	else if (ret == '*')//'*'就是玩家使用的棋子
	{
		printf("你赢了\n");
	}
	else//我们假定'#'是电脑使用的棋子
	{
		printf("电脑赢了\n");
	}

3)游戏内部函数的实现

游戏的整体思路现在已经构思完毕了,现在就是如何实现的问题了,现在我们看看还有哪些函数我们需要实现?发现这些函数都是游戏内部具体的功能,所以我们要在头文件里声明并在对应的源文件中实现。
三子棋.h中列举出需要实现的函数:

//初始化棋盘
void Board_initial(char arr[ROW][COL], int row, int col);
//打印棋盘
void Board_print(char arr[ROW][COL], int row, int col);
//玩家下棋
void Player_move(char arr[ROW][COL], int row, int col);
//电脑下棋
void Computer_move(char arr[ROW][COL], int row, int col);
//判断输赢
char is_win(char arr[ROW][COL], int row, int col);

然后我们要在三子棋.c中实现这些函数,为了三子棋.h中能够声明这些函数,我们要在三子棋.ctest.c中包含头文件三子棋.h,我们在两个源文件中肯定都要用到#define ROW 3,#define COL 3,以及#include<stdio.h>,为了简略,我们直接把这些写到三子棋.h这个文件里,这样在两个源文件里就不需要这些了
所以完整的三子棋.c这样写:

#define _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
#include<time.h>//后来需要增加的库函数直接在这里加上就可以了
#define ROW 3
#define COL 3
//初始化棋盘
void Board_initial(char arr[ROW][COL], int row, int col);
//打印棋盘
void Board_print(char arr[ROW][COL], int row, int col);
//玩家下棋
void Player_move(char arr[ROW][COL], int row, int col);
//电脑下棋
void Computer_move(char arr[ROW][COL], int row, int col);
//判断输赢
char is_win(char arr[ROW][COL], int row, int col);

好的,接下来让我们一一实现这些函数:

Board_initial

一开始的时候,我们希望棋盘是空的,那就把二维数组的每一个元素都变成空格就好了

void Board_initial(char arr[ROW][COL], int row, int col)
{
	int i, j;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			arr[i][j] = ' ';
		}
	}
}
Board_print

先看看我们需要打印的棋盘的样子:
在这里插入图片描述
大概就是
空格+数组元素+空格+竖杠+空格+数组元素+空格+竖杠+空格+数组元素+空格
减号+减号+减号+竖杠+减号+减号+减号+竖杠+减号+减号+减号
这一个不太好理解,读者可以自己在纸上画一画或者看代码理解。
然后重复找规律可以知道大概是这个样子:

void Board_print(char arr[ROW][COL], int row, int col)
{
	int i, j;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			printf(" %c ", arr[i][j]);
			if (j != col - 1)
				printf("|");
		}
		printf("\n");
		if (i != row - 1)
		{
			for (j = 0; j < col; j++)
			{
				printf("---");
				if (j != col - 1)
					printf("|");
			}
		}
		printf("\n");
	}
}
Player_move

首先给出提示,让玩家知道该下棋了,并且使用两个变量x,y来储存你想下的坐标位置

printf("玩家回合,请输入下棋坐标:\n");
int x, y;
scanf("%d %d", &y, &x);

但这时候我们要考虑到一些问题

  1. 玩家给出的坐标是否合法?(是否超过棋盘范围)
  2. 玩家想要下棋的地方是否已被占用?

所以我们要检测这两项标准,如果没有满足上述两个标准,就要重新落子,知道正确落子为止,这时候要借助循环

void Player_move(char arr[ROW][COL], int row, int col)
{
	printf("玩家回合,请输入下棋坐标:\n");
	int x, y;
	while(1)
	{
		scanf("%d %d", &y, &x);//为什么x给y,y给x?
		if (x<1 || x>ROW || y<1 || y>COL)
		{
			printf("你是准备把棋子下自己头顶上吗??重新输入:\n");
		}
		else if (arr[x - 1][y - 1] != ' ')//这里为什么是x-1和y-1?
		{
			printf("该位置已被占用,请重新输入:\n");
		}
		else
		{
			break;//上面两个条件都满足,就说明下的位置合法,这时候跳出循环
		}
	}
	arr[x - 1][y - 1] = '*';//下棋子
}

解答代码里的两个问题,第一,为什么x和y要反着输入?
因为我们玩家不会说都很了解C语言的二维数组,都是默认x是横坐标而y是纵坐标,但是在二维数组中,第一个[ ]里是表示第几行,是纵坐标,第二个[ ]里是表示第几列,是横坐标,和我们日常的习惯不同。所以我们反着输入,就和日常习惯一样了。

第二,为什么是x-1和y-1,同理,我们的玩家也不知道C语言数组下标要从0开始,所以一般我们都是认为是从1开始的,所以会输入1,2,3而不是0,1,2,所以要减一和数组去对应。
这样你就可以理解这段代码为什么要这样写了。

Computer_move

电脑走步子呢,就是考一个叫随机数的东西了,在库函数里有一个叫随机数函数 rand 函数,但是生成这个随机数我们又需要用到随机数种子函数srand()函数。
但是问题来了,我们的srand函数如要生成一个随机数,其实是需要一个随机数的。
这不是矛盾吗???你要生成随机数又需要一个随机数???所以我们要想起他办法搞出来一个类似于随机数的东西。比如时间,时间每一分每一秒都在变化,而且你每次调用函数的时间都是不确定的,所以就相当于建立了一个随机数。我们先使用srand函数和时间戳函数创建一个随机数

srand((unsigned int)time(NULL));

这里为什么是要强制类型转换以及为什么time函数的参数为什么是NULL,我们可以查找time函数和srand函数的功能来研究
在这里插入图片描述
在这里插入图片描述
如果想深入了解可以自己查找一些资料,这里不多说,只要知道怎么用的就行。至于这些函数的头文件,rand,srand就在stdio.h里而time函数在time.h里,需要记得包含一下。

接下来的问题和我们自己下棋很类似,随机数产生的数肯定是可能越界的,我们怎样保证机器下的棋子不越界?
很简单,我们只需要把生成的随机数对ROW和COL取模即可

x = rand() % row;
y = rand() % col;

接下来和玩家下棋一样要判断这个位置是否被占用最后写出来是这样的:

void Computer_move(char arr[ROW][COL], int row, int col)
{
	int x, y;
	printf("电脑的回合:\n");
	while(1)
	{
		x = rand() % row;
		y = rand() % col;
		if (arr[x][y] == ' ')//下的位置合法才停下并放上棋子
		{
			arr[x][y] = '#';
			break;
		}
	}
}
is_win

好了,最后就是判断输赢的阶段,我们很熟悉三子棋判断输赢的方法,就是分别看行,列和斜着的有没有三个相同棋子连在一起。但是如果我们还不知道这三个相连的棋子是谁的,所以我们直接将这三个棋子返回去给ret,根据ret的内容来判定是谁赢了或者是平局
但是呢,也有可能两个人都没有赢得胜利,要继续下棋,或者是棋盘已经下满了,平局这时候返回e给ret即可。

char is_win(char arr[ROW][COL], int row, int col)
{
	int i, j;
	//判断每一列
	for (i = 0; i < ROW - 2; i++)
	{
		for (j = 0; j < COL; j++)
		{
			if (arr[i][j] == arr[i + 1][j] && arr[i][j] == arr[i + 2][j] && arr[i][j] != ' ')
			{
				return arr[i][j];
			}
		}
	}

	//判断每一行
	for (j = 0; j < COL - 2; j++)
	{
		for (i = 0; i < ROW; i++)
		{
			if (arr[i][j] == arr[i][j + 1] && arr[i][j] == arr[i][j + 2] && arr[i][j] != ' ')
			{
				return arr[i][j];
			}
		}
	}

	//判断对角线
	for (j = 0; j < COL - 2; j++)
	{
		for (i = 0; i < ROW - 2; i++)
		{
			if (arr[i][j] == arr[i + 1][j + 1] && arr[i][j] == arr[i + 2][j + 2] && arr[i][j] != ' ')
			{
				return arr[i][j];
			}
		}
	}
	
	//接下来讨论下满了的情况:
	int flag = 0;
	for (i = 0; i < ROW; i++)
	{
		for (j = 0; j < COL; j++)
		{
			if (arr[i][j] == ' ')//如果一个空格都没有flag就是0,说明下满了
				flag = 1;
		}
	}
	if (flag)
		return 'c';//c就是continue,表示游戏继续
	else
		return 'e';//e就是equal,表示平局
}

好了,到这里我们所有函数都实现完了,就得到了我们开始效果展示里的代码。
。。。到这里,笔者已经写出一万多字了,可见是多么的复杂,所以一时半会理解不了的地方,建议自己多研究或者私信给小颖帮助你解答一些问题。
然后后面的扫雷游戏其实和这个函数大差不差,只要你把三子棋学会的差不多了,其实扫雷你就会有思路。

建议在这里停个半天一天的,给三子棋彻底消化了再继续看下面的。


二、扫雷游戏

1.效果展示

扫雷·.h

#define _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
#include<time.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define EASY 10

//初始化数组
void Board_initial(char board[ROWS][COLS], char ch);

//棋盘的打印
void Board_print(char board[ROWS][COLS], int row, int col);

//设置雷
void Set_mine(char mine[ROWS][COLS],int row, int col);

//排查雷
void Find_mine(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col);

扫雷.c

#include"扫雷.h"

//初始化数组
void Board_initial(char board[ROWS][COLS], char ch)
{
	int i, j;
	for (i = 0; i < ROWS; i++)
	{
		for (j = 0; j < COLS; j++)
		{
			board[i][j] = ch;
		}
	}
}

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

//设置雷
void Set_mine(char mine[ROWS][COLS], int row, int col)
{
	int count = EASY;
	int x, y;
	while (count)
	{
		x = rand() % row + 1;
		y = rand() % col + 1;
		if (mine[x][y] == '0')
		{
			mine[x][y] = '1';
			count--;
		}
	}
}

//统计雷的个数
int mine_count(char mine[ROWS][COLS], int x, int y)
{
	return (mine[x - 1][y - 1] +
		mine[x - 1][y] +
		mine[x - 1][y + 1] +
		mine[x][y - 1] +
		mine[x][y + 1] +
		mine[x + 1][y - 1] +
		mine[x + 1][y] +
		mine[x + 1][y + 1] - 8 * '0');

}
//排查雷
void Find_mine(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col)
{
	printf("请输入你想要排查的坐标:\n");
	int x, y;
	int left = row * col - EASY;
	while(left)
	{
		scanf("%d %d", &y, &x);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (mine[x][y] == '1')
			{
				printf("恭喜你,排到了依托答辩 )_( \n");
				Board_print(mine, ROW, COL);
				break;
			}
			else if (mine[x][y] == '0' && show[x][y] == '*')
			{
				show[x][y] = mine_count(mine, x, y) + '0';
				Board_print(show, ROW, COL);
				left--;
			}
			else
				;
		}
		else
		{
			printf("你是在国外排雷???");
		}
	}
	if (left == 0)
	{
		printf("你找到了所有的雷~~\n");
	}
}

test.c

#include"扫雷.h"
void menu()
{
	printf("----------------------\n");
	printf("-----*	1. play	 *----\n");
	printf("-----*	0. exit	 *----\n");
	printf("----------------------\n");
}

void game()
{
	//创建两个数组,一个数组用来记录雷的信息,一个展现给玩家看
	char mine[ROWS][COLS];
	char show[ROWS][COLS];

	//初始化数组,记录雷的数组里'0'表示非雷,'1'表示雷,玩家看到的数组里*表示未排查的
	Board_initial(mine,'0');
	Board_initial(show, '*');

	//棋盘的打印
	//Board_print(mine, ROW, COL);//在测试的时候可以把含有雷信息的数组打印
	Board_print(show, ROW, COL);

	//设置雷
	Set_mine(mine, ROW, COL);
	//Board_print(mine, ROW, COL);

	//排查雷
	Find_mine(show, mine, ROW, COL);

}

int main()
{
	int input;
	srand((unsigned int)time(NULL));

	menu();

	do
	{
		printf("请选择: ");
		scanf("%d", &input);
		switch (input)
		{
		case 0:
			break;
		case 1:
			game();
			break;
		default:
			printf("你输入了个啥玩意???\n");
			break;
		}
	} while (input);
	return 0;
}

你可以先把代码复制到你的编译器里去尝试一下这个游戏。然后再过来打代码会事半功倍

2.分解讲解

其实只要你掌握了三子棋的逻辑,可以发现扫雷其实是差不太多的,你对比一下两个的代码,发现很多都是一模一样。比如主函数里的内容:
那不是直接Ctrl C,Ctrl V就直接搞定了?

int main()
{
	int input;
	srand((unsigned int)time(NULL));

	menu();

	do
	{
		printf("请选择: ");
		scanf("%d", &input);
		switch (input)
		{
		case 0:
			break;
		case 1:
			game();
			break;
		default:
			printf("你输入了个啥玩意???\n");
			break;
		}
	} while (input);
	return 0;
}

还是需要有三个文件这里不多说。主函数内容一模一样,我们就主要学习game函数的实现

1)game函数

在扫雷游戏的实现时,我们要黄建两个数组,一个用来放雷的信息,所以这个数组肯定是不能给玩家看到的。所以我们还要一个显示排雷页面的数组,这两个数组的大小必须是一样大小,这样才可以正确的定义。
假设我们现在要用的是一个99的棋盘,我们在排雷的时候,是不是要看我们排查数组的这个坐标周围八个坐标里有没有雷,如果我们设置数组的大小如果也是99,那么在排查周围一圈的时候,是不是会越界?,所以我们额外再棋盘外面加上一圈,设置数组的大小为9+2即可。然后设定这外面的一圈没有雷,即可满足条件。注意,在打印棋盘的时候,这外面的一圈是用不到的。

和前面写三子棋的时候同理,我们定义宏来方便以后对游戏数据的改变,那么我们就可以定义出这样的宏

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

这时候你就可以理解为什么后面要加2了

2)具体函数的内部实现

Board_initial

初始化数组的时候,我们要把含雷信息的数组mine全部初始化为字符0,表示没有雷,而给玩家看的数组全部初始化为*,表示这里还没有排查。我们如果单独写两个函数来分别初始化这两个数组不是很麻烦?所以我们在初始化的时候可以多穿第一个参数就是要初始化成的那个字符。

void Board_initial(char board[ROWS][COLS], char ch)
{
	int i, j;
	for (i = 0; i < ROWS; i++)
	{
		for (j = 0; j < COLS; j++)
		{
			board[i][j] = ch;
		}
	}
}
Board_print

无疑我们要把棋盘的状况打印出来给玩家看,所以打印函数是必不可少的,其实和三子棋的逻辑几乎没有区别,只是游戏界面可以不太一样了,这个可以根据自己的需要设计出不同的格式,我在这里把横坐标和纵坐标都加上了一圈提示数字。

void Board_print(char board[ROWS][COLS], int row, int col)
{
	int i, j;
	printf("  ");
	for (i = 1; i <= row; i++)
	{
		printf("%d ", i);
	}
	printf("\n  ");
	for (i = 1; i <= row; i++)
	{
		printf("- ");
	}
	printf("\n");
	for (i = 1; i <= row; i++)
	{
		printf("%d|", i);
		for (j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
	printf("\n");
}

效果是这样:
在这里插入图片描述

Set_mine

我们需要使用随机数在mine数组里随机设置雷的位置并且使用字符1表示,为什么不用其他字符代替我们后面会说。

		x = rand() % row + 1;
		y = rand() % col + 1;
		if (mine[x][y] == '0')//保证不会重复在同一个位置设置雷,在这个地方没有雷的情况下设置雷
		{
			mine[x][y] = '1';
		}

但是我们肯定要设置不止一个雷呀,所以我们定义一个宏EASY表示简单模式的雷的个数并使用循环来设置足够数量的雷

void Set_mine(char mine[ROWS][COLS], int row, int col)
{
	int count = EASY;
	int x, y;
	while (count)
	{
		x = rand() % row + 1;
		y = rand() % col + 1;
		if (mine[x][y] == '0')
		{
			mine[x][y] = '1';
			count--;
		}
	}
}
Find_mine

现在到了最关键的时候,就是排雷的过程,首先我们输入坐标,如果这里是雷,就爆炸并打印雷的情况,死有瞑目嘛。如果没有雷,则在给玩家看到棋盘show上这个位置,并给出周围八个坐标里雷的数目:

		scanf("%d %d", &y, &x);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (mine[x][y] == '1')//被炸死
			{
				printf("恭喜你,排到了依托答辩 )_( \n");
				Board_print(mine, ROW, COL);
				break;
			}
			else if (mine[x][y] == '0' && show[x][y] == '*')
			{
				show[x][y] = mine_count(mine, x, y) + '0';//给排完雷的地方赋上雷的信息
				Board_print(show, ROW, COL);//给玩家看show数组现在的状态
			}
			else
				;

我们发现需要一个函数来统计我们排查的坐标周围八个坐标的雷的情况
这时候我们只需要把含有雷信息的数组和我们排查的坐标传递给该函数即可,然后我们怎么统计雷的个数呢???这时候就用到了字符0和字符1,如果这是数字0和数字1,我们是不是只要把周围八个坐标里的数字一加,就有了雷的总数。但是现在是字符啊,所以我们稍微变动即可,就是字符n-字符0=数字n利用这个公式我们既可以写出代码:

//统计雷的个数
int mine_count(char mine[ROWS][COLS], int x, int y)
{
	return (mine[x - 1][y - 1] +
		mine[x - 1][y] +
		mine[x - 1][y + 1] +
		mine[x][y - 1] +
		mine[x][y + 1] +
		mine[x + 1][y - 1] +
		mine[x + 1][y] +
		mine[x + 1][y + 1] - 8 * '0');

}

当然如果我们没有被炸死,是不是还要继续排雷,知道排完雷或者被炸死,所以我们要考虑循环:

while(1)
	{
		scanf("%d %d", &y, &x);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (mine[x][y] == '1')
			{
				printf("恭喜你,排到了依托答辩 )_( \n");
				Board_print(mine, ROW, COL);
				break;
			}
			else if (mine[x][y] == '0' && show[x][y] == '*')
			{
				show[x][y] = mine_count(mine, x, y) + '0';
				Board_print(show, ROW, COL);
			}
			else
				;
		}
		else
		{
			printf("你是在国外排雷???");
		}
	}

但是什么时候可以判断排完雷呢?首先我们统计一下除了类以外还有多少格子?是这么多:
int left = row * col - EASY;总数-雷数
所以我们没排出一个雷就把left减减,当排完雷left就是0

void Find_mine(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col)
{
	printf("请输入你想要排查的坐标:\n");
	int x, y;
	int left = row * col - EASY;
	while(left)
	{
		scanf("%d %d", &y, &x);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (mine[x][y] == '1')
			{
				printf("恭喜你,排到了依托答辩 )_( \n");
				Board_print(mine, ROW, COL);
				break;
			}
			else if (mine[x][y] == '0' && show[x][y] == '*')
			{
				show[x][y] = mine_count(mine, x, y) + '0';
				Board_print(show, ROW, COL);
				left--;
			}
			else
				;
		}
		else
		{
			printf("你是在国外排雷???");
		}
	}
	if (left == 0)
	{
		printf("你找到了所有的雷~~\n");
	}
}

然后就比较完善了,自己可以尝试一下,记得在进行检测的时候可以把含有雷的信息的数组也打印出来,方便实验

其实这样写出的扫雷和我们在电脑里玩的还不太一样,我们在电脑里玩的,如果我们排查的坐标周围八个坐标的周围也没有雷,会自动打开,就是点一个地方可以排出一大片,这个功能如果你很感兴趣可以自己尝试写一写。


总结

到这里,基本上就把三子棋和扫雷游戏讲完了,一开始不懂很正常,其实可以边看边打,然后多去思考里面的逻辑关系就行了,小颖已经打了四五遍这个代码也才能自己独立打出来,多练习就好了。相信这两个游戏的学习之后,你对C语言的理解会更深,对C语言的应用也会更熟练,但是我们还有很多要学习,我们一起继续学习吧~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小颖加油啊~~

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

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

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

打赏作者

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

抵扣说明:

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

余额充值