用C语言实现三子棋

在学习了函数的基本调用和二维数组后,我们其实就可以尝试写一个三子棋来玩一玩。

若有需要的话,完整代码在我的仓库里的三子棋文件夹中:Lanload (Lanloading) - Gitee.com

但是做一个小游戏还是需要一个基本的思路的,思路如下:

1.要先有棋盘,初始化一个棋盘,棋盘有行有列,为了方便之后扩大自定义棋盘大小使用define定义变量方便修改


2.要拿一个数组存放棋子数据,初始化棋盘时,塞入空格


3.接下来就是下棋,获得玩家下棋所输入的坐标,-1后加入board数组中


4.电脑下棋的逻辑也写完了,在目前不太聪明的版本中,利用随机数进行下棋。


5.接下来是回合制的实现,需要加入游戏结束的判断,分别为玩家胜利,电脑胜利,平局

创建一个头文件

为了不让代码显得过于冗长且不易阅读,在此编写代码时,我们最好先自定一个头文件,用来存放我们自定的函数方法。

自定头文件很简单,于解决方案资源管理器中,右击头文件,新建一个头文件项目。

 

新建好头文件后,我们将在主程序中调用它

#include"head.h"

int main ()
{
        
}

这样做有什么好处呢?就好比做菜前的食材准备一样,放在一个专门的盒子里,哪里需要做菜,我们就把这个材料盒子推到哪里就行。

所需变量的创建

下棋,我们最先需要的是先有一个棋盘,所以,我们先将绘制棋盘的工具和材料准备好,由于棋盘的摆布为3X3,我们需要用一个二维数组来存放棋子的数据。

相应的,棋盘最开始上头应该什么也没有,而为了方便我们用符号来代表棋子,我们用char类型来定义一个二维数组。

棋盘的大小为了方便之后修改,我们在头文件用define定义棋盘的长和宽,请注意,定义时最好使用大写以方便辨认。

#define ROW 3//行
#define COL 3//列

有了列和行后,我们就可以创建一个二维数组了

	char board[ROW][COL] = { 0 };

先用0将二维数组初始化,之后再向其中填充空格以充当空位棋子

这个时候二维数组里大概的元素排列是这样的:

0  0  0

0  0  0

0  0  0

打印主菜单与主菜单互动

 我们创建一个函数,为其命名为menu(),由于只是简单的打印图案,这一部分可以随意更换

void menu()
{
	printf("======= 三子棋 =======\n");
	printf("======= 1.play =======\n");
	printf("======= 0.exit =======\n");
	printf("======================\n");
}

 接下来则是玩家与主菜单的互动,输入1开始游戏,输入0退出游戏

这一部分的编写可以比较随意,但是不能过于复杂,我个人选择了使用while来达成菜单的效果     代码如下:

int main()
{
	menu();

	char board[ROW][COL] = { 0 };
	intboard(board, ROW, COL);
	int input = 0;
	int palyer = 0;

	while (1)
	{
		scanf("%d", &input);
		if (input == 1)
		{
			printf("游戏开始!\n");

		}
		else if (input == 0)
		{
			printf("退出游戏\n");
			break;
		}
		else
		{
			printf("输入错误,请重新选择!\n");
		}

	}
	return 0;
}

初始化棋盘

在每局游戏开始之前,我们都需要初始化棋盘,也就是将二维数组里的元素都替换成空格,方便传达这个位置没有棋子的信息。

既然创建了头文件,那直接在头文件里定义就没什么意义了,我们再创建一个.c文件来编写函数的方法。

与函数传参原理相同,我们需要为形参初始化

现在头文件中定义我们的函数

void intboard(char board[ROW][COL], int  row, int col);

然后在.c文件中调用head.h头文件,编写intboard函数的程序

#include"head.h"


//初始化棋盘
void intboard(char board[ROW][COL],int  row, int col)
{
	int i = 0;
	int n = 0;
	for (i = 0; i < row; i++)
	{
		for (n = 0; n < col; n++)
		{
			board[i][n] =' ';
		}
	}
}

利用for循环为二维数组中的元素放置空格以占用。

绘制棋盘

接下来就是绘制棋盘线了,我们可以先看一下预期效果,然后向着这个样式去编写程序

 可以看到,我们在每一行上先打印二维数组里的元素,然后打印  |  下一行再打印3个-,每3

个-打印一次|。

但是我们要注意的是,不能就嗯打印,里面还要放棋子进去呢,所以我们的程序编写的时候要一边打印一边放入初始化后的二维数组里的元素。

为了让棋盘横竖对齐,打印元素的时候在元素前后应加上空格

代码如下:

//绘制棋盘
void drawboard(char board[ROW][COL], int  row, int col)
{
	int i = 0;
	
	for (i = 0; i < row; i++)
	{
		int n = 0;
		for (n = 0; n < col; n++)
		{
			printf(" %c ", board[i][n]);//每过3个字符打印一次|,空格之间放入棋盘数组中的元素
			if (n < col - 1)
			{
				printf("|");
			}
		}
		//先打印‘|’和放入元素
		printf("\n");//打完一行换一行
		if (i < row - 1)
		{
			int n = 0;
			for (n = 0; n < col; n++)
			{
				printf("---");//打印下一行,还有-
				if (n < col - 1)
					printf("|");
			}
			printf("\n");
		}
	}
}

玩家和电脑下棋方法

进行游戏的时候,首先应该是获取玩家所下棋子的坐标,这个实现很简单,我们用*来指代玩家的棋子,#指代电脑的棋子。

玩家回合

当我们获取玩家输入的坐标后,我们首先不能硬性要求玩家知晓数组的下标计数原则是从0开始的,所以,我们需要对玩家输入的坐标-1来获得对应数组的下标。

获取下标后,我们就应该将对应数组下标的元素替换成 * 来完成下棋的动作

但是注意,当玩家输入错误坐标时,或者在那个坐标上已经有了棋子的时候,是不能让玩家下下去的,所以我们也需要程序先判断那个坐标是不是空的才进行赋值操作。

代码如下:

//玩家下棋
void playerturn(char board [ROW][COL],int row , int col)
{
	int a = 0;
	int b = 0;

	while (1)
	{
		printf("玩家的回合!\n请输入坐标下棋.>");

		scanf("%d %d", &a, &b);

		if (board[a - 1][b - 1] == ' ')//将玩家输入的坐标减一,并检测是否是空位
		{
			board[a - 1][b - 1] = '*';
			drawboard(board, ROW, COL);
			break;

		}
		else
		{

			printf("坐标输入错误或已有棋子 请重新输入\n");

			printf("\n");

			drawboard(board, ROW, COL);
		}
	}

}

电脑回合

为了不使代码过于复杂,我们让电脑随机挑位置下棋。

而为了达成随机条件,则需要生成随机数。随机数的生成我们可以借助rand函数来进行生成。

rand函数在生成随机数的时候,会随机生成一个种子值,然后rand函数会借由这个种子值生成随机数,但是每一次只产生一次种子值rand函数所产生的随机数不是完全随机的,所以我们在使用rand函数之前还需要借助srand函数来使得rand函数的种子值不断变化以达成真正的随机数生成。而一直不断变化的值,那就是系统时间了。

srand(unsigned seed)通过参数seed改变系统提供的种子值,从而可以使得每次调用rand函数生成的伪随机数序列不同,从而实现真正意义上的“随机”。通常可以利用系统时间来改变系统的种子值,即srand(time(NULL)),可以为rand函数提供不同的种子值,进而产生不同的随机数序列。

rand函数位于#include <stdlib.h>的头文件内。

为了使用time函数,也需要使用<time.h>的头文件。

我们先在main函数中先实现随机数的生成

int main()
{
	menu();
	srand((unsigned int)time(NULL));//生成随机数

}

然后开始编写电脑的下棋方法,代码如下:

void comturn(char board[ROW][COL], int row, int col)
{
	int x = 0;
	int y = 0;

	printf("电脑的回合!\n");


	while (1)
	{
		y = rand() % col;//限制取数的大小,防止电脑下到其他位置去。
		x = rand() % row;

		if (board[x][y] ==' ')
		{
			board[x][y] = '#';
			drawboard(board, ROW, COL);
			printf("\n");
			break;

		}
	}
}

游戏主体逻辑

游戏的主体逻辑主要是玩家和电脑轮流下棋,在每一次下完棋后绘制一次棋盘并且检查是否有人胜利,而检测是谁胜利可以由返回的字符来确认,当触发胜利条件时返回触发的那颗棋子,是谁下的就谁赢,当然,不能排除平局的可能性,当棋盘上的子都下满了之后如果还是没有触发胜利条件则宣布平局。

游戏代码如下:

void game(char board[ROW][COL], int row, int col)
{
	char ret = 'n';


	do
	{
		//玩家回合
		playerturn(board, ROW, COL);
		ret = check(board, ROW, COL);//用ret接收来自check的返回值

		if (ret == '*')
			break;
		//电脑回合
		comturn(board, ROW, COL);
		ret = check(board, ROW, COL);

		if (ret == '#')
			break;


	} while (ret == 'c');
	if (ret == '*')
		printf("玩家获胜!\n");
	else if (ret == '#')
		printf("电脑获胜!\n");
	else 
		printf("平局!\n");

}

检测游戏输赢与平局

我们拟定一个叫check的函数来帮助我们检测游戏的输赢。

char check(char board[ROW][COL], int row, int col);

由于要返回最后胜利的棋子,返回值类型设置为char

三子棋的规则非常简单,3个子连一块的一方获胜,所以我们的逻辑并不复杂,但是做的判断可能较多,不过无非就是3中情况,行,列,对角线,设置对应的条件,在触发时返回对应的字符即可。

当然,每一次检测完毕之后,没有满足任何人获胜时,我们应该也返回一个值

代码如下:

char check(char board[ROW][COL], int row, int col)
{
	int a = 0;
	int b = 0;
	//行检测
	for (a = 0; a < row; a++)
	{
		if (board[a][0] == board[a][1] && board[a][1] == board[a][2] && board[a][1] != ' ')
		//不仅要检查3个子是否相等还要排除3个空格就触发胜利的情况
		return board[a][1];	
	}
	//列检测
	for (b = 0; b < col; b++)
	{
		if (board[0][b] == board[1][b] && board[1][b] == board[2][b]  && board[1][b] != ' ')
		return board[1][b];
		
	}
	//对角线检测
	if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ')
	{
		return board[0][0];
	}
	else if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[1][1] != ' ')
	{
		return  board[0][0];
	}
	if(doubleko(board[ROW][COL], row, col))//平局检测
	{
		return 'd';
	}

	return 'c';
}

这里有一个注意点,本人曾天真的以正常逻辑直接判断3个元素相等也就是元素1 == 元素2 ===元素3 来判断胜利,逻辑上来看是正确的,但是对于计算机来说,它是走一步看一步的,当元素1==元素2的时候这一部分已经相当于被处理成1了,这样说比较主观,真正的原因则是双目操作符的特性导致,这个时候,计算机比较的则是元素3是不是等于1了,这样的话代码的逻辑就会出现问题。

所以判断条件应改成1 == 2 && 2 ==3才是正确的。

平局检测

check的代码已经有点长了,为了更好的去阅读和使用,我们再声明一个doubleok的函数来检测平局。

因为平局只有是和不是的情况,用if判断返回值就可以实现了,所以在这里我们将返回值设定成整型

int doubleko(char board[ROW][COL], int row, int col);

平局的逻辑很简单,代码如下:

//平局判断,只要场上没有空格且没有赢家的时候触发
int doubleko(char board[ROW][COL], int row, int col)
{
	int a = 0;
	int b = 0;

	for (a = 0; a < row; a++)
	{
		for (b = 0; b < col; b++)
		{
			if (board[a][b] == ' ');
			return 0;
		}
	}
	return 1;
}

将其放在check函数中即可

主函数部分

我们所有的逻辑与方法都写完了,接下来就是把零部件放进我们的主函数里了

代码如下:

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

	char board[ROW][COL] = { 0 };
	intboard(board, ROW, COL);
	int input = 0;
	int palyer = 0;

	while (1)
	{
		scanf("%d", &input);
		if (input == 1)
		{
			printf("游戏开始!\n");

			drawboard(board, ROW, COL);//绘制棋盘

			game(board,ROW,COL);//进入游戏

			menu();//游戏函数结束后再次打印主菜单

			intboard(board, ROW, COL);
            //打印后初始化棋盘,相当于清空棋盘,然后重新进入循环,检测玩家是否要再来一局


		}
		else if (input == 0)
		{
			printf("退出游戏\n");
			break;
		}
		else
		{
			printf("输入错误,请重新选择!\n");
		}

	}
	return 0;
}

以上就是三子棋的主要逻辑与写法了,当然,电脑随机下棋会显得笨笨的,如果可以的话我们也可以尝试将它的下棋逻辑优化,让它变得更难对付。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值