基于C语言 --- 自己写一个三子棋小游戏

news2024/11/17 0:54:40

C语言程序设计笔记---019

  • 初阶三子棋小游戏(开源)
    • 1、arr_main.c程序大纲
    • 2、arr_game1.h
    • 3、arr_game1.c
      • 3.1、 自定义初识化函数 InitBoard( ) 和 自定义显示函数 DisPlayBoard( )
      • 3.2、 自定义玩家下棋函数 PlayerMove( )
      • 3.4、 自定义电脑下棋函数 ComputerMove( )
      • 3.5、 输赢判断函数 IsWin( )
    • 4、结束语

初阶三子棋小游戏(开源)

前言:
游戏规则:
(1)、在打印的数组棋盘里,输入坐标,下棋。
(2)、坐标格式:x(空格) y
(3)、x横坐标,y竖坐标
(4)、连成三个棋子为一线则判定胜利
如图所示
在这里插入图片描述

采用模块化编写
arr_game1.c执行主要逻辑程序
arr_game1.h存放头文件或函数声明等程序
arr_main.c放主函数逻辑程序

1、arr_main.c程序大纲

首先,从以往玩游戏的经验来谈,我们需要为游戏写一个游戏开始菜单,由玩家选择是否开始游戏。
这里可以借助所学的menu( )自定义函数,设计一个简易的菜单

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

当我们选择1,则开始游戏;当选择0,则退出游戏。
那么就得思考,如何对玩家得选择进行判定
1.利用 scanf( ) 函数获取输入值,将获取的值,借用 do while 循环语句和switch( )选择语句,进行下一步。
2.当选择1,开始游戏则执行game()自定义,游戏主逻辑执行程序
3.当选择0,switch中 进入case 0 : 的入口,执行退出游戏,并且 do while( ),判定为0,则退出程序
4.当玩家误选择非法数值,则default : 提示玩家输入错误

int input = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("\n*********** 三子棋游戏开始 *************\n\n");
			game();//游戏执行逻辑函数
			break;
		case 0:
			printf("\n退出游戏\n");
			break;
		default:
			printf("\n选择错误请重新选择\n\n");
			break;
		}

	} while (input);

接下来,主要阐述game( )函数内容,游戏的执行逻辑
1.当玩家选择1,开始游戏后,会显示整个空棋盘
如何实现空棋盘的显示呢?
(1)、利用所学的数组知识可以联想到,棋盘不过是由一个个字符拼接而成,在需要下棋的地方,放置空格字符进行占位,达到肉眼所见的空棋盘。
(2)、又因为棋盘是一个平面,平面由一条条线组成,线又由一个个点组成,首先得定义和初识化一个二维数组 board[ROW][COL] ,由几行几列的方式实现一个面的显示/打印
(3)、自定义初识化函数 InitBoard( ) ,自定义显示棋盘函数 DisPlayBoard( ) ;
(4)、函数的参数,可想而知,需要数组名(board) — 首元素的地址指定需操作的数组,需要行(ROW)和列(COL)指定操作的元素或坐标或地址;

void game()
{
	//定义棋盘大小
	char board[ROW][COL] = { 0 };
	//初识化棋盘
	InitBoard(board,ROW,COL);
	//显示棋盘
	DisPlayBoard(board, ROW, COL);
	
}

2.当我们完成棋盘的初识化和显示后,那么默认玩家先下棋,电脑再下棋;
所以就得写一个玩家下棋的自定义逻辑函数和电脑下棋的自定义函数,交替下棋直到游戏判定输赢或平局
(1)、玩家下棋自定义函数 PlayerMove( ) ;
(2)、电脑下棋自定义函数 ComputerMove( ) ;
(3)、判断输赢函数 IsWin( );
那么很容易思考到,交替进行下棋,就是反复的调用玩家下棋函数和电脑下棋函数,同时不停的判断每一步棋是否达到输赢的结果。所以需要一个循环来实现,这里就用while循环,实现不停的下棋。
但是,不停的下棋,始终在死循环,那么就思考利用,break跳出循环,所以 IsWin( )函数是具备返回值的,可以由IsWin( )函数的返回值进行判定跳出。 且由于棋局均有字符进行的操作,所以确定了 IsWin( )数据类型也为 char 类型。
(4)、IsWin( )跳出循环的返回值判定
a、玩家获胜 - - - - ‘ * ’
a、电脑获胜 - - - - ‘ # ’
a、平局 - - - - ‘ Q ’
a、游戏继续 - - - - ‘ C ’

//游戏执行逻辑函数
void game()
{
	//定义棋盘大小
	char board[ROW][COL] = { 0 };
	//初识化棋盘
	InitBoard(board,ROW,COL);
	//显示棋盘
	DisPlayBoard(board, ROW, COL);
	//根据IsWin()返回值,判断输赢
	char ret = 0;
	//下棋
	while (1)
	{
		//玩家下棋
		PlayerMove(board, ROW, COL);
		//判断输赢
		ret = IsWin(board, ROW, COL);
		if (ret != 'C')
		{
			break;
		}
		//显示棋局
		DisPlayBoard(board, ROW, COL);
	    //电脑下棋
	    ComputerMove(board, ROW, COL);
		//判断输赢
		ret = IsWin(board, ROW, COL);
		if (ret != 'C')
		{
			break;
		}
		//显示棋局
		DisPlayBoard(board, ROW, COL);
	}
	if (ret == '*')
		printf("\n玩家赢\n\n");
	else if (ret == '#')
		printf("\n电脑赢\n\n");
	else
		printf("\n平局\n\n");
	//显示棋局
	DisPlayBoard(board, ROW, COL);
}

arr_main.c程序大纲展示

#include "arr_game1.h"

//游戏菜单
void menu()
{
	printf("****************************************\n");
	printf("************** 1.play game *************\n");
	printf("************** 0.game over *************\n");
	printf("****************************************\n");
}
//游戏执行逻辑函数
void game()
{
	//定义棋盘大小
	char board[ROW][COL] = { 0 };
	//初识化棋盘
	InitBoard(board,ROW,COL);
	//显示棋盘
	DisPlayBoard(board, ROW, COL);
	//根据IsWin()返回值,判断输赢
	char ret = 0;
	//下棋
	while (1)
	{
		//玩家下棋
		PlayerMove(board, ROW, COL);
		//判断输赢
		ret = IsWin(board, ROW, COL);
		if (ret != 'C')
		{
			break;
		}
		//显示棋局
		DisPlayBoard(board, ROW, COL);
	    //电脑下棋
	    ComputerMove(board, ROW, COL);
		//判断输赢
		ret = IsWin(board, ROW, COL);
		if (ret != 'C')
		{
			break;
		}
		//显示棋局
		DisPlayBoard(board, ROW, COL);
	}
	if (ret == '*')
		printf("\n玩家赢\n\n");
	else if (ret == '#')
		printf("\n电脑赢\n\n");
	else
		printf("\n平局\n\n");
	//显示棋局
	DisPlayBoard(board, ROW, COL);
}

int main()
{
	srand((unsigned int)time(NULL));//初识化随机值生成器
	int input = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("\n*********** 三子棋游戏开始 *************\n\n");
			game();//游戏执行逻辑函数
			break;
		case 0:
			printf("\n退出游戏\n");
			break;
		default:
			printf("\n选择错误请重新选择\n\n");
			break;
		}

	} while (input);
	return 0;
}

2、arr_game1.h

用于存放所自定义的函数和头文件等声明的程序
通俗易懂,就不多赘述,详见代码注释的说明。

#include <stdio.h>
#include <time.h>//调用time函数,所需声明的头文件
#include <stdlib.h>//调用srand和rand函数,所需声明的头文件

//宏定义棋盘大小
#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);

3、arr_game1.c

用于存放对 arr_main.c 程序大纲做提到的函数进行封装,实现具体的功能的程序
说明:基于arr_main.c 程序大纲逻辑对代码进行讲解
注意:这里均以3*3的棋盘为例哦

3.1、 自定义初识化函数 InitBoard( ) 和 自定义显示函数 DisPlayBoard( )

首先,根据需求我们需要一个棋盘才可以正常的下棋.
如何让棋盘初识化呢?
1.根据所学的二维数组知识,便可以知道,当我们遍历二维数组的每一个元素,使得填充为空格字符,便可以由InitBoard( ) 达到效果。

//初识化棋盘
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] = ' ';
		}
	}
}

如图所示
在这里插入图片描述
如图所示
在这里插入图片描述

2.但是,这样的棋盘全是空格字符,只有程序员可见字符空格,玩家并不知道,所以将对棋盘进行修饰美化,使得大众化。所以借助 DisPlayBoard( ) 达到修饰棋盘的效果。
这里使用一些分隔符,将棋盘划分出,三组,并且数组元素加分隔符为一组。

//打印棋盘
void DisPlayBoard(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]);
			printf("|");
		}
		printf("\n");//每行打印完换行
	}
	printf("\n");
}

如图所示:
在这里插入图片描述

但是目前的效果达不到理想的棋盘效果,导致最后一组的分隔符,也被打印了出来。
所以思考可知:由 if (j < col - 1) 进行判断让最后一行的分割符被约束不打印出来。
然后打印完一行,换行一次
同理,每一列打印完,使用 if (i < row - 1) 使得最后一列的分隔符不被打印出来
所以优化后得到以下代码:

//打印棋盘
void DisPlayBoard(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("---");
				if (j < col - 1)
					printf("|");
			}
			printf("\n");//每行打印完换行
		}
	}
	printf("\n");
}

如图所示
在这里插入图片描述
棋盘显示最终效果如图所示
在这里插入图片描述

3.2、 自定义玩家下棋函数 PlayerMove( )

1.首先,我们下棋,需要给棋子一个坐标,并且需要以作为玩家的角度,思考坐标的取值范围。因为玩家不一定知道数组的下标从0开始,所以我们定义 x,y 坐标变量,使得 if (x >= 1 && x <= row && y >= 1 && y <= col )//符合玩家下棋坐标的逻辑
2.当我们坐标符合了范围,还得判定玩家输入错误坐标超出范围时,给予提示信息,超出范围,重新输入。
3.当玩家输入坐标范围正确,但是此时的坐标处已经被上一步棋子占用时,也得提示重新输入。

//玩家下棋
void PlayerMove(char board[ROW][COL], int row, int col)
{
	//定义下棋的坐标
	int x = 0;
	int y = 0;
	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 - 1][y - 1] = '*';
				break;
			}
			else
			{
				printf("\n坐标被占用,请重新输入坐标\n");
			}
		}
		else 
		{
			printf("\n坐标超出棋盘范围,请重新输入棋子,坐标\n");
		}
	}
}

如图所示
在这里插入图片描述

3.4、 自定义电脑下棋函数 ComputerMove( )

1.下棋的逻辑与玩家如出一辙,不多赘述。
2.这里强调一下,电脑如何实现产生随机的坐标下棋?
首先,在之前的篇章链接: link 里提到的srand函数与rand函数的应用原理相同。通过初识化srand随机值生成器函数,提供变化的随机值范围,给rand随机值函数,将rand()%row 和 rand()%col,就可以得到符合范围的数组元素坐标。
然后根据srand的参数要求,放一个时刻变化的数据,这里就用到了time时间转换函数,相互作用即可。

查阅资料文档的具体参数:
void srand( unsigned int seed ); 头文件 <stdlib.h>
int rand(void ); 头文件 <stdlib.h>
time_t time( time_t *timer ); 头文件 <time.h>

所以得到:

srand((unsigned int)time(NULL));
x = rand() % row;
y = rand() % col;

注意:srand只需要整个工程初识化一次即可

//电脑下棋
void 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;
		if (board[x][y] == ' ')
		{
			board[x][y] = '#';
			break;
		}
	}
}

如图所示
在这里插入图片描述

3.5、 输赢判断函数 IsWin( )

由arr_main.c程序大纲提到的思路可知:
如何判定输赢:
玩家赢 — ‘*’
电脑赢 — ‘#’
平局 ---- ‘Q’
继续游戏 — ‘C’
所以可以明确 IsWin( ) 的返回值类型为char字符型

根据三子棋的游戏规则,三个棋子连城一线即可判定获胜,所以有:
(1)、一行三个棋子
(2)、一列三个棋子
(3)、对角线三个棋子
那么如何判定平局呢?
顾名思义,当我们的棋盘被占用完,还是没有连成一线,则判定为平局。
这里利用 int IsFull( )函数,遍历判断数组每个元素是否已用完即可,当返回0,则仍然有空位,继续游戏;当返回1,则所有元素均被占用且未连成一线,判定为平局。


//判断输赢
//玩家赢 --- '*'
//电脑赢 --- '#'
//平局 ---- 'Q'
//继续游戏 --- 'C'
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][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[1][i] != ' ')
		{
			return board[1][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];//*/#
	}
	//判断平局
	int ret = Isfull(board, ROW, COL);
	if (ret == 1)//棋盘无空位,平局
	{
		return 'Q';
	}
	//以上所有情况不满足时,继续游戏
	return 'C';
}

输赢结果如图所示
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4、结束语

相信通过这样一个三子棋的小游戏,更具掌握了对数组的操作以及对自定义函数的深刻认识;
如果觉着文章对您有所帮助,请不要吝啬的一赞三连哦,谢谢阅读,不足之处还请多多指教。

三子棋源码获取链接: 三子棋

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

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

相关文章

成为一名黑客(网络安全),需要掌握哪些黑客技能?

前言 黑客技能是一项非常复杂和专业的技能&#xff0c;需要广泛的计算机知识和网络安全知识。你可以参考下面一些学习步骤&#xff0c;系统自学网络安全。 在学习之前&#xff0c;要给自己定一个目标或者思考一下要达到一个什么样的水平&#xff0c;是学完找工作&#xff08;…

使用DataX实现mysql与hive数据互相导入导出

一、概论 1.1 什么是DataX DataX 是阿里巴巴开源的一个异构数据源离线同步工具&#xff0c;致力于实现包括关系型数据库(MySQL、Oracle 等)、HDFS、Hive、ODPS、HBase、FTP 等各种异构数据源之间稳定高效的数据同步功能。 1.2 DataX 的设计 为了解决异构数据源同步问题&#xf…

【外卖系统】分类管理业务

公共字段自动填充 需求分析 对于之前的开发中&#xff0c;有创建时间、创建人、修改时间、修改人等字段&#xff0c;在其他功能中也会有出现&#xff0c;属于公共字段&#xff0c;对于这些公共字段最好是在某个地方统一处理以简化开发&#xff0c;使用Mybatis Plus提供的公共…

简单记录牛客top101算法题(初级题C语言实现)判断回文字符串 反转字符串 合并两个有序的数组

1. 判断是否为回文字符串 给定一个长度为 n 的字符串&#xff0c;请编写一个函数判断该字符串是否回文。如果是回文请返回true&#xff0c;否则返回false。   字符串回文指该字符串正序与其逆序逐字符一致。 //示例 输入&#xff1a;"ranko" 返回值&#xff1a;fa…

DevOps系列文章之 自动化测试大全(单测和集成测试)

自动化测试业界主流工具 核心目标&#xff1a; 主要是功能测试和覆盖率测试 业界常用主流工具 GoogleTest GoogleTest是一个跨平台的(Liunx、Mac OS X、Windows 、Cygwin 、Windows CE and Symbian ) C单元测试框架&#xff0c;由google公司发布&#xff0c;为在不同平台上为编…

案例:缺陷个数与返工工作量强相关

某公司积累了21个项目缺陷个数与返工工作量的数据&#xff0c;如下表所示&#xff1a; 项目序号缺陷修复工时缺陷数1943314452299040536347446471385496071061370246774066812232189276652810830213781162678126111511381110514209032015144023516516078417710010301875601239…

3.Makefile变量的用法(附示例)

一、本节概要 本专栏所有内容围绕Makefile官方文档进行刨析,给出详细具体示例做辅助理解手撕Makefile官方手册 二、Makefile中的变量 1、没有使用变量的makefile 以下是不使用变量的makefile完整示例: edit: main.o kbd.o command.o display.o insert.o search.o files…

django channels实战(websocket底层原理和案例)

1、websocket相关 1.1、轮询 1.2、长轮询 1.3、websocket 1.3.1、websocket原理 1.3.2、django框架 asgi.py在django项目同名app目录下 1.3.3、聊天室 django代码总结 小结 1.3.4、群聊&#xff08;一&#xff09; 前端代码 后端代码 1.3.5、群聊&#xff08;二&#xff09…

网络编程 IO多路复用 [epoll版] (TCP网络聊天室)

//head.h 头文件 //TcpGrpSer.c 服务器端 //TcpGrpUsr.c 客户端 通过IO多路复用实现服务器在单进程单线程下可以与多个客户端交互 API epoll函数 #include<sys/epoll.h> int epoll_create(int size); 功能&#xff1a;创建一个epoll句柄//创建红黑树根…

TypeScript算法题实战——剑指 Offer篇(5)

目录 一、平衡二叉树1.1、题目描述1.2、题解 二、数组中数字出现的次数2.1、题目描述2.2、题解 三、数组中数字出现的次数 II3.1、题目描述3.2、题解 四、和为s的两个数字4.1、题目描述4.2、题解 五、和为s的连续正数序列5.1、题目描述5.2、题解 六、翻转单词顺序6.1、题目描述…

《cuda c编程权威指南》01- 用gpu输出hello world

学习一门新语言的最好方式就是用它来编写程序。 目录 1. 使用cpu输出hello world 2. 使用gpu输出hello world 3. CUDA编程结构 1. 使用cpu输出hello world hello.cu #include <stdio.h>void helloFromCPU() {printf("hello world from cpu!\n"); }int m…

Zabbix分布式监控配置和使用

目录 1 Zabbix监控的配置流程2 添加主机组3 添加模板4 添加主机5 配置图形6 配置大屏7 新建监控项7.1 简介7.2 添加监控项7.3 查看数据7.4 图表 8 新建触发器8.1 概述8.2 添加触发器8.3 显示触发器状态 1 Zabbix监控的配置流程 在Zabbix-Web管理界面中添加一个主机&#xff0c;…

【Golang 接口自动化00】为什么要用Golang做自动化?

目录 为什么使用Golang做自动化 最终想实现的效果 怎么做&#xff1f; 写在后面 资料获取方法 为什么使用Golang做自动化 顺应公司的趋势学习了Golang之后&#xff0c;因为没有太多时间和项目来实践&#xff0c;怕止步于此、步Java缺少练习遗忘殆尽的后尘&#xff0c;决定…

【C++进阶之路】多态篇

文章目录 前言一、概念1.分类2.实现条件①重写虚函数1.1总结三重1.2 final与override ②父类的指针或者引用2.1普通调用VS多态调用 3.抽象类3.1. 纯虚函数3.2. 接口继承和实现继承 二、原理及使用1.虚函数表 —— 虚表2.默认成员函数2.1构造函数2.2析构函数 3. 多继承3.1普通的…

python速成之循环分支结构学习

循环结构 应用场景 我们在写程序的时候&#xff0c;一定会遇到需要重复执行某条或某些指令的场景。例如用程序控制机器人踢足球&#xff0c;如果机器人持球而且还没有进入射门范围&#xff0c;那么我们就要一直发出让机器人向球门方向移动的指令。在这个场景中&#xff0c;让…

AD21原理图的高级应用(六)原理图设计片段的使用

&#xff08;六&#xff09;原理图设计片段的使用 Altium Designer 的片段功能可以很方便地重复使用一些单元模块,其中包括原理图的电路模块、PCB(包括布线)和代码模块。例如在工程中需要设计电源模块,而别的工程中又恰好有比较完善的电源模块,这时就可以通过片段功能重复地使用…

一文了解 Android 车机如何处理中控的旋钮输入?

前言 上篇文章《从实体按键看 Android 车载的自定义事件机制》带大家了解了 Android 车机支持自定义输入的机制 CustomInputService。事实上&#xff0c;除了支持自定义事件&#xff0c;对于中控上常见的音量控制、焦点控制的旋钮事件&#xff0c;Android 车机也是支持的。 那…

测试|测试用例方法篇

测试|测试用例方法篇 文章目录 测试|测试用例方法篇1.测试用例的基本要素&#xff1a;测试环境&#xff0c;操作步骤&#xff0c;测试数据&#xff0c;预期结果…2.测试用例带来的好处3.测试用例的设计思路&#xff0c;设计方法&#xff0c;具体设计方法之间的关系**设计测试用…

linux设备驱动的poll与fasync

什么是fasync 在 Linux 驱动程序中&#xff0c;fasync 是一种机制&#xff0c;用于在异步事件发生时通知进程。它允许进程在等待设备事件时&#xff0c;不必像传统的轮询方式那样持续地查询设备状态。 具体来说&#xff0c;当进程调用 fcntl(fd, F_SETFL, O_ASYNC) 函数时&am…

lib-flexible修改配置适配更多不同分辨率

找到设置宽度的地方 然后根据你的屏幕最大多大呀&#xff0c;最小多小呀设置一下 if (width / dpr < 1980) { width 1980 * dpr; } else if (width / dpr > 5760) { width 5760 * dpr; }