C语言实现三子棋(超详细)

三子棋简介:

 三子棋是一种民间传统游戏,又叫九宫棋、圈圈叉叉棋、一条龙、井字棋等。游戏分为双方对战,双方依次在9宫格棋盘上摆放棋子,率先将自己的三个棋子走成一条线就视为胜利,而对方就算输了。

编写三子棋,可以让我们学习如何组织一个较大的代码。

我们将代码分为三个模块:

test.c---用来测试游戏逻辑

game.c---函数的实现

game.h---函数的声明

其中game.c和game.h为游戏的模块

代码实现思路:

首先,我们要在屏幕上打印一个菜单,让玩家选择1进入游戏,选择0退出游戏,选择不是1不是0就告诉玩家选择错误,请重新选择。于是在测试模块test.c中:

#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"//包含自己的头文件要用""
void menu()
{
  printf("***************************\n");
  printf("*****     1.play      *****\n");
  printf("*****     0.exit      *****\n");
  printf("***************************\n");
}

int main()
{
  int input = 0;
   do
   {
	 menu();
	 printf("请选择:\n");
	 scanf("%d", &input);
	 switch (input)
	 {
	   case 1:
		 game();//game函数即实现三子棋
		 break;
	   case 0:
		 printf("退出游戏\n");
		 break;
	   default:
		 printf("选择错误,请重新选择:\n");
		 break;
     }
	}while (input);//设置选择1进入游戏,0退出游戏的便捷之处在于:若选择1(或不是0的整数),
//input为真,继续进入循环,若选择0,为假则打印"退出游戏"后退出循环。
	return 0;
}

printf,scanf函数需要引头文件,所以我们在game.h中:

 

 到这里我们可以运行一下代码,检查一下是否出现问题,在编写较大的代码时,要养成这个好习惯,不然到最后代码出了问题就不好发现是哪个地方错误了。

好的,接下来我们要做的就是对game()函数的实现了。

想要下棋,首先我们要有一个棋盘,并且还要记录下来玩家和电脑下棋的位置,我们可以在电脑上输出这样一个简易的棋盘:

   |   |
---|---|---
   |   |   
---|---|---
   |   |

 

这样一个三行三列的棋盘和二维数组非常相似,所以我们可以创建一个3*3的二维数组来记录棋子,考虑到以后可能我们要玩更大棋盘的游戏,这样的话所有涉及到二维数组大小的值都要进行更改,为了方便我们在game.h中定义一个行(row)和列(column)

注意!#define 定义的符号都是大写的。未来我们只需要在头文件中更改行列就可以了,接下来,我们要对棋盘进行初始化和打印,我们先在测试逻辑模块test.c中写出我们需要的功能:

#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void menu()
{
	printf("***************************\n");
	printf("*****     1.play      *****\n");
	printf("*****     0.exit      *****\n");
	printf("***************************\n");
}

void game()
{
	//存放数据需要一个3*3的二维数组
	char board[ROW][COL] = { 0 };
	//初始化(initialization)棋盘
	Initboard(board, ROW, COL);
	//显示棋盘
	DisplayBoard(board, ROW, COL);
}
int main()
{
	int input = 0;
	do
	{
		menu();
		printf("请选择:\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误,请重新选择:\n");
			break;
		}
	} while (input);
	return 0;
}

写出了我们需要的函数,就要在game.h中进行声明:

#pragma once

//头文件的包含
#include<stdio.h>

#define ROW 3
#define COL 3

//函数的声明

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

//显示棋盘
void DisplayBoard(char board[ROW][COL], int row, int col);

 在game.c中进行编写实现:

#define _CRT_SECURE_NO_WARNINGS 1
#include"game.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, j = 0;
	for (i = 0; i < row; i++)//共打印row行
	{
		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");
		}
	}
}

 

 到这里我们又可以运行一下,检查自己写的棋盘是否正确打印出来。如果一切顺利那接下来我们就要考虑开始下棋了。首先玩家下棋,玩家输入要落子的坐标,这里我们要注意,二维数组第一个元素的坐标应该是[0][0],但玩家可能不是程序员,在玩家的眼里第一个元素的坐标就是[1][1],所以x的范围应该是1~ROW,y的范围应该是1~COL。玩家落子后,要打印一下棋盘,让玩家看到自己下棋的位置,然后轮到电脑落子,电脑落子是随机的,但不能在已经落过子的地方下棋,玩家也是一样,当电脑落子后应打印一下棋盘,让玩家看到当前棋盘落子情况。如此循环,直到有一方胜利或者出现平局,所以在每次落子后,我们都要判断一下是否出现胜负或平局。按照这个思路,我们在逻辑测试代码模块test.c中:

#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void menu()
{
	printf("***************************\n");
	printf("*****     1.play      *****\n");
	printf("*****     0.exit      *****\n");
	printf("***************************\n");
}

void game()
{
	//存放数据需要一个3*3的二维数组
	char board[ROW][COL] = { 0 };
	//初始化棋盘
	Initboard(board, ROW, COL);
	//显示棋盘
	DisplayBoard(board, ROW, COL);

	while (1)
	{
		//玩家下棋
		PlayerMove(board, ROW, COL);
		//打印棋盘
		DisplayBoard(board, ROW, COL);
		//判断输赢
		
		//电脑下棋
		ComputerMove(board, ROW, COL);
		//打印棋盘
		DisplayBoard(board, ROW, COL);
		//判断输赢

	}
}
int main()
{
	int input = 0;
	srand((unsigned int)time(NULL));
	do
	{
		menu();
		printf("请选择:\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误,请重新选择:\n");
			break;
		}
	} while (input);
	return 0;
}

在游戏实现模块game.c中:

#define _CRT_SECURE_NO_WARNINGS 1
#include"game.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, j = 0;
	for (i = 0; i < row; i++)
	{
		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 PlayerMove(char board[ROW][COL], int row, int col)
{
	int x = 0, y = 0;
	printf("玩家下棋:\n");
	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][y]
			{
				board[x - 1][y - 1] = '*';//假设玩家的棋子是‘*’
				break;
			}
			else//不能在已落子的地方下棋
				printf("坐标被占用,请重新输入\n");
		}
		else//坐标非法,玩家输入坐标超出范围
			printf("坐标非法,请重新输入\n");
	}
}

//电脑随机下棋
void ComputerMove(char board[ROW][COL], int row, int col)
{
	int x = 0, y = 0;
	printf("电脑下棋:\n");
	
	while (1)//只有电脑落子成功,方可跳出循环
	{
		x = rand() % row;
		//调用rand()函数生成一个随机数,再模上一个行,
		//得到一个随机的行的坐标,比如row=3,rand()%3=0、1、2
		y = rand() % col;//同上
		//要使用rand()函数生成随机数,需要调用srand()函数来设置随机数的生成器,
		//且整个工程中srand()只需要调用一次即可(在test.c中调用)
		//这些函数需要的头文件在game.h中包含

		if (board[x][y] == ' ')//判断生成的坐标[x][y]是否为空
		{
			board[x][y] = '#';//不为空,电脑落子
			break;//落子成功,跳出循环
		}
		//若[x][y]不为空,再次进入循环生成随机坐标
	}
}

在函数声明模块game.h中:

#pragma once

//头文件的包含
#include<stdio.h>
#include<stdlib.h>
#include<time.h>

#define ROW 3
#define COL 3

//函数的声明

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

//显示棋盘
void DisplayBoard(char board[ROW][COL], int row, int col);

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

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

写到这里,我们可以运行一下代码,如果没有错误的话,现在已经可以实现玩家和电脑下棋的交互了

 接下来我们要做的就是判断每次落子后的输赢情况,我们在代码逻辑测试模块test.c中写下IsWin()函数,用来判断棋盘的三行,三列,对角线是否相同,或者未出现相同而且棋盘已满的情况,为了维护游戏的继续与终止,IsWin()函数要告诉我们四种情况:

1、玩家赢;

2、电脑赢;

3、平局;

4、游戏继续;

//判断输赢
//玩家赢--返回'*'
//电脑赢--返回'#'
//平局  --返回'Q'
//继续  --返回'C'
char IsWin(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[1][1] != ' ')
	{
		return board[1][1];
	}
	//判断对角线
	if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[1][1] != ' ')
	{
		return board[1][1];
	}
	//判断是否平局
	if (IsFull(board,row,col))//设计一个IsFull函数来判断是否平局
                          //若平局则返回1,进入if语句,若没有平局返回0
	{
		return 'Q';
	}
	//游戏继续
	return 'C';//若没有一方赢,也没有平局,则代码走到这里,游戏继续
}

这个是我们写出来的IsWin()函数,要注意的是这个函数的返回值设计,我们在比较完行、列、对角线后若发现有一方胜利,我们无需判断相同的符号是'#'还是'*',而是直接返回相同的那个符号,再在逻辑测试模块中进行分析。

到这里,我们已经基本完成了所有的代码设计,最后只需要在test.c中分析每次落子后的棋盘胜负情况了,于是我们要接受IsWin函数的返回值并进行分析游戏的进行与终止,而电脑玩家轮流落子的过程是一个循环,只有游戏出现结果才会终止,跳出循环,让我们来看一下完整的代码。

这是test.c中的代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void menu()
{
	printf("***************************\n");
	printf("*****     1.play      *****\n");
	printf("*****     0.exit      *****\n");
	printf("***************************\n");
}

void game()
{
	//存放数据需要一个3*3的二维数组
	char board[ROW][COL] = { 0 };
	//初始化棋盘
	Initboard(board, ROW, COL);
	//显示棋盘
	DisplayBoard(board, ROW, COL);
	int ret = 0;
	while (1)
	{
		//玩家下棋
		PlayerMove(board, ROW, COL);
		//打印棋盘
		DisplayBoard(board, ROW, COL);
		//判断输赢
		ret = IsWin(board, ROW, COL);
		if ('C' != ret)
		{
			break;//若返回值不是'C',则必为平局、玩家赢、电脑赢
			//其中一种,而这三种无论哪一种都意味着游戏结束。
			//跳出循环,宣布结果
		}
		//电脑下棋
		ComputerMove(board, ROW, COL);
		//打印棋盘
		DisplayBoard(board, ROW, COL);
		//判断输赢
		ret = IsWin(board, ROW, COL);
		if ('C' != ret)//电脑落完子也要判断输赢
		{
			break;
		}
	}
    //宣布结果
	if ('*' == ret)
	{
		printf("玩家赢\n");
	}
	else if ('#' == ret)
	{
		printf("电脑赢\n");
	}
	else
	{
		printf("平局\n");
	}
}
int main()
{
	int input = 0;
	srand((unsigned int)time(NULL));
	do
	{
		menu();
		printf("请选择:\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误,请重新选择:\n");
			break;
		}
	} while (input);
	return 0;
}

这是game.c中的代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include"game.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, j = 0;
	for (i = 0; i < row; i++)
	{
		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 PlayerMove(char board[ROW][COL], int row, int col)
{
	int x = 0, y = 0;
	printf("玩家下棋:\n");
	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][y]
			{
				board[x - 1][y - 1] = '*';//假设玩家的棋子是‘*’
				break;
			}
			else//不能在已落子的地方下棋
				printf("坐标被占用,请重新输入\n");
		}
		else//坐标非法,玩家输入坐标超出范围
			printf("坐标非法,请重新输入\n");
	}
}

//电脑随机下棋
void ComputerMove(char board[ROW][COL], int row, int col)
{
	int x = 0, y = 0;
	printf("电脑下棋:\n");
	
	while (1)//只有电脑落子成功,方可跳出循环
	{
		x = rand() % row;
		//调用rand()函数生成一个随机数,再模上一个行,
		//得到一个随机的行的坐标,比如row=3,rand()%3=0、1、2
		y = rand() % col;//同上
		//要使用rand()函数生成随机数,需要调用srand()函数来设置随机数的生成器,
		//且整个工程中srand()只需要调用一次即可(在test.c中调用)
		//这些函数需要的头文件在game.h中包含

		if (board[x][y] == ' ')//判断生成的坐标[x][y]是否为空
		{
			board[x][y] = '#';
			break;//落子成功,跳出循环
		}
		//若[x][y]不为空,再次进入循环生成随机坐标
	}
}

int IsFull(char board[ROW][COL], int row, int col)
{
	int i = 0, j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			if (board[i][j] == ' ')//找到空格,即棋盘没满
				return 0;
		}
	}
	return 1;//未找到' '跳出两层for循环后来到这里,即棋盘满了。
}

//判断输赢
//玩家赢--返回'*'
//电脑赢--返回'#'
//平局  --返回'Q'
//继续  --返回'C'
char IsWin(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[1][1] != ' ')
	{
		return board[1][1];
	}
	//判断对角线
	if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[1][1] != ' ')
	{
		return board[1][1];
	}
	//判断是否平局
	if (IsFull(board,row,col))
	{
		return 'Q';
	}
	//游戏继续
	return 'C';
}

这是game.h中的代码:

#pragma once

//头文件的包含
#include<stdio.h>
#include<stdlib.h>
#include<time.h>

#define ROW 3
#define COL 3

//函数的声明

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

//显示棋盘
void DisplayBoard(char board[ROW][COL], int row, int col);

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

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

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

结束语:

运行无误后,我们便可以玩一下我们写出来的三子棋小游戏,这个版本玩起来玩家赢并不难,难的是怎么让电脑赢,哈哈哈,快来试一试吧!

代码中有不足,或者不懂的地方,欢迎在评论区指正或提问哦!

  • 20
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值