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

news2024/9/22 3:42:23

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

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的话就要点击建立项目,而不是源代码哦(其他编译器同理)。

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

渗透测试-环境部署

环境部署 一、kali的环境部署二、DNSenum工具&#xff1a;深入挖掘目标域名的DNS信息三、安装Burpsuite四、安装根证书五、JSFinder的使用六、App、小程序、公众号提取 一、kali的环境部署 1.配置SSH 修改配置文件 vi /etc/ssh/sshd_config 找到 # PermitRootLogin prohibit…

收藏!7个小众宝藏的开发者学习类网站

1、simplilearn 地址&#xff1a;https://www.simplilearn.com/ simplilearn是全球排名第一的在线学习网站&#xff0c;它的课程由世界知名大学、顶级企业和领先的行业机构通过实时在线课程设计和提供&#xff0c;其中包括顶级行业从业者、广受欢迎的培训师和全球领导者。 2、…

java springboot通过application配置文件生成随机值并控制范围

我们找到 项目的 application 配置文件 这里我们还是习惯用 yml格式的 我们在配置文件中 写出 ${random.} 的时候 他就会将所有可配置的随机类型都提示出来了 有 整数 长整星 字符串 uuid 这里 我们来个模板 testcase:book:id: ${random.int}name: ${random.value}date: ${r…

[跑代码]BK-SDM: A Lightweight, Fast, and Cheap Version of Stable Diffusion

Installation(下载代码-装环境) conda create -n bk-sdm python3.8 conda activate bk-sdm git clone https://github.com/Nota-NetsPresso/BK-SDM.git cd BK-SDM pip install -r requirements.txt Note on the torch versions weve used torch 1.13.1 for MS-COCO evaluation…

了解ConcurrnetHashMap 吗?

程序员的公众号&#xff1a;源1024&#xff0c;获取更多资料&#xff0c;无加密无套路&#xff01; 最近整理了一波电子书籍资料&#xff0c;包含《Effective Java中文版 第2版》《深入JAVA虚拟机》&#xff0c;《重构改善既有代码设计》&#xff0c;《MySQL高性能-第3版》&…

kafka中的常见问题处理

文章目录 1. 如何防⽌消息丢失2. 如何防⽌重复消费3. 如何做到消息的顺序消费4. 如何解决消息积压问题4.1 消息积压问题的出现4.2 消息积压的解决⽅案 5. 实现延时队列的效果5.1 应用场景5.2 具体方案 1. 如何防⽌消息丢失 ⽣产者&#xff1a;1&#xff09;使⽤同步发送 2&…

决策树(Classification and Regression Tree)

学了数据结构的树后&#xff0c;一直没发现树有哪些应用。学而时习&#xff08;实践&#xff09;之&#xff0c;不亦说乎&#xff1f;故特地上网查了查树的应用&#xff0c;在下阐释&#xff1a; 1.文件系统&#xff1a;文件和目录的组织通常以树的形式表示&#xff0c;允许高效…

掌握Python BentoML:构建、部署和管理机器学习模型

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com BentoML是一个开源的Python框架&#xff0c;旨在简化机器学习模型的打包、部署和管理。本文将深入介绍BentoML的功能和用法&#xff0c;提供详细的示例代码和解释&#xff0c;帮助你更好地理解和应用这个强大的工…

【C++】异常处理 ③ ( 栈解旋 | 栈解旋概念 | 栈解旋作用 )

文章目录 一、栈解旋1、栈解旋引入2、栈解旋概念3、栈解旋作用 二、代码示例 - 栈解旋1、代码示例2、执行结果 一、栈解旋 1、栈解旋引入 C 程序 抛出异常后 对 局部变量的处理 : 当 C 应用程序 在 运行过程 中发生异常时 , 程序会跳转到异常处理程序 , 并执行一些操作以处理异…

10.30 作业 C++

设计一个Per类&#xff0c;类中包含私有成员:姓名、年龄、指针成员身高、体重&#xff0c;再设计一个Stu类&#xff0c;类中包含私有成员:成绩、Per类对象p1&#xff0c;设计这两个类的构造函数、析构函数和拷贝构造函数。 #include <iostream>using namespace std;clas…

【C语言学习疑难杂症】第6期:C语言中如何打印一些特殊字符,比如打印扩展ascii码字符

首先我们来看下ascii表和ascii拓展表: ascii表中的字符只有128个,是从0-127,而拓展ascii表的内容是128-255。拓展表中它们都是一些特殊的字符,如果我们想答应ascii拓展码中的一些字符应该要怎么操作呢? 比如下面的代码: unsigned char a = 176, b = 219;printf("%…

客餐书房一体布局,新中式风格禅意十足。福州中宅装饰,福州装修

你是否曾经遇到过这样的痛点&#xff1a;装修时不知道该选择什么样的风格&#xff0c;让家居空间显得既时尚又实用&#xff1f;如果你对此感到困惑&#xff0c;那么新中式风格可能正是你想要的选择&#xff01; 今天&#xff0c;我们将一起探讨一种别样的家居布局&#xff0c;它…

openGauss学习笔记-136 openGauss 数据库运维-例行维护-检查数据库性能

文章目录 openGauss学习笔记-136 openGauss 数据库运维-例行维护-检查数据库性能136.1 检查办法136.2 异常处理 openGauss学习笔记-136 openGauss 数据库运维-例行维护-检查数据库性能 136.1 检查办法 通过openGauss提供的性能统计工具gs_checkperf可以对硬件性能进行检查。 …

单词拆分 II

题目链接 单词拆分 II 题目描述 注意点 s 和 wordDict[i] 仅有小写英文字母组成wordDict 中所有字符串都 不同词典中的同一个单词可能在分段中被重复使用多次以任意顺序 返回所有这些可能的句子 解答思路 使用深度优先遍历回溯解决本题&#xff0c;每一层从idx开始遍历s&a…

OSG编程指南<十六>:OSG渲染到纹理RTT及三维纹理体渲染技术简介

1、渲染到纹理&#xff08;RTT&#xff09; 1.1 RTT介绍 RTT&#xff08;Render to Texture&#xff09;即渲染到纹理。在普通的图形渲染流程中&#xff0c;最终结果是渲染到帧缓存中&#xff0c;然后才会显示到屏幕上。而RTT则是将场景渲染到一张纹理上&#xff0c;并且在之后…

知识蒸馏代码实现(以MNIST手写数字体为例,自定义MLP网络做为教师和学生网络)

dataloader_tools.py import torchvision from torchvision import transforms from torch.utils.data import DataLoaderdef load_data():# 载入MNIST训练集train_dataset torchvision.datasets.MNIST(root "../datasets/",trainTrue,transformtransforms.ToTens…

Unity 注释的方法

1、单行注释&#xff1a;使用双斜线&#xff08;//&#xff09;开始注释&#xff0c;后面跟注释内容。通常注释一个属性或者方法&#xff0c;如&#xff1a; //速度 public float Speed;//打印输出 private void DoSomething() {Debug.Log("运行了我"); } …

老师旁听公开课到底听什么

经常参加公开课是老师提升自己教学水平的一种方式。那么&#xff0c;在旁听公开课时&#xff0c;老师应该听什么呢&#xff1f; 听课堂氛围 一堂好的公开课&#xff0c;应该能够让学生积极参与&#xff0c;课堂气氛活跃&#xff0c;而不是老师一个人唱独角戏。如果老师能够引导…

第16关 革新云计算:如何利用弹性容器与托管K8S实现极速服务POD扩缩容

------> 课程视频同步分享在今日头条和B站 天下武功&#xff0c;唯快不破&#xff01; 大家好&#xff0c;我是博哥爱运维。这节课给大家讲下云平台的弹性容器实例怎么结合其托管K8S&#xff0c;使用混合服务架构&#xff0c;带来极致扩缩容快感。 下面是全球主流云平台弹…

Windows系列:windows2003-建立域

windows2003-建立域 Active Directory建立DNS建立域查看日志xp 加入域 Active Directory 活动目录是一个包括文件、打印机、应用程序、服务器、域、用户账户等对象的数据库。 常见概念&#xff1a;对象、属性、容器 域组件&#xff08;Domain Component&#xff0c;DC&#x…