C语言小游戏之三子棋(井字棋)(1.5w字超详细讲解)

news2025/8/3 6:41:21

hello,csdn的伙伴们,大家好,我们已经学习到了分支与循环,函数,数组这三大块知识,那么我们现在就可以尝试综合运用前面所学的知识,来完成一个简单的小游戏-----三子棋(井字棋)


目录

 一、采用多文件,分模块来实现

二、建立游戏基本框架

1.为什么是使用do while语句?

2.打印出菜单

 3.实现通过选择菜单,跳转至相应界面

4.测试前面的逻辑

 三、具体游戏的逻辑实现

1.在我们的case 1下加上我们的游戏函数且定义和声明好我们的函数

 2.思考游戏的棋盘是什么样子的,该如何绘制?

 3.数据如何存储,游戏该如何去玩?

4.设计部分游戏功能函数

 5.初始化棋盘模块的实现(init_board)

 6.打印棋盘模块的实现(print_board)

(1)一些需要注意的思考

(2)尝试实现功能

(3)更进一步优化

7.截止至目前已经实现功能,所写的代码展示

8. 玩家下棋(player_move)

(1)玩家下棋函数的定义和声明

 (2)实现玩家下棋

(3)测试玩家下棋模块

(4)经典的错误标准的零分

(5)玩家下棋的代码

9.电脑下棋(computer_move)

(1)电脑下棋函数的声明和定义

(2)电脑下棋的具体实现

(3)电脑下棋的代码测试

(4)电脑下棋的代码展示

10.判断输赢

(1)判断输赢的声明和定义以及判断输赢的基本逻辑

 (2)具体函数的实现

  (3)代码测试

(4)本模块代码

四、最终代码展示(分文件)

1.test.c文件

2.game.h文件

3.game.c文件

五、代码展示(三个文件合成为一个文件)

总结


 一、采用多文件,分模块来实现

我们在之前讲到过,在我们以后完成一个项目时候,我们使用的是多文件,分模块的进行完成。那么今天我们也采用这个方法

我们创立三个文件:

test.c   //测试的逻辑

game.h

game.c  //游戏的实现

二、建立游戏基本框架

1.为什么是使用do while语句?

我们想要完成一个三子棋游戏,那么我们使用一个简单的do while 逻辑

 在这段代码中,我们为了使我们的代码逻辑清晰,我们直接使用一个test函数,test里面的函数执行我们游戏的逻辑,而在test函数里面,我们注意到,我们使用了一个do while 循环,这是考虑到,我们完成一个游戏肯定不可能只玩了一把就自动退出去了,我们希望玩完一把后可以由我们自己来选择是否进行下一把或者结束游戏。所以我们必须使用一个循环,但是我

们又必需得先玩一把在考虑是否玩下去,所以这就是使用do while语句的原因了。

2.打印出菜单

是一个游戏肯定要有他的界面,所以我们do while进入以后直接开始打印菜单。不妨我们就将菜单设为meau函数吧,如下图所示,这块不难理解

 3.实现通过选择菜单,跳转至相应界面

 如上图所示,我们通过一个input,来记录我们输入的值,并通过switch语句来跳转至相应的功能。需要解释的就是while里面为什么是input就可以了?其实这句话是一个简写,我们的全写应该是input!=0,当他不等于0的时候,我们可以继续循环,如果等于0,循环结束。而这个的真值表恰好与input本身的真值表相同,因为只有0为假,非零都为真。所以这里使用input就是很合理的。

4.测试前面的逻辑

我们写代码一定要写一点测一点。而不是一股脑写到最后,代码几百个bug无从下手。

下面是截止至目前我们目前所写的代码,这些代码全部是属于test.c这个文件里面的。

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

运行结果为

 可见我们所预想的逻辑是全部满足了。这样我们才可以放心的往下继续写下去。

 三、具体游戏的逻辑实现

1.在我们的case 1下加上我们的游戏函数且定义和声明好我们的函数

我们想要实现的是按一个1,就能开始玩游戏,而不是就单单弹出一个进入三子棋游戏然后就没有下文了。我们在这块封装一共函数game,这个函数的返回类型是void,不需要传参,如下所示

 2.思考游戏的棋盘是什么样子的,该如何绘制?

相信大家小时候玩过三子棋或者说井字棋小游戏。这个游戏的棋盘是一个#字,如下图所示

 当然这是我们平时所画的棋盘。那么在我们c语言中我们如何制作一共形状和功能都差不多的棋盘呢。我们是这样思考的,如下图所示,三个-和一些|刚好可以组成一些正方形的格子。

 3.数据如何存储,游戏该如何去玩?

根据上面的棋盘,我们其实不难得出,这个数据需要一个3*3的数组来存储。这些数据的类型我们可以使用一个char来实现,玩家打印*字符,电脑打印#字符,而我们最开始其实那些似乎没有棋子的地方,我们可以看成一个空格字符。所以最开始我们的数组都是空格字符

在玩游戏的时候,我们可以选择输入一个坐标,这是玩家下棋,然后电脑下棋。这样循环下去。直到有人赢了或者平局。

4.设计部分游戏功能函数

根据上面的分析可以得到,我们手下需要定义一个char类型的数组,这个数组的类型是3*3,并且我们需要对其初始化,这个初始化的我们可以封装成一个函数,init_board,这个函数不难想象要三个参数,分别是数组名,行和列。然后初始化完后我们可以写一个打印棋盘的函数。如下图所示

 不过值得注意的是,又很多人在传这个数组的时候,传了一个board[3][3]过去,注意这是一个经典的错误标准的零分,这个传进去的是第三行第三列的元素,而且这个元素甚至还越界了。这就错上加错了。我们应该传一个数组名过去,数组名也就是首元素的地址。如下图所示就是错误的

 当然我们这样设计这些函数,我们其实就把代码写死了。 未来我们想要打印一个5行5列的数组就修改比较麻烦。所以我们可以这样做,使用一个define,如下图所示,我们在game.h中将ROW,和COL都设置为3。然后我们在test.c中就可以引入game.h这个头文件。将game这个函数里面的3都可以改为行和列了。这样未来我们在想进行一些修改的时候就很简单了。

当然上图中有一处我们刚刚提到过的经典的错误标准的零分,不知道你有没有发现呢?答案就是我们此处数组传参仍然是错的,我们只需要传数组名,这下相信大家都记住了吧。

 5.初始化棋盘模块的实现(init_board)

 我们在game.h中声明函数,在game.c中实现

game.h如下所示

代码为

void init_board(char board[3][3], int row, int col);

 game.c如下所示,注意不要忘记game.h这个头文件,因为我们声明是放在了game.h中

 代码实现为

void init_board(char board[3][3], 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] = ' ';
		}
	}
}

 6.打印棋盘模块的实现(print_board)

(1)一些需要注意的思考

我们初始化完后,我们就想要打印出来检验一下我们初始化后的结果,所以我们现在来实现打印棋盘

game.h如下所示

 代码为

void print_board(char board[3][3], int row, int col);

game.c如下所示(注意,在此处,可能有小伙伴们发现头文件里面多了个stdio.h的头文件,这样的话,game.c中虽然使用了printf但是就不需要重复添加这个头文件了,因为我们直接引用了game.h这个头文件。而且我们还可以将test.c这个头文件给删除掉了)

和上文所说的一样,写一点测一点。我们运行测试一下,我们发现,没有棋盘。所以说,我们这个打印棋盘的代码是不完整的,我们需要完善一下。

 我们是这样思考这个打印的函数的。我们将下图看成三组逻辑,打印一行空格行和一个分割行是一组逻辑,最后一行也是一个分割行,只不过这个不用打印。

(2)尝试实现功能

那么代码实现如下所示

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

运行结果如下所示

可是这样的话,我们会发现,这样其实就被写死了,以后想要修改就很难修改了。比如我们将行和列都改为十的话。就会发现和我们所设想的棋盘大大不符合

(3)更进一步优化

 所以我们要改。要优化代码,那么怎么优化呢?其实我们可以在我们上面的代码上进行修改,我们上面的代码是由两次打印,我们将两次打印都进行展开。具体到每一列上操作。第一次打印就把一个数据和一个|字符当成一组逻辑,第二次打印就把---|当成一组逻辑。当然每一组逻辑最后一次是不需要打印|的。所以根据这些分析,我们已经可以推导出代码了

void print_board(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");
		}
	}
}

 运行结果为

可见符合了我们的预期。

7.截止至目前已经实现功能,所写的代码展示

作为一个新手,刚开始写这种小游戏的时候,常常读着读着就已经晕了。这是因为新手暂时还没有一种全局的目光。所以为了方便读者理解,我们将截止至目前已经实现的代码在此处进行一下展示方便读者更好的阅读下去。

test.c模块

#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"

void meau()
{
	printf("********************************\n");
	printf("********     1.play      *******\n");
	printf("********     0.exit      *******\n");
	printf("********************************\n");
}
void game()
{
	char board[ROW][COL];
	init_board(board, ROW, COL);
	print_board(board, ROW, COL);
}
void test()
{
	int input = 0;
	do
	{
		meau();
		printf("请输入>:\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("进入三子棋游戏\n");
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
	} while (input);
}
int main()
{
	test();
	return 0;
}

game.c模块

#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"

void init_board(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] = ' ';
		}
	}
}
void print_board(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");
		}
	}
}

game.h模块

#pragma once
#include<stdio.h>

#define ROW 3
#define COL 3

//头文件声明函数

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

//打印棋盘
void print_board(char board[ROW][COL], int row, int col);

8. 玩家下棋(player_move)

有了棋盘,自然就需要下棋了。

(1)玩家下棋函数的定义和声明

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

 

 (2)实现玩家下棋

我们要让玩家下棋,那么我们不妨就让玩家通过输入坐标来进行下棋,如下图所示,这样的话,就可以使玩家输入一个坐标。

 

有了坐标,我们首先要通过这个坐标来判断是否在我们棋盘的范围内,也就是是否合法,而且要注意的是,玩家可不是程序员,他们不知道这个数组的下标是从0开始的,所以我们要在玩家输入的坐标上-1,而且我们这样应该是一个循环,坐标非法时候需要重新输入坐标,当我成功下棋以后,我们可以在里面放入一个break语句来打破循环。

 

坐标合法,但是如果这个位置之前有人下过棋了呢,这种情况也是不允许的,所以我们要在坐标合法里面加入一条判断,这个位置是一个空格字符,不能有棋子。

 (提示:此处存在一处错误,这是我们之前所讲过的经典的错误标准的零分,细心的你发现了吗,没有发现的话,那就接着往下看吧!后文会讲解的)

(3)测试玩家下棋模块

我们试着测试一下,在test.c种加入打印棋盘,方便我们查看

运行结果为

输入成功案例

 

坐标非法案例

 

此处位置已经被下了棋案例

这部分我们可以使用两次玩家下棋来完成我们先将test.c在进行一次玩家输入和打印,这是因为我们目前这是第一次下棋,只有在第二次下棋才会出现这种情况

 我们试着下棋

我们发现居然不符合我们的预期了!!!,这是怎么回事呢,其实这个错误细心的同学肯定在前文中已经发现了。

(4)经典的错误标准的零分

这个错误有太多人犯了,所以在此处故意设下了一个坑,当然也是提醒大家,细心的重要性。当然这个错误看似简单,但是在这个一个很简单的项目里可不简单,即便我在前文中提醒过此处有一个经典错误标准零分,但还是有人找不到,那如果将整个游戏写完之后在想找到这个错误是很困难的。这么一个小错误对于这个游戏而言是很致命的。而这个错误很难发现,所以这就再次提醒大家,一定要细心,细心。写一部分测一部分。否则未来在企业中,那种超大规模的代码想要调试出来是极其困难的!!!

 

修改后再次测试

这下就是符合我们的预期了

 

(5)玩家下棋的代码

void player_move(char board[ROW][COL], int row, int col)
{
	int x = 0; int y = 0;
	printf("玩家下棋\n");
	while (1)
	{
		printf("请输入坐标>:\n");
		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("该位置已经有棋子了,请重新下棋");
			}
		}
		else
		{
			printf("坐标非法,请重新输入\n");
		}
	}

}

9.电脑下棋(computer_move)

(1)电脑下棋函数的声明和定义

我们发现我们使用了一个while循环,这样是为了实现电脑和人交互下棋。

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

 

 

(2)电脑下棋的具体实现

电脑下棋是如何实现的呢,我们可以制作一个简单版本的电脑下棋。也就是让电脑随机下。当然想让电脑智能下棋的话对于目前的阶段还是比较困难的。我们目前只完成电脑随机下。想要让电脑随机下你,那么就必须得要让电脑产生一个随机的坐标,这个坐标不能非法,也必须在一个没有下过棋子的位置才可以,这样一思考,其实就简单了。这可比我们的人下棋要简单一点。唯一一点需要注意的就是如何生成随机坐标。其实随机坐标的生成也不难理解,我们之前在讲解猜数字小游戏的时候已经讲解过了。这里给出链接:猜数字小游戏详解,有了随机坐标。那么电脑下棋的步骤也就很简单了。

具体实现如下

 当然有些人觉得这个电脑下棋的代码有一些没有必要的东西,比如x和y不需要+1,如果这样做,还能少了一层判断。因为他是电脑内部操作的。不需要玩家来认识,这样做当然是可以的,但是我们这样写形式上与玩家下棋一致。更容易理解。

(3)电脑下棋的代码测试

还是和之前一样,写一点测一点,满足我们的预期。

 

(4)电脑下棋的代码展示

void computer_move(char board[ROW][COL], int row, int col)
{
	int x = 0;
	int y = 0;
	printf("电脑下棋\n");
	while (1)
	{
		x = rand() % row + 1;
		y = rand() % col + 1;
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (board[x - 1][y - 1] == ' ')
			{
				board[x - 1][y - 1] = '#';
				break;
			}
		}
	}
}

10.判断输赢

我们已经实现了玩家和电脑的下棋,那么游戏肯定是有输赢的,我们接下来来实现游戏的输赢

(1)判断输赢的声明和定义以及判断输赢的基本逻辑

我们思考一下游戏一共有多少种状态呢?

答案是四种,分别为,电脑赢,玩家赢,平局,游戏继续

我们将电脑赢记作#

玩家赢记作*

平局记作 Q

游戏继续记作C

这样一来的话,我们就可以构建出我们的函数了,吧目前的数组和行列传给他,他生成一个返回值。然后根据这个返回值就能判断出游戏的状态,从而得出结论了

函数的定义和声明如下

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

 输赢的基本逻辑实现如下

 我们直接给出目前test.c文件中的所有代码

#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"

void meau()
{
	printf("********************************\n");
	printf("********     1.play      *******\n");
	printf("********     0.exit      *******\n");
	printf("********************************\n");
}
void game()
{
	char board[ROW][COL];
	//初始化棋盘
	init_board(board, ROW, COL);
	//打印棋盘
	print_board(board, ROW, COL);
	//玩家下棋
	char ret = ' ';
	//ret用来接受一个返回值,用来判断输赢
	while (1)
	{
		player_move(board, ROW, COL);
		print_board(board, ROW, COL);
		ret = is_win(board, ROW, COL);
		//判断游戏是否继续
		if (ret != "C")
		{
			break;//如果不继续,打破循环
		}
		computer_move(board, ROW, COL);
		print_board(board, ROW, COL);
		//判断游戏是否继续
		ret = is_win(board, ROW, COL);
		if (ret != "C")
		{
			break;//如果不继续,打破循环
		}
	}
	if (ret == '*')
	{
		printf("玩家赢\n");
	}
	else if (ret == '#')
	{
		printf("电脑赢\n");
	}
	else if (ret == 'Q')
	{
		printf("平局\n");
	}
}
void test()
{
	srand((unsigned int)time(NULL));
	int input = 0;
	do
	{
		meau();
		printf("请输入>:\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("进入三子棋游戏\n");
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
	} while (input);
}
int main()
{
	test();
	return 0;
}

 (2)具体函数的实现

我们先来实现一下谁赢了的场景,如下图所示,一共有四种判断方式,这种方式其实是比较写死了。不适合进行推广到N子棋。这个后续是可以进行优化的。但是我们今天先不做优化,我们今天的目标就只是三子棋。不过我们后续会专门出一篇文章,来将三子棋的各大功能进行全方位的升级。今天我们只讨论基础版本的三子棋。

 判断玩输赢,那么还需要平局和游戏继续两种状态,平局其实不难理解,就是整个棋盘都满了,还没有决出胜负。所以我们是需要将判断平局放在决出胜负之后的。判断棋盘是否满,我们可以封装一共函数。is_full,这个函数判断是否满。如果满返回1,否则返回0。

具体代码实现如下,这个代码没必要写在头文件声明,因为这个代码其实只需要被判断输赢函数使用即可

 然后is_win剩余的逻辑为

  (3)代码测试

玩家赢

 电脑赢

 

和棋

这块当时好不容易下成了和棋,结果网卡了没传上........不过好歹还有没上传成功时候的正在处理的截图,还是可以使用的。

 

(4)本模块代码

//判断棋盘是否满了
int is_full(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 is_win(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][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[0][0] != ' ')
	{
		return board[0][0];
	}
	//判断副对角线
	if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[0][2] != ' ')
	{
		return board[0][2];
	}
	if (is_full(board,row,col) == 1)
	{
		return 'Q';
	}
	return 'C';
}

四、最终代码展示(分文件)

我们代码是三个文件的

1.test.c文件

#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"

void meau()
{
	printf("********************************\n");
	printf("********     1.play      *******\n");
	printf("********     0.exit      *******\n");
	printf("********************************\n");
}
void game()
{
	char board[ROW][COL];
	//初始化棋盘
	init_board(board, ROW, COL);
	//打印棋盘
	print_board(board, ROW, COL);
	//玩家下棋
	char ret = ' ';
	//ret用来接受一个返回值,用来判断输赢
	while (1)
	{
		player_move(board, ROW, COL);
		print_board(board, ROW, COL);
		ret = is_win(board, ROW, COL);
		//判断游戏是否继续
		if (ret != 'C')
		{
			break;//如果不继续,打破循环
		}
		computer_move(board, ROW, COL);
		print_board(board, ROW, COL);
		//判断游戏是否继续
		ret = is_win(board, ROW, COL);
		if (ret != 'C')
		{
			break;//如果不继续,打破循环
		}
	}
	if (ret == '*')
	{
		printf("玩家赢\n");
	}
	else if (ret == '#')
	{
		printf("电脑赢\n");
	}
	else if (ret == 'Q')
	{
		printf("平局\n");
	}
}
void test()
{
	srand((unsigned int)time(NULL));
	int input = 0;
	do
	{
		meau();
		printf("请输入>:\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("进入三子棋游戏\n");
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
	} while (input);
}
int main()
{
	test();
	return 0;
}

2.game.h文件

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROW 3
#define COL 3

//头文件声明函数

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

//打印棋盘
void print_board(char board[ROW][COL], int row, int col);

//玩家下棋
void player_move(char board[ROW][COL], int row, int col);

//电脑下棋
void computer_move(char board[ROW][COL], int row, int col);

//判断输赢
char is_win(char board[ROW][COL], int row, int col);

3.game.c文件

#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"

void meau()
{
	printf("********************************\n");
	printf("********     1.play      *******\n");
	printf("********     0.exit      *******\n");
	printf("********************************\n");
}
void game()
{
	char board[ROW][COL];
	//初始化棋盘
	init_board(board, ROW, COL);
	//打印棋盘
	print_board(board, ROW, COL);
	//玩家下棋
	char ret = ' ';
	//ret用来接受一个返回值,用来判断输赢
	while (1)
	{
		player_move(board, ROW, COL);
		print_board(board, ROW, COL);
		ret = is_win(board, ROW, COL);
		//判断游戏是否继续
		if (ret != 'C')
		{
			break;//如果不继续,打破循环
		}
		computer_move(board, ROW, COL);
		print_board(board, ROW, COL);
		//判断游戏是否继续
		ret = is_win(board, ROW, COL);
		if (ret != 'C')
		{
			break;//如果不继续,打破循环
		}
	}
	if (ret == '*')
	{
		printf("玩家赢\n");
	}
	else if (ret == '#')
	{
		printf("电脑赢\n");
	}
	else if (ret == 'Q')
	{
		printf("平局\n");
	}
}
void test()
{
	srand((unsigned int)time(NULL));
	int input = 0;
	do
	{
		meau();
		printf("请输入>:\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("进入三子棋游戏\n");
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
	} while (input);
}
int main()
{
	test();
	return 0;
}

五、代码展示(三个文件合成为一个文件)

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROW 3
#define COL 3

//头文件声明函数

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

//打印棋盘
void print_board(char board[ROW][COL], int row, int col);

//玩家下棋
void player_move(char board[ROW][COL], int row, int col);

//电脑下棋
void computer_move(char board[ROW][COL], int row, int col);

//判断输赢
char is_win(char board[ROW][COL], int row, int col);
void meau()
{
	printf("********************************\n");
	printf("********     1.play      *******\n");
	printf("********     0.exit      *******\n");
	printf("********************************\n");
}
void game()
{
	char board[ROW][COL];
	//初始化棋盘
	init_board(board, ROW, COL);
	//打印棋盘
	print_board(board, ROW, COL);
	//玩家下棋
	char ret = ' ';
	//ret用来接受一个返回值,用来判断输赢
	while (1)
	{
		player_move(board, ROW, COL);
		print_board(board, ROW, COL);
		ret = is_win(board, ROW, COL);
		//判断游戏是否继续
		if (ret != 'C')
		{
			break;//如果不继续,打破循环
		}
		computer_move(board, ROW, COL);
		print_board(board, ROW, COL);
		//判断游戏是否继续
		ret = is_win(board, ROW, COL);
		if (ret != 'C')
		{
			break;//如果不继续,打破循环
		}
	}
	if (ret == '*')
	{
		printf("玩家赢\n");
	}
	else if (ret == '#')
	{
		printf("电脑赢\n");
	}
	else if (ret == 'Q')
	{
		printf("平局\n");
	}
}
void test()
{
	srand((unsigned int)time(NULL));
	int input = 0;
	do
	{
		meau();
		printf("请输入>:\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("进入三子棋游戏\n");
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
	} while (input);
}
int main()
{
	test();
	return 0;
}
//初始化棋盘
void init_board(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] = ' ';
		}
	}
}
//打印棋盘
void print_board(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");
		}
	}
}
//玩家下棋
void player_move(char board[ROW][COL], int row, int col)
{
	int x = 0; int y = 0;
	printf("玩家下棋\n");
	while (1)
	{
		printf("请输入坐标>:\n");
		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("该位置已经有棋子了,请重新下棋");
			}
		}
		else
		{
			printf("坐标非法,请重新输入\n");
		}
	}

}

//电脑下棋
void computer_move(char board[ROW][COL], int row, int col)
{
	int x = 0;
	int y = 0;
	printf("电脑下棋\n");
	while (1)
	{
		x = rand() % row + 1;
		y = rand() % col + 1;
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (board[x - 1][y - 1] == ' ')
			{
				board[x - 1][y - 1] = '#';
				break;
			}
		}
	}
}
//判断棋盘是否满了
int is_full(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 is_win(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][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[0][0] != ' ')
	{
		return board[0][0];
	}
	//判断副对角线
	if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[0][2] != ' ')
	{
		return board[0][2];
	}
	if (is_full(board, row, col) == 1)
	{
		return 'Q';
	}
	return 'C';
}


总结

本节主要讲解了三子棋的详细实现,为大家熟练的打通了各种思考逻辑,为什么是这样子,常见的错误。同时最终也给出了三子棋的最终代码,分文件和合并为一个文件都给出了具体的代码。如果对你有帮助,不要忘记点赞加收藏哦!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/15937.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Allegro DFM Ravel Rule工具使用指导书

Allegro DFM Ravel Rule工具使用指导书 Allegro任何一个版本都支持DFM Ravel Rule检查,即便是166的版本 打开后的界面如下所示 可以检查项目 测试点,阻焊,走线,丝印,过孔,milling,装配,outline相关的DFM检查 可以让违反规则的设计处以DRC的形式报出来 避免加工问题 …

基于数字孪生打造智慧园区运营平台,助力园区数字化转型

在各行各业数字化转型的浪潮中&#xff0c;园区也在经历数字化转型发展&#xff0c;从传统园区向智慧园区不断演进。传统园区缺乏系统性规划&#xff0c;基于单点功能建设&#xff0c;存在系统孤立、管理粗放且服务不足等问题&#xff0c;难以满足人们日益增长的多样化需求。在…

第四章. Pandas进阶—数据合并

第四章. Pandas进阶 4.6 数据合并 数据合并主要使用的是Merge方法和Concat方法 1.数据合并(merge函数) 1).语法&#xff1a; pandas.merge(right,how‘inner’, on“None”, left_on“None”, right_on“None”, left_indexFalse, right_indexFalse... )参数说明: right&…

甘露糖-聚乙二醇-马来酰亚胺 mannose-PEG-MAL 马来酰亚胺-PEG-甘露糖

甘露糖-聚乙二醇-马来酰亚胺 mannose-PEG-MAL 马来酰亚胺-PEG-甘露糖&#xff0c;溶于大部分有机溶剂&#xff0c;如&#xff1a;DCM、DMF、DMSO、THF等等。在水中有很好的溶解性 中文名称&#xff1a;甘露糖-马来酰亚胺 英文名称&#xff1a;mannose-MAL 别称&#xff1a;…

【LeetCode 每日一题】53. 最大子数组和

01 题目描述 给你一个整数数组 nums &#xff0c;请你找出一个具有最大和的连续子数组&#xff08;子数组最少包含一个元素&#xff09;&#xff0c;返回其最大和。 子数组 是数组中的一个连续部分。 02 示例 示例1&#xff1a; 输入&#xff1a;nums [-2,1,-3,4,-1,2,1,-5…

从 React 源码彻底搞懂 Ref 的全部 api

ref 是 React 里常用的特性&#xff0c;我们会用它来拿到 dom 的引用。 它一般是这么用的&#xff1a; 函数组件里用 useRef&#xff1a; import React, { useRef, useEffect } from "react";export default function App() {const inputRef useRef();useEffect(…

几乎涵盖了近半年90%的Java面试题,可以肝起来了

前言 很多人在问&#xff1a;八股文还有必要背吗&#xff1f; 近半年来大家听到的、用到的不少&#xff0c;带来的争议也不断。 有人奉为面试神器&#xff0c;全文背诵。有人觉得八股文铺天盖地实际作用不大&#xff0c;还害人不浅… 我觉得不是背不背八股文的问题&#xff0c…

【机器学习并行计算】2 parameter server参数服务器

使用ps实现异步梯度下降。 14年提出的。 异步 vs 同步 可以看出异步运行效率非常高。 异步梯度下降的流程 ps架构流程 worker&#xff1a; 首先从参数服务器拉取最新的参数&#xff1b;然后用自己节点上的数据计算梯度&#xff1b;最后把梯度推给参数服务器参数服务器&#xf…

最快速的文件传输软件,解析镭速文件传输软件

想到每天都需要进行文件传输&#xff0c;就会烦躁&#xff0c;要是有一夸最快速的文件传输软件的话&#xff0c;这样就可以节省大量的时间了&#xff0c;那么针对于用户的这一个需求&#xff0c;我们来介绍一下镭速的文件传输软件&#xff0c;看是否是那么快&#xff0c;快到你…

无监督端到端框架:IVIF

VIF-Net: An Unsupervised Framework for Infrared and Visible Image Fusion &#xff08; VIF-Net: 红外和可见光图像融合的无监督框架&#xff09; &#xff08;本文理解上的难易程度&#xff1a;易&#xff09; 在本文中&#xff0c;我们提出了一种用于红外和可见图像融合…

css 动画实现节流效果

今天在做节流操作时&#xff0c;无意间看到可以用css动画去实现节流效果&#xff0c;然后一顿操作发现果然可以&#xff0c;记录一下 CSS pointer-events 属性 一、 用css中的pointer-events&#xff08;指针事件&#xff09;、animation&#xff08;动画&#xff09;以及:act…

WordPress做缓存Memcached Is Your Friend+Batcache

宝塔面板有两个地方有Memcached,一个是在软件商店的运行环境里面,一个是在php扩展里面,我们先安装PHP扩展中的Memcached ,然后wp后台搜索Memcached Is Your Friend安装插件。WordPress做缓存很给力。缓存命中率保持在 90%以上的WordPress 本地缓存加速方案。 默认我们看到命…

E-Payment Integrator Delphi Edition

E-Payment Integrator Delphi Edition 通过为组件提供处理信用卡和电子支票交易的直观界面&#xff0c;减轻了集成电子支付支持的复杂性。开发人员无需学习复杂的套接字编程或安全实现。通过使用电子支付集成器&#xff0c;开发人员能够针对当前支持的任何支付网关进行定位和开…

比较复杂的策略路由综合实验

下面是网络技能大赛策略这个模块的要求&#xff0c;单独拿出来整理一下 R1、R2、R3间运行OSPF&#xff0c;进程号20&#xff0c;规划单区域&#xff1a;区域0&#xff1b; VSU、R2、R3间运行OSPF&#xff0c;进程号21&#xff0c;规划单区域&#xff1a;区域0&#xff1b; …

JavaScript之事件高级(53rd)

1、注册事件(绑定事件) 给元素添加事件&#xff0c;称为注册事件或者绑定事件。 注册事件有两种方式&#xff1a;传统方式和方法监听注册方式 1、addEventListener事件监听方式 1、eventTarget.addEventListener()方法将指定的监听器注册到 eventTarget&#xff08;目标对…

springMVC异常处理的知识点+异常处理案例

springMVC异常处理的知识点异常处理案例 异常介绍&#xff1a; 我们知道系统中的异常包括两类&#xff1a;预期异常和运行时异常RuntimeException,前者通过捕获异常从而获取异常信息&#xff0c;后者主要通过规范代码开发、测试等手段减少运行时异常的发生 在SpringMVC处理异…

有关服务器虚拟化的常见问题解答

虚拟化”一词经常使用&#xff0c;尤其是与服务器相关的时候。以下是一些有关服务器虚拟化常见问题的解答。 什么是服务器虚拟化? 虚拟化是一个经常应用于范围广泛的技术的术语。从本质上讲&#xff0c;虚拟化技术就是使用分布式软件硬件。在服务器虚拟化领域&#xff0c;这意…

Android

直接运行 最新版的 apktool 可以通过brew安装&#xff0c;命令如下 brew install apktool # 验证安装结果apktool -version (Mac)反编译Android APK详细操作指南[ApkTool,dex2jar,JD-GUI] - CrazyCodeBoy的技术博客官网|CrazyCodeBoy|Devio|专注移动技术开发(Android&I…

【计算机网络】广域网协议分析

注&#xff1a;最后有面试挑战&#xff0c;看看自己掌握了吗 文章目录广域网WAN广域网局域网区别PPP协议----链路层------广域网PPP协议组成3个ppp状态图PPP协议帧格式HDLC协议--------链路层------广域网帧格式------无监信PPP协议和HDLC&#x1f343;博主昵称&#xff1a;一拳…

通用Mapper获取数据表中id为0解决方法。千万别瞎改int为integer了

项目场景&#xff1a; 最近准备自己写一个框架。由于是舍弃了成熟框架&#xff0c;所以在集成一些组件的时候&#xff0c;发现了一些之前没有注意过的问题。 这次是集成通用mapper时出现的一个问题。。。 问题描述 使用通用Mapper的selectAll()方法后&#xff0c;得到的id值都…