C语言实现三子棋

Posted 夏.冬

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言实现三子棋相关的知识,希望对你有一定的参考价值。

利用C语言实现三子棋(井字棋)

相信大家在小时候都用纸和笔与小伙伴们玩过一个经典的游戏——井字棋,即三子棋,看完这篇文章后你就可以用C语言写出属于自己的三子棋了。

实现三子棋用到的C语言知识点
实现该游戏写出的代码行数相比猜数字游戏是较多的,大概有两百多行,因此可以分为三个文件,一个头文件,两个源文件,头文件中对函数进行声明,源文件中对函数进行定义。

此次用到的知识点与上篇猜数字游戏的知识点大致相同,又新加了二维数组,如果你对二维数组还不了解,建议先学习二维数组的相关知识后再阅读本篇文章。
此次代码并不复杂,只是较为繁琐,对逻辑思维能力有一定的要求,写代码时要逻辑清晰,以免被绕进去。其中大多是对自定义函数的利用,并无太多难点。

鄙人也只是一个C语言初学者,写了很多遍才能够独立写出这个游戏的代码,如果有不足或者错误的地方,还请大家指正。

具体的代码实现

首先创建出一个头文件,两个源文件,10_30.c文件用来存放主函数、菜单、游戏步骤等内容,game.1.c文件用来写具体的游戏代码,game.1.h是头文件,用来对函数进行声明。


这里的主函数及菜单写法与上篇的猜数字游戏写法完全相同,有不懂的地方可参考上篇内容。

#include<stdio.h>
void menu()
{
	printf("*****************************************\\n");
	printf("*****  1 .play             0. exit  *****\\n");
	printf("*****************************************\\n");
}
int main()
{
	int input = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("三子棋\\n");
			break;
		case 0:
			printf("退出游戏\\n");
			break;
		default:
			printf("选择错误\\n");
			break;
		}
	} while (input);
	return 0;
}

完成后,再编写game函数内部的代码

一、定义二维数组 - 存放棋盘信息

首先,我们要定义一个二维数组,用来存放走出的棋盘信息。

void game()
{
	//数组 - 存放走出的棋盘信息
	char board[ROW][COL] = { 0 };
}

其中,ROW为行数,COL为列数,那么这里你可能会有疑问,为什么不直接将行数和列数写成3呢?不要着急,我们先把ROW和COL的用法搞清楚。
这时切换到头文件中

这两行代码的意思是将ROW和COL的值定义为3,这样的好处是如果以后要改棋盘的大小,只需要在头文件中修改ROW和COL的值即可,就无需修改每个二维数组括号中的值了。

既然ROW和COL的值是在头文件中定义的,那么我们就要引用一下头文件。

#include "game.1.h"即为引用我们的头文件。

这里涉及到了一个知识点:引用自带的头文件是用尖括号<>,而引用我们自己创建的头文件要用双引号" " 。

将这个问题搞清楚后,我们继续往下写代码。

二、初始化棋盘

将数组即我们的棋盘创建后,应该初始化棋盘,即将数组中的内容全部设为空格,目的是打印棋盘时会打印出一个空棋盘。如果数组中的内容不全为空格,棋盘中就会打印出内容(如果数组的内容全为0,打印时也不会打印出内容)。

//游戏的整个算法实现
void game()
{
	//数组 - 存放走出的棋盘信息
	char board[ROW][COL] = { 0 };
	//初始化棋盘
	InitBoard(board, ROW, COL);
}

初始化棋盘时我们需要自定义一个函数,函数传参时需要传三个内容,数组名,数组的行数和列数。

既然初始化棋盘是游戏的一部分,那我们就在game.1.c文件中实现函数。

首先要在头文件中声明一下函数,该函数无需返回值,所以为void。

声明后,我们在game.1.c文件中定义函数。(将声明函数的代码去掉分号)

我们在该文件中用了头文件中声明的函数,那么我们也要引用一下头文件。

初始化棋盘即把数组中每个元素的内容都定义为空格。

#include "game.1.h"
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] = ' ';
		}
	}
}

注:既然两个源文件都要引用头文件stdio.h,那就可以只在头文件中引用stdio.h,其他两个源文件引用了头文件game.1.h也就相当于引用了头文件stdio.h。

三、打印棋盘

初始化棋盘完成后,我们进行下一步,打印棋盘。

    //打印棋盘
	//   |   |   
	//---|---|---
	//   |   |   
	//---|---|---
	//   |   |
	DisplayBoard(board, ROW, COL);

打印棋盘的效果如图所示,我们同样需要自定义一个函数,在头文件中声明,并在game.1.c文件中实现。

那么我们该如何美观的打印出棋盘呢?如果将行数与列数改变,又如何让打印出的棋盘大小相应改变呢?

我们不妨将棋盘拆解一下,我们再来回顾一下棋盘的样子。

//打印棋盘
	//   |   |   
	//---|---|---
	//   |   |   
	//---|---|---
	//   |   |

我们可将一行数据与一行分割行视为一个整体部分,为了方便下面的讲解,把这个整体部分暂命名为小黑。

即:

    //   |   |   
	//---|---|---

每打印一次打印出一个小黑,打印三次就可以打印出整个棋盘。

void DisplayBoard(char board[ROW][COL], int row, int col)
{
	int i = 0;
	for (i = 0;i < row;i++)
	{
		//1. 打印一行的数据
		int j = 0;
		for (j = 0;j < col;j++)
		{
			printf(" %c ", board[i][j]);
			if (j < col - 1)
			{
				printf("|");
			}//end of if
		}//end of for
		printf("\\n");//每打印完一行换行
	}//end of for
}

使用如上代码我们就完成了一行数据的打印。

看到这里你肯定还存有疑惑,我们再对这串代码解析一遍。

printf(" %c ",arr[i][j]);
//这里的意思是打印空格 字符 空格,其中的字符就是我们的棋子,加上两个空格是为了让我们的棋盘更加美观
if (j < col - 1)
	{
		printf("|");
	}
//这里的意思是最后一列是不需要打印"|"的,所以加上一个if语句进行选择
//将如上两个打印的内容加起来即可以打印出" 棋子 | 棋子 | 棋子 "的内容

这串代码只是打印出小黑的一半内容,我们还需要打印一行分割行,即打印出"—"。

void DisplayBoard(char board[ROW][COL], int row, int col)
{
	int i = 0;
	for (i = 0;i < row;i++)
	{
		//1. 打印一行的数据
		int j = 0;
		for (j = 0;j < col;j++)
		{
			printf(" %c ", board[i][j]);
			if (j < col - 1)
			{
				printf("|");
			}//end of if
		}//end of for
		printf("\\n");//每打印完一行换行
		//2. 打印分割行
		if (i < row - 1)//最后一行不需要打印分割行
		{
			for (j = 0;j < col;j++)
			{
				printf("---");
				if (j < col - 1)//最后一列不需要打印"|
				"
				{
					printf("|");
				}//end of if
			}//end of for
		}//end of if
		printf("\\n");//每打印完一行换行
	}//end of for
}

使用如上代码便可打印出完整的棋盘。

运行结果如下图

四、下棋

打印完棋盘后我们就可以下棋了,下棋又分为玩家下棋与电脑下棋两部分,我们这里假设玩家先下。

下棋肯定是要双方下多次的,不可能玩家下一次电脑下一次游戏就结束了,因此这里用while循环。

下完一次棋后打印棋盘,显示出下的棋子的位置。这里再次创建一个自定义函数,依然要在头文件中声明,在game.1.c文件中使用。


我们将x,y作为玩家要下的坐标,我们知道数组第一个元素的下标为0,第一行第一个元素的下标为0 0,但我们不能要求每个玩家都是程序员,在任何一个非程序员的玩家的认知中,第一行第一个数的下标都+++++是1 1。因此如果玩家下的坐标为x,y,那我们就要输入x-1,y-1。

其次,玩家并不会关心你的棋盘有多大,所以玩家输入的坐标就很有可能超出棋盘范围,因此我们要判断玩家所输坐标的合法性,若坐标不合法,给出提示,并让玩家重新输入,因此此处要用到循环。同时玩家可能下完一个坐标以后再下相同的坐标,因此也要判断并给出提示,如果该处坐标等于空格,则说明此处并未被占用,若不等于空格,则说明此处坐标已被占用。

void PlayerMove(char board[ROW][COL], int row, int col)
{
	int x = 0;
	int y = 0;
	printf("玩家走:>\\n");
	while (1)
	{
		printf("请输入要下的坐标:>");
		scanf("%d%d", &x, &y);
		//判断x,y坐标的合法性
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (board[x - 1][y - 1] == ' ')
			{
				board[x - 1][y - 1] = '*';//设玩家下的棋子用*表示
				break;//这里表示玩家下完棋跳出循环,轮到电脑下棋
			}
			else
			{
				printf("该坐标被占用\\n");
			}
		}
		else
		{
			printf("坐标非法,请重新输入\\n");
		}
	}
}

此时玩家下棋的代码就已经完成了,我们再完成电脑下棋的代码。


电脑下棋则要用到生成随机数,生成随机数要用rand、srand、time三个库函数,生成随机数的具体方法上篇已具体介绍,这里不再讲解。

电脑下棋无需判断坐标是否非法,只需判断坐标是否被占用。

void ComputerMove(char board[ROW][COL], int row, int col)
{
	int x = 0;
	int y = 0;
	printf("电脑走:>\\n");
	while (1)
	{
		x = rand() % row;//生成0-2的随机值
		y = rand() % col;//生成0-2的随机值
		//此处x、y的值已符合下标范围,所以无需-1
		if (board[x][y] == ' ')
		{
			board[x][y] = '#';//设电脑下的棋子用#表示
			break;
		}
	}
}

运行结果如下图:

将以上代码完成后,还需要判断玩家和电脑的输赢,玩家每下完一步就要判断输赢,同样,电脑每下完一步也要判断输赢。

这里同样也需要一个自定义函数,创建并初始化一个变量ret,用来存储函数IsWin()的返回值。

我们在头文件里声明函数,并注释一下各种情况下函数的返回值,在game.1.c文件中实现函数。


IsWin函数的返回类型为char,所以要定义为char。

//下棋
	while (1)
	{
		//玩家下棋
		PlayerMove(board, ROW, COL);
		DisplayBoard(board, ROW, COL);
		//判断玩家是否赢
		ret = IsWin(board, ROW, COL);
		//等于C则说明游戏要继续,不等于C则说明游戏已分出胜负,跳出循环,到while循环下的选择语句中判断结果
		if (ret != 'C')
		{
			break;
		}
		//电脑下棋
		ComputerMove(board, ROW, COL);
		DisplayBoard(board, ROW, COL);
		//判断电脑是否赢
		ret = IsWin(board, ROW, COL);
		if (ret != 'C')
		{
			break;
		}
	}//end of while
	if (ret = '*')
	{
		printf("玩家赢\\n");
	}
	else if (ret = '#')
	{
		printf("电脑赢\\n");
	}
	else
	{
		printf("平局\\n");
	}

接下来再在game.1.c文件中完善IsWin函数的内容。

胜利有三种情况,横三行连成一线,竖三列连成一线,以及两条对角线连成一线。

除了判谁胜谁负,还要判断是否平局,以及判断游戏是否继续。

char IsWin(char board[ROW][COL], int row, int col)
{
	int i = 0;
	//横三行
	for (i = 0;i < row;i++)
	{
	    //判断一行三个棋子是否相等
	    //注意不能写成xx==xx==xx的形成,要用&&连接两个等式
	    //board[1][1]!=' '是为了一开始棋盘都为空格时,不会直接被判断为跳出循环
		if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][1] != ' ')
		{
		    //满足条件时说明棋子都相同,只要返回上述任意一个棋子即可
		    //因为棋子符号与规定的返回符号相同
			return board[i][1];
		}
	}
	//竖三列
	for (i = 0;i < col;i++)
	{
	    //判断一列三个棋子是否相等
		if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[1][i] != ' ')
		{
			return board[1][i];
		}
	}
	//两条对角线
	//判断对角线上三个棋子是否相等
	if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ')
	{
		return board[1][1];
	}
	if (board[2][0] == board[1][1] && board[1][1] == board[0][2] && board[1][1] != ' ')
	{
		return board[1][1];
	}
	//判断是否平局
	//即判断棋盘是否已满
	if (1 == IsFull(board, ROW, COL))
	{
		return 'Q';
	}
	//若以上条件都不满足,则说明结果还没出,游戏继续
	return 'C';
}

判断是否平局时,依然需要自定义一个函数IsFull。

IsFull函数的实现代码如下:

//返回1表示棋盘满了
//返回0表示棋盘没满
int IsFull(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++)
		{
			if (board[i][j] == ' ')
			{
				return 0;
			}//end of if
		}//end of for
	}//end of for
	return 1;
}

上述所有代码写完,说明这个游戏就做完啦,运行结果如下图:


如果你能看到这里,那么恭喜你已经可以写出自己的三子棋了,一定要多写几遍,直到能把这个代码完整独立的写下来,对自己的逻辑能力的锻炼真的很大。

完整代码如下:


//10_30.c
#include "game.1.h"
void menu()
{
	printf("*****************************************\\n");
	printf("*****  1 .play             0. exit  *****\\n");
	printf("*****************************************\\n");
}
//游戏的整个算法实现
void game()
{
	char ret = 0;
	//数组 - 存放走出的棋盘信息
	char board[ROW][COL] = 以上是关于C语言实现三子棋的主要内容,如果未能解决你的问题,请参考以下文章

《C语言入门》三子棋C语言实现(详细版)

C语言游戏跟电脑battle三子棋

C语言实现三子棋游戏

详细解读C语言实现三子棋

三子棋(C语言实现)

c语言三子棋