三子棋(C 语言)

目录

一、游戏设计的整体思路

(1)提供一个菜单让玩家选择人机对战、玩家对战或者退出游戏,且一局结束以后还可以继续选择直到退出。

(2)使用一个 3*3 二维数组来表示一个棋盘,存放双方的落子情况并进行显示。且显示时使用一些符号进行分隔让棋盘更加合理。

(3)初始棋盘应为空,然后提示先手方下棋,要对先手方落子位置进行判断(是否合法,是否该位置已经落子),最后先手方下棋完毕后应检查棋局状态(获胜、平局或继续);同理后手方下棋。

(4)若平局或者任意一方获胜则结束对局,显示结果。

本次代码使用多文件形式,一共有三个文件,一个头文件包含各种声明,一个 .c 文件进行测试,一个 .c 文件实现函数功能。

二、各个步骤的代码实现

1. 菜单及循环选择的实现

(1)主函数代码的实现

c 复制代码
// 头文件
#include <stdio.h>
#include "Tic_tac_toe.h"

int main()
{
	// 所需变量
	int select = 0;
	// 选择
	do
	{
		// 菜单
		menu();
		// 选择
		scanf("%d", &select);
		// 判断
		switch (select)
		{
		case 1 :  // 人机对战
			PVE();
			break;
		case 2 :
			PVP();  // 玩家对战
			break;
		case 0 :
			printf("游戏结束!\n");
			break;
		}
	} while (select);

	return 0;
}

(2)头文件中的声明

c 复制代码
// 菜单
void menu();

(3)函数实现

c 复制代码
// 菜单
void menu()
{
	printf("**************************************\n");
	printf("**********      1. PVE      **********\n");
	printf("**********      2. PVP      **********\n");
	printf("**********      0. exit     **********\n");
	printf("**************************************\n");
}

(4)运行效果如下

2. 棋盘的初始化和显示

棋盘的初始化和显示分别设计为两个函数 ------ InitBoard()、PrintBoard()。首先,需要在主函数创建棋盘,且大小使用符号常量,方便替换。然后把棋盘的信息传入人机对战和玩家对战函数中,并在头文件和实现文件中进行相应的声明和定义。

(1)主函数创建棋盘

c 复制代码
int main()
{
	// 所需变量
	int select = 0;
	char board[ROW][COL] = { 0 };  // 创建棋盘
	//...
}

(2)头文件进行相应声明

c 复制代码
// 初始化棋盘
void InitBoard(char board[][COL], int row, int col);

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

// 人机对战模式
void PVE(char board[][COL], int row, int col);

// 玩家对战模式
void PVP(char board[][COL], int row, int col);

(3)实现文件进行定义

c 复制代码
// 初始化棋盘
void InitBoard(char board[][COL], int row, int col)
{
	int i;
	for (i = 0; i < row; ++i)
	{
		int j;
		for (j = 0; j < col; ++j)
			board[i][j] = ' ';
	}
}

// 打印棋盘
void PrintBoard(char board[][COL], int row, int col)
{
	// 打印棋盘时,需要适当添加符号分隔
	int i;
	for (i = 0; i < row; ++i)
	{
		int j;
		for (j = 0; j < col; ++j)
			printf(" ---");
		// 下一行
		printf("\n");
		for (j = 0; j < col; ++j)
			printf("| %c ", board[i][j]);
		// 补齐改行分隔
		printf("|");
		// 下一行
		printf("\n");
	}
	// 补齐最后一行分隔
	for (i = 0; i < col; ++i)
		printf(" ---");
	
	printf("\n");
}

// 人机对战模式
void PVE(char board[][COL], int row, int col)
{
	printf("\n人机对战:\n");
	// 初始化棋盘
	InitBoard(board, row, col);
	// 显示棋盘
	PrintBoard(board, row, col);
}

// 玩家对战模式
void PVP(char board[][COL], int row, int col)
{
	printf("\n玩家对战:\n");
	// 初始化棋盘
	InitBoard(board, row, col);
	// 显示棋盘
	PrintBoard(board, row, col);
}

运行效果如下:

3. 轮流下棋及结果判断实现

这里假设人机对战时,玩家使用符号 '#',机器人使用符号 '*';玩家对战时,一号玩家使用符号 '#',二号玩家使用符号 '*'。玩家下棋和机器人下棋分别使用两个函数 ------ PMove() 和 EMove()。PMove() 函数需要使用一个额外的变量判断是 1 号玩家下棋,还是 2 号玩家下棋。

判断结果函数 is_win() 需要判断每行是否相同、每列是否相同,倘若其中有一个实现,则有一方获胜,倘若没有则需要使用函数 is_full() 判断棋盘是否已满,若满则平局,否则继续。is_full() 函数也可以直接实现在 is_win() 函数内部。

(1)头文件函数声明

c 复制代码
// 玩家下棋
void PMove(char board[][COL], int row, int col, int who);

// 机器人下棋
void EMove(char board[][COL], int row, int col);

// 判断是否获胜
void is_win(char board[][COL], int row, int col);

(2)实现文件函数定义

c 复制代码
// 玩家下棋
void PMove(char board[][COL], int row, int col, char who)
{
	// 所需变量
	int x, y;
	do
	{
		// 输入坐标
		printf("玩家下棋(输入坐标中间用空格隔开):\n");
		scanf("%d %d", &x, &y);
		// 判断合法性
		if ((x < 0 || x > row || y < 0 || y > col) || board[x - 1][y - 1] != ' ')
		{
			printf("坐标非法,请重新输入!\n");
		}
		else
		{
			board[x - 1][y - 1] = who;
			break;
		}

	} while (1);
}

// 机器人下棋
void EMove(char board[][COL], int row, int col)
{
	// 机器人是随机下棋的,这里就要获取随机数了
	// 需要包含头文件 stdlib.h 和 time.h 
	// 设置随机数种子
	srand((unsigned)time(0));
	printf("电脑下棋:\n");
	while (1)
	{
		int x = rand() % row;  // 0 - row - 1
		int y = rand() % col;  // 0 - col - 1
		if (board[x][y] == ' ')
		{
			board[x][y] = '*';
			break;
		}
	}
}

// 判断是否获胜
char is_win(char board[][COL], int row, int col)
{
	// 判断每行
	int r = 1;
	int i;
	for (i = 0; i < row; ++i)
	{
		// 重置判断符号
		r = 1;
		// 若首字符为空则下一行
		if (board[i][0] == ' ')
			continue;
		// 判断改行
		int j;
		for (j = 1; j < col; ++j)
		{
			r *= (board[i][j - 1] == board[i][j]);
			// 判断
			if (!r)
				break;
		}
		if (r)
			return board[i][0];
	}

	// 判断每列
	int c = 1;
	int j;
	for (j = 0; j < col; ++j)
	{
		// 重置判断符号
		c = 1;
		// 首字符为空则下一列
		if (board[0][j] == ' ')
			continue;
		// 判断该列
		int i;
		for (i = 1; i < row; ++i)
		{
			c *= (board[i - 1][j] == board[i][j]);
			// 判断
			if (!c)
				break;
		}
		if (c)
			return board[0][j];
	}

	// 判断对角线
	int d = 1;
	if (board[0][0] != ' ')
	{
		for (i = 1; i < row; ++i)
		{
			d *= (board[i - 1][i - 1] == board[i][i]);
			// 判断
			if (!d)
				break;
		}
		if (d)
			return board[0][0];
	}

	if (board[0][col] != ' ')
	{
		d = 1;
		for (i = 1; i < row; ++i)
		{
			d *= (board[i - 1][col - i + 1] == board[i][col - i]);
			// 判断
			if (!d)
				break;
		}
		if (d)
			return board[0][col];
	}

	// 判断棋盘是否已满
	int full = 1;
	for (i = 0; i < row; ++i)
	{
		for (j = 0; j < col; ++j)
		{
			if (board[i][j] == ' ')
			{
				// 未满
				return 'C';
			}
		}
	}
	// 已满
	return 'D';

}

上述代码中 is_win() 函数中所使用的是通用判断方法,即使改变了符号常量 ROW 和 COL 的值也可以是判断。(如果实在理解不了,可以只当 3*3 的特殊情况来实现判断,当然,也可能是我的代码写的不好)。

(3)程序运行结果:

这里值展示人机对战的三种结果:

玩家获胜:

电脑获胜:

平均:

4. 结果判断实现

该代码直接放在了函数 PMove() 和函数 EMove() 中,也可以单独写一个函数。

(1)实现文件函数定义

c 复制代码
// 人机对战模式
void PVE(char board[][COL], int row, int col)
{
	printf("\n人机对战:\n");
	// 初始化棋盘
	InitBoard(board, row, col);
	// 显示棋盘
	PrintBoard(board, row, col);
	// 开始下棋
	int ret = 0;
	do
	{
		// 玩家下棋
		PMove(board, row, col, '#');
		// 显示棋盘
		PrintBoard(board, row, col);
		// 判断
		ret = is_win(board, row, col);
		if (ret != 'C')
			break;
		// 机器人下棋
		EMove(board, row, col);
		// 显示棋盘
		PrintBoard(board, row, col);
		// 判断
		ret = is_win(board, row, col);
		if (ret != 'C')
			break;
	} while (1);
	// 判断最终结果
	if (ret == '#')
		printf("玩家获胜!\n");
	else if (ret == '*')
		printf("电脑获胜!\n");
	else
		printf("平局!\n");
}

// 玩家对战模式
void PVP(char board[][COL], int row, int col)
{
	printf("\n玩家对战:\n");
	// 初始化棋盘
	InitBoard(board, row, col);
	// 显示棋盘
	PrintBoard(board, row, col);
	// 开始下棋
	int ret = 0;
	do
	{
		// 玩家 1 号下棋
		PMove(board, row, col, '#');
		// 显示棋盘
		PrintBoard(board, col, row);
		// 判断
		ret = is_win(board, row, col);
		if (ret != 'C')
			break;
		// 玩家 2 号下棋
		PMove(board, row, col, '*');
		// 显示棋盘
		PrintBoard(board, col, row);
		// 判断
		ret = is_win(board, row, col);
		if (ret != 'C')
			break;
	} while (1);
	// 判断最终结果
	if (ret == '#')
		printf("玩家 1 号获胜!\n");
	else if (ret == '*')
		printf("玩家 2 号获胜!\n");
	else
		printf("平局!\n");
}

(2)演示结果

在上一次演示结果中已经体现。

三、所有代码

分三个文件,分别是头文件 Tic_tac_toe.h,测试文件 test.c,和函数实现文件 Tic_tac_toe.c。

(1)Tic_tac_toe.h

c 复制代码
// 常量声明
#define ROW 3
#define COL 3

// 函数声明

// 菜单
void menu();

// 初始化棋盘
void InitBoard(char board[][COL], int row, int col);

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

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

// 机器人下棋
void EMove(char board[][COL], int row, int col);

// 判断是否获胜
char is_win(char board[][COL], int row, int col);

// 人机对战模式
void PVE(char board[][COL], int row, int col);

// 玩家对战模式
void PVP(char board[][COL], int row, int col);

(2)test.c

c 复制代码
// 头文件
#include <stdio.h>
#include "Tic_tac_toe.h"

int main()
{
	// 所需变量
	int select = 0;
	char board[ROW][COL] = { 0 };
	// 选择
	do
	{
		// 菜单
		menu();
		// 选择
		scanf("%d", &select);
		// 判断
		switch (select)
		{
		case 1 :  // 人机对战
			PVE(board, ROW, COL);
			break;
		case 2 :
			PVP(board, ROW, COL);  // 玩家对战
			break;
		case 0 :
			printf("游戏结束!\n");
			break;
		default :
			printf("选择错误请重新选择!\n");
			break;
		}
	} while (select);

	return 0;
}

(3)Tic_tac_toe.c

c 复制代码
// 头文件
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "Tic_tac_toe.h"

// 函数定义

// 菜单
void menu()
{
	printf("**************************************\n");
	printf("**********      1. PVE      **********\n");
	printf("**********      2. PVP      **********\n");
	printf("**********      0. exit     **********\n");
	printf("**************************************\n");
}

// 初始化棋盘
void InitBoard(char board[][COL], int row, int col)
{
	int i;
	for (i = 0; i < row; ++i)
	{
		int j;
		for (j = 0; j < col; ++j)
			board[i][j] = ' ';
	}
}

// 打印棋盘
void PrintBoard(char board[][COL], int row, int col)
{
	// 打印棋盘时,需要适当添加符号分隔
	int i;
	for (i = 0; i < row; ++i)
	{
		int j;
		for (j = 0; j < col; ++j)
			printf(" ---");
		// 下一行
		printf("\n");
		for (j = 0; j < col; ++j)
			printf("| %c ", board[i][j]);
		// 补齐改行分隔
		printf("|");
		// 下一行
		printf("\n");
	}
	// 补齐最后一行分隔
	for (i = 0; i < col; ++i)
		printf(" ---");
	
	printf("\n");
}

// 玩家下棋
void PMove(char board[][COL], int row, int col, char who)
{
	// 所需变量
	int x, y;
	do
	{
		// 输入坐标
		printf("玩家下棋(输入坐标中间用空格隔开):\n");
		scanf("%d %d", &x, &y);
		// 判断合法性
		if ((x < 0 || x > row || y < 0 || y > col) || board[x - 1][y - 1] != ' ')
		{
			printf("坐标非法,请重新输入!\n");
		}
		else
		{
			board[x - 1][y - 1] = who;
			break;
		}

	} while (1);
}

// 机器人下棋
void EMove(char board[][COL], int row, int col)
{
	// 机器人是随机下棋的,这里就要获取随机数了
	// 需要包含头文件 stdlib.h 和 time.h 
	// 设置随机数种子
	srand((unsigned)time(0));
	printf("电脑下棋:\n");
	while (1)
	{
		int x = rand() % row;  // 0 - row - 1
		int y = rand() % col;  // 0 - col - 1
		if (board[x][y] == ' ')
		{
			board[x][y] = '*';
			break;
		}
	}
}

// 判断是否获胜
char is_win(char board[][COL], int row, int col)
{
	// 判断每行
	int r = 1;
	int i;
	for (i = 0; i < row; ++i)
	{
		// 重置判断符号
		r = 1;
		// 若首字符为空则下一行
		if (board[i][0] == ' ')
			continue;
		// 判断改行
		int j;
		for (j = 1; j < col; ++j)
		{
			r *= (board[i][j - 1] == board[i][j]);
			// 判断
			if (!r)
				break;
		}
		if (r)
			return board[i][0];
	}

	// 判断每列
	int c = 1;
	int j;
	for (j = 0; j < col; ++j)
	{
		// 重置判断符号
		c = 1;
		// 首字符为空则下一列
		if (board[0][j] == ' ')
			continue;
		// 判断该列
		int i;
		for (i = 1; i < row; ++i)
		{
			c *= (board[i - 1][j] == board[i][j]);
			// 判断
			if (!c)
				break;
		}
		if (c)
			return board[0][j];
	}

	// 判断对角线
	int d = 1;
	if (board[0][0] != ' ')
	{
		for (i = 1; i < row; ++i)
		{
			d *= (board[i - 1][i - 1] == board[i][i]);
			// 判断
			if (!d)
				break;
		}
		if (d)
			return board[0][0];
	}

	if (board[0][col] != ' ')
	{
		d = 1;
		for (i = 1; i < row; ++i)
		{
			d *= (board[i - 1][col - i + 1] == board[i][col - i]);
			// 判断
			if (!d)
				break;
		}
		if (d)
			return board[0][col];
	}

	// 判断棋盘是否已满
	int full = 1;
	for (i = 0; i < row; ++i)
	{
		for (j = 0; j < col; ++j)
		{
			if (board[i][j] == ' ')
			{
				// 未满
				return 'C';
			}
		}
	}
	// 已满
	return 'D';

}


// 人机对战模式
void PVE(char board[][COL], int row, int col)
{
	printf("\n人机对战:\n");
	// 初始化棋盘
	InitBoard(board, row, col);
	// 显示棋盘
	PrintBoard(board, row, col);
	// 开始下棋
	int ret = 0;
	do
	{
		// 玩家下棋
		PMove(board, row, col, '#');
		// 显示棋盘
		PrintBoard(board, row, col);
		// 判断
		ret = is_win(board, row, col);
		if (ret != 'C')
			break;
		// 机器人下棋
		EMove(board, row, col);
		// 显示棋盘
		PrintBoard(board, row, col);
		// 判断
		ret = is_win(board, row, col);
		if (ret != 'C')
			break;
	} while (1);
	// 判断最终结果
	if (ret == '#')
		printf("玩家获胜!\n");
	else if (ret == '*')
		printf("电脑获胜!\n");
	else
		printf("平局!\n");
}

// 玩家对战模式
void PVP(char board[][COL], int row, int col)
{
	printf("\n玩家对战:\n");
	// 初始化棋盘
	InitBoard(board, row, col);
	// 显示棋盘
	PrintBoard(board, row, col);
	// 开始下棋
	int ret = 0;
	do
	{
		// 玩家 1 号下棋
		PMove(board, row, col, '#');
		// 显示棋盘
		PrintBoard(board, col, row);
		// 判断
		ret = is_win(board, row, col);
		if (ret != 'C')
			break;
		// 玩家 2 号下棋
		PMove(board, row, col, '*');
		// 显示棋盘
		PrintBoard(board, col, row);
		// 判断
		ret = is_win(board, row, col);
		if (ret != 'C')
			break;
	} while (1);
	// 判断最终结果
	if (ret == '#')
		printf("玩家 1 号获胜!\n");
	else if (ret == '*')
		printf("玩家 2 号获胜!\n");
	else
		printf("平局!\n");
}

四、总结

本次三子棋的代码编写总体来说还可以,基本上都实现了上述功能,其中的某些代码本人均是根据符号常量来编写的,也就是即使符号常量改变,本代码依旧可以实现。当然,本人水平有限,有的地方看不懂可能是本人代码写的太烂,这里先道个歉。

当然代码还有很多可以提升的地方,比如优化棋盘,可以使用鼠标来下棋,提升电脑的水平等。

相关推荐
hummhumm18 分钟前
第 20 章 - Golang 网络编程
java·服务器·开发语言·网络·后端·python·golang
wjs20241 小时前
SQLite Truncate Table
开发语言
ZLRRLZ1 小时前
【C++】string类(附题)
开发语言·c++
小贾要学习1 小时前
【C++】类和对象(一)
开发语言·c++
建群新人小猿2 小时前
退款成功订阅消息点击后提示订单不存在
java·开发语言·前端
仙长道号-Linux真人2 小时前
gvim添加至右键、永久修改配置、放大缩小快捷键、ctrl + c ctrl +v 直接复制粘贴、右键和还原以前版本(V)冲突
c语言·开发语言·vim
y先森2 小时前
js实现导航栏鼠标移入时,下划线跟随鼠标滑动
开发语言·前端·javascript
chuxinweihui3 小时前
C语言项⽬实践-贪吃蛇
c语言·开发语言·数据结构·学习·链表
‘’林花谢了春红‘’3 小时前
C++ stack 容器
开发语言·c++
网络安全King3 小时前
【D04】网络安全基本命令
开发语言·网络·安全·web安全·php