三子棋(n行n列n子棋)详解

        哈喽亲爱的小伙伴们大家好,真的是好久不见。前一阵子过完年之后进行了漫长的“请年茬”,现在刚刚开学,从今天开始更新,基本上不出意外还是两天一篇左右。上一期给小伙伴们讲解了数组,这期来给大家简单介绍一个小程序应用,那就是三子棋。

        五子棋相信大家都玩过,其实远离基本上是完全相同的,就是改一下胜利条件的代码,三子棋就是9宫格嘛,三子连线即为胜利,现在我们就来简单的写一下这个代码。

        首先呢,我们肯定是先写出main函数,然后写出程序大框架。例如下代码

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

//菜单函数,在屏幕上打印菜单
void menu()
{
	printf("*************  三子棋游戏  ************\n");
	printf("************0.play   1.exit************\n");
}

//测试函数,整个程序流程
void test()
{
	int a;
	scanf("%d", &a);
	switch (a)
	{
	case 0:
		{
			printf("三子棋游戏\n");
			break;
		}

	case 1:
		{
			printf("退出游戏!\n");
			break;
		}

	default:
		{
			printf("请输入正确指令!\n");
		}
	}
}

//主函数,程序入口
int main()
{
	menu();
	test();
	return 0;
}

 运行截图:

         这样我们程序大框就写好了,再简单加以修饰。那么首先当我们玩完一局游戏时,应该再一次弹出菜单,而不是直接退出游戏,相信大家很容易想到,应该用循环来解决。事实上什么循环都可以实现我们想要的结果,我们应该根据情况选择。对于这个小游戏来说,我们显然希望上来直接运行游戏,然后玩完一把在进入循环,显然用我们的do while循环比较合适。由于while判断的是括号为真进入循环,那么我们就可以巧妙利用一下,直接用菜单时我们选择的结果作为条件,把退出游戏选项改为0,这样只要我们输入的不是0(即我们不想退出游戏),那么while自然判定为继续循环,完美的符合了我们的需求,如下代码:

void test()
{
	int a;
	do
	{
		menu();
		scanf("%d", &a);
		switch (a)
		{
		case 0:
			{
				printf("退出游戏!\n");
				break;
			}

		case 1:
			{
				printf("三子棋游戏\n");
				break;
			}

		default:
			{
				printf("请输入正确指令!\n");
			}
		}
	} while (a);
}

运行结果: 

         现在我们大框就写好了,该我们的重头戏游戏函数了。显然当我们玩这个游戏的时候,我们选择1不是为了看屏幕打印出一句三子棋游戏,而应该是真正的游戏,现在我们开始写一下游戏的代码。

        现在我们开始思考,当我们选择了1,我们希望看到的时一个棋盘,所以我们应该在game()函数中先写一个打印棋盘函数,例如下代码:

void game()
{
	Dispiayboard(borad, ROW, COL);
}

        打印函数包括三个参数:棋盘(数组),行,列

        然而事实上,我们想写出打印函数,必须得有棋盘给它打印啊,也就是打印函数传参得是实参啊,所以其实我们game函数最开始不是打印棋盘,而是创建棋盘并初始化棋盘。只不过想让玩家看见的是打印棋盘,这就是我们所说的用户视角和程序员视角。所以game函数开头应该是这样的:

#define ROW 3
#define COL 3


void game()
{
	//创建棋盘
	char borad[ROW][COL] = {0};
	//初始化棋盘
	Initializeboard(borad, ROW, COL);
	//打印棋盘
	Dispiayboard(borad, ROW, COL);
}

        这里行列我们采用#define来定义,这是因为在这个程序中行列我们要多次使用行列值,如果我们后期想要修改,一个一个改显然是不现实的,采用#define来进行全局定义,利于后期修改维护,比如把这个代码升级为五子棋。

        下面我们先来写初始化棋盘函数,其实就是简单的把数组的值初始化为空格,这样在我们没有下棋的时候,打印出来就是“空位”一样。

        具体实现使用双层for循环,例如下代码:

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

        然后我们来实现具体的函数。由于我们在vs中写代码,是不能进行贴图的,那么我们就要先办法利用符号来使输出结果尽可能像一个棋盘。所以当我们利用符号和printf函数来写打印棋盘的函数,依旧是双层for循环:

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

运行截图:

         具体代码就不做详细讲解了,其实就是为了让打印出的结果看起来更像棋盘,我们把#define的定义改成5,结果如图,说明我们写的代码是利于后期维护的,说这里的原因是更好帮助大家理解为什么用#define定义行列,在写代码时行列相关数据都用含row,col的表达式来写,利于代码后期的更新处理。

         好了,写到这里,我们就可以简单的打印出棋盘,接下来就是下棋过程的写作。增加game函数内容:

void game()
{
	//创建棋盘
	char borad[ROW][COL] = {0};
	//初始化棋盘
	Initializeboard(borad, ROW, COL);
	//打印棋盘
	Dispiayboard(borad, ROW, COL);
	while (1)
	{
		//玩家下棋,根据返回值制造结果
		int ret1 = Playermove(borad, ROW, COL);
        //返回值为0说明胜利
		if (ret1 == 0)
		{
            //打印棋盘
			Dispiayboard(borad, ROW, COL);
			printf("\n");
			Win();
			printf("\n\n\n");
			break;
		}
        //返回值为2表示平局
		else if (ret1 == 2)
		{
            //打印棋盘
			Dispiayboard(borad, ROW, COL);
			printf("\n");
			Draw();
			printf("\n\n\n");
			break;
		}
		//电脑下棋,根据返回值制造结果
		int ret2 = Computermove(borad, ROW, COL);
		//打印结果
		Dispiayboard(borad, ROW, COL);
		if (ret2 == 0)
		{
			printf("\n");
			Defeat();
			printf("\n\n\n");
			break;
		}
		else if (ret2 == 2)
		{
			printf("\n");
			Draw();
			printf("\n\n\n");
			break;
		}
	}
}

        分别实现:

//玩家下棋
int Playermove(char borad[ROW][COL], int row, int col)
{
	int x, y, z;
    //利用do while循环和状态值z实现坐标不合法时重新输入
	do
	{	
		z = 1;
		printf("请输入要下的坐标:\n");
		scanf("%d%d", &x, &y);
        //验证当前是否被占用或者超出范围
		if (borad[x-1][y-1] == ' ')
		{
            //坐标可用改变状态值z以实现跳出循环
			z = 0;
		}
		else
		{
			printf("坐标不合法(被占用或者不在范围),请重新输入!\n");
		}
	} while (z);
    //将玩家下棋位置标记为“*”
	borad[x-1][y-1] = '*';
	//返回输赢结果
	return Iswin(borad, '*', x-1, y-1);
}
//电脑下棋
int ret2 = Computermove(borad, ROW, COL);
//打印结果
Dispiayboard(borad, ROW, COL);
if (ret2 == 0)
{
	printf("\n");
	Defeat();
	printf("\n\n\n");
	break;
}
else if (ret2 == 2)
{
	printf("\n");
	Draw();
	printf("\n\n\n");
	break;
}

        相对而言,胜利条件的判定比较难写,考察一个人的逻辑思维,代码中我写了详细的注释,判断胜利代码如下:

//判断输赢,根据返回值,0为胜利(玩家代码调用玩家胜利,电脑代码调用电脑的胜利,此函数只用于判定是否n字连珠,后面在game函数打印输赢),2为平局,1继续游戏
int Iswin(char borad[ROW][COL], char type, int x, int y)
{
	//横向判定“---”
	int temp1 = 1;
	temp1 += Cleft(borad, type, x, y);
	temp1 += Cright(borad, type, x, y);
	//竖向判定“|”
	int temp2 = 1;
	temp2 += Cover(borad, type, x, y);
	temp2 += Cdown(borad, type, x, y);
	//左斜判定“/”
	int temp3 = 1;
	temp3 += Cldial(borad, type, x, y);
	temp3 += Cldiar(borad, type, x, y);
	//右斜判定“\”
	int temp4 = 1;
	temp4 += Crdial(borad, type, x, y);
	temp4 += Crdiar(borad, type, x, y);
	if ((temp1==NUM) || (temp2==NUM) || (temp3==NUM) || (temp4==NUM))
	{
		return 0;
	}
	else
	{
        //判断平局函数
		return Isfull(borad, ROW, COL);
	}
}

        其实三子棋的判断条件相当好写,完全可以直接用if写,虽然情况比较多,但是易于理解,这里为了程序的后期更改方便(比如改为5字棋),用了递归查同累计的方法判定输赢,例如下代码:

//下面用了八个递归查同,即向左递归查与此坐标“符号”相同的位置的个数

//向左查同
int Cleft(char borad[ROW][COL], char type, int x, int y)
{
	int count = 0;
	if((y-1 >= 0) && (borad[x][y-1] == type))
	{
		count ++;
		count += Cleft(borad, type, x, y-1);
	}
	return count;
}

//向右查同
int Cright(char borad[ROW][COL], char type, int x, int y)
{
	int count = 0;
	if((y+1 < COL) && (borad[x][y+1] == type))
	{
		count ++;
		count += Cright(borad, type, x, y+1);
	}
	return count;
}

//向上查同
int Cover(char borad[ROW][COL], char type, int x, int y)
{
	int count = 0;
	if((x-1 >= 0) && (borad[x-1][y] == type))
	{
		count ++;
		count += Cover(borad, type, x-1, y);
	}
	return count;
}

//向下查同
int Cdown(char borad[ROW][COL], char type, int x, int y)
{
	int count = 0;
	if((x+1 < ROW) && (borad[x+1][y] == type))
	{
		count ++;
		count += Cdown(borad, type, x+1, y);
	}
	return count;
}

//向左斜左查同
int Cldial(char borad[ROW][COL], char type, int x, int y)
{
	int count = 0;
	if((x+1 < ROW) && (y-1 >= 0) && (borad[x+1][y-1] == type))
	{
		count ++;
		count += Cldial(borad, type, x+1, y-1);
	}
	return count;
}

//向左斜右查同
int Cldiar(char borad[ROW][COL], char type, int x, int y)
{
	int count = 0;
	if((x-1 >= 0) && (y+1 < COL) && (borad[x-1][y+1] == type))
	{
		count ++;
		count += Cldiar(borad, type, x-1, y+1);
	}
	return count;
}

//向右斜左查同
int Crdial(char borad[ROW][COL], char type, int x, int y)
{
	int count = 0;
	if((x-1 >= 0) && (y-1 >= 0) && (borad[x-1][y-1] == type))
	{
		count ++;
		count += Crdial(borad, type, x-1, y-1);
	}
	return count;
}

//向右斜右查同
int Crdiar(char borad[ROW][COL], char type, int x, int y)
{
	int count = 0;
	if((x+1 < ROW) && (y+1 < COL) && (borad[x+1][y+1] == type))
	{
		count ++;
		count += Crdiar(borad, type, x+1, y+1);
	}
	return count;
}

//平局判定,遍历棋盘发现没有空位置且之前并没有判定输赢
int Isfull(char borad[ROW][COL], int row, int col)
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			if (borad[i][j] == ' ')
			{
				return 1;
			}
		}
	}
	return 2;
}

        那么到这里这个三子棋小游戏就做好了,效果图:

 

        当然啦,这个机器人并没有算法支持它下棋,用的时随机生成坐标,是个“人工智障” ,小伙伴自己玩可以把平局当成目标,还是有所难度滴。好啦,这期内容就到这里啦,想把三子棋升级的话只需要更改下图的三个值,行row,列col,n子连珠n的值:

        比如改成9 9 5 就是9行9列的5子棋。另写一篇文章给大家完整代码。如果觉得这篇文章对你有帮助欢迎点赞转发评论区交流,关注小白阿g,让小白不再白学,亲爱的小伙伴们下期见。

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值