超详细三子棋讲解,体会分模块写法

三子棋详解

三子棋

    • 三子棋详解
  • 一. 问题描述
  • 二. 三子棋简单描述
  • 三. 三子棋步骤详解
    • 1.菜单
    • 2.创建棋盘
    • 3.初始化棋盘
    • 4.打印棋盘
    • 5.玩家下棋
    • 6.电脑下棋
    • 7.判断输赢
    • 8.打印最后结果

一. 问题描述

用C语言分模块写出三子棋代码
游戏头文件  game.h
游戏源文件  game.c
主源文件  test.c

二. 三子棋简单描述

我们要实现一个如下三子棋(简单逻辑):
    1.游戏开始,进入游戏界面,玩家自行选择是否开始游戏
    2.创建一个空棋盘,让玩家可以下棋
    3.创建过后,打印棋盘让玩家可以观察棋盘大小
    4.玩家进行落子(以坐标形式进行落子)--(x y)
      其中x(1<=x<=3)  y(1<=y<=3)
    5.玩家进行落子后,须判断玩家落子是否正确
    6.电脑下棋
    7.每一轮落子过后判断是否游戏输赢决定是否结束游戏或继续下棋

三. 三子棋步骤详解

1.菜单

    1.玩家需要一个可视化的菜单
        菜单应该实现可以让玩家自行选择是否游戏
    在主源文件 test.c 中 代码如下:
void menu() //菜单界面
{
	printf("***********************\n");
	printf("**1.play***0.退出游戏**\n");
	printf("***********************\n");
}

#include
int main()
{
	int input = 0;//存储玩家的选择输入
	printf("  欢迎来到三子棋\n");
	menu();
	scanf("%d", input);//玩家进行选择
}
该处菜单代码可根据自行要求美化

效果展示  

超详细三子棋讲解,体会分模块写法_第1张图片

2.创建棋盘

 菜单完成后,考虑玩家的选择,若玩家选择 1. 进行游戏则进入游戏代码模块
 若选择 2.则选择退出游戏结束进程。考虑到这个游戏至少需要完成一次,因此我们写一个do while();循环来完成。由于棋盘是一个三行三列的数组,因此我们创建一个二维数组棋盘。
 在主源文件 test.c 中 代码如下:
void game()
{
	//创建棋盘
	char board[ROW][COL] = { 0 };//存储棋盘
}

int main()
{
	int input = 0;
	srand((unsigned)time(NULL));
	do
	{
		printf("  欢迎来到三子棋游戏\n");
		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;
}

其中,合理运用玩家的输入input的值来控制了这个循环。可以达到玩家输错后为一个非0的数字也可以继续选择,同时结束一局游戏后,又可以选择继续重新开始。

 代码展示

超详细三子棋讲解,体会分模块写法_第2张图片
在game.h中 定义行列大小

#define ROW 3  //三行
#define COL 3  //三列

在这使用自定义的行列有如下好处
1.提高代码可读性,防止与后续代码中出现的值歧义
2.方便后续创建一个新棋盘,更改自定义大小即可获得一个几行几列的数组

3.初始化棋盘

在创建了一个三行三列的数组后,这个棋盘里应该没有任何东西,因此须将棋盘进行初始化。

由于棋盘的初始化属于游戏模块,因此将其分装到 game.h 的头文件中进行声明,在 game.c 中进行实现(下述游戏模块的函数均如此分装不在赘述)

test.c 中代码如下:

void game()
{
	//创建棋盘
	char board[ROW][COL] = { 0 };//存储棋盘

	//棋盘初始化
	Initboard(board, ROW, COL);
}

game.h中进行函数体声明,代码如下:

#define ROW 3  //三行
#define COL 3  //三列

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

game.c中进行函数体的实现,代码如下:

void Initboard(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] = ' ';//表示这个二维数组中均初始化为空白
		}
	}
}

4.打印棋盘

在进行初始化后,玩家无法看见棋盘,因此我们需要写一个函数将棋盘打印出来,让玩家观察棋盘

test.c中代码如下:

void game()
{
	//创建棋盘
	char board[ROW][COL] = { 0 };//存储棋盘

	//棋盘初始化
	Initboard(board, ROW, COL);

	//打印初始化后的棋盘
	print_board(board, ROW, COL);
}

game.h中代码如下:

#define ROW 3  //三行
#define COL 3  //三列

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

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

在实现这个打印棋盘的函数时,进行逻辑分析
我们想要获得一下形式的棋盘
超详细三子棋讲解,体会分模块写法_第3张图片
观察这个棋盘,我们可以将其分为:
超详细三子棋讲解,体会分模块写法_第4张图片

1.数据行:玩家以及电脑落子后每一行中的数据
让每一组数据都落在中间那个空格上

2.每一行的分割行 数据 + | 第三行时没有分割行
超详细三子棋讲解,体会分模块写法_第5张图片

3.每一列的分割行 _ _ _ 第三列没有分割行 _ _ _
超详细三子棋讲解,体会分模块写法_第6张图片
game.c中代码如下:

print_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++)
		{
			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("--- ");
		}
		printf("\n");//打印每一列数据后换行
	}
}

由于test.c 以及game.c 中都需要引用一些头文件,为更方便和简介,将这些头文件都放入game.h中,test.c 以及 game.c只需 #include“game.h” 即可使用

//函数体申明
#include
#include
#include

效果展示:
超详细三子棋讲解,体会分模块写法_第7张图片

5.玩家下棋

玩家落子之后,应当判断是否落子正确(包括落子位置即坐标是否正确,以及落子位置是否被占用)在落子。由于下棋时一个人和电脑反复下直至分出胜负,因此为一个循环过程。

test.c 中代码如下:

	while (1)
	{
		//玩家下棋
		playmove(board, ROW, COL);
		print_board(board, ROW, COL);//每一次下棋后打印出棋盘目以便观察
	}

game.h 中代码如下:

//函数体申明
#include
#include
#include

#define ROW 3  //三行
#define COL 3  //三列

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

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

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

game.c 中代码如下:

playmove(char board[ROW][COL], int row, int col)
{
	int x = 0;
	int y = 0;

	while (1) //玩家下棋由于存在落子问题,因此采用循环直至这一次落子成功
	{
		printf("当前为玩家下棋\n");
		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");
	}
}

效果展示:

超详细三子棋讲解,体会分模块写法_第8张图片

6.电脑下棋

test.c 中代码如下:

		computermove(board, ROW, COL);
		print_board(board, ROW, COL);

game.c 中代码如下:

computermove(char board[ROW][COL], int row, int col)
{
	//电脑随机下棋,须产生一个随机坐标
	int x = 0;
	int y = 0;
	
	printf("电脑落子:\n");
	while (1)
	{

		//必须在循环里面,限制范围
		x = rand() % row;  //限定电脑随机生成的坐标范围
		y = rand() % col;  //rand需设置随机数的生成起点

		if (board[x][y] == ' ')
		{
			board[x][y] = '#';//电脑落子成功用 # 表明
			break;
		}
	}
}

game.h 中代码如下:

//函数体申明
#include
#include//rand的头文件
#include //随机数起点设置以时间为起点

#define ROW 3  //三行
#define COL 3  //三列

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

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

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

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

超详细三子棋讲解,体会分模块写法_第9张图片

电脑不够按照随机下棋不够灵活,可以将其完善,例如第一步先判断玩家是否下在中间位置(2 2),若不在电脑可将第一步先下在此处,会更有利,由于我们这是一个非常初阶的三子棋,就暂时不考虑这些,有兴趣的朋友们可以考虑一下。

7.判断输赢

 在判断输赢时,做如下约定:
 判断输赢情况
 1.电脑赢 #三连--返回 1
 2.玩家赢 *三连--返回 2
 3.棋盘未满,且双方都未赢,接着下--返回 3
 4.棋盘满了,双方未赢,判定平局--返回 4

test.c 中代码如下:

void game()
{
	//创建棋盘
	char board[ROW][COL] = { 0 };//存储棋盘

	//棋盘初始化
	Initboard(board,ROW,COL);

	//打印初始化后的棋盘
	print_board(board, ROW, COL);

	int m = 0;

	while (1)
	{
		//玩家下棋
		playmove(board, ROW, COL);
		print_board(board, ROW, COL);

		m = Iswin(board, ROW, COL);

		if (m != 3)//棋盘未满且双方都还未分出胜负下才有意义判断输赢
		{
			break; //还未分出胜负并且需要继续下棋时,及时跳出判断继续下棋
		}

		//判断输赢
		//m = Iswin(board, ROW, COL);
		
		//电脑下棋
		computermove(board, ROW, COL);
		print_board(board, ROW, COL);
		m = Iswin(board, ROW, COL);
		if (m !=3)//在不是平局的前提下
		{
			break; //还未分出胜负并且需要继续下棋时,及时跳出判断继续下棋
		}
	}
	if (m == 1)
		printf("电脑获胜\n");
	else if (m == 2)
		printf("玩家获胜!\n");
	else
		printf("平局\n");

}

game.h 中代码如下:

//函数体申明
#include
#include
#include

#define ROW 3  //三行
#define COL 3  //三列

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

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

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

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

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

game.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;    //一旦找到有一个空格,说明棋盘未满,立即返回
		}
	}
	return 1;  //前面循环结束都未找到空格,说明棋盘满了

}


//从此处开始阅读

int 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][1] != ' ')
		{
			if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][1] == '*')
				return 2;
			else
				return 1;
		}

	}

	//竖三列
	//前提不为初始化的0三子
	int j = 0;
	for (j = 0; j < col; j++)
	{
		if (board[0][j] == board[1][j] && board[1][j] == board[2][j] && board[1][j] != ' ' )
		{
			if(board[0][j] == board[1][j] && board[1][j] == board[2][j] && board[1][j] =='*')
				return 2;
			else
				return 1;
		}
	}

	//对角线判断
	//前提不为初始化0的三子
	if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ')
	{
		if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] == '*')
			return 2;
		else
			return 1;
	}
	if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[1][1] != ' ')
	{
		if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[1][1] == '*')
			return 2;
		else
			return 1;
	}


	//若前面均为分出输赢,此时则有可能为平局或者继续

	//若平局 此时棋盘应该已经下满,没有空位可以继续下
	if (IsFull(board, row, col) )  //返回1则条件成立进入
	{
		return 4;
	}

	//前面没有人赢,棋盘也没有满,此时应该处于需要继续下棋
	return 3;
}

以上判断输赢时,可考虑将电脑胜利以 ‘ #’返回,玩家胜利以‘ *’返回,可以省去在判断时该处是电脑三子还是玩家三子,即可将下面代码替换:
超详细三子棋讲解,体会分模块写法_第10张图片

替换为:

	int i = 0;
	for (i = 0; i < row; i++)
	{
		if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][1] != ' ')
		{
			/*if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][1] == '*')
				return 2;
			else
				return 1;*/
			return board[i][1];//这个三子后期中一个子返回的值无论是‘#’还是‘*’,均可以直接判断输赢
		}

	}

在上述判断输赢时,采取的是最原始的方法,将行列的三个数组坐标列出来进行判断,此处优化空间很大,可以试着去优化它。

效果展示:
1.玩家获胜
超详细三子棋讲解,体会分模块写法_第11张图片
2.电脑获胜
超详细三子棋讲解,体会分模块写法_第12张图片
3.平局
超详细三子棋讲解,体会分模块写法_第13张图片

8.打印最后结果

最后,无论是电脑还是玩家赢了等情况,我们均需要让玩家观察最后的棋盘情况,因此我们在这还需要给判断输赢后的结果打印出来
test.c 中加入这个之前的打印函数即可:

void game()
{
	//创建棋盘
	char board[ROW][COL] = { 0 };//存储棋盘

	//棋盘初始化
	Initboard(board,ROW,COL);

	//打印初始化后的棋盘
	print_board(board, ROW, COL);

	int m = 0;

	while (1)
	{
		//玩家下棋
		playmove(board, ROW, COL);
		print_board(board, ROW, COL);

		m = Iswin(board, ROW, COL);

		if (m != 3)//棋盘未满且双方都还未分出胜负下才有意义判断输赢
		{
			break; //还未分出胜负并且需要继续下棋时,及时跳出判断继续下棋
		}

		//判断输赢
		//m = Iswin(board, ROW, COL);
		
		//电脑下棋
		computermove(board, ROW, COL);
		print_board(board, ROW, COL);
		m = Iswin(board, ROW, COL);
		if (m !=3)//棋盘未满且双方都还未分出胜负下才有意义判断输赢
		{
			break; //还未分出胜负并且需要继续下棋时,及时跳出判断继续下棋
		}
	}
	if (m == 1)
		printf("电脑获胜\n");
	else if (m == 2)
		printf("玩家获胜!\n");
	else
		printf("平局\n");

	//游戏结束后,打印出棋盘让玩家观察输赢情况
	print_board(board, ROW, COL);

}

在此,我们的这个三子棋简单版就写完了!各位大佬给个关注(仰望大佬)
超详细三子棋讲解,体会分模块写法_第14张图片

你可能感兴趣的:(c语言,开发语言)