【C初阶】三子棋


要求

分块编写程序实现三子棋的基本功能。
(将程序中会用到的自定义函数和头文件单独用.h和.c文件独立写出来。)


一、三子棋的功能分块

  1. 游戏菜单的显示;
  2. 棋盘的显示(涉及到棋盘的初始化);
  3. 玩家下棋对棋盘的改变;
  4. 电脑下棋的棋盘的改变;
  5. 判断游戏输赢,决定下一步操作;

二、编程思路

1.菜单显示函数

显示菜单,让玩家进行选择进行游戏还是直接退出。代码如下:

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

2.棋盘初始化

将棋盘初始化为全为空格。代码如下:

void	init_board(char a[ROW][COW], int row, int cow) {
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < cow; j++)
		{
			a[i][j] = ' ';
		}
	}
}

3.打印棋盘

将已经存进数组中的字符打印出来,显示为相应的棋盘。代码如下:

void print_board(char a[ROW][COW], int row, int cow) {
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < cow; j++)
		{
			if (j<cow-1)
			{
				printf(" %c |", a[i][j]);
			}
			else
			{
				printf(" %c \n", a[i][j]);
			}
		}
		if (i<row-1)
		{
			for (int j = 0; j < cow-1; j++)
			{
				printf("----");
			}
			printf("---\n");
			//棋盘换行间隔
		}
	}
}

4.玩家下棋

让玩家输入要落子的坐标,判定落子的坐标能否落子,若能落子,对数组进行修改,然后显示棋盘。玩家落子部分代码如下:

void player_move(char a[ROW][COW], int row, int cow) {
	printf("输入你要落子的坐标:\n");
	int r = 0, c = 0;
	while (1)
	{
		scanf("%d %d", &r, &c);
		if (r > row || r < 1 || c>cow || c < 1)
		{
			printf("输入坐标超出棋盘范围,请重新输入!\n");
		}
		else if (a[r - 1][c - 1] != ' ')
		{
			printf("该坐标已落子,请重新输入!\n");
		}
		else
		{
			a[r - 1][c - 1] = '*';
			break;
		}
	}
}

5.电脑下棋

通过生成随机数来确定电脑要落子的坐标,判断生成坐标能否落子,不能落子则重新生成随机数再重复判定,直至能够成功落子。
需要注意的是这里生成随机数又需要用到rand函数,属于stlib.h头文件的库函数,在使用前需要调用srand函数,其中srand函数需要输入的是unsigned int 类型的变量,可引入上节提到的time函数来引入时间戳,从而使生成的随机数能一直变化。
代码如下:

void computer_move(char a[ROW][COW], int row, int cow) {
	int r = 0, c = 0;
	while (1)
	{
		r = rand() % row;
		c = rand() % cow;
		if (a[r][c] == ' ')
		{
			a[r][c] = '#';
			printf("电脑落子:\n");
			break;
		}
	}
}

6.输赢判定

个人认为这个程序最麻烦的就是这个输赢判定的函数部分的编写,整体思路不难,但是循环一个接一个,对我这个初学者来说比较麻烦。
如果极限于三子棋本身而言,判定逻辑很简单,就是整行整列或者对角线元素相同即可获得胜利。那么我们在写判定条件的时候可以用穷举法,对三行或者三列进行循环,循环内部穷举每一行的三个元素同时等于同一个元素,即可判断胜利。然后对角线部分单独写一个判断条件。将可能的正反两条对角线的具体坐标给穷举出来然后判断就可以了。
考虑到前边编程的时候并不是锁死了就是三行三列的棋盘来写的程序,而是用的COW、ROW来表示,这样就可以自己来确定棋盘的大小。但是如果用cow、row来进行判定输赢的话,想再使用上面说的穷举法就很不现实了。故此需要用到循环体来遍历进行判定。此处代码较为麻烦。(可能是自己的判定算法有点太笨了~~~)
设判定输赢的函数返回不同的字符代表不同的决定。玩家赢、电脑赢、平局以及继续四种可能。

//判断输赢
//若返回值为*,玩家赢;
// 返回值为#,电脑赢;
// 返回值为Q,平局;
//返回值为C,游戏继续
char is_win(char a[ROW][COW], int row, int cow) {
	int row_player_flag = 0;
	int row_computer_flag = 0;
	int cow_player_flag = 0;
	int cow_computer_flag = 0;
	int diag_player_flag = 0;
	int diag_computer_flag = 0;
	int undiag_player_flag = 0;
	int undiag_computer_flag = 0;
	for (int i = 0; i < row; i++)			
	{
		row_player_flag = 0;
		row_computer_flag = 0;
		for (int j = 0; j < cow; j++)
		{
			if (a[i][j]!='*')
			{
				row_player_flag = 1;
				break;
			}
		}
		for (int j = 0; j < cow; j++)
		{
			if (a[i][j] != '#')
			{
				row_computer_flag = 1;
				break;
			}
		}
		if (!row_player_flag)
			return '*';
		if (!row_computer_flag)
			return '#';
	}
//判断每行

	for (int j = 0; j < cow; j++)
	{
		cow_player_flag = 0;
		cow_computer_flag = 0;
		for (int i = 0; i < row; i++)
		{
			if (a[i][j] != '*')
			{
				cow_player_flag = 1;
				break;
			}
		}
		for (int i = 0; i < row; i++)
		{
			if (a[i][j] != '#')
			{
				cow_computer_flag = 1;
				break;
			}
		}
		if (!cow_player_flag)
			return '*';
		if (!cow_computer_flag)
			return '#';
	}
	//判断每列
	//判断对角线
	for (int i = 0; i < row; i++)
	{
		//判断正对角线
		if (a[i][i] != '*')
		{
			diag_player_flag = 1;
		}
		if (a[i][i] != '#')
		{
			diag_computer_flag = 1;
		}
		//判断反对角线
		if (a[i][cow-i-1] != '*')
		{
			undiag_player_flag = 1;
		}
		if (a[i][cow-i-1] != '#')
		{
			undiag_computer_flag = 1;
		}
	}
	if (!diag_player_flag)
		return '*';
	if (!diag_computer_flag)
		return '#';
	if (!undiag_player_flag)
		return '*';
	if (!undiag_computer_flag)
		return '#';
	//判断棋盘是否满了
	if (!full(a, row, cow))
	{
		return 'Q';
	}
	return 'C';
}

在这当中有用到一个full函数来进行判定棋盘是否满了,遍历整个数组是否还有空格,其代码如下:

static int full(char a[ROW][COW], int row, int cow) {
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < cow; j++)
		{
			if (a[i][j] == ' ')
				return 1;
		}
	}
	return 0;
}

7.主函数

把上述几个功能函数分块完成,主函数就简单了。

int main() {
	srand((unsigned int)time(NULL));
	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 (1);
	return 0;
}

其中的game函数便是整个下棋最关键的部分,就是要将上诉各个分块函数进行整合。

void game() {
	char flag = 0;
	init_board(board, ROW, COW);//初始化棋盘
	print_board(board, ROW, COW);//打印棋盘
	do
	{
		player_move(board, ROW, COW);//玩家落子
		print_board(board, ROW, COW);//打印棋盘
		flag = is_win(board, ROW, COW);
		if (flag == '*')
		{
			printf("玩家赢了\n");
			break;

		}
		if (flag == '#')
		{
			printf("电脑赢了\n");
			break;

		}
		if (flag == 'Q')
		{
			printf("平局\n");
			break;

		}
		computer_move(board, ROW, COW);
		print_board(board, ROW, COW);
		flag = is_win(board, ROW, COW);
		if (flag=='*')
		{
			printf("玩家赢了\n");
			break;
		}
		if (flag == '#')
		{
			printf("电脑赢了\n");
			break;
		}
		if (flag == 'Q')
		{
			printf("平局\n");
			break;
		}
	} while (flag=='C');
}

到此,所有代码结束,程序能够正常运行。并且能够自定义棋盘的大小,从而实现N子棋的功能。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值