【C初阶】-三子棋

今天我们将运用前面所学的知识向大家介绍一个应用实例:三子棋。
相信大家都玩过五子棋小游戏,三子棋和五子棋的游戏原理是相同的,只要一方玩家在横、竖和斜直线上将三颗棋子连在一起,那么就会获得游戏的胜利。
接下来我们开始介绍C语言的实现方法。

首先,我想先简单的向大家展示三子棋程序一次成功的运行过程。
如下图:
在这里插入图片描述
我们现在可以简单的先分析一下这个游戏。
游戏刚开始给了我们一个菜单,让我们进行选择,输入1开始游戏,输入0结束游戏。接下来我们开始游戏,然后系统给了我们一个3*3的棋盘,并提示玩家先走,下棋的步骤是输入你所想要下位置的坐标,紧接着电脑下,然后当玩家胜利的时候,就会提示玩家赢,同时结束这局游戏。这局游戏结束后会紧跟着再次打印出菜单,让你选择是不是要继续一局游戏。
好了,整个游戏的基本逻辑我们已经大致了解了,接下来我就带领大家逐步实现这个游戏的所有代码。

我们应该在一个复杂的工程中,我们首先要做的就是完成它的主函数部分。
我们发现当程序开始运行后,就会直接打印一个菜单给我们,这说明了这个程序必须要执行一次,所以我们可以采用do while循环来设置主程序,让程序只要运行起来就会打印菜单供玩家选择。

代码如下:

#define _CRT_SECURE_NO_WARNINGS
int main()
{
	int input = 0;
	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;
}

主函数的代码逻辑非常清楚,先打印菜单,然后根据你所选择的数字判断接,如果选择1,我们直接进入游戏模块(这个后面会详细分析),选择0则退出游戏。关于退出游戏的原理,我们应该注意,这个do while循环的判断语句就是输入的数input,如果选择0就会结束本次循环,这个程序也就走完了,也就相当于我们退出游戏了。而当你输入1游戏结束后,do while循环并没有结束,还会再次执行。所以说一局游戏结束这个程序并不会终止,而只有当你主动输入0退出游戏的时候,这个程序才会结束。

主函数的部分我们先分析到这里,接下来按照顺序,我们要现在屏幕上打印一个菜单供玩家选择。这个打印菜单的步骤我们可以在一个menu函数中实现,其实前面所学的猜数字游戏中我们也曾介绍过类似写法。
menu函数的代码如下:

void menu()
{
	printf("******************************\n");
	printf("*******   1. play      *******\n");
	printf("*******   0. exit      *******\n");
	printf("******************************\n");
}

由于menu函数只是实现打印功能不需要返回值,所以我们函数类型我们定义为void。
这里需要交给大家一点小经验,当我们写这种复杂代码的时候不要一口气把代码写完,然后才运行。这样如果我们在写的过程中万一哪一步出bug,调试起来会非常困难。所以建议大家写一小步之后就运行一下看看结果是否正确
这里我们运行的时候记得先将主函数的game函数删掉,因为此时我们还没有定义,所以编译起来会出错。
我们来看运行结果:
在这里插入图片描述
可以看到,我们已成功的将菜单打印了出来,完成了第一步。

接下来玩家输入0和其他值前面我们已经提到过了,当玩家输入1的时候,也就是要开始进行游戏了。我们将整个游戏的所有逻辑写在game函数中。
大家应该明白,这是一个游戏程序,所以这段代码的主要精华全部集中在这个game函数中,接下来我们就要好好的来研究这个game函数。

好接下来游戏开始,我们看到他首先会打印一个33的棋盘给我们,所以第一步我们应该先实现一个33棋盘的打印。我们发现当我们没有给棋盘上写东西的时候,棋盘上什么都没有,那么真的是什么都没有吗,大家肯定能想到那些地方只是暂时打上了空格而已,所以我们应该先把这块3*3的棋盘初始化为空格。我们将这个初始化过程写在InitBoard函数中。
这里要想大家讲一下,由于game函数是游戏的核心思想,我们会用大量的函数来实现它,所以不建议将游戏函数的代码写在主函数所在的源文件中,这样不方便调用。假如我们以后想要单独的把这个游戏拿出来,那不是要删掉好多东西。所以我建议大家重新创建一个源文件,将游戏模块的所有函数写在里面,然后在创建一个头文件game.h.来声明这些函数。而game函数直接调用game.h这个库函数即可,这样既简洁明了,又能方便调用。

接下来我们来谈这个InitBoard函数
初始化一个33棋盘,真的很简单,我们只需要定义一个3行3列的二维数组,然后给他的值全部赋上空格即可。那么我们是不是应该这样定义这个数组char board[3][3]= { 0 };这种写法我们来实现这个33的棋盘当然可以实现,但是大家有没想到,这样写我们就会把这个代码给写死了,如果你以后想实现一个4*4的棋盘,那你岂不是要把这个game函数每处的行列值都进行更改,这种做法显然有点挫。所以建议大家在我们的game.h头文件中设置两个全局变量,分别赋给行和列的值,这样以后我们更改程序的时候只需改变全局变量的值即可
接下来我们来看InitBoard函数的代码:

void InitBoard(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++)
		{
			board[i][j] = ' ';
		}
	}
}

这个初始化的逻辑非常简单,这里我就不再多提了,大家只需注意传给函数的参数分别是数组的首地址,数组的行和列。

棋盘初始化完之后我们就要来实现这个棋盘了,这里再给大家看一下这个棋盘的样子,如下图:
在这里插入图片描述
我们已经知道这个棋盘是由一个3*3数组中九个元素组成的,那么我们拿出来一个元素来分析即可。
我们可以看到,每个元素由上下两个部分组成。上面是’空格’‘符号’‘空格’’|‘组成,而最后一列没有’|’,下面部分由’—‘和’|‘组成,而最后一行没有’—’,最后一列没有’|’
我们可以设置一个DisplayBoard函数来实现,实现代码如下:

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]);
   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");
 }
}

这里需注意,数组里存放的’ %c ',其它字符都是在数组外打印的。
写到这里我们就可以将棋盘打印出来了。

棋盘打印出来之后接下来就需要在棋盘上下棋了,通过刚才的演示我们可以看到玩家对输入要下的坐标,然后就会在棋盘上打印出来。接下来电脑也会输入在棋盘上打印。所以我们需要写两个函数来实现玩家和电脑下棋的逻辑。

先看玩家的函数,我们用一个PlayerMove函数来实现。
其实逻辑很简单,我们根据玩家输入的坐标和数组下标联系,然后在该位置填入字符。这里我们需要注意的是,玩家输入的起始坐标是1,而数组下标的起始值是0,所以我们在运算的时候应该给玩家的坐标减1.
同时我们也要判断玩家输入坐标的合法性,我们知道玩家输入坐标的范围是1-3,如果超出这个范围,我们就应该提示玩家重新输入。或者玩家输入的坐标处已经被下过了,我们也应该提示玩家重新输入。
代码如下:

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);
		//如果坐标范围合法
		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");
		}
	}
}

接下来我们来看电脑的函数,我们用一个ComputerMove函数来实现。
电脑需要输入一个随机数的坐标,这里我们用rand函数来获得这两个随机数。我们知道这两个数的范围是
0-2,所以我们给所得到的数模3后,它的范围就是0-2.同时我们需要srand函数来设置rand函数的起点,把起点放在一个时间戳中,这些我们在猜数字游戏中都曾经讲到过。同时我们也要判断电脑所生成数字的合法性,如果那个位置已经有人下过,那么就直接重新获得一个随机数。
代码如下:

void ComputerMove(char board[ROW][COL], int row, int col)
{
	printf("电脑走:>\n");
	while (1)
	{
		int x = rand() % row;//0-2
		int y = rand() % col;//0-2
		if (board[x][y] == ' ')
		{
			board[x][y] = '#';
			break;
		}
	}
}

写到这里我们已经成功的在棋盘上下棋了,到这里大家也都明白我们只剩下最后一步了,那就是判断输赢。同样判断输赢的函数我们用 CheckWin函数来实现。我们可以简单的来分析一下这个函数所要实现的功能:
判断的时候我们应该是玩家和电脑每下完一步我们都要检查游戏是否结束。检查的结果无非是以下几种,
①游戏还未结束②玩家赢③电脑赢④平局。所以这个函数最终需要给game函数一个返回结果,然后game函数通过判断返回结果来判断这局游戏的输赢。所以这个函数需要定义返回值。我们定义的返回值如下:
① 玩家赢 - ‘*’
② 电脑赢 - ‘#’
③ 平局了 - ‘Q’
④ 继续 - ‘C’
我们先看game函数中的代码:

void game()
{
	char ret = 0;
	//设计一下三子棋游戏
	//存储数据的
	char board[ROW][COL] = { 0 };//数组应该初始化为空格
	InitBoard(board, ROW, COL);//初始化棋盘 - 空格
	//打印棋盘
	DisplayBoard(board, ROW, COL);
	//分析以下发现:
	//在游戏玩的过程中
	//1. 玩家赢 - '*'
	//2. 电脑赢 - '#'
	//3. 平局了 - 'Q'
	//4. 继续   - 'C'
	while (1)
	{
		PlayerMove(board, ROW, COL);
		//判断输赢
		ret = CheckWin(board, ROW, COL);
		if (ret != 'C')
		{
			break;
		}
		DisplayBoard(board, ROW, COL);
		ComputerMove(board, ROW, COL);
		//判断输赢
		ret = CheckWin(board, ROW, COL);
		if (ret != 'C')
		{
			break;
		}
		DisplayBoard(board, ROW, COL);
	}
	if (ret == '*')
	{
		printf("玩家赢\n");
	}
	else if (ret == '#')
	{
		printf("电脑赢\n");
	}
	else if (ret == 'Q')
	{
		printf("平局\n");
	}
	DisplayBoard(board, ROW, COL);
}

接下来我们再来分析CheckWin函数
CheckWin函数函数要实现的功能就是检查当前的情况来返回相应的值。我们来一一分析一下。
怎样才能获取胜利呢,其实无非也就以下三种情况。
①同一行的三个元素相等
②同一列的三个元素相等
③对角线的三个元素相等
我们分别可以用相应的逻辑来实现,如果判断成功,则说明有一方取得了胜利,我们只需返回检测位置的元素就可以在game函数中判断谁输谁赢了。
接下来我们要判断平局的情况,平局的情况只有一种可能,那就是棋盘上的点都下满后还未决出胜负,所以我们只需判断棋盘上是否已经下满。这个逻辑我们可以用一个IsFull函数来实现。

IsFull函数的功能很简单,只需判断数组的每个元素是否都不等于空格,如果是的话,返回1,然后就可以在CheckWin函数中检测IsFull函数的返回值来判断是否平局。
而这个函数我们仅仅只是想要在CheckWin函数中使用,不需要在其他地方使用,所以我们可以用static来修饰这个函数,让这个函数只在当前的源文件中有效。当然,你也可以不修饰。
代码如下:

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;//没空格
}

判断完平局之后,如果CheckWin函数还没有结束那就只剩下游戏继续这一种可能了。所以我们可以在以上两种可能判断完的后面再加一条返回语句,告诉game函数输赢和平局都未判断出来,游戏需要继续运行。
代码如下:

char CheckWin(char board[ROW][COL], int row, int col)
{
	int i = 0;
	//行是否有3个相等
	for (i = 0; i < row; i++)
	{
		if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][0] != ' ')
		{
			return board[i][0];
		}
	}
	//列的判断
	for (i = 0; i < col; i++)
	{
		if (board[0][i] == board[1][i] && board[1][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[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 (IsFull(board, row, col) == 1)
	{
		return 'Q';
	}
	//不是平局,游戏继续
	return 'C';
}

以上就是三子棋的所有逻辑。接下来把完整的代码留给大家。

test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu()
{
	printf("******************************\n");
	printf("*******   1. play      *******\n");
	printf("*******   0. exit      *******\n");
	printf("******************************\n");
}

void game()

{
	char ret = 0;
	//设计一下三子棋游戏
	//存储数据的
	char board[ROW][COL] = { 0 };//数组应该初始化为空格
	InitBoard(board, ROW, COL);//初始化棋盘 - 空格
	//打印棋盘
	DisplayBoard(board, ROW, COL);
	//分析以下发现:
	//在游戏玩的过程中
	//1. 玩家赢 - '*'
	//2. 电脑赢 - '#'
	//3. 平局了 - 'Q'
	//4. 继续   - 'C'
	while (1)
	{
		PlayerMove(board, ROW, COL);
		//判断输赢
		ret = CheckWin(board, ROW, COL);
		if (ret != 'C')
		{
			break;
		}
		DisplayBoard(board, ROW, COL);
		ComputerMove(board, ROW, COL);
		//判断输赢
		ret = CheckWin(board, ROW, COL);
		if (ret != 'C')
		{
			break;
		}

		DisplayBoard(board, ROW, COL);
	}
	if (ret == '*')
	{
		printf("玩家赢\n");
	}
	else if (ret == '#')
	{
		printf("电脑赢\n");
	}
	else if (ret == 'Q')
	{
		printf("平局\n");
	}
	DisplayBoard(board, ROW, COL);
}

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;
}

game.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "game.h"

void InitBoard(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++)
		{
			board[i][j] = ' ';
		}
	}
}

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]);
			if (j\n");
	while (1)
	{
		printf("请输入坐标:>");
		scanf("%d%d", &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");
		}
	}
}

void ComputerMove(char board[ROW][COL], int row, int col)
{
	printf("电脑走:>\n");
	while (1)
	{
		int x = rand() % row;//0-2
		int y = rand() % col;//0-2
		if (board[x][y] == ' ')
		{
			board[x][y] = '#';
			break;
		}
	}
}

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;//没空格
}

char CheckWin(char board[ROW][COL], int row, int col)
{
	int i = 0;
	//行是否有3个相等
	for (i = 0; i < row; i++)
	{
		if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][0] != ' ')
		{
			return board[i][0];
		}
	}
	//列的判断
	for (i = 0; i < col; i++)
	{
		if (board[0][i] == board[1][i] && board[1][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[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 (IsFull(board, row, col) == 1)
	{
		return 'Q';
	}
	//不是平局,游戏继续
	return 'C';
}

game.h

#define _CRT_SECURE_NO_WARNINGS 1
#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 CheckWin(char board[ROW][COL], int row, int col);
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值