关于三子棋的实现(小白式写法,步骤超级详细)

代码的分工

首先,在未来我们完成一个项目的时候,肯定不止我们一个人完成,一定是多人分工完成的,因此我们的代码最好不要写在一个c语言文件当中,分开写更美观也可以更直观的对比和查看代码的实现。
在这里插入图片描述就像这样,创建1.c文件用于主函数的实现,也就是main函数写在这里面,然后创建一个game.c文件,用于游戏代码的具体实现,最后再创建一个头文件,用来引用#include<stdio.h>这种头文件,然后再分别再game.c和1.c中引用自己创建的test.h这个头文件即可。(创建自己的头文件(test.h)的作用就是当多个.c文件需要引用相同的头文件时,方便多个.c文件引用头文件,也就是.c文件引用一个你自己创建的头文件就可以了:#include"test.h",显得引用头文件不会这么繁琐)

例如:
这是我们自己创建的头文件,里面放了我们这个项目工程需要用的所有。

1.创建菜单

在玩家进行游戏的时候,需要创建一个菜单,供玩家选择,所以我们需要先创建一个简易的菜单,有加入游戏和退出游戏,然后选择错误时可以重新选择这几种要求即可。

创建菜单的函数:
一个非常简易的菜单在这里插入图片描述

然后我们要实现的是玩了游戏不够,可以继续玩,选择错误可以重新选择。因此需要将游戏的进入或者退出放在一个do while循环之中,这样可以保证代码不用判断,优先执行一次,然后再判断选择的是退出游戏还是进入游戏,这里需要注意的是,要将进入游戏设置为1,退出游戏设置为0;然后再将这个1或者0放在do while循环的判断条件处,即可实现选0时退出游戏,并且循环终止,不再提供下一次选择,选择1时,进入游戏,当游戏完成一次时,因为判断条件是1,循环继续,可以继续选择是否进入游戏,这样就实现了游戏的循环进行。

int main()
{
	//打印菜单
	menu();//打印菜单的函数实现在上面一个图,很简单的一个代码,所以只放了一个图在这。
	int input = 0;
	srand((unsigned int)time(NULL));**//这个先不用管,跟着步骤走,后面会讲**
	do
	{
		printf("请选择:\n");
		scanf("%d", &input);
		switch (input)//用switch方便也更直观地体现选择
		{
		case 1:
			game();//进入游戏
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误,重新选择\n");
			break;
		}
	} while (input);

}

2.总体步骤

1.初始化棋盘为空格
2.打印棋盘(打印完整的棋盘)
3.玩家下棋(下完之后打印一遍棋盘,并判断输赢)
4.电脑下棋(下完之后打印一遍棋盘,并判断输赢)

3.创建菜单

三子棋的棋盘很重要,我们需要把棋盘初始化完成,至少要一个符合逻辑的三子棋棋盘。
在文章的开头我们就说过,以后一个项目的实现肯定不会是在一个.c文件里实现,一定是多个文件共同实现,我们可以在我们自己创建的头文件中包含这个项目需要使用的头文件,实现三子棋本质上是将一些字符存进一个二维数组里面,因此,我们需要创建一个3行3列的二维数组,很多读者肯定认为直接创建一个arr[3][3]的数组就可以了,我们的目光不妨放长远一点,万一以后我们需要把棋盘变大怎么办?我们可以在test.h这个头文件里用#define定义两个标识符常量,当我们以后需要棋盘变大时,直接更改这两个标识符常量的值即可。
在这里插入图片描述

4.然后我们开始初始化棋盘为空格

首先我创建的游戏运行流程是在一个名为game的函数里面,在这个函数里面我们要初始化棋盘,当然肯定不会直接在这个游戏运行流程game函数里面把棋盘初始化,而是需要在外部重新创建一个函数用来初始化棋盘。我们开头就说过,函数的具体实现把他写在game.c这个文件当中,因此我们要在这个文件中写出初始化棋盘的具体实现。

首先棋盘的元素是一个3行3列的二维数组,所以在game()函数内要创建一个3行3列的二维数组用来存放三子棋的元素

void game()
{
	char board[ROW][COL] = { 0 };
	Initboard(board, ROW, COL);//传参,二维数组,和ROW=3,COL=3传过去,为什么等于3,
	//因为我们在test.h头文件里用#define定义过
}

要想实现很简单,就是把一个三行三列的二位数组内的元素全部初始化为空格。用两个for循环嵌套即可实现,就和平常的操作一样,只不过里面放的是‘ ’。

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] = ' ';
		}
	}
}

5.打印棋盘

上一步我们已经把二维数组里的元素全部初始化为空格了,下一步就是要打印一个完整的棋盘了
例如:在这里插入图片描述

就像这个棋盘一样,那我们要怎么实现呢?其实很简单,不难看到棋盘是由横线竖线和二维数组的元素组成的,要打印棋盘肯定还要包括棋盘内的元素,也就是说二维数组的所有元素都要在这个函数里打印一遍,然后再打印我们的棋盘。打印棋盘是和打印二维数组元素一起的,所以我们需要把棋盘的打印放在二维数组元素打印的for循环之中,用它的哪一行哪一列来判断是否需要打印横线或者竖线
具体来看代码:

void Displayboard(char board[ROW][COL], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++)//**创建一个for循环来打印二维数组的元素**
	{
		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)//同理,只需要打印第一行与第二行之间和第二行与第三行之间的横线,看我的棋盘可知。
		{
			int j = 0;
			for (j = 0; j < col; j++)
			{
				printf("---");
				if (j < col - 1)//与上第一行元素打印竖线分割同理
				{
					printf("|");
				}
			}
			printf("\n");//打印完换行
		}
	}
}

6.玩家下棋(玩家下*)

玩家下棋的本质就是给我们创建的二维数组赋值,那么用我们平时给二维数组赋值的代码方式即可,但是值得注意的是这里我们需要判断玩家下的位置是否在棋盘内用一个if else语句即可判断,然后下子位置正确的时候又要判断下子的位置是否有之前下过的棋子,因此在里面又要嵌套一个if else判断语句。
还有一个比较重要的点就是,程序员知道数组的下标是从0开始的,但是玩家不知道,所以我们在后台处理代码时需要把玩家的落子位置减1。

void Playgamers(char board[ROW][COL], int row, int col)
{
	int x = 0;
	int y = 0;
	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");
		}
	}
}

在以上步骤完成以后,我们需要打印一次棋盘,来给玩家看看刚才落子的结果:
在这里插入图片描述
玩家下完一次棋,我们需要判断一次是否胜利,也就是行,列或对角线有没有胜利:这里玩家胜利返回,电脑胜利返回#,暂时没有胜负游戏继续返回C,游戏平局返回Q*
在这里插入图片描述
判断是否胜利在后面,电脑下棋之后也需要再次判断一次,这里我们只在这里说一次代码的具体实现,后面便不再重复叙述了。

1.首先我们先判断行是否有相等的且不为空格的三个元素,这里判断是否是三个空格的元素很重要,因为如果没有加上,那么可能会导致刚刚开始下棋就判赢或者输了。判断行,列我们可以固定为1列2列3列,然后行做一个循环,循环三行,也就是分别判断三行。这里需要注意的是,因为玩家下的是*,所以在判断玩家胜利之后会返回一个*,也就代表了玩家胜利。直接return 这一行其中一个数组元素即可。

char Victory(char board[ROW][COL], int row, int col)
{
	//行
	int i = 0;
	for (i = 0; i < row; i++)//列固定1列2列3列,行用循环是为了分别判断1行2行3行
	{
		if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][0] != ' ')
		{
			return board[i][1];
		}
	}
}

2.其次,我们判断列胜利,与行胜利类似,列胜利就是一列中有三个一样的元素并且元素不等于空格
(‘ ’),需要固定行为1行2行3行,然后列弄一个循环,分别判断1列2列3列。(这个和行写在同一个函数里面)

	//列
	for (i = 0; i < col; i++)
	{
		if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][i] != ' ')
		{
			return board[1][i];
		}
	}

3.之后,在判断对角线胜利,与之前的也类似,不过这里我们直接就可以写出对角线的坐标,因为对角线只有两组数,所以我们可以直接写出它的数组判断,就不用写循环了。判断对角线的元素相等且不等于空格(‘ ’)。

	//对角线
	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];
	}

4.最后,对弈肯定会出现平局的情况,平局的情况无非就是棋盘下满了,但是还没有分出胜负,所以我们用逆向思维判断棋盘是否有空格,有空格就返回C继续下棋,如果是平局就返回Q代表平局,之后在
game()函数里判断返回的是什么,然后打印出谁胜谁负或者平局即可。

	if (IsFull(board, row, col) == 1)//这里调用了一个判断平局的函数,分开写这样看着代码没有这么繁琐
	{
		return 'Q';
	}
	else
	{
		return 'C';
	}
}

这里我们调用一个判断平局的函数:这里具体为什么返回0和1可以看上面这个代码↑,返回1则是平局,返回0则没有平局,游戏继续返回C。

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;//出现空格则返回0
			}
		}
	}
	return 1;//没有出现则返回1.
}

7.电脑下棋(电脑下#)

在电脑下棋之前,我们需要先判断返回的是什么字符,也就是要判断输赢:如果返回的是C,则游戏继续,如果返回的是其他符号,则退出循环,去看返回的是什么字符判断谁胜谁负。

在这里插入图片描述
这是输出谁胜谁负的代码,写在game()后面即可。

	if (ret == '*')
	{
		printf("玩家赢!\n");
	}
	else if (ret == '#')
	{
		printf("电脑赢!\n");
	}
	else
	{
		printf("平局!\n");
	}
}

然后就到了电脑下棋,电脑下棋讲究一个随机性,我们要想在C语言中体现这种随机性,就需要时间戳这个应用。具体你们可以在csdn里搜索使用方法,简单来说rand()是取随机数的东西,然后srand是使用rand的前提,也就是想用rand就要有srand这个启动器,,所以两个都要包括,但是就像启动电脑一样,我们不会重复启动,所以srand这个启动器只用包含在main函数里,调用一次就行了。

在这里插入图片描述
和玩家下棋的实现比较类似,电脑下棋的方便之处就在于rand函数模上row取得的数值就不会超过row这个数,也就是说我们的row的值是3,是知道的,那么电脑落子的地方就不会超过这个值。只需要判断落子的位置是否是空格,也就是是否可以落子即可。

void computergamers(char board[ROW][COL], int row, int col)
{
	printf("电脑下棋:\n");
	int x = 0;
	int y = 0;
	while (1)
	{
		x = rand() % row;//固定了电脑的落子范围不会超过棋盘
		y = rand() % col;//固定了电脑的落子范围不会超过棋盘
		if (board[x][y] == ' ')//只需要判断落子的位置是否是空格即可
		{
			board[x][y] = '#';
			break;
		}
	}
}

电脑下棋之后和玩家下棋的步骤是类似的,接下来就是要打印一编棋盘,供玩家查看,然后判断输赢。这一整个游戏过程是一个循环的过程,我们需要把他放在一个循环体里面,玩家下完电脑下,电脑下完玩家下,直到分出胜负或者棋盘下满。

8.展示一下这个游戏的运行思路

void game()
{
	char board[ROW][COL] = { 0 };
	//初始化棋盘为空格
	Initboard(board, ROW, COL);
	//打印棋盘
	Displayboard(board, ROW, COL);
	char ret = 0;
	//开始下棋
	while (1)
	{
		//玩家下棋
		Playgamers(board, ROW, COL);
		//下完一次打印一次棋盘
		Displayboard(board, ROW, COL);
		//判断是否胜利
		ret = Victory(board, ROW, COL);
		//电脑下棋
		if (ret != 'C')
		{
			break;
		}
		computergamers(board, ROW, COL);
		//下完打印棋盘
		Displayboard(board, ROW, COL);
		//判断是否胜利
		Victory(board, ROW, COL);
		if (ret != 'C')
		{
			break;
		}
	}
	if (ret == '*')
	{
		printf("玩家赢!\n");
	}
	else if (ret == '#')
	{
		printf("电脑赢!\n");
	}
	else
	{
		printf("平局!\n");
	}
}

9.所有代码

main.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"test.h"
void menu()
{
	printf("********************************\n");
	printf("**************1.play************\n");
	printf("**************0.exit************\n");
	printf("********************************\n");


}	
void game()
{
	char board[ROW][COL] = { 0 };
	//初始化棋盘为空格
	Initboard(board, ROW, COL);
	//打印棋盘
	Displayboard(board, ROW, COL);
	char ret = 0;
	//开始下棋
	while (1)
	{
		//玩家下棋
		Playgamers(board, ROW, COL);
		//下完一次打印一次棋盘
		Displayboard(board, ROW, COL);
		//判断是否胜利
		ret = Victory(board, ROW, COL);
		//电脑下棋
		if (ret != 'C')
		{
			break;
		}
		computergamers(board, ROW, COL);
		//下完打印棋盘
		Displayboard(board, ROW, COL);
		//判断是否胜利
		ret=Victory(board, ROW, COL);
		if (ret != 'C')
		{
			break;
		}
	}
	if (ret == '*')
	{
		printf("玩家赢!\n");
	}
	else if (ret == '#')
	{
		printf("电脑赢!\n");
	}
	else
	{
		printf("平局!\n");
	}
}
int main()
{
	//打印菜单
	menu();
	int input = 0;
	srand((unsigned int)time(NULL));
	do
	{
		printf("请选择:\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误,重新选择\n");
			break;
		}
	} while (input);

}

game.c(函数的具体实现)

#define _CRT_SECURE_NO_WARNINGS 1
#include"test.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] = ' ';
		}
	}
}
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)
		{
			int j = 0;
			for (j = 0; j < col; j++)
			{
				printf("---");
				if (j < col - 1)
				{
					printf("|");
				}
			}
			printf("\n");
		}
	}
}
void Playgamers(char board[ROW][COL], int row, int col)
{
	int x = 0;
	int y = 0;
	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 computergamers(char board[ROW][COL], int row, int col)
{
	printf("电脑下棋:\n");
	int x = 0;
	int y = 0;
	while (1)
	{
		x = rand() % row;
		y = rand() % col;
		if (board[x][y] == ' ')
		{
			board[x][y] = '#';
			break;
		}
	}
}
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;
			}
		}
	}
	return 1;
}

char Victory(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][1];
		}
	}
	//列
	for (i = 0; i < col; i++)
	{
		if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][i] != ' ')
		{
			return board[1][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 (IsFull(board, row, col) == 1)
	{
		return 'Q';
	}
	else
	{
		return 'C';
	}
}


test.h(包含的所有头文件写在这一个头文件文件里面)

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>


#define ROW 3
#define COL 3

到这里我们的三子棋代码就写完了,总结一下,我这个代码优缺点,就是当你可以把棋盘变大,但是判断输赢那里就无法做到更新,还需要改进,优点就是代码模块分组清楚,便于我们查看。这个代码可能有些不足,欢迎各位在评论区讨论。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值