手撕三子棋

news2024/11/30 14:25:51

代码思路:

1.多文件的使用(test.c  game.c  game.h)

2.宏定义的使用

3.打印菜单

4.创建二维数组

5.初始化数组

6.打印棋盘

7.玩家下棋

8.电脑下棋

9.判断输赢

10.代码全析总结

(1)多文件的使用:

 在路径下面创建一个头文件和两个源文件

test.c        里面是 主函数 调用的函数 菜单的打印

game.c        这里面是 游戏主体 的实现

game.h        除了 主函数 之外的 所有函数 以及 宏 所存放的地方

(2)宏定义的使用:

 在我的头文件里面使用宏定义:将每一行,每一列都定义为3

        如果想要修改棋盘的行和列,只需要在头文件的ROW COL后面修改数字即可,这样很方便,提高了效率。

(3)打印菜单:

        为了保证游戏能够多次进行,或者说我们玩玩一把不过瘾,需要再玩一把。我们可以直接把菜单的打印和游戏函数的实现放到一个循环里面。

        因为上来直接可以让你选择是否玩游戏,所以说不管三七二十一直接用do while循环,代码的实现如下:

	int input = 0;
	do
	{
		menu();
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("三子棋游戏开始\n");
			game();//整个游戏的实现主体在这个game函数里面。
			break;
		case 0:
			printf("退出游戏!\n");
			break;
		default:
			printf("选择错误,请重新选择:\n");
			break;
		}
	} while (input);

        

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

        这个循环的控制条件放的是input,为什么要放input呢?初始化input为0,当你选择非零的时候,就会进入case1或者default,循环的结果为真,所以进入循环让你继续游戏或者重新选择。input是零的话就为假,直接跳出循环。

(4)创建二维数组:

char board[ROW][COL] = { 0 };//咱们暂且将二维数组初始化为0。

(5)初始化数组:

        我们现在要充分利用函数的思想,所以要创建一个初始化数组的函数

函数的调用:

InitBoard(board, ROW, COL);//初始化数组

函数的声明:

//初始化数组
void InitBoard(char board[ROW][COL],int row,int col);

函数的实现:

初始化二维数组为空格的话,需要用两个for循环来遍历

//初始化棋盘为空格
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] = ' ';
		}
	}
}

(6)打印棋盘:(重难点!)

函数的调用:

PrintBoard(board, ROW, COL);//打印数组

函数的声明:

//打印数组
void PrintBoard(char board[ROW][COL], int row, int col);

函数的实现:

        棋盘的打印需要用分割线来分隔开 

1.打印数据(包括:“空格+%c+空格” +“|”)

2.打印分割线(包括 :“---”+“|”)

见下图:

        红 蓝总共为一组,每一行需要打印这么多,只不过在最后那一列不用打印,到时候加一个限制条件即可。 

//版本2---打印棋盘
void PrintBoard(char board[ROW][COL], int row, int col)
{
	//1.打印数据
	int i = 0;//这个i相当于作用域内的全局变量
	//所以下面的第2个条件照样使用。
	for (i = 0; i < row; i++)
	{
		int j = 0;
		for (j = 0; j < col; j++)
		{
			printf(" %c ", board[i][j]);
			if (j < col - 1)//限制最后一个,不用打印。
				printf("|");
		}
		printf("\n");//打印完一行后要换行

		//2.打印分割线
		if (i < row - 1)//如果是三子棋的话,只需要打印两行分割线即可,这里的row是3,所以说这需要i=0,1
		{
			int j = 0;
			for (j = 0; j < col; j++)
			{
				printf("---");
				if (j < col - 1)//限制最后一个不用打印。
					printf("|");
			}
			printf("\n");
		}
	}
}

值得注意的是:这个 打印棋盘 是重难点,需要多次反复思考

(7)玩家下棋:(重难点!)

函数的调用:

PlayerMove(board, ROW, COL);//玩家下棋

函数的声明:

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

函数的实现:

//玩家下棋
void PlayerMove(char board[ROW][COL], int row, int col)//这一步仔细想想!!!!
{
	int x = 0;
	int y = 0;
	printf("玩家下棋:>");
	while (1)
	{
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (board[x - 1][y - 1] == ' ')//因为数组的下边从0开始,所以输的数要-1
			{
				board[x-1][y-1] = '*';//符合坐标范围而且是空,才能落子
				break;//当你成功落完子之后,需要赶紧跳出循环,等待电脑下棋。
			}
			else//这个else的对立面,就是空格处有*或#,不能再落子了
				printf("坐标被占用,请重新输入:");
		}
		else//超出范围无法落子
		{
			printf("坐标非法,请重新输入:");
		}
	}
}

代码的每一步解析,在注释中需要读者仔细阅读反复理解

(8)电脑下棋:(重难点!)

函数的调用:

ComputerMove(board, ROW, COL);//电脑下棋

函数的声明:

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

函数的实现:

//电脑下棋
void ComputerMove(char board[ROW][COL], int row, int col)
{
	int x = 0;
	int y = 0;
	
	printf("电脑下棋\n");
	while (1)//这里我们为什么要用一个死循环?如果电脑所下的地方被占用,那么就让它一直循环产生一个随机数,直到产生一个没有被占用的地方。
	{
		// X和Y要放循环内部,让他每一次循环都会产生随机数,不然就会电脑不下棋,形成死循环。
		x = rand() % row;//产生0~row-1的数字
		y = rand() % col;
		if (board[x][y] == ' ')
		{
			board[x][y] = '#';
			break;//这里的break意思是指电脑下棋成功后直接跳出循环。
		}
	}
}

这里电脑下棋需要运用,我们之前在猜数字游戏学过的随机数的产生。

rand---srand---time函数

同样,每一步的解析和疑问都在代码的注释中,需要读者反复阅读理解。

(9)判断输赢:(重难点!)

游戏所进行的状态无非就这几种:

玩家赢---'*'
电脑赢---'#'
平局---'Q'

继续游戏---'C'

后面的字母代表,如果某种情况成立,那么就返回后面所对应的字符:

那么我们就需要创建一个判断输赢的函数(另外在判断平局的时候比较特殊,需要创建一个判断平局的函数:IsFull):

函数的调用:

ret = IsWin(board, ROW, COL);//ret是创建的变量用来接收它的返回值,数据类型:char
IsFull(board, ROW, COL);

函数的声明:

//判断输赢
char IsWin(char board[ROW][COL], int row, int col);
//判断平局
int IsFull(char board[ROW][COL], int row, int col);

函数的实现:

char IsWin(char board[ROW][COL], int row, int col)//因为返回的是字符,所以要用char
{
	//赢
	//行
	int i = 0;
	for (i = 0; i < row; i++)
	{
		//这种判断方法也是很有局限的,也是确定了只能用三子棋,不能用5×5 6×6 10×10的棋盘。
		if (board[i][0] == board[i][1]&& board[i][1] == board[i][2]&& board[i][0] != ' ')//记得这里两个条件判断的时候一定要两两分开!!!
			return board[i][0];
	}
	//列
	for (i = 0; i < row; i++)
	{
		if (board[0][i] == board[1][i]&& board[1][i] == board[2][i]&&board[1][i] != ' ')
			return board[0][i];
	}
	//对角线
	if (board[0][0] == board[1][1]&& board[1][1] == board[2][2]&& board[1][1] != ' ')
		return board[0][0];
	if (board[2][0] == board[1][1]&& board[1][1] == board[0][2]&& board[1][1] != ' ')
		return board[0][2];
	//判断平局
	if (IsFull(board, ROW, COL) == 1)
		return 'Q';
	else
		return '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;//如果返回1那么是平局,平局对应上方的第130行,正好返回Q
}

以上内容是所有三子棋的一步一步的分析,不理解的地方,需要反复阅读认真理解

(10)代码全析总结:

1.test.c:

#define _CRT_SECURE_NO_WARNINGS 1 
#include <stdio.h>
#include "game.h"
void menu()
{
	printf("*********************************\n");
	printf("********	1.play	 *********\n");
	printf("********	0.exit	 *********\n");
	printf("*********************************\n");
}
void game()
{
	//设置一个变量来接收是否平局
	char ret = 0;//这里因为返回一个字符,所以要用char类型
	char board[ROW][COL] = { 0 };
	InitBoard(board, ROW, COL);//初始化数组
	PrintBoard(board, ROW, COL);//打印数组
	while (1)
	{
		PlayerMove(board, ROW, COL);//玩家下棋
		PrintBoard(board, ROW, COL);//打印数组

		ret = IsWin(board, ROW, COL);
		if (ret != 'C')
			break;

		ComputerMove(board, ROW, COL);//电脑下棋
		PrintBoard(board, ROW, COL);//打印数组

		ret = IsWin(board, ROW, COL);
		if (ret != 'C')
			break;
	}
	if (ret == '*')
		printf("玩家赢\n");
	if (ret == '#')
		printf("电脑赢\n");
	if (ret == 'Q')
		printf("平局\n");
}

int main()
{
	srand((unsigned int)time(NULL));
	int input = 0;
	do
	{
		menu();
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("三子棋游戏开始\n");
			game();//整个游戏的实现主体在这个game函数里面。
			break;
		case 0:
			printf("退出游戏!\n");
			break;
		default:
			printf("选择错误,请重新选择:\n");
			break;
		}
	} while (input);
	return 0;
}

2.game.c :

#define _CRT_SECURE_NO_WARNINGS 1 
#include "game.h"
//初始化棋盘为空格
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] = ' ';
		}
	}
}
//版本1
//void PrintBoard(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++)
//		{
//			printf("%c", board[i][j]);
//		}
//		printf("\n");
//	}
//}


//版本2---打印棋盘
void PrintBoard(char board[ROW][COL], int row, int col)
{
	//1.打印数据
	int i = 0;//这个i相当于作用域内的全局变量
	//所以下面的第2个条件照样使用。
	for (i = 0; i < row; i++)
	{
		int j = 0;
		for (j = 0; j < col; j++)
		{
			printf(" %c ", board[i][j]);
			if (j < col - 1)//限制最后一个,不用打印。
				printf("|");
		}
		printf("\n");//打印完一行后要换行

		//2.打印分割线
		if (i < row - 1)//如果是三子棋的话,只需要打印两行分割线即可,这里的row是3,所以说这需要i=0,1
		{
			int j = 0;
			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("玩家下棋:>");
	while (1)
	{
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (board[x - 1][y - 1] == ' ')//因为数组的下边从0开始,所以输的数要-1
			{
				board[x-1][y-1] = '*';//符合坐标范围而且是空,才能落子
				break;//当你成功落完子之后,需要赶紧跳出循环,等待电脑下棋。
			}
			else//这个else的对立面,就是空格处有*或#,不能再落子了
				printf("坐标被占用,请重新输入:");
		}
		else//超出范围无法落子
		{
			printf("坐标非法,请重新输入:");
		}
	}
}
//电脑下棋
void ComputerMove(char board[ROW][COL], int row, int col)
{
	int x = 0;
	int y = 0;
	
	printf("电脑下棋\n");
	while (1)//这里我们为什么要用一个死循环?如果电脑所下的地方被占用,那么就让它一直循环产生一个随机数,直到产生一个没有被占用的地方。
	{
		// X和Y要放循环内部,让他每一次循环都会产生随机数,不然就会电脑不下棋,形成死循环。
		x = rand() % row;//产生0~row-1的数字
		y = rand() % col;
		if (board[x][y] == ' ')
		{
			board[x][y] = '#';
			break;//这里的break意思是指电脑下棋成功后直接跳出循环。
		}
	}
}

char IsWin(char board[ROW][COL], int row, int col)//因为返回的是字符,所以要用char
{
	//赢
	//行
	int i = 0;
	for (i = 0; i < row; i++)
	{
		//这种判断方法也是很有局限的,也是确定了只能用三子棋,不能用5×5 6×6 10×10的棋盘。
		if (board[i][0] == board[i][1]&& board[i][1] == board[i][2]&& board[i][0] != ' ')//记得这里两个条件判断的时候一定要两两分开!!!
			return board[i][0];
	}
	//列
	for (i = 0; i < row; i++)
	{
		if (board[0][i] == board[1][i]&& board[1][i] == board[2][i]&&board[1][i] != ' ')
			return board[0][i];
	}
	//对角线
	if (board[0][0] == board[1][1]&& board[1][1] == board[2][2]&& board[1][1] != ' ')
		return board[0][0];
	if (board[2][0] == board[1][1]&& board[1][1] == board[0][2]&& board[1][1] != ' ')
		return board[0][2];
	//判断平局
	if (IsFull(board, ROW, COL) == 1)
		return 'Q';
	else
		return '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;//如果返回1那么是平局,平局对应上方的第130行,正好返回Q
}

3.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 PrintBoard(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);
//玩家赢--'*'
//电脑赢--'#'
//平局---'Q'
//int IsFull(char board[ROW][COL], int row, int col);
//继续游戏---'C'

以上就是三子棋的全分析

如果对你有帮助,记得点赞👍+关注哦!
我的主页还有其他文章,欢迎学习指点。关注我,让我们一起学习,一起成长吧!

请添加图片描述

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

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

相关文章

MySQL 关于缓存的 “杂七杂八”

开头还是介绍一下群&#xff0c;如果感兴趣polardb ,mongodb ,mysql ,postgresql ,redis 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;在新加的朋友会分到2群&#xff08;共…

中心极限定理模拟

中心极限定理模拟 文章目录 中心极限定理模拟 [toc] 设服从均值为 μ \mu μ、方差为 σ 2 < ∞ \sigma^2<\infty σ2<∞的任意一个总体&#xff0c;抽取样本量为 n n n的样本&#xff0c;当 n → ∞ n\to\infty n→∞&#xff0c;样本均值 X ˉ \bar{X} Xˉ的抽样分布…

云原生之使用Docker部署webos私有云盘

云原生之使用Docker部署webos私有云盘 一、webos介绍1. webos简介2. webos特点 二、本次实践介绍1. 本次实践简介2. 本次部署环境规划 三、检查本地Docker环境1. 检查Docker版本2. 检查Docker服务状态 四、下载webos镜像五、部署webos1. 创建挂载目录2. 创建webos容器3. 查看we…

光栅化过程 (顶点片元着色)

片元为什么是三角形&#xff1a; 1 三角形是最基本的多边形&#xff1b; 2 任何多边形都可以拆分成三角形&#xff1b; 3 空间内任何三个点的连线一定是平面&#xff1b; 4 三角形有清晰的内部和外部定义&#xff1b; 5 三角形只要定义顶点的属性就可以计算三角形内部点的渐变关…

mac iterm2设置rz sz文件传输

1、安装lrzsz $ brew install lrzsz 2、创建并设置iterm2-send-zmodem.sh sudo vim /usr/local/bin/iterm2-send-zmodem.sh # /usr/local/bin/iterm2-send-zmodem.sh#!/bin/bash # Author: Matt Mastracci (matthewmastracci.com) # AppleScript from http://stackoverflow.com…

【云原生】使用外网Rancher2.5.12搭建阿里云内网K8s 1.20集群

目录 一、目标二、解决方案三、草图四、版本信息五、资源规划六、必要条件七、开始部署1、安装Docker2、安装Rancher3、解析Rancher Server URL域名4、创建K8s集群5、注册K8s集群节点 八、验证 一、目标 在云平台搭建一套高可用的K8s集群 二、解决方案 第一种&#xff1a;使…

创客匠人产品怎么样?

创客匠人产品经过多年的更新与送代&#xff0c;现如今应用范畴已经逐渐完善&#xff0c;客户群体也越来越来越广泛。虽然有很多人都有听说过创客匠人&#xff0c;但是对于创客匠人的功能和性质可能并不是得分的了解&#xff0c;为了便于大家选择&#xff0c;下面就由小编我为大…

谈谈几种分布式锁实现

大家好&#xff0c;我是易安&#xff01;今天我们呢谈一谈常见的分布式锁的几种实现方式。 什么是分布式锁 在JVM中&#xff0c;在多线程并发的情况下&#xff0c;我们可以使用同步锁或Lock锁&#xff0c;保证在同一时间内&#xff0c;只能有一个线程修改共享变量或执行代码块…

Linux监听器 -- inotify

inotify作为Linux系统的一个监听器&#xff0c;能够监听文件或者目录的变化。 inotify接口 inotify的接口主要有三个&#xff0c;分别是inotify_init、inotify_add_watch 和 inotify_rm_watch。下面分别进行详细介绍。 inotify_init 函数用于创建inotify句柄&#xff0c;函数…

一图看懂 pytz 模块:现代以及历史版本的世界时区定义数据库,资料整理+笔记(大全)

本文由 大侠(AhcaoZhu)原创&#xff0c;转载请声明。 链接: https://blog.csdn.net/Ahcao2008 一图看懂 pytz 模块&#xff1a;现代以及历史版本的世界时区定义&#xff0c;将时区数据库引入 Python&#xff0c;资料整理笔记&#xff08;大全&#xff09; &#x1f9ca;摘要&am…

算法小课堂(七)字符串

一、概念 1.1相关概念 0C中有两种字符串表示形式&#xff1a;C风格字符串和string类类型。 C风格字符串是 字符数组使用null字符\0终止的一维字符数组&#xff0c;字符指针是一个指针变量&#xff0c;里面存放一个字符的地址 而string类处理起字符串来会方便很多&#xff0c;完…

Golang中的数组和切片

目录 数组 基础知识 声明并初始化一个数组 遍历一个数组 切片 基础知识 声明并初始化一个切片 向切片中添加元素 切片的遍历和切片表达式 数组和切片的区别 数组 基础知识 数组是一种由固定长度的特定类型元素组成的序列&#xff0c;元素可以是任何数据类型&#x…

数组——知识点大全(简洁,含使用演示和代码)

目录 一.一维数组的创建 1.数组的基本形式 2.变长数组 3.数组的初始化 二.数组的本质 三.一维数组的使用 1.访问数组成员 2.计算数组的大小 四.一维数组在内存中的存储 五.二维数组 1.二维数组的形式 2.二维数组的初始化规则 六.二维数组的使用 1.打印二维数组 …

Linux下新加新磁盘分区及挂载

一&#xff1a;新插入磁盘查看 查看插入磁盘 法1&#xff1a;$sudo fdisk -l 法2&#xff1a; $sudo lsblk 二&#xff1a;磁盘分区及格式化 1: 分区 $sudo fdisk /dev/nvme0n1 进入分区工具后&#xff0c;我们可以输入 m 看指令说明&#xff1a; Command (m for help): …

前端框架比较:Vue.js、React、AngularJS三者的优缺点和应用场景

章节一&#xff1a;引言 在当前的互联网开发中&#xff0c;前端框架已经成为了不可或缺的一部分。然而&#xff0c;前端框架如此之多&#xff0c;该如何选择呢&#xff1f;Vue.js、React和AngularJS是目前比较受欢迎的三个前端框架&#xff0c;它们各自有着不同的优缺点和应用…

MySQL笔记(三) 联结、组合查询、全文本搜索、视图、索引、触发器、事务

文章目录 联结关系表为什么要使用联结维护引用完整性 内部联结联结多个表创建高级联结使用表别名 自联接自然联结外部联结 OUTER JOIN外部联结的类型 使用带聚集函数的联结要点 组合查询 UNION创建规则注意 全文本搜索查询拓展布尔文本搜索总结 视图为什么使用视图视图的规则和…

虚拟机与主机互传文件方法分享

现在虚拟机的使用已经非常普及&#xff0c;无论新手学习&#xff0c;还是运维工程师搭建虚拟化平台&#xff0c;都会使用到虚拟机。对个人用户来说&#xff0c;非常方便就能搭建很多操作系统进行学习&#xff1b;对企业用户来说更是降低了服务器的硬件成本。 使用虚拟机的时候…

本周日直播,全链路数据治理实践论坛开放报名

5月14日&#xff0c;09:00-12:00&#xff0c;由阿里云资深技术专家温绍锦老师出品的 DataFun Summit 2023&#xff1a;数据治理在线峰会-全链路数据治理论坛&#xff0c;将邀请来自阿里、Aloudata大应科技、爱奇艺的4位专家就相关主题进行深度分享&#xff0c; 出品人&#xff…

JAVA-继承

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 目录 文章目录 前言 1.为什么需要继承 2. 继承的语法 3.子类构造方法 4.super和this的异同 5.final 关键字 总结 前言 继承是面向对象语法的三大特征之一。在以后写…

【Python 装饰器成长路径】零基础也能轻松掌握的学习路线与参考资料

Python装饰器是Python语言的一个重要特性&#xff0c;可以让代码更加简洁、优雅&#xff0c;并且让代码重用性更加高效。本文针对Python装饰器的学习路线&#xff0c;参考资料和优秀实践进行详细介绍。 文章目录 一、学习路线二、参考资料三、优秀实践 一、学习路线 了解函数…