看完这个即便你是菜鸟你也会用C语言实现三子棋

1. 三子棋实现的基本思路

三子棋是我们在儿时玩过的最经典的游戏之一,游戏的规则很简单,在一个 3×3 的棋盘中,两人交替下棋,当某一方的三个棋子连成三点一线,那么即为胜利。
那么问题来了,我们怎样巧妙的用编程的思维去完成这样一个小项目呢?首先,我们清楚,如果我们在一个源文件中将所有相关的代码全部挤进去,那么写到一半的时候你可能会窒息,“我写到哪了?”“我在写什么?”,一连串发自灵魂的自问可能会让你明白这世界并不美好。既然这样我们何不把写的代码按功能分类,分别放到不同的源文件中去呢?因此我们就可以在主函数 main.c 、功能函数 game.c 、以及头文件 game.h 中进行编译啦。
有了这样的一个大前提,我们知道游戏一般都会有菜单,让你选择玩游戏还是退出,当你进入了游戏,棋盘会出现在你的面前,等待你下的第一步棋,你下完了,电脑会自动下完第二步,这样一直循环,一直等到决出胜负。
这个时候你不会天真的以为事情就这么的顺风顺水吧,让我们来看一看我们到底要实现几个函数:初始化棋盘、打印棋盘、玩家下棋、电脑下棋、判断胜负,况且我们如何存储这个棋盘的棋子的数据呢?显然我们可以用一个二维数组来存储每一个格子的信息,接下来让我们一步一步地来实现。

2. 主函数

int main()
{
    
    
	int input = 0;//初始化输入变量
	srand((unsigned int)time(NULL));//配置随机函数
	do
	{
    
    
		menu();//显示菜单
		printf("请选择:");
		scanf("%d", &input);
		switch (input)//做出选择:玩还是退出
		{
    
    
		case 1:
			game();//选择玩,进入游戏函数
			break;
		case 0:
			printf("退出游戏\n");//选择不玩,退出游戏,程序终止
			break;
		default:
			printf("选择错误,请重新输入\n");//输入不合法,再次输入
			break;
		}
	} while (input);
	return 0;
}

有了以上的主函数作为参考,接下来的实现就是要我们一步一个脚印写出来了。首先我们观察上述代码是否有未知的需求,显然 menu() 函数和 game() 函数我们都是未知的,接下来我们来实现。

3. menu函数和game函数

就像在第一点说的那样,游戏的实现主要在这一部分,我们需要将各种功能都封装成函数。我们设棋盘有 ROW 行,COL 列,而不是死板的将行和列都设置为 3 ,这样我们把ROW和COL定义在头文件中 ,就优化了代码的可更改性。另外,在所有要封装的函数中,判断胜负的函数是需要返回值的,根据返回值的不同,通过 if 语句的判断从而打印出不同的结果。我们假设返回字符 * 代表玩家胜利,返回 # 代表电脑胜利,返回 C 代表游戏继续(没有分出胜负),返回 Q 代表平局。

void menu()//打印游戏菜单界面
{
    
    
	printf("*********************\n");
	printf("****   1.PLAY   *****\n");
	printf("****   0.EXIT   *****\n");
	printf("*********************\n");

}
void game()
{
    
    
	char ret;//初始化返回变量
	char board[ROW][COL];//定义棋盘数组
	InitBoard(board, ROW, COL);//将棋盘数组内的元素都初始化为空格
	DisplayBoard(board, ROW, COL);//打印出棋盘,让棋盘可视化
	while (1)//进入游戏循环
	{
    
    
		PlayerMove(board, ROW, COL);//玩家下棋
		DisplayBoard(board, ROW, COL);//打印出棋盘,让棋盘可视化
		ret = IsWin(board, ROW, COL);//判断是否分出了胜负
		if (ret != 'C')//只有返回C时游戏才没有分出胜负
		{
    
    
			break;
		}
		ComputerMove(board, ROW, COL);//电脑下棋
		DisplayBoard(board, ROW, COL);//打印出棋盘,让棋盘可视化
		ret = IsWin(board, ROW, COL);//判断是否分出了胜负
		if (ret != 'C')//只有返回C时游戏才没有分出胜负
		{
    
    
			break;
		}
	}
	//退出循环则是已经分出胜负的情况
	if (ret == '*')
	{
    
    
		printf("玩家赢!\n");
	}
	else if (ret == '#')
	{
    
    
		printf("电脑赢!\n");
	}
	else
	{
    
    
		printf("平局!\n");
	}

4. game.c中的具体实现函数

4.1 初始化函数 InitBoard()

在游戏开始之前,我们需要将表示棋盘的数组里面的元素全部初始化,大家想一想,在什么都没有操作的棋盘上是不是什么都没有,但是我们不能把数组初始化为没有呀,什么都没有只是视觉上的表现,我们不妨将数组每个元素初始化为空格。

//char board[ROW][COL]  用于接收棋盘数据的数组
//row  用于接收棋盘的行数
//col  用于接收棋盘的列数
void InitBoard(char board[ROW][COL], int row, int col)
{
    
    
	int i = 0;
	for (i = 0; i < row; i++)
	{
    
    
		int j = 0;
		for (j = 0; j < col; j++)
		{
    
    
			board[i][j] = ' ';//将每一个数组元素初始化为空格
		}
	}
}

4.2 打印棋盘函数 DispayBoard()

虽然我们已经将棋盘的每个元素初始化为了空格,但只是在计算机内存完成的操作,我们的肉眼是无法很好的观察到的,既然要下棋,我们总要将整个棋盘可视化吧,我们计划将棋盘设计成以下格式:
在这里插入图片描述
我们观察到棋盘一共有五行,第一三五行放置了数组元素与分割线,二四行放置了分隔线,这是一个 3×3 的棋盘,我们试想,如果我们仅仅将棋盘打印出只符合 3×3 棋盘的分隔线,你那未必也太挫了吧,我们想要修改头文件中的数据,而棋盘的大小也跟着改变,这样也就方便了以后程序的优化升级。
我们观察这个棋盘的规律:第一行是由九个空格两条竖线组成的,我们不妨将三个空格和一条竖线看作一组,循环打印,当打印到第三组的时候利用条件语句不打印不就可以了吗?再继续往下观察,可见第一行和第二行的布局与第三行和第四行的布局完全一样,我们也不妨将这两行也看做一组循环打印,至于最后一行,利用相同的方法,即打印到最后一次时不打印下面横竖线分隔线的那一部分。

void DisplayBoard(char board[ROW][COL], int row, int col)
{
    
    
	int i = 0;//循环变量
	for (i = 0; i < row; i++)
	{
    
    
		int j = 0;//嵌套循环变量
		//打印有数组元素的行
		for (j = 0; j < col; j++)
		{
    
    
			printf(" %c ", board[i][j]);// "空格%c空格"
			if (j < col - 1)//当没有打印到最后一次时,打印竖线
			{
    
    
				printf("|");
			}
		}
		printf("\n");
		if (i < row - 1)//最后一组的分隔线不用打印
		{
    
    
			int j = 0;
			for (j = 0; j < col; j++)
			{
    
    
				printf("---");
				if (j < col - 1)//最后一次的竖线不用打印
				{
    
    
					printf("|");
				}
			}
			printf("\n");
		}
	}
}

打印出的结果如图所示
在这里插入图片描述

4.3 玩家操作函数 PlayerMove()

玩家的操作无非就是将棋子放入想要放入的位置中,我们设玩家棋子为字符 * ,我们的目的就是将这颗星号放入数组中,这就需要用到二维数组的坐标作为传参,让玩家输入坐标,从而将对应的二维数组元素置为星号。此时我们就得考虑了,输入的坐标大于了二维数组本身的范围怎么办?下过棋子的地方还能不能继续下呢?对应的输入坐标和二维数组的坐标时相同的吗?
显然我们需要给定一个输入坐标的范围;下过棋子的地方再下棋子就会报错,利用循环重新输入;输入的坐标减一才是对应的二维数组的坐标。

void PlayerMove(char board[ROW][COL], int row, int col)
{
    
    
	printf("玩家走:");
	while (1)
	{
    
    
		printf("请输入坐标:\n");
		int x, y;
		scanf("%d%d", &x, &y);
		//给定输入坐标的合法范围
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
    
    
		    //该坐标下元素不是空格,说明已经被占用
			if (board[x - 1][y - 1] != ' ')
			{
    
    
				printf("该格子被占用,请重新输入:\n");
			}
			else
			{
    
    
			    //没有被占用,将其置为 *
				board[x - 1][y - 1] = '*';
				break;
			}
		}
		else
		//其他不合法的输入
		{
    
    
			printf("输入错误,请重新输入\n");
		}
	}
}

4.4 电脑下棋函数 ComputerMove()

不会吧不会吧,不会有人真的以为电脑会动脑子吧,这里的电脑下棋只不过是利用计算机产生两个随机数作为坐标,然后将电脑的棋子放置到棋盘中,除了要生成随机的坐标,其他的操作都与玩家下棋相似。这时的随机数就成问题了,随机数那么大,坐标那么小怎么办?我们将随机数模上行数或者列数,假如行列都是 3 ,我们不就得到 0,1,2 了吗?当然了,随机数函数 rand() 是需要 srand进行配置的,将时间戳作为变量,可以得到不同的随机数, srand 的配置在主函数中。

void ComputerMove(char board[ROW][COL], int row, int col)
{
    
    
	printf("电脑走:\n");
	while (1)
	{
    
    
		int x = rand() % row;//生成随机的行坐标
		int y = rand() % col;//生成随机的列坐标
	    //坐标没有被占用,电脑下棋
		if (board[x][y] == ' ')
		{
    
    
			board[x][y] = '#';
			break;
		}
	}
}

4.5 判断胜负函数 IsWin()

既然要胜利,就要棋子连成三点一线,无非只有四种情况:三个在同一行;三个在同一列;三个在对角线上;三个在斜对角线上。如果没有分出胜负呢?没有分出胜负,要么是平局,要么还要继续下棋直到分出胜负为止。那么我们怎么才能知道是否平局,是否要继续呢?可以知道,如果连棋盘都满了都没有分出胜负,你还不服想耍赖想继续下,可惜棋盘不允许,这时就是平局了;若棋盘还没满,也没有哪一方赢,游戏就得继续。看来有这么多的情况,这个函数是需要返回值的,我们按照第三节所说的那样来设置返回值。在上面我们提到要判断是否棋盘已满,我们不妨将判断棋盘是否已满也封装成一个函数。

char IsWin(char board[ROW][COL], int row, int col)
{
    
    
	int i = 0;
	for (i = 0; i < row; i++)
	{
    
    
		if (board[i][0] == board[i][1] && board[i][0] == board[i][2] && board[i][0] != ' ')//当行上出现三点一线
		{
    
    
			return board[i][0];//谁赢就返回什么
		}
	}
	for (i = 0; i < col; i++)
	{
    
    
		if (board[0][i] == board[1][i] && board[0][i] == board[2][i] && board[0][i] != ' ')//当列上出现三点一线
		{
    
    
			return board[0][i];
		}
	}
	if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[0][0] != ' ')//当对角线上出现三点一线
	{
    
    
		return board[0][0];
	}
	if (board[0][2] == board[1][1] && board[2][0] == board[1][1] && board[0][2] != ' ')//当斜对角线上出现三点一线
	{
    
    
		return board[0][2];
	}
	if (IsFull(board, ROW, COL) == 1)//棋盘满了,平局
		return 'Q';
	if (IsFull(board, ROW, COL) == 0)//继续
		return 'C';

}

4.6 判断棋盘是否已满函数 IsFull()

我们遍历整个数组,如果出现了空格,则棋盘没满。用 0 和 1 作为返回值,0代表没满,1代表已满。

static int IsFull(char board[ROW][COL], int row, int col)//判断棋盘是否已满
{
    
    
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++)
	{
    
    
		for (j = 0; j < col; j++)
		{
    
    
			if (board[i][j] == ' ')  //若棋盘上出现空格,则棋盘还没满
				return 0;
		}
	}
	return 1;
}

5. 头文件

我们实现完了所有的具体功能函数,需要在头文件中添加函数声明,并且在 game.c 和 main.c 中引入 头文件 game.h

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

#define ROW 3
#define COL 3

void InitBoard(char board[ROW][COL], int row, int col);//初始化数组
void DisplayBoard(char board[ROW][COL], int row, int col);//打印棋盘
void PlayerMove(char board[ROW][COL], int row, int col);//玩家下棋
void ComputerMove(char board[ROW][COL], int row, int col);//电脑下棋
char IsWin(char board[ROW][COL], int row, int col);//判断输赢

6. 实现效果

我们来看一看玩家赢得效果
在这里插入图片描述在这里插入图片描述
试一试退出游戏
在这里插入图片描述平局
在这里插入图片描述
电脑赢
在这里插入图片描述

以上就是本篇博客得全部内容,感谢老爷们得观看,记得赞赞点一手哦~

猜你喜欢

转载自blog.csdn.net/weixin_52606524/article/details/113746035
今日推荐