C语言经典项目实战——三子棋

Posted Zheng"Rui

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言经典项目实战——三子棋相关的知识,希望对你有一定的参考价值。

三子棋,又称井字棋,是我们经常爱玩的游戏,今天来学习,如何用C语言来实现一个简单的三子棋小游戏。

一、分析三子棋项目需求

首先对我们要实现的三子棋项目,分析它的需求。为了了解它需要哪些函数,我们先模拟一遍下棋的过程、

/*
	1.初始化棋盘
	
	2、玩家落子
	2.1 展示棋盘
	2.2 判断胜负
	
	3、电脑落子
	3.1 展示棋盘
	3.2 判断胜负
*/

在这个步骤中,我们看到,要实现三子棋,起码要实现,棋盘创建,期盼展示,玩家落子,电脑落子,胜负判断等功能的函数。由此就可以开展,三子棋的实现。

二、各个功能实现

首先在play.h,这个头文件中,写下棋盘的初始化信息,如棋盘为三行三列

#ifndef _PLAY_H
#define _PLAY_H

#define ROW 3
#define COL 3

#endif

注意:这里的#ifdef 后面加的名字可以是任意的,但一般都是自己的大写文件名,这一句代码的作用是,防止在头文件中重复申明了多个函数,或者是结构体。

然后进行函数申明,(这里我们还没有写主函数,也没有进行任何一个变量定义,但是没有关系,我们先进行函数的申明,然后再play.c文件中,进行函数定义)


void menu();//菜单
void InitBoard(char board[ROW][COL]);//初始化棋盘
void ShowBoard(char board[ROW][COL]);//展示棋盘
void Player(char board[ROW][COL]);//玩家下棋
void Computer(char board[ROW][COL]);//电脑下棋
char IsWin(char board[ROW][COL]);//判断胜负

根据上一步的需求分析,我们知道主要实现的就是,初始化棋盘,棋盘展示,玩家下棋,电脑下棋,判断胜负,这几个函数,所以我们先将其定义出来,其中的参数我们都向其中传入棋盘board。

接下来,进行函数实现,注意,接下来的函数定义,都在play.c中实现。
首先看菜单函数的实现。
下棋的时候,我要知道按什么按键开始游戏,按什么按键退出游戏,所以需要一个菜单函数

1.menu()函数

void menu()
{
	printf("请开始游戏吧!\\n");
	printf("---------------------------------\\n");
	printf("-----1.开始--------0.退出--------\\n");
	printf("---------------------------------\\n");

}

这个函数向玩家提示,按1开始游戏,按0退出。
来看它的显示效果

在这里插入图片描述

2.InitBoard()函数

接下来实现棋盘的初始化功能


void InitBoard(char board[ROW][COL])
{
	for (int i = 0; i < ROW; i++)
	{
		for (int j = 0; j < COL; j++)
		{
			board[i][j] = ' ';
		}
	}
}

我们遍历整个二维数组,将其中的每个单元都赋值为‘ ’(空格),先对其进行初始化。

然后实现棋盘展示功能
实现这个功能的时候,并不是把这个二维数组简单的按顺序打印(当然也可以,只要你不嫌简陋) 这里,我们想个我们的棋盘加上一些边界,让棋盘看上去美观一点、就像这样:
在这里插入图片描述
在这些空格中放入棋子。

3.ShowBoard()函数

接下来看,如何打印这个棋盘。

void ShowBoard(char board[ROW][COL])
{
	for (int i = 0; i < ROW * 2 + 1; i++)
	{
		if (i % 2 == 0)
		{
			for (int j = 0; j < COL; j++)
			{
				printf("----");
			}
			printf("\\n");
		}
		else
		{
			for (int j = 0; j < COL * 2 + 1; j++)
			{
				if (j % 2 == 0)
				{
					printf("|");
				}
				else
				{
					printf(" %c ", board[i / 2][j / 2]);
				}
			}
			printf("\\n");
		}
	}
}

注意:我上面给的只是一开始没下棋时候的棋盘,而我们用这个函数的时候,不仅要展示棋盘的边界,还要展示其中的棋子。所以,我们将整个棋盘打印的时候看成是7行7列的一个数组,然后去打印。
观察棋盘,我们发现横线只在0,2,4,6行出现,数据在1,3,5行出现,竖线在0,2,4,6列出现,数据在1,3,5列出现。由此就可以打印出我们期待的棋盘的样子。

4.Player()函数

然后实现玩家下棋


void Player(char board[ROW][COL])
{
	while (1)
	{
		printf("请输入你的座标:");
		int x, y;
		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] = 'X';
				break;
			}
			else
			{
				printf("该位置已经有棋子!\\n");
			}
		}
		else
		{
			printf("输入错误。\\n");
		}
	}
}

在这个函数中,要注意使用了while函数,而且还是给了判断条件1,也就是说,如果在这个函数中不使用break的话,就陷入了死循环。为什么要这么设计呢,因为,我们要预防用户输入的棋子坐标不正确 或者 此位置上有其他棋子的情况,需要重新输入,只有当输入正确时,才结束循环,否则一直输入。
看一下函数效果

在这里插入图片描述

5.Computer()函数

下面来看电脑下棋功能的实现
和玩家下棋类似,只不过这一次,不需要我们在手动输入,而是采用随机数,进行随机位置下棋。


void Computer(char board[ROW][COL])
{
	while (1)
	{
		int x = rand() % ROW;
		int y = rand() % COL;
		if (board[x][y] == ' ')
		{
			board[x][y] = 'O';
			break;
		}
	}
}

我们让随机数对行数和列数取模,这样就不会有越界的情况,同时使用while循环,防止电脑下在了已经有棋子的地方,来保证电脑棋子的正确性。
下面来看函数效果、
在这里插入图片描述
不管是电脑下棋,还是玩家下棋,下过一步之后都要进行胜负判断,判断是否应该结束游戏

7.IsWin()函数

所以下面来实现,判断胜负函数:


char IsWin(char board[ROW][COL])
{
	for (int 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 (int j = 0; j < COL; j++)
	{
		if (board[0][j] == board[1][j] && board[1][j] == board[2][j] && board[0][j] != ' ')
		{
			 return board[0][j];
		}
	}
	if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ')
	{
		return board[0][0];
	}
	if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[0][2] != ' ')
	{
		return board[0][2];
	}
	if (IsFull == 0)
	{
		return 'Q';
	}
	return 'n';
}

在这里我们给函数定义一个返回值,目的是通过返回值来判断是否出现赢家,还是应该继续游戏。三子棋判断胜利的条件十分简单,只要判断每一行是否全为相同字母,或者每一列,或者每一个对角线,是否有三个相同的棋子,如果有,则返回这个棋子,这样我们就能在调用它的时候知道,哪一方获得胜利,这里有个小技巧,就是我们不用设置一个字母来放置判断条件然后在最后统一返回,而是在每一次判断后,如果成功就直接返回,这样可以减少代码量。
在判断完是否有赢家之后,如果这个函数还没有结束,就说明没有赢家,那我们就要判断棋盘是否已经满了,如果满了,但是没有出现赢家,说明应该按照平局处理,如果没有赢家,棋盘也没满,则说明结果正常,游戏继续。
观察代码,如果玩家胜利的话,就返回’X’,电脑胜利就返回’O’,平局的话就返回’Q’,如果游戏继续的话就返回’n’,这里我们把返回’n’看作正常情况,那么只要这个函数返回的不是字符n,就说明要么玩家胜利,要么电脑胜利,要么平局,不论如何,游戏应该结束了。利用这个特征,对我们之后调用这个函数,非常有帮助。

同时,判断棋盘有没有满的函数也很简单,只要遍历一遍棋盘,如果发现有空格,说明没满,返回1,如果遍历完成之后,也没有找到空格,说明棋盘满了,返回0;
下面来看具体实现:


int IsFull(char board[ROW][COL])
{
	for (int i = 0; i < ROW; i++)
	{
		for (int j = 0; j < COL; j++)
		{
			if (board[i][j] == ' ')
			{
				return 1;
			}
		}
	}
	return 0;
}

到此处为止,三子棋项目所需要的所有函数已经完成,之后要进行的就是对于这些函数的调用问题。我们仍然设置一个叫RunGame的函数来进行游戏的调用,目的就是为了保证主函数的简短和可读性。

三、RunGame函数和main函数的实现

为了对主函数进行化简,我们设置了RunGame函数,在三子棋这个项目中,因为本身游戏所用到的函数并不多,也不需要复杂的调用,所以这个RunGame函数是可以省略的,直接在主函数中实现,但是需要知道,在其他大一点的项目中,最好添加上这一项,保证可读性。
同时,添加上这个函数,还有一个好处,就是这个函数代表了一次游戏的执行过程,这样我们就能实现,让玩家结束一局游戏之后,选择继续游戏,还是退出游戏。
下面来看具体实现:


void RunGame()
{
	char board[ROW][COL];
	InitBoard(board);
	ShowBoard(board);
	char temp;
	while (1)
	{
		Player(board);
		ShowBoard(board);
		temp = IsWin(board);
		if (temp != 'n')
		{
			break;
		}
		Computer(board);
		ShowBoard(board);
		temp = IsWin(board);
		if (temp != 'n')
		{
			break;
		}
	}
	if (temp == 'X'){
		printf("玩家胜利!\\n");
	}
	else if (temp == 'O')
	{
		printf("电脑胜利!\\n");
	}
	else if (temp == 'Q')
	{
		printf("平局!\\n");
	}
}

既然是游戏的一次执行过程,那么在游戏的最开始,我们需要先定义一个二维数组当作我们的棋盘,然后调用InitBoard函数来初始化,之后向玩家展示这个初始化好的空棋盘。对于一个游戏来说,只要游戏没有结束,那么我们就应该一直执行它,所以,需要在一个死循环中来执行游戏,当if判断应该结束游戏时,才跳出循环。
这里巧妙地运用了IsWin函数的返回值,用temp来接收函数返回值,每次判断不需要,逐个字符去判断是否相等,而是判断是否为’n’,如果为’n’,说明游戏继续,如果不为’n’,不管它是什么,都说明游戏应该结束,这时跳出循环,在循环体外,判断temp,来说明到底是什么情况。

由于我们之前已经做了很多的铺垫,所以主函数就会十分简短,下面来看主函数的调用


srand((unsigned)time(NULL));
	int input;
	do{
		menu();
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			RunGame();
			break;
		case 0:
			break;
		default:
			break;
		}
	} while (input);
	return 0;
}

第一句的sand语句,是为了种下一个随机种子,我们在Computer函数中,要求电脑用随机数生成数据,但是,其实生成的是伪随机数,我们重复运行函数就会发现,电脑下的棋都会出现在固定位置,为了解决这个问题,我们引入随机种子,这个种子的位置可以随意,但一定要在调用Computer函数之前。
之后是一个do~while循环,通过我们输入的数据,如果输入1,则开始游戏,输入0则退出游戏。

四、项目的整体分析

到这里,整个三子棋游戏也就完成了,下面来对我们实现的三子棋游戏进行总结。
首先我们完成了真正的三子棋的大部分必备功能,包括棋盘初始化,玩家和电脑相会对弈,游戏判断胜负等等,它搭建起来一个三子棋游戏的框架,就算是真正的的三子棋游戏,它的内核实现,也是这样的。当然,这个项目也存在问题,就是,我们虽然实现了电脑下棋,但是真正运行就会发现,我们设置的这个电脑玩家总是“笨笨的”,它不知道往哪里下棋可以赢得比赛,这是因为,我们给电脑的只是一个随机数,我们可以保证这个随机数一定是符合要求的,但是不能保证这个随机数会帮助电脑取得胜利,所以和这样的电脑下棋是无聊的。
但是,前面提到,这个项目已经明确了三子棋的框架结构,我们还可以设置两个真人玩家互相对弈,只需要把玩家下棋的代码复制一份,给玩家2,然后调用的时候,让玩家2取代电脑的位置就可以实现,甚至,在掌握网络有关知识后,还可以实现,两个人联网对弈,这都是基于我们目前实现的这个简易的三子棋框架的。

五、完整代码

play.h

#ifndef _PLAY_H
#define _PLAY_H

#include<stdio.h>
#include<time.h>
#include<stdlib.h>

#define ROW 3
#define COL 3

void menu();//菜单
void RunGame();
void InitBoard(char board[ROW][COL]);//初始化棋盘
void ShowBoard(char board[ROW][COL]);//展示棋盘
void Player(char board[ROW][COL]);//玩家下棋
void Computer(char board[ROW][COL]);//电脑下棋
char IsWin(char board[ROW][COL]);//判断胜负
int IsFull(char board[ROW][COL]);


#endif

play.c


#define _CRT_SECURE_NO_WARNINGS 1
#include"play.h"
void menu()
{
	printf("请开始游戏吧!\\n");
	printf("---------------------------------\\n");
	printf("-----1.开始--------0.退出--------\\n");
	printf("---------------------------------\\n");

}

void InitBoard(char board[ROW][COL])
{
	for (int i = 0; i < ROW; i++)
	{
		for (int j = 0; j < COL; j++)
		{
			board[i][j] = ' ';

		}
	}
}

void ShowBoard(char board[ROW][COL])
{
	for (int i = 0; i < ROW * 2 + 1; i++)
	{
		if (i % 2 == 0)
		{
			for (int j = 0; j < COL; j++)
			{
				printf("----");
			}
			printf("\\n");
		}
		else
		{
			for (int j = 0; j < COL * 2 + 1; j++)
			{
				if (j % 2 == 0)
				{
					printf("|");
				}
				else
				{
					printf(" %c ", board[i / 2][j / 2]);
				}
			}
			printf("\\n");
		}
	}

}

void Player(char board[ROW][COL])
{
	while (1)
	{
		printf("请输入你的座标:");
		int x, y;
		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] = 'X';
				break;
			}
			else
			{
				printf("该位置已经有棋子!\\n");
			}
		}
		else
		{
			printf("输入错误。\\n");
		}
	}
}

void Computer(char board[ROW][COL])
{
	while (1)
	{
		int x = rand() % ROW;
		int y = rand() % COL;
		if (board[x][y] == ' ')
		{
			board[x][y] = 'O';
			break;
		}
	}
}

char IsWin(char board[ROW][COL])
{
	for (int 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 (int j = 0; j < COL; j++)
	{
		if (board[0][j] == board[1][j] && board[1][j] == board[2][j] && board[0][j] != ' ')
		{
			 return board[0][j];
		}
	}
	if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ')
	{
		return board[0][0];
	}
	if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[0][2] != ' ')
	{
		return board[0][2];
	}
	if (IsFull == 0)
	{
		return 'Q';
	}
	return 'n';
}

int IsFull(char board[ROW][COL])
{
	for (int i = 0; i < ROW; i++)
	{
		for (int j = 0; j < COL; j++)
		{
			if (board[i][j] == ' ')
			{
				return 1;
			}
		}
	}
	return 0;
}

void RunGame()
{
	char board[ROW][COL];
	InitBoard(board);
	ShowBoard(board);
	char temp;
	while (1)
	{
		Player(board);
		ShowBoard(board);
		temp = IsWin(board);
		if (temp != 'n')
		{
			break;
		}
		Computer(board);
		ShowBoard(board);
		temp = IsWin(board);
		if (temp != 'n')
		{
			break;
		}
	}
	if (temp == 'X'){
		printf("玩家胜利!\\n");
	}
	else if (temp == 'O')
	{
		printf("电脑胜利!\\n");
	}
	else if (temp == 'Q')
	{
		printf("平局!\\n");
	}
}

test.c


#define _CRT_SECURE_NO_WARNINGS 1

#include"play.h"

int main()
{
	
	/*
	1.初始化棋盘
	2、玩家落子
	2.1 展示棋盘
	2.2 判断胜负
	3、电脑落子
	3.1 展示棋盘
	3.2 判断胜负
	*/
	
	srand((unsigned)time(NULL));
	int input;
	do{
		menu();
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			RunGame();
			break;
		case 0:
			break;
		default:
			break;
		}
	} while (input);
	return 0;
}

以上是关于C语言经典项目实战——三子棋的主要内容,如果未能解决你的问题,请参考以下文章

C语言实现三子棋

三子棋分析与实现——C语言

C语言实战小游戏:井字棋(三子棋)大战!文内含有源码

用C语言实现三子棋游戏(附上思路+项目展示+源代码)

追寻童年回忆~三子棋C语言

c语言实现三子棋