C语言实现三子棋(详解)

一. 主要思路

游戏的主要思路如下,一方面是我们整个的游戏思路,另一方面则是我们实现功能的游戏主体了。(写完博客才发现图上多了一道竖线,大家原谅我把==)
在这里插入图片描述

二.思路梳理

第一,同我们之前实现的扫雷一样,整个游戏里,棋盘的布置,用来布置棋盘的元素都是和我们后面息息相关的,一开始得先想好。

三子棋,棋盘是三行三列,但我们在判断输赢时会碰到元素越界的问题,仅仅三行三列够不够呢?

对于三子棋,甚至五子棋,再高一点,我们其实都可以用判断所有行所有列,对角线来判断输赢,越界问题也就不存在了。
在这里插入图片描述

第二, 我们用空格 ’ ’ 初始化棋盘是没问题的,但是下棋的元素会不会影响我们判断呢,上面说了可以用所有行列对角线来判断输赢,也就不需要字符相加减来计算,因此下棋的元素没有影响。

三.主体思路实现

在梳理完大概的问题后,就可以开始写代码了。最先的肯定是我们的大致结构了。如扫雷一般,函数声明以及一些常用变量放在一起,大致结构放在一个源文件里,游戏功能实现放在另一个源文件里。

大致结构就没什么好说的了,具体代码如下:

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

}

int main()
{
	int input = 0;
	printf("游戏开始\n");
	srand((unsigned int)time(NULL));
	
	do
	{
		menu();
		printf("请输入:>\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("游戏开始\n");
			//最开始可以先删除
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default :
			printf("输入错误\n");
			break;
		}

	} while (input);
	return 0;
}

四.初始化棋盘及布置

之前已经对棋盘初始化和布置的问题进行了说明,就不再赘述。这里布置包含了展示棋盘,毕竟布置完肯定得给玩家看,由于棋盘都是空格,所以可以加上一些横竖线对棋盘布置一下。 笔者定义了两个常量ROW,COL,都为3代表行列,*表示玩家下棋,#表示电脑下棋。具体代码如下:

void init_board(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 show_board(char board[ROW][COL], int row, int col)
{
	int i = 0;
	int j = 0;
	int n = 1;
	for (i = 0; i < row; i++)
	{
		if (i > 0)
		{
			for (n = 0; n < col; n++)
			{
				printf("----");
			}
			printf("\n");
		}		
		for (j = 0; j < col; j++)
		{
			printf("%c", board[i][j]);
			if (j < col)
			{
				printf(" | ");
			}
			
		}		
			printf("\n");								
	}
	printf("\n");
}

这里都没有什么要注意的地方,注意换行,对棋盘展示的结果不满意的也可以自己改改。

在这里插入图片描述

五.下棋

下棋分为两部分,一部分是玩家下棋,另一部分是电脑下棋,咱们默认玩家先下。

1 玩家下棋

玩家下棋就比较简单了,要注意是否越界,另一个就是坐标是不是已经被人下过了,具体代码如下:

void player1(char board[ROW][COL], int row, int col)
{
	printf("玩家回合请输入\n");
	int x = 0; 
	int y = 0;
	int flag = 1;
	
	while (flag)
	{
		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] = '*';
				flag = 0;
			}	

		}
		else
		{
			printf("坐标越界,请重新输入\n");
		}
	}
}

这里我们判断的思路其实是有点取巧的,只要是空格那就能下,也有玩家故意在已经下了的地方下棋,碰到这种情况上述代码运行起来是没有提示的,但这也属于极端情况,这里只是作此说明。另外,我们看到棋盘自然想到3行3列,输入也是3行3列,实际是0到2,这个要注意一下。

2. 电脑下棋

在描述电脑下棋之前,我想告诉大家,电脑或者说计算机在执行命令时是缺少我们人类的常识的,我们认为该理所当然的东西在计算机这就犯了难,所以对一些判断条件要写的严格一点!

电脑下棋,需要随机值,生成随机值的方法以前说了,不再赘述,重点在于随机值要放进循环里,不然很可能只生成一个随机值,且不满足条件,代码就会卡住。具体代码如下:

void player2(char board[ROW][COL], int row, int col)
{

	int flag = 1;
	printf("电脑回合\n");
	
	//注意电脑的判断范围和玩家有一点不一样,
	while (flag)
	{	
		//随机数的生成放在循环内,不然只生成了一个不满足条件的随机数就会卡在这
		int x = rand() % 3;
		int y = rand() % 3;

		//对于是否能落子的条件判断一定写成等于' '就好,
		if (x >= 0 && x <= row - 1 && y >= 0 && y <= col - 1)
		{
			if (board[x][y] == ' ')
			{
				board[x][y] = '#';
				flag = 0;
			}
		}
	}
}

电脑下棋的行列范围是0到2,要注意和玩家下棋的不同。

六.判断输赢

在判断输赢前我们先梳理一下思路。对于输赢,有三种结果,玩家赢,电脑赢,棋盘下满平局,那该先判断平局吗?

实际上会出现棋盘下满了但是有人获胜的可能,所以我们先判断输赢,再判断是否平局。

对于判断,首先是判断的方法,开头讲过,我们可以直接判断所有行和列及两条对角线。整个判断的流程就应该是玩家游戏,展示棋盘,接着判断,判断是否有人获胜,再判断是否和棋,电脑下棋也是这么判断。

按照这样的流程顺序走下去,那么我们每一方下棋都要判断两次,所以我们这里改变一下,在下棋的循环中只判断棋盘是否满了,如果满了,就跳出循环判断是否有人获胜,没人获胜再判断是否和棋,这样整个流程就简单一些。到此,整个游戏就完成了,源码如下:

game.h

#define  _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<time.h>
#include<stdlib.h>

#define ROW 3
#define COL 3

void init_board(char board[ROW][COL], int row, int col);
void show_board(char board[ROW][COL], int row, int col);
void player1(char board[ROW][COL], int row, int col);
void player2(char board[ROW][COL], int row, int col);
char is_win(char board[ROW][COL], int row, int col);

test.c

#define  _CRT_SECURE_NO_WARNINGS 1
#include"game1.h"

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

void game()
{
	char board[ROW][COL] = { 0 };
	char win = 0;
	init_board(board, ROW, COL);
	show_board(board, ROW, COL);

	while (1)
	{
		//玩家下棋
		player1(board, ROW, COL);
		show_board(board, ROW, COL);
		win = is_win(board, ROW, COL);
		if (win != 'N')				
		{	
			//不等于n说明游戏无法进行下去了,就判断输赢
			break;		
		}
		
		//电脑下棋
		player2(board, ROW, COL);
		show_board(board, ROW, COL);
		win = is_win(board, ROW, COL);
		if (win != 'N')
		{
			break;
		}	
	}
	if (win == '*')
	{
		printf("玩家胜利\n");
	}
	if (win == '#')
	{
		printf("电脑胜利\n");
	}
	if (win == 'Y')
	{
		printf("平局\n");
	}

}

int main()
{
	int input = 0;
	printf("游戏开始\n");
	srand((unsigned int)time(NULL));
	
	do
	{
		menu();
		printf("请输入:>\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("游戏开始\n");
			//最开始可以先删除
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default :
			printf("输入错误\n");
			break;
		}

	} while (input);
	return 0;
}

game.c

#define  _CRT_SECURE_NO_WARNINGS 1
#include"game1.h"

void init_board(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 show_board(char board[ROW][COL], int row, int col)
{
	int i = 0;
	int j = 0;
	int n = 1;
	for (i = 0; i < row; i++)
	{
		if (i > 0)
		{
			for (n = 0; n < col; n++)
			{
				printf("----");
			}
			printf("\n");
		}		
		for (j = 0; j < col; j++)
		{
			printf("%c", board[i][j]);
			if (j < col)
			{
				printf(" | ");
			}
			
		}		
			printf("\n");								
	}
	printf("\n");
}

void player1(char board[ROW][COL], int row, int col)
{
	printf("玩家回合请输入\n");
	int x = 0; 
	int y = 0;
	int flag = 1;
	
	while (flag)
	{
		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] = '*';
				flag = 0;
			}	

		}
		else
		{
			printf("坐标越界,请重新输入\n");
		}
	}
}

void player2(char board[ROW][COL], int row, int col)
{

	int flag = 1;
	printf("电脑回合\n");
	
	//注意电脑的判断范围和玩家有一点不一样,
	while (flag)
	{	
		//随机数的生成放在循环内,不然只生成了一个不满足条件的随机数就会卡在这
		int x = rand() % 3;
		int y = rand() % 3;

		//对于是否能落子的条件判断一定写成等于' '就好,
		if (x >= 0 && x <= row - 1 && y >= 0 && y <= col - 1)
		{
			if (board[x][y] == ' ')
			{
				board[x][y] = '#';
				flag = 0;
			}
		}
	}
}

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++)
	{	
		//判断条件写严格一点,不然会出现一些bug!!!
		if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][0] != ' ')
		{
				return board[i][0];
		}
		/*if (board[i][0] == 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][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] != ' ') ||
		(board[2][0] == board[1][1] && board[1][1] == board[0][2] && board[1][1] != ' '))
	{
		return board[1][1];
	}

	/*if ((board[0][0] == board[1][1]  == board[2][2] && board[1][1] != ' ') ||
		(board[2][0] == board[1][1]  == board[0][2] && board[1][1] != ' '))
	{
		return board[1][1];
	}*/

	int n = is_full(board, ROW, COL);
	if (n == 1)
	{
		//棋盘满了,返回y,没满返回n,不等于n说明游戏无法进行下去了,接着判断输赢
		return 'Y';
	}
	else
	{
		return 'N';
	}

}

七.代码优化

对于上述实现的三子棋,其实有很多可以再优化的地方。
第一, 玩家和电脑随先下棋的问题,实际上可以利用随机数来判断谁先下棋,也还算好实现。
第二,棋盘的扩展问题,因为我们定义的是全局变量,玩家如果有意愿的话,实际上也可以做到4子棋,5子棋等等,而我们后续的判断条件上仅仅是考虑了三子棋,没有考虑到ROW, COL的变化,涉及到这一点再来写判断条件就会比较困难了,各位有兴趣的可以试试。

最后还是要提一嘴,对于这样有计算机参与的游戏,与玩家不同,计算机进行游戏时代码的判断一定要非常严格,非常具体,不然就很容易出现一些因为语法的问题而发生的错误,正例如笔者在判断输赢is_win函数内写了两个判断条件,被注释掉的判断条件就是不太严谨的,容易因为语法出现bug。这也算是笔者最近写三子棋和扫雷最深刻的体会了(因为这个花了好多时间==)。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值