IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 用C语言来实现五子棋小游戏 -> 正文阅读

[C++知识库]用C语言来实现五子棋小游戏

目录

一、五子棋的准备工作

二、五子棋的具体实现

1.棋盘

2.人机对战

2.1 玩家下棋

2.2 电脑下棋

2.3 判断输赢

三、总结


一、五子棋的准备工作

我们在写一个项目的时候,首先第一步就是应该规划一个项目应该有哪些文件,然而五子棋的代码逻辑比较简单,这里我就分了三个文件来写:

  • ????????game.h(这个文件用来存储头文件和函数的声明),
  • ? ? ? ? game.c(这个文件用来存储具体的实现代码),
  • ? ? ? ? test.c(这个文件用来测试我们的代码)

第二步就是规划一个项目应该有哪些功能或者说有哪些机制,比如说五子棋,他可能就有人机对战,玩家下一步,电脑下一步,判断棋盘有没有一片区域是连成一块的,这些都是我们要考虑到的,并且把他们一个个封装成函数,整个逻辑就可以运行起来,下面是具体要实现的一些功能的函数并且还有相应的头文件,我都放到game.h里面了

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

//棋盘的长度和宽度
#define ROW 5
#define COL 5


//初始化
void board_init(char arr[ROW][COL], int row, int col);

//打印
void board_display(char arr[ROW][COL], int row, int col);

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

//电脑下棋
computer_move(char arr[ROW][COL], int row, int col);

//判断是否赢了
char is_win(char arr[ROW][COL], int row, int col);

二、五子棋的具体实现

1.棋盘

下五子棋,第一步就是要有一个棋盘,我们常见可能有3*3的棋盘或5*5的棋盘,用来下井字棋最合适,我本来以为我写的棋盘可能会和下面这些图一样。

?但是没想到自己写的棋盘长这样,其实也还能看。

?下面就是具体实现过程:?

? ? ? ? 我们会发现棋盘的每一个格子是由上下两部分组成的,首先是上面部分是由两个空格加上中间的数据区域加上右边的竖杠(|),然后是下面部分是由三个减号(-)加上右边的竖杠(|),但是我们会发现我们数据区域好像一开始也是空格,那是因为在实现打印棋盘的函数之前,我先给所有区域都初始化为空格,为什么要初始化空格呢?首先初始化空格可以帮助我们占一个位置,如果你不初始化,很可能就是随机值,到时候打印出来就会很奇怪,那为什么一定要初始化空格呢?首先空格可以帮助我们占一个位置,就是把右边空格顶过去,如果你初始化为0,就不会帮你占位置,右边的空格就和左边的空格挨在一起,就会很难看,下面是初始化的代码:

//初始化
void board_init(char arr[ROW][COL], int row, int col) {
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++) {
		for (j = 0; j < col; j++) {
			arr[i][j] = ' ';
		}
	}
}

这样我们很快就分析出,棋盘第一行是数据区域,第二行是分隔线,然后以此类推,每一行都是这样的,那我们就想先用for循环遍历第一行把数据的那一部分先打印出来,然后用第二个for循环把分割线也打印出来,但是这样就会有一个问题,我们会发现图中右边和下边多出了一行分割线。

?所以我们还要加一个限制条件,当j<col-1时,我就不再打印竖杠(|),当i<row-1时,我就不再打印减号(-),具体打印棋盘实现代码如下:

//打印
void board_display(char arr[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 ", arr[i][j]);
			if (j < col - 1)
				printf("|");
		}
		printf("\n");
		for (j = 0; j < col; j++) {
			if (i < row - 1) {
				printf("---");
				if (j < col - 1)
					printf("|");
			}
		}
		if (i < row - 1)
			printf("\n");
	}
}


2.人机对战

棋盘已经有了,下一步就是人机对战,大致逻辑就是玩家走一步,电脑走一步,然后每次判断棋盘有没有已经连成一片的区域,如果有,则返回连成那一片区域里的棋子,返回*(星号)玩家胜利,返回#(井号)电脑胜利,返回q代表游戏平局,就是所有棋盘都下满了,结果还没有连成一块,就平局,如果上面三条都不符合,则返回c(continue)表示游戏继续,这就是五子棋里面的所有逻辑。

2.1 玩家下棋

玩家要怎么下棋呢?我们这个棋盘有可能是3*3或5*5,最好的方式就是利用坐标来下棋,每次让玩家输入坐标,输入一个合理的坐标,我们就在棋盘相应的位置放置一颗*(星号),当然我们必须去检查玩家输入的坐标,玩家可能好奇心上来,比如输入一个(10 10),很明显超出了棋盘范围,这时就要友好的提示一下,说玩家“输入坐标不合法,请重新输入”,很明显这里应该设置一个循环,直到玩家输入了正确的坐标,我才跳出这个循环,那我们分析完之后,这里代码就好写了,首先定义两个变量x,y,然后让玩家输入,但是玩家不知道我们这个棋盘是从0开始的,可能一开始以为是从1开始的,其实问题也不大,我们只需要一开始设置条件x>=1且x<=row且y>=1且y<=col,玩家输入的范围就只能在1到棋盘最大界限之间,然后后面给玩家输入的数字-1就行,比如玩家输入了两个数(1 1),我们程序自动为这两个数-1,并且在相应位置上放上*(星号),这样玩家就能看到具体效果了。

?下面是具体实现代码:

//玩家下棋
void player_move(char arr[ROW][COL], int row, int col) {
	int x = 0;
	int y = 0;
	printf("玩家下棋\n");
	printf("请输入坐标:>");
	scanf("%d%d", &x, &y);
	while (1) {
		if (x >= 1 && x <= row && y >= 1 && y <= col) {
			if (arr[x - 1][y - 1] == ' ') {
				arr[x - 1][y - 1] = '*';
				break;
			}
			else {
				printf("已有旗子,请重新输入\n");
			}
		}
		else {
			printf("输入坐标不合法,请重新输入\n");
		}
		printf("玩家下棋\n");
		printf("请输入坐标:>");
		scanf("%d%d", &x, &y);
	}
}

2.2 电脑下棋

电脑怎么下棋呢?其实就更简单了,比玩家下的逻辑还简单一点,电脑下棋就随机下,哪里有位置,就往哪里下,首先定义x,y两个变量,并给这两个变量赋值成随机值,这个随机值范围是0到棋盘最大界限之间,这里就可以调用rand()函数,但是rand()函数的随机值的范围是0-32767,我们肯定要控制一下,看了文档之后,才知道这个函数是可以控制的,其实只需要用这个函数随机出来的值去%(取模)棋盘的最大界限值,假设棋盘的最大界限值是3,然后随机值对3取模,那得出来的结果一定是0-3之间的数,但是不包括3,这样就拿到我们想要的结果了,但是好像还是有问题,每次随机的结果竟让都是一样的。

这不是同一张图,而是我运行了两次程序,却发现每次结果都是一样的,然后我又去看了文档,之后才明白,在用这个函数之前,还要再调用一个srand()函数,才能使rand()函数,每次出来的值都是不一样的,但是srand()函数,也要传一个随机值,那这不就完了,我现在想要的就是随机值,结果你告诉我,还得传个随机值给你,其实文档解释的很清楚,只要是一直动的数字就行了,什么东西会一直动呢?那就是时间,具体来说是时间戳,时间戳就是指(北京时间1970年01月01日08时00分00秒)起至现在的总秒数。那问题现在就简单多了,得到时间戳传给你不就完了,时间戳也有个函数叫做time(),这个函数就可以返回一个时间戳,没有参数就在括号里面写NULL,但是这个函数返回值类型是time_t,好家伙我都没见过这个类型,转到定义才发现是typedef重新定义的一个名字,但是typedef写的又是另一个类型__time64_t,好家伙更没见过了,再次转到定义,才发现就是一个int64位类型,但是srand函数的参数要一个unsigned int的类型,这就更简单了,老师讲过强制类型转换,我们只需要转换一下,这样我们的问题就都解决了。

这样每次结果都不一样,这才是我想要的,剩下就简单了,判断棋盘的格子里面是不是空格,如果是空格,则就可以落子了,不是空格,就说明这个格子被占用了,那我们就重新获取随机数,直到可以落子,然而我们发现这又是一个循环,内容都分析差不多了,代码就很容易实现了,代码如下:

//电脑下棋
computer_move(char arr[ROW][COL], int row, int col) {

	printf("电脑下棋\n");
	while (1) {
		int x = rand() % row;
		int y = rand() % col;
		if (arr[x][y] == ' ') {
			arr[x][y] = '#';
			break;
		}
	}
}

2.3 判断输赢

玩家下棋,电脑下棋,我们都说完了,只剩下判断输赢了,如果返回*(星号),则玩家赢,如果返回#(井号),则电脑赢,如果返回q,则平局,以上情况都不满足,则返回c(continue),游戏继续。判断输赢其实很简单,五子棋,棋盘为5*5,看的就是谁能连成5个棋子,那他的情况也就那么几种,先看每一行有没有连成的棋子,再看每一列有没有连成的棋子,最后再看两条对角线,如果都没有连成5个棋子,判断棋盘是否已满,如果满了,则平局,没满则继续下棋,棋盘5*5就这么些情况,如果棋盘再大一点的话,判断东西可能就复杂了,我们先实现简单的,后面有能力再去实现复杂的。然后具体操作逻辑就是对每一行或每一列遍历,首先我会先把第一行或第一列的第一个字符先保存下来,这个字符可能是#(井号)也可能是*(星号),还可能是空格,然后我再一个一个的跟后面的字符比较,如果全部相同,则返回当前的字符,当然在比较之前一定要加条件,首先这个字符一定不能是空格,不是空格我才接着和后面的字符比较,全部相同则返回当前字符,比较之中如果有一个不相同,赶紧break,没有必要进行下一次比较了。然后后面的逻辑都是这样处理的,包括两条对角线也是这样处理的。还有个平局的逻辑没讲到,其实也很简单,创建一个函数is_full(),这个函数专门用来看棋盘满没满,如果上面四种情况都不满足,就来看棋盘有没有满,其实棋盘有没有满也很好判断,就是遍历一遍棋盘看还有没有多余的空格,如果一个空格都没有了,说明平局了,这时候我们就会返回q,游戏就平局了。如果以上五种情况都没有发生,说明游戏还没有结束,则返回c(continue),游戏继续,一直到两方有一方赢了,或者平局,游戏才会结束。整体逻辑就是这样,分析完了,就开始写代码了,代码如下:

//判断是否满了
static int is_full(char arr[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 (arr[i][j] == ' ') {
				return 1;
			}
		}
	}
	return 0;
}

//判断是否赢了
char is_win(char arr[ROW][COL], int row, int col) {
	char tmp = 0;
	int i = 0;
	int j = 0;

	//判断是否有一行连成一片
	for (i = 0; i < row; i++) {
		tmp = arr[i][0];
		for (j = 1; j < col; j++) {
			if (tmp == arr[i][j] && tmp != ' ') {
				if (j == col - 1) {
					return tmp;
				}
			}
			else {
				break;
			}
		}
	}

	//判断是否有一列连成一片
	for (j = 0; j < col; j++) {
		tmp = arr[0][j];
		for (i = 1; i < row; i++) {
			if (tmp == arr[i][j] && tmp != ' ') {
				if (i == row - 1) {
					return tmp;
				}
			}
			else {
				break;
			}
		}
	}

	//判断左对角线是否有连成一片
	tmp = arr[0][0];
	for (i = 1; i < row; i++) {
		if (tmp == arr[i][i] && tmp != ' ') {
			if (i == row - 1) {
				return tmp;
			}
		}
		else {
			break;
		}
	}

	//判断右对角线是否有连成一片
	tmp = arr[0][col - 1];
	for (i = 0, j = col - 1; i < row; i++, j--) {
		if (tmp == arr[i][j] && tmp != ' ') {
			if (i == row - 1) {
				return tmp;
			}
		}
		else {
			break;
		}
	}


    //判断是否满了
	int ret = is_full(arr, row, col);
	if (ret == 1)
		return 'c';
	else {
		return 'q';
	}
}

全部逻辑已经写完了,剩下就是测试代码了

游戏测试完成之后,逻辑和自己想到的差不多,基本功能也实现了,基本没有什么太大的问题,可能以后有时间会再优化一下代码,或者写一些更好玩的游戏。


三、总结

其实感觉写代码挺爽的,特别是用自己学会的东西,来实现一个比较简单的小程序,这个游戏可能有200多行代码吧!我觉得挺开心的,因为可以把老师在课堂上教的东西,学以致用,这更能证明你平时有没有努力的练习,就和我讲的一样,想成功一定要多努力,努力不一定能成功,但不努力一定不会成功。上面所有代码我都会放在我的码云上,如果有兴趣可以去玩一下哟!希望大家能多多支持哟!
链接:https://gitee.com/smnhaaa/c-project.git

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-08-06 10:25:04  更:2022-08-06 10:26:53 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年4日历 -2024/4/19 23:28:42-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码