有趣的代码——井字棋游戏的实现

前面我们讲解过一个猜数字游戏的实现,想来应该让大家感受到了属于编程的趣味性,并且在实现过程中应该也收获了知识。但猜数字这种简单的游戏肯定满足不了大家对于游戏的高标注、严要求,估计玩不了多久就会没有兴趣了,所以,今天在这里和大家分享一个更好玩,也更有实现难度的小游戏——井字棋!相信大家都不会对这个游戏陌生的(可能有朋友不久前还在学校和小伙伴一起玩呢),那么今天就让我们一起通过编程把这个小游戏实现吧。

1.井字棋游戏的大致流程

首先,由计算机产生一个3*3大小的棋盘,并显示玩家和计算机各自使用的棋子,然后由玩家、计算机轮流在上面落子,哪一方先3颗棋子连成一条线,则显示该方获胜,如果棋局填满,还没有一方三子连线,则判定为和棋。

2.游戏实现的思路

还记得我在猜数字游戏的实现中说过的一句话吗?没错,我们在写编程题或者小游戏代码时,最重要的就是理清实现思路——主体是什么?为了实现目的要创建哪些函数?函数的功能都是什么?只有当我们心中有了一个大体的框架,知道该做些什么时,我们才能更高效地编写代码,完成程序设计。

井字棋游戏的算法如下:

1.menu(提供游戏菜单,由玩家选择是否进行游戏:按“1”开始游戏,按“0”退出游戏,按其他则显示“选择错误,请重新选择”。)

2.game( )(进行游戏)

        2.1 InitBoard(初始化棋盘)

        2.2 DisplayBoard(打印未放置任何棋子的棋盘,并显示玩家和计算机分别分配的棋子类型,并提示玩家先下棋)。

        2.3 PlayerMove(玩家下棋),在棋盘上显示位置,并ret=IsWin(判断是否胜利)。

        2.4 ComputerMove(电脑下棋)在棋盘上显示位置,并ret=IsWin(判断是否胜利)。

        2.5 根据ret(定义判断结果的变量)判断最终结果,若玩家胜利,则显示“亲爱的玩家,恭喜你获得游戏胜利!”;若电脑胜利,则显示“亲爱的玩家,请不要因失败而气馁,期待你的下一次开始!”;若平局,则显示“亲爱的玩家,该局游戏平局!”

3.本轮游戏结束,打印游戏菜单并再次询问玩家选择。

如上面就是井字棋游戏大体框架的算法呈现。

我们在思考大体框架时不用过多在意具体函数的实现方法,可以先起个能表达其功能的函数名并把它放在需要的位置。(说白了就是先搞一个空壳函数占位置,等到大体框架调试完毕不再有问题后,再去实现这些空壳函数)大体框架代码如下:

#include<stdio.h>
int main()
{
	int input;
    do
    {    //menu函数在调试大体框架时,也可以是空壳函数
         menu();//打印游戏菜单。我们建立函数可以让主函数不至于太过冗长,而且增加了代码的可读性,使程序模块化。
         printf("请选择:>");
         scanf("%d", &input);
         switch(input)
         {
             case 1:
                 game();//guess是用来猜测并判断是否正确的函数,这里也是空壳函数
                 break;
             case 0:
                 printf("游戏结束\n");
                 break;
             default:
                 printf("选择错误,请重新选择\n");
                 break;
           }
     }while(input);
	return 0;
}

可能有些朋友会产生这样疑惑:为什么在上面的算法中出现的DisplayBoard、PlayerMove、ComputerMove和IsWin等函数没有出现呢?因为上面这些函数都是在进行游戏中的分支函数,所以我们在大体框架中就不需要写那么详细。

menu函数的代码实现如下:

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

 game函数的实现涉及到一个新的知识点,我们在后面一点再详细介绍。

3.多文件程序

实际上在C语言中,我们根据程序文件的数量,可以将C语言程序分为单文件程序和多文件程序,单文件程序就是所有程序代码都在一个源程序文件中,多文件程序中通常包含一个或多个自定义头文件和一个或多个源程序文件。严格地讲,结构化程序应该使用多文件结构,尤其对于大型程序。

3.1多文件程序的构成

多文件程序中通常包含一个或多个自定义头文件和一个或多个源程序文件,每个文件称为程序文件模块。通常编程环境都使用工程来管理程序文件模块,若将若干个程序文件模块添加到一个工程中,再点击“连接”按钮,就能将这些程序文件模块连接成一个可执行文件。

在多文件程序中,头文件通常包含程序文件模块的共享信息,如符号常量定义、数据类型定义、全局变量定义和函数原型。因为当我们把这些符号常量和全局变量放到头文件中,然后用#include预处理指令将这个头文件包含到源程序文件中,这样就不必重复定义这些符号常量和全局变量,从而避免重复工作,减少差错和编译运行。在多文件程序中,源程序通常包含主函数和其他函数定义,相应的函数原型一般放在头文件中。由于整个程序的运行只能从主函数main开始,所以,有且只有一个源程序文件包含主函数。一般将主函数放到一个源程序文件中,将其他函数定义组成若干个源程序文件。具体情况如下图:

3.2将源程序文件分解为多个程序文件模块

如果源程序文件的规模较大,应该将源程序文件分解为几个程序文件模块;如果一个项目需要多人开发,应该将任务分解,每个人编写的程序代码放在增加的程序文件模块中。如何将源程序文件分解为多个程序文件模块呢?在多文件程序中,一般将函数定义在源程序文件中,相应的函数原型放在头文件中,为了方便编译器检查头文件中的函数原型与源程序文件中的函数定义是否一致,在定义函数的源程序文件中也包含该头文件。

综合上面两大段话和一张图片,相信大家对于多文件程序应该有了自己的初步认识,但是为了加深大家的理解程度,方便大家记忆,下面我分享一下个人的理解归纳(当然,有朋友有更透彻的见解,欢迎评论区谈论):

在我看来,多文件程序本质上是供多人协同开发大型项目的,而我们个人在写代码量较大的程序也可以使用创建多文件程序,因为这样不仅会使代码模块化,而且会使主函数很清晰简洁,提高我们的工作效率。而多文件程序的组成可以看作“目录+目录内容的具体介绍+主函数”——我们可以把包含程序文件模块共享信息的头文件看作“目录”,里面有我们要使用的符号常量定义、全局变量定义、函数原型等;而大多数源文件就是“目录内容的具体介绍”,也就是说大部分源文件是用来实现“目录”中函数的定义的,它们是一个个小小的模块,在源程序中定义完毕,等待主函数的调用;每个项目有且仅有一个主函数,而这个主函数就是借用“目录”中的所有内容来完成程序设计最终目的的。简言之,我们可以把程序需要解决的问题看作学习中的难题,含主函数main的源程序文件就是我们最后写的“答案”,包含程序文件模块共享信息的头文件就是我们解决难题所需的课本的“目录”,除含main函数的源程序文件就是目录中涉及到的知识点的“详细讲解”。

 4.井字棋游戏的代码实现

上面围绕多文件程序介绍那么久,相信聪明的大家我们接下来要干什么了吧😏

game函数的代码如下:


//游戏的整个算法实现 
void game()
{
	char ret = 0;
	//数组—存放走出的棋盘信息 通过数组建立一个棋盘,为什么是数组相信看过数组讲解的可以会明白。
	char board[ROW][COL] = { 0 };//建立行列,理应让棋盘上都是空格,0是不可打印字符 
	//初始化棋盘
	InitBoard(board, ROW, COL);
	//打印棋盘,使玩家看到棋盘
	DisplayBoard(board, ROW, COL);
	while (1)//还记得菜单不 
	{
		//玩家下棋 
		PlayerMove(board, ROW, COL);
		DisplayBoard(board, ROW, COL);
		//判断是否游戏结束 
		ret = IsWin(board, ROW, COL);
		if (ret != 'C')
		{
			break;
		}
		//电脑下棋
		ComputerMove(board, ROW, COL);
		DisplayBoard(board, ROW, COL);
		//判断 
		ret = IsWin(board, ROW, COL);
		if (ret != 'C')
		{
			break;
		}
	}
	if (ret == '*')
	{
		printf("亲爱的玩家,恭喜你获得游戏胜利!\n\n");
	}
	else if (ret == '#')//if ,else if,else后面都没有分号 
	{
		printf("亲爱的玩家,请不要因失败而气馁,期待你的下一次开始!\n\n");
	}
	else
	{
		printf("亲爱的玩家,该局游戏平局!\n\n");
	}
}

game函数的算法已经展示出来了,那么我们就可以给出井字棋游戏代码实现这个“难题”的“答案”了——含main主函数的源程序程序,如下:

/*   main.c    */
#include<stdio.h>

#include"game.h"

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


//游戏的整个算法实现 
void game()
{
	char ret = 0;
	//数组—存放走出的棋盘信息
	char board[ROW][COL] = { 0 };//建立行列,理应让棋盘上都是空格,0是不可打印字符 
	//初始化棋盘
	InitBoard(board, ROW, COL);
	//打印棋盘,使玩家看到棋盘
	DisplayBoard(board, ROW, COL);
	while (1)//还记得菜单不 
	{
		//玩家下棋 
		PlayerMove(board, ROW, COL);
		DisplayBoard(board, ROW, COL);
		//判断是否游戏结束 
		ret = IsWin(board, ROW, COL);
		if (ret != 'C')
		{
			break;
		}
		//电脑下棋
		ComputerMove(board, ROW, COL);
		DisplayBoard(board, ROW, COL);
		//判断 
		ret = IsWin(board, ROW, COL);
		if (ret != 'C')
		{
			break;
		}
	}
	if (ret == '*')
	{
		printf("玩家赢\n");
	}
	else if (ret == '#')//if ,else if,else后面都没有分号 
	{
		printf("电脑赢\n");
	}
	else
	{
		printf("平局\n");
	}
}
int main()
{
	int input;
	srand((unsigned int)time(NULL));//我们要想让电脑自己下棋,肯定要设置符合棋盘的随机数,不懂随机数的可以看看猜数字中的介绍
    do
    {    //menu函数在调试大体框架时,也可以是空壳函数
         menu();//打印游戏菜单。我们建立函数可以让主函数不至于太过冗长,而且增加了代码的可读性,使程序模块化。
         printf("请选择:>");
         scanf("%d", &input);
         switch(input)
         {
             case 1:
                 game();//guess是用来猜测并判断是否正确的函数,这里也是空壳函数
                 break;
             case 0:
                 printf("游戏结束\n");
                 break;
             default:
                 printf("选择错误,请重新选择\n");
                 break;
           }
     }while(input);
	return 0;
}

虽然我们完成了难题,但是就像有些论文要求我们标注参考文献一样,我们还要写出自己参考了“哪本书”——头文件,避免我们用的知识点“老师”——系统无法识别。

game函数中所涉及内容的头文件的代码如下:

/*    game.h    */
#pragma once
#define ROW 3
#define COL 3

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

//声明
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);


//告诉我们四种游戏的状态
//玩家赢-'*'
//电脑赢-'#'
//平局-'Q'
//继续-'C'

char IsWin(char board[ROW][COL], int row, int col);

有目论,肯定要实现,不然用不了。

game函数中所涉及内容的源程序文件如下:

/*    game.c    */
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
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] = ' ';
		}
	}
}
void DisplayBoard(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++)
		{
			//1.打印一行数据
			printf(" %c ", board[i][j]);//printf( %c  |  %c  |  %c  ) 可以直接打印棋盘 ,但效果不好,太死板了 
			if (j < col - 1)
				printf("|");
		}
		printf("\n");
		//2.打印分割线
		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;
	int y = 0;
	printf("玩家走:>\n");

	while (1)
	{
		scanf("%d%d", &x, &y);
		//判断左标的合理性
		if (x >= 1 && x <= row && y >= 1 && y <= col && board[x - 1][y - 1] == ' ')
		{
			//玩家理解的坐标和数组不同
			board[x - 1][y - 1] = '*';
			break;
		}
		else
		{
			printf("坐标非法,请重新输入\n");
		}
	}
}
void ComputerMove(char board[ROW][COL], int row, int col)
{
	int x = 0;
	int y = 0;
	printf("电脑走:>");
	while (1)
	{
		x = rand() % row;
		y = rand() % col;
		if (board[x][y] == ' ')
		{
			board[x][y] = '#';
			break;
		}
	}
	printf("电脑下的坐标为:%d %d\n", x + 1, y + 1);
}


int IsFull(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++)
		{
			if (board[i][j] == ' ')
				return 0;
		}
	}
	return 1;
}
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] == board[i][2] && board[i][1] != ' ')
		{
			return board[i][1];
		}
	}
	//竖三 
	for (i = 0; i < col; i++)
	{
		if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][i] == board[2][i] && board[1][i] != ' ')
		{
			return board[1][i];
		}
	}
	//两个对角线
	if (board[0][0] == board[1][1] && board[0][0] == board[2][2] && board[1][1] == board[2][2] && board[1][1] != ' ') //这有传递性比两组即可但不能是空格 
		return board[1][i];
	if (board[2][0] == board[1][1] && board[1][1] == board[0][2] && board[1][1] != ' ')
		return board[1][1];
	//判断是否平局
	if (1 == IsFull(board, ROW, COL))
	{
		return 'Q';
	}
	return 'C';
}

各位朋友们,代码到这里的话已经是完结了。大家只需要按照顺序分别建立两个源程序文件和一个头文件(当然注意代码细节哦),应该就能实现这个很经典的小游戏喽🤭

不过,值得一提的是大家记住一定要把多个程序文件模块添加到一个工程中,再点击“连接”按钮,将这些程序文件模块连接成一个可执行文件。如果是用VS就是在新建的项目中再创建一个源程序文件和一个头文件;用Dev的话就要点击建立项目,而不是源代码哦(其他编译器同理)。

 当然,这个代码还有很多值得开发的地方,比如让笨笨的电脑变聪明一点,让井字棋变成五子棋等等,希望大家不要被限制了想象,多多开动脑筋,期待在下一次发布关于井字棋升级的文章时我们有同样的升级方案!😀

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/205874.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

8. 队列

队列(queue)是一种遵循先入先出规则的线性数据结构。顾名思义&#xff0c;队列模拟了排队现象&#xff0c;即新来的人不断加入队列的尾部&#xff0c;而位于队列头部的人逐个离开。 如下图所示&#xff0c;我们将队列的头部称为“队首”&#xff0c;尾部称为“队尾”&#xff…

基于Web邮箱的邮件系统

题目: 基于web的邮件收发系统设计与实现 摘 要 计算机的应用已经越来越广泛&#xff0c;它从产生到完善已经差不多有50年左右的历史&#xff0c;更新换代速度非常快&#xff0c;在人们生活、工作中都发挥了不可替代的作用&#xff0c;几乎所有行业都离不开它&#xff0c;已经成…

【数值计算方法(黄明游)】矩阵特征值与特征向量的计算(三):Jacobi 旋转法【理论到程序】

文章目录 一、Jacobi 旋转法1. 基本思想2. 计算过程演示 二、Python实现迭代过程&#xff08;调试&#xff09; 矩阵的特征值&#xff08;eigenvalue&#xff09;和特征向量&#xff08;eigenvector&#xff09;在很多应用中都具有重要的数学和物理意义。Jacobi 旋转法是一种用…

06、基于内容的过滤算法Tensorflow实现

06、基于内容的过滤算法Tensorflow实现 开始学习机器学习啦&#xff0c;已经把吴恩达的课全部刷完了&#xff0c;现在开始熟悉一下复现代码。对这个手写数字实部比较感兴趣&#xff0c;作为入门的素材非常合适。 05、基于梯度下降的协同过滤算法中已经介绍了协同过滤算法的基…

用最少数量的箭引爆气球[中等]

优质博文&#xff1a;IT-BLOG-CN 一、题目 有一些球形气球贴在一堵用XY平面表示的墙面上。墙面上的气球记录在整数数组points&#xff0c;其中points[i] [xstart, xend]表示水平直径在xstart和xend之间的气球。你不知道气球的确切y坐标。一支弓箭可以沿着x轴从不同点完全垂直…

Springboot-注册注解【springboot常用注解】

1.组件注册 1.1 使用的注解 Configuration:普通配置类,替代以前的配置文件,配置类本身也是容器的组件|SpringBootConfiguration:Springboot配置类,与Configuration功能一样|Bean:替代以前的Bean标签,如果没有在Bean标签内定义名字,则默认组件的名字为方法名,可以直接修改注解…

在gitlab上使用server_hooks

文章目录 1. 前置条件2. Git Hook2.1 Git Hook 分为两部分&#xff1a;本地和远程2.1.1 本地 Git Hook&#xff0c;由提交和合并等操作触发&#xff1a;2.1.2 远程 Git Hook&#xff0c;运行在网络操作上&#xff0c;例如接收推送的提交&#xff1a; 3. 操作步骤3.1 对所有的仓…

Linux下的文件IO之系统IO

1. 知识点 读入写出&#xff0c;切记以我们程序为中心向文件或者别的什么东西读入写出&#xff08;输入流输出流&#xff09; 人话就是 文件向我们程序就是读入 程序向文件或者别的什么就是写出 2. open打开文件 open.c /****************************************************…

C++ 学习之函数成员指针的一个小细节

看看下面的代码&#xff0c;你能看出错误吗 class A { public:void fun(){}}; int main() {A a;void (A:: * p)() &A::fun;(*p)(); } 这段代码在调用成员函数时存在问题。正确的方式是使用对象来调用成员函数&#xff0c;而不是通过指针。以下是修正后的代码&#xff1a…

STM32CubeIDE(CUBE-MX hal库)----定时器

系列文章目录 STM32CubeIDE(CUBE-MX hal库)----初尝点亮小灯 STM32CubeIDE(CUBE-MX hal库)----按键控制 STM32CubeIDE(CUBE-MX hal库)----串口通信 文章目录 系列文章目录前言一、定时器二、使用步骤三、HAL库实验代码三、标准库代码 前言 STM32定时器是一种多功能外设&#…

异常 Exception 02

异常 Exception 02 六、异常处理1、基本介绍2、异常处理的方式3、示意图 try-catchthrows1、介绍2、注意事项 自定义异常1、基本概念2、自定义异常的步骤3、实例4、throw和throws的区别 六、异常处理 1、基本介绍 异常处理就是当异常发生时,对异常处理的方式。 2、异常处理的…

以STM32CubeMX创建DSP库工程方法一

以STM32CubeMX创建DSP库工程方法 略过时钟树的分配和UART的创建等&#xff0c;直接进入主题生成工程文件 它们中的文件功能如下&#xff1a; 1&#xff09;BasicMathFunctions 基本数学函数&#xff1a;提供浮点数的各种基本运算函数&#xff0c;如向量加减乘除等运算。 2&…

VBA代码解决方案第8讲:用FindPrevious进行重复搜索及利用LIKE查找

《VBA代码解决方案》(版权10028096)这套教程是我最早推出的教程&#xff0c;目前已经是第三版修订了。这套教程定位于入门后的提高&#xff0c;在学习这套教程过程中&#xff0c;侧重点是要理解及掌握我的“积木编程”思想。要灵活运用教程中的实例像搭积木一样把自己喜欢的代码…

货代FOB条款卖方必备的知识:发货人都要承担哪些费用呢?

据统计&#xff0c;中国出口中以FOB成交的占到70%&#xff0c;但专家指出&#xff1a;FOB对出口商的风险更大&#xff0c;有可能造成货、款两空的结局。 目前我国出口合同以FOB价格条款成交的比例越来越大&#xff0c;而且收货人指定船公司的少&#xff0c;指定境外货代的多&am…

3款厉害的小工具,小黑子都在用!

大家好&#xff0c;我是 Javapub。 程序员与普通人最大的区别是什么&#xff0c;当然是会使用工具。基于一些同学经常问我的问题&#xff0c;接下来给大家分享几款我经常使用的工具&#xff0c;主打一个提升效率。 第一款 Everything 用 windwos 的同学都体会过&#xff0c;…

带大家做一个,易上手的家常土豆片

还是先从冰箱里那一块猪瘦肉 搞一点蒜和生姜 切成小块 装进一个碗里 这里一点就够了 一条绿皮辣椒 切片 三个左右干辣椒 随便切两刀 让它小一点就好了 一起装一个碗 一大一小两个土豆切片 猪肉切片 起锅烧油 然后 下肉翻炒 等肉变颜色捞出来 然后放入土豆 和小半碗水 让…

JeecgBoot低代码开发—Vue3版前端入门教程

JeecgBoot低代码开发—Vue3版前端入门教程 后端接口配置VUE3 必备知识1.vue3新特性a. https://v3.cn.vuejs.org/b.setup的用法c.ref 和 reactive 的用法d.新版 v-model 的用法e.script setup的用法 2.TypeScript基础 后端接口配置 如何修改后台项目路径 http://127.168.3.52:8…

【玩转 EdgeOne】| 腾讯云下一代边缘加速CDN EdgeOne 是安全加速界的未来吗?

目录 前言边缘加速与安全加固边缘计算与CDN的融合EdgeOne优秀的安全特性EdgeOne卓越的性能表现灵活的配置和管理生态系统的支持与发展技术创新与未来展望EdgeOne试用结束语 前言 在当下互联网的迅猛发展的时刻&#xff0c;云计算和边缘计算技术的快速发展为网络加速领域带来了…

awk从放弃到入门(10):awk内置函数

awk从放弃到入门&#xff08;10&#xff09;&#xff1a;awk内置函数 算数函数字符串函数其他函数 本博文转载自 这篇文章中的知识点是建立在前文的基础上的&#xff0c;如果你还没有掌握前文中的知识&#xff0c;请先参考之前的文章。 注&#xff1a;在阅读这篇文章之前&…

Abbyy FineReader16最新版本有哪些新功能?

在数字化时代&#xff0c;数据处理和转换变得非常重要&#xff0c;Abbyy FineReader 就是一款专门用于处理、转换和识别图像和 PDF 文件的软件。在本文中&#xff0c;我们将会详细介绍 Abbyy FineReader 的功能以及适合使用该软件的电脑。 ABBYY Finereader 16-安装包下载如下&…