勤时当勉励 岁月不待人
C/C++ 游戏开发
三子棋及五子棋详解
- 前言
- 三子棋的实现
- 1. 打印菜单
- 2.初始化并打印棋盘
- 棋盘初始化函数功能的实现
- 打印棋盘
- 3.玩家下棋与模拟电脑下棋
- 玩家下棋
- 电脑下棋
- 随机数的生成
- 4.判断输赢
- 我们的game函数
- 源码
- game.h
- game.c
- test.c
- 总结
前言
当我们有一定的C语言基础后,就可以独立的设计一些小项目了,今天我来带大家一步一步实现三子棋这个简单的项目并把它改进为五子棋。跟着我一步一步来,我相信到最后你也能独立的编出属于自己的三子棋。话不多说,我们开始吧!
三子棋的实现
- 三子棋虽然是一个小项目,但是整个过程中还是分不同的模块来实现不同的功能的,接下来我通过画图的方式讲一下具体由哪些模块并带大家一块一块的具体分析实现。
- 所有代码分三部分实现,它们分别是负责声明函数的头文件“game.h”,负责函数功能实现的“game.c”,以及负责测试(即用户使用)的“test.c”.
这里为什么要分几部分封装函数我在下面这篇博客中的自定义函数中提到过,感兴趣可以去看看,这里是链接:【C语言初阶】万字解析,带你0基础快速入门C语言(下)
1. 打印菜单
- 这一步其实是非常简单的,代码如下
void menu()//打印菜单
{
printf("*****************************\n");
printf("*********** 1.play **********\n");
printf("*********** 0.exit **********\n");
printf("*****************************\n");
}
- 好了,一个简单的菜单就写好了,但是此时的1和0是没有任何的选择效果的,我们接下来就让我们的菜单真正能做到输入1开始输入0结束的效果。
int main()
{
srand((unsigned int)time(NULL));//暂时先不管,后面会讲这是什么
int input = 0;
do {
menu();
printf("请选择:> ");
scanf("%d",&input);
switch (input)
{
case 1:
game(); //进入游戏
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误,请重新输入\n");//输入的非0非1时
break;
}
}while(input);
return 0;
}
- 来测试一下我们这个菜单的效果
- 非常完美,菜单算是打印完成了,我们进行下一步。
2.初始化并打印棋盘
- 现在我们输入1进入游戏,首先要进行的就是对棋盘的初始化以及打印
- 我们不妨把黑子白子设为’*‘与’#',由于我们的棋盘是分行与列的,要想打印一个棋盘把我们的”棋子“放进去,使用二维数组再合适不过。
- 代码如下
void game()
{
char board[Row][Col] = { 0 };
InitBoard(board, Row, Col);//初始化棋盘的函数
PrintBoard(board,Row, Col);//打印棋盘的函数
}
- 此时有人肯定好奇Row和Col是什么
- 为了方便我们以后改变棋盘的大小,我们用常数定义了Row和Col
#define Col 3
#define Row 3
- 这样在我们以后想改变这个棋盘的大小时只需要改变定义的Col与Row的值就好,不需要把程序中每个有关棋盘大小的代码都修改一遍,是不是非常方便?
- 我们继续
当我们把二维数组以及对应的参数设计并放进函数里后,我们就要来实现具体函数的功能了,先讲讲初始化函数的实现。
棋盘初始化函数功能的实现
凡是函数我们在调用时都得先声明再使用,因此我们现在头文件中先声明一下。
void InitBoard(char board[Row][Col], int row, int col);//初始化棋盘
具体实现:
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] = ' ';//把棋盘上的每一个元素都初始化为空格,方便我们落子
}
}
}
- 其实没啥好讲的,无非是两个for循环嵌套,看看以上代码后的注释相信聪明的你理解应该没啥问题。
打印棋盘
- 把棋盘初始化好后,我们就要开始尝试确定棋盘的格式来打印棋盘了,首先还是得在头文件中声明函数。
void PrintBoard(char board[Row][Col], int row, int col);//打印棋盘
代码的实现
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]);//使我们的之后落的“棋子”能以字符的形式放进棋盘里。
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");//一行打完换下一行
}
}
}
-
这里讲一下为啥有时候i和j的范围是(row-1)和(col-1)
-
其实非常简单啊,主要是为了美观。比如咱们打印一个3*3的棋盘,实际上只需要两行以及两列的分隔线就够了,再更加直接一点,如果我们想把一条线分割成三部分,是不是只需要切两次就足够了?
-
可能上面这段画有点抽象,咱们配合下面实际打印出的棋盘的图来想象一下。
-
配合图看是不是就直观多了?
-
如果觉得ok的话,那我们接下来来实现下棋的这部分代码
3.玩家下棋与模拟电脑下棋
- 啊,我相信咱们程序猿的水平可能战胜阿尔法狗有点困难,但是与电脑随机落子下棋肯定绰绰有余,甚至咱们得让着它才能让它赢
- 接下来我们讲讲我们该怎么下
玩家下棋
- 同上,引用函数得先声明
void PlayBoard(char board[Row][Col], int row, int col);//玩家落子
- 那么我们现在想想,我们该怎么下呢?
- 首先咱们得先判断一下咱们下棋是否合法,总不能下着下着下到棋盘外了吧?? - 其次,我们得判断一下我们现在选择的这个坐标是否有棋子,一个坐标下无法存放两个棋子的
代码实现一下:
void PlayBoard(char board[Row][Col], int row, int col)
{
int x, y = 0;
while (1)
{
//先提示一下此时轮到玩家了以及此时玩家应该做什么,
printf("玩家下棋>:\n");
printf("请输入要下的坐标,中间用空格隔开\n");
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");//当判断此坐标不为空格时,说明该坐标已经有“棋子”了
}
else
printf("坐标非法,请重新输入\n");//选择的坐标不在我们规定的棋盘内
}
}
- 注意:
- 在判断该位置是否有“棋子”时,我们使用的是
board[x - 1][y - 1]
.。 - 程序和我们程序猿当然知道咱们的数组下标是从0开始的呀,但是,如果是一个没接触过编程的人来玩这个游戏时,ta会想当然的认为棋盘的坐标是从1开始的,比如第一行第一列的坐标,我们知道是(0,0),可ta想的却是(1,1),为了方便这些用户游玩我们的三子棋,我们最好就把每个位置的坐标设置成从1开始。这就是上面判断是否有“棋子”以及落子时使用
board[x - 1][y - 1]
的原因。
电脑下棋
- 我们自己与自己玩自然无趣,此时不妨让电脑陪我们下棋。
- 其实电脑下棋与我们玩家下棋类似,但是我们首先要解决一个问题,怎么让它随机的落子呢?
- 为了让电脑能随机的下棋,我们得保证它每一次落子的坐标都变化,那有没有什么东西是一直在变化能被我们使用实现生成随机落子坐标的呢?
- 此时我们想到,有一个东西正无时无刻不在发生变化,那就是时间,恰好符合我们的要求。
- 接下来先讲讲怎么通过时间戳生成随机数。
随机数的生成
- 什么是时间戳?
- 时间戳是计算机科学中常见的术语,是指一种记录时间的方式,通常用于记录某个事件的发生时间或者文件的创建、修改时间等信息。简单的说,时间戳就是一串数字,表示从某个固定时间点(1970年1月1日00:00:00)开始到现在的时间长度,通常以秒为单位计算。
- 生成随机数的函数
#include<stolib>//使用该函数所需的头文件
rand()
- 我们通过msdn来认识一下它
- rand的返回值为一个整型,注意最后一句话哦:
- 博主只会用,英语能力有限,这里征用一下百度翻译
- 也就是说,在使用rand之前,我们得先调用一下srand为其设定生成随机数的起点(或者生成器)”
- srand函数msdn定义如下:
- 当我们调用srand函数时,必须满足的条件是传给它的值是一个变化的值,此时就可以用我们前面讲到的时间戳了
#include<time.h>//使用time函数所需头文件
srand((unsigned int)time(NULL));//把时间置空传给srand同时由于srand要求参数必须为unsigned int型,把time(NULL)强制类型转换一下
- 注意,rand生成的其实是一个伪随机数。
- 好了,到此我们就可以开始尝试实现电脑下棋了
- 先在头文件中声明函数
void ComputerBoard(char board[Row][Col], int row, int col);//电脑下棋
具体代码如下:
void ComputerBoard(char board[Row][Col], int row, int col)
{
int x = 0;
int y = 0;
printf("电脑下棋:>\n");//提示此时轮到电脑下
while(1)
{
x = rand() % row;//为了防止落子到棋盘外,使生成的随机数范围为0到row-1
y = rand() % col;//同理,生成0到col-1的数
if (board[x][y] == ' ')//判断此时该坐标是否被占用
{
board[x][y] = '#';//落子
break;
}
}
- 除了上面随机数的生成,其他都与玩家下棋类似,这里不做缀叙了。
4.判断输赢
- 这里我们离实现三子棋就差一步了,那就是判断输赢。
- 这里我们把具体的情况总结一下
- 1)玩家下的棋存在连续三个横着或竖着或者两条对角线,此时玩家胜
- 2)同理,上面也是电脑的获胜条件。
- 3)当我们把棋盘所有格子(坐标)都占满了,此时还没有出现上面两种情况,说明此时平局。
首先还是先在头文件里声明函数
int IsFull(char board[Row][Col], int row, int col);//棋盘是否已满
int Is_Win(char board[Row][Col], int row, int col);//判断谁赢
具体代码:
int IsFull(char board[Row][Col], int row, int col)//判断棋盘是否已满
{
int i ,j;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
if (board[i][j] == ' ')//还有空格,说明没满
return 0;
}
}
return 1;//没有空格了,说明已满,返回1
}
int Is_Win(char board[Row][Col], int row, int col)
{
int x = 0;
int y = 0;
//竖着
if (board[x][0] == board[x][1] && board[x][1] == board[x][2] && board[x][0] !=' ')
{
return board[x][0];//返回三个坐标中随意哪个都行,因为如果满足上面获胜条件,三个坐标中一定放着‘*’或者‘#’,下面的判断同理
}
//横着
if (board[0][y] == board[1][y] && board[1][y] == board[2][y] && board[0][y] != ' ')
{
return board[0][y];
}
//两条对角线
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];
if (IsFull(board, row, col) == 1)//返回值如果为1说明棋盘满了
{
return 'Q';
}
return 'C';
}
- 注意:
- 上面这段代码中,如果玩家赢了,就返回‘*’
- 如果电脑赢了,就返回‘#’
- 如果平局,返回‘Q’
- 如果上面三种情况都没发生,说明还没分出胜负,返回‘C’
我们的game函数
当我们把上面要实现的功能都封装在一个函数中,就能实现我们的三子棋游戏啦,此时我们来讲讲game函数中上述函数是怎样组合放置的。
- 由于这里的game在主程序所在文件中,不需要声明
void game()
{
char board[Row][Col] = { 0 };
InitBoard(board, Row, Col);
PrintBoard(board,Row, Col);
char ret = 0;
while (1)
{
PlayBoard(board, Row, Col);//玩家下棋
PrintBoard(board, Row, Col);//打印此时的棋盘
ret = Is_Win(board, Row, Col);//把返回值存放在ret里
if (ret != 'C')//判断是否分出胜负,返回‘C’才说明需要继续
break;
ComputerBoard(board, Row, Col);//电脑下棋
PrintBoard(board, Row, Col);//打印此时的棋盘
ret = Is_Win(board, Row, Col);//与上面同理
if (ret != 'C')
break;
}
if (ret == '*')
printf("玩家赢\n");
if (ret == '#')
printf("电脑赢\n");
if (ret == 'Q')
printf("平局\n");
}
- 这样我们的三子棋项目就完成了,我们来具体玩一下吧
这里博主提醒大家:
游戏虽好,可不要贪玩哦!!!
源码
以下为三子棋具体的每个文件中的源码哦,需要自取
game.h
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define Col 3
#define Row 3
void InitBoard(char board[Row][Col], int row, int col);//初始化棋盘
void PrintBoard(char board[Row][Col], int row, int col);//打印棋盘
void PlayBoard(char board[Row][Col], int row, int col);//玩游戏
void ComputerBoard(char board[Row][Col], int row, int col);//电脑下
int IsFull(char board[Row][Col], int row, int col);//棋盘是否已满
int Is_Win(char board[Row][Col], int row, int col);//判断谁赢
game.c
#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 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]);
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");
}
}
}
void PlayBoard(char board[Row][Col], int row, int col)
{
int x, y = 0;
while (1)
{
printf("玩家下棋>:\n");
printf("请输入要下的坐标,中间用空格隔开\n");
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");
}
else
printf("坐标非法,请重新输入\n");
}
}
void ComputerBoard(char board[Row][Col], int row, int col)
{
int x = 0;
int y = 0;
printf("电脑下棋:>\n");
while(1)
{
x = rand() % row;//生成0-row-1的数
y = rand() % col;//生成0-col-1的数
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
int IsFull(char board[Row][Col], int row, int col)
{
int i ,j;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
if (board[i][j] == ' ')
return 0;
}
}
return 1;
}
int Is_Win(char board[Row][Col], int row, int col)
{
int x = 0;
int y = 0;
//竖着
if (board[x][0] == board[x][1] && board[x][1] == board[x][2] && board[x][0] !=' ')
{
return board[x][0];
}
//横着
if (board[0][y] == board[1][y] && board[1][y] == board[2][y] && board[0][y] != ' ')
{
return board[0][y];
}
//对角线
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];
if (IsFull(board, row, col) == 1)
{
return 'Q';
}
return 'C';
}
test.c
#include"game.h"
void menu()//打印菜单
{
printf("*****************************\n");
printf("*********** 1.play **********\n");
printf("*********** 0.exit **********\n");
printf("*****************************\n");
}
void game()
{
char board[Row][Col] = { 0 };
InitBoard(board, Row, Col);
PrintBoard(board,Row, Col);
char ret = 0;
while (1)
{
PlayBoard(board, Row, Col);
PrintBoard(board, Row, Col);
ret = Is_Win(board, Row, Col);
if (ret != 'C')
break;
ComputerBoard(board, Row, Col);
PrintBoard(board, Row, Col);
ret = Is_Win(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:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
}while(input);
return 0;
}
总结
以上就是今天的所有内容啦,希望你在看完后不要光看不练,只有自己亲手敲过之后,才能知道自己到底会不会,眼高手低是万万不可的哦!!
- 如果你有任何疑问欢迎在评论区指出或者私信我哦!
- 创作不易,如果感觉文章内容对你有所帮助的话,留下三连再走呗!你们的支持就是我创作的动力,下次再见啦!!!
-