经过这么几篇博客的浏览,相信大家的编程功底已经进了一个台阶,我们也不能只居于写一些数学小游戏来练习编程能力,编程的背后确实需要大量的数学思维和运算,但是我们最终还是要走出来,面对一个真正的程序,又该如何编写呢?
想必大家生活中一定玩过五子棋,不论横竖斜,只要五颗连在一起就算赢,另一名玩家要在阻止对方赢的情况下保证自己的胜利,当然我们今天的目的并不是来探讨怎么下五子棋的,我们学编程的当然是要干一些和编程有关的事了,我们的目的就是要来实现这种小游戏的实现,byC语言,这也是每个程序员修炼时期必经之路,通过这些小程序的不断练习来提升自己对于边些程序的逻辑和基本功,五子棋的话呢,有些繁冗,我们今天要完成的是三子棋,如果可以完全听懂的话可以自己下来试一下五子棋的实现,大同小异,废话少说我们开始。
我们大家可以先想象平时点进一款游戏的时候先映入眼帘的肯定是游戏的操作界面,上面提示着你开始或退出或一些其他针对于你自己操作的设置,当然这也一样,我们开始也要设计一个操作界面,不必太过于精美,毕竟我们又不是干这个的,能让人看懂就可以,因为我们这个游戏过于简单,所以我们只需要这几项:开始,退出。没有其他设置,没有音量画面的调节,但这又如何实现呢?你可以直接使用printf函数打印这几个字,但如果你有一丁点审美的话,加上几个字符或许会更好些:
void game_interface()
{
printf("******************************\n");
printf("******1.play******0.exit******\n");
printf("******************************\n");
printf("请选择:");
}
这只是个示范,如果你是个重视主干的人,大可不必去想怎么设计得更好看,写好你的代码是你的唯一任务。好了,有了封面,按照正常的逻辑,我们应该进入选择环节,你要玩or退出,给个选择,这样吧,反正你对着电脑说我要玩它也没反应,输入1就运行游戏,0就退出游戏,假设你输入得是1,进入游戏后应该做什么呢?哦对了,我需要先接收这个1,你需要一个input变量,需要scanf语句,然后可以用一个switch语句,然后分支三个case语句,分别对应1,0,default,没忘记吧朋友,因为我们输入的是1,所以要进入游戏,当然游戏的封面是我们封装的一个函数,那么进游戏我们是不是也可以封装成一个函数呢?欧克,先提示玩家您已进入游戏,然后就可以进入你游戏的函数了,如果输入的是0,就是要退出,先提示一下您已经退出游戏,然后可以直接用break语句跳出,退出程序,default
用来接收除1,0以外的数,如果输入其他数直接提示输入错误,请重新输入,大家有没有发现这个操作其实是个循环,如果我是个网瘾少年,对这个游戏毫无抵抗力,一直想玩,或者说我是个文盲,我不认得1和0,我就一直输入2~9,那程序也不能运行一次就结束对吧,所以只要是非0都要重新进入选择,也就是重新显示游戏封面供玩家选择,所以这是一个循环结构,我们可以用do while来实现,还记得它的功能吗,与一般循环不同的是,无论条件是否为真,它都要先执行一次,正好适合我们的设计,所以大致如下:
void test()
{
int input = 0;
do
{
game_interface();
scanf("%d\n", &input);
switch (input)
{
case 1:
printf("游戏开始:\n");
game_settings();
break;
case 0:
printf("您已退出\n");
break;
default:
printf("错误,请重新输入:");
break;
}
} while (input);
}
现在你可以先跑一下你的代码,看看输入数字后能否正常的运行和退出,如果可以,请继续跟我来看,我们看到case1是需要一个游戏的函数的,case0和default什么都不需要,因为这是你自己选的,我们只需退出和让你重新选择即可,好了你现在选了1进入了case1,进入了game_settings(),这就来到了我们这款小游戏的核心,95%需要通过这个函数来完成,大家平时在下棋的时候都是在棋盘上下的吧,就算条件不允许拿笔画也要画一幅棋盘出来,所以如同画画一般,有合适的纸张才能画出惊世之作,棋盘是必不可少的。好,我来问大家一个问题,如果我把一个棋盘一行一行切开的话他像什么呢?巧克力?收起你的想象,就算是真像巧克力我也要说它更像数组,一个个小格子连在一起,好了,现在我不切了,相比大家脑子里已经浮现了几个字:二维数组,没错,我们通过创造一个二维数组来完成棋盘,
棋盘的大小由你来指定,当然我们这是三子棋嘛,也可以设置3*3,当然并不建议直接int board[3][3];这种写法,因为这并是动态的,什么叫动态什么叫静态这个概念以后会提到,说白了就是一个是活的一个是死的,如果是静态的以后你想更改的话你需要把board后面的数字都改掉,暴力更改,不是不行,不推荐,别忘记全局变量嘛,它本来就不怎么出来你还忘记它,可以创建两个全局变量ROW COL代表行和列,格式自然也就改成了:int board[ROW][COL];更改的话只需更改全局变量的赋值即可。还有一点,第二次提出一下头文件的概念,大家知道stdio.h是头文件,stdlib.h是头文件,这个头文件我能不能看啊,在哪里啊,大家可以在磁盘中打开你的vs,有一个文件专门放置C语言已经规定好的头文件,以我的为例大家也可以去找找你们的:
大家可以发现这些文件都是以.h为后缀的,可以理解这些就是头文件,用头文件的目的就是来告诉vs我们要用头文件里的一些功能来帮助我完成我的代码,当然这些功能也可以我们来指定,所以我们也可以创建头文件,和创建源文件一样在其上面找到头文件右击,创建头文件,我们可以把全局变量,我们需要引的头文件和一些函数的声明放在这里,然后我们的test.c文件只需要引我们创建的头文件就可以,以为需要的东西都已经在里面了,好了我们继续game_settings()函数,有了初始的棋盘,我们需要对其进行初始化,不能让它全是随机值对吧,所以我们可以再封装一个InitBoard()函数,大家有没有发现之前的函数都没有设计传参,我们只是需要它们帮我们我弄成一些任务,所以并不需要传参,但是到了初始化棋盘的时候就不一样了,我们起码需要把原料给人家,它才能换给我们一个我们想要的棋盘,
传棋盘和两个全局变量即可:void InitBoard(int board[ROW][COL],int ROW,int COL),当然不要忘记声明函数,在自己创建的的头文件声明就好,初始化很简单,我先在玩之前让它称为全' '的棋盘,想必不用我说大家也能想到,两成for循环的嵌套就可以完成初始化:
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] = ' ';
}
}
}
初始化完成我想看一下棋盘的效果,其实我们这个游戏棋盘本来就是要一直展示给玩家的,所以我们封装一个DispalyBoard()函数用来展示棋盘,当然和Initboard传参一样,一样的声明,我们需要的也是两个for循环,但是想让我们的棋盘更像一个棋盘的话,一些分割线是可以要的,它并不像初始化棋盘的' '那样,影响棋盘,它只是打印上去让玩家看上去更清楚一点,如果只是每个字符之间做到分割的效果,我们需要在行和列,也就是i和j<row-1和<col-1时打印'|',因为最后一行不用打印,只需要让棋盘中间分割即可
void Displayboard(char board, int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
//打印一行数据
printf(" %c ", board[i][j]);
if (j < col - 1)
{
printf("|");
}
//打印分割线
if (i < row - 1)
{
for (j = 0; j < col; j++)
{
printf("---");
if (j < col - 1)
printf("|");
}
}
}
}
完成这些以后,我们可以完整的看到棋盘的样貌了,我已经迫不及待在上面下棋了,我们再来设计供双方玩家下棋的函数吧,作为程序员要想清楚我们面向的是用户,不能设计半天搞成两个真人下棋了,当然如果你有一个挚友愿意陪你玩这个游戏当我没说,请你珍惜他。一般来说我们设计一个真人玩家一个电脑玩家,这样无论你什么时候想你朋友的时候都可以拿来玩玩,开个玩笑,我们就设计成PlayerMove()和ComputerMove(),也是熟悉的传参和声明,(因为游戏就是围绕棋盘展开的,所以参数只需要这些)我们先来完成
玩家走,首先要提示玩家要怎么走,输入想下在棋盘的坐标,注意在程序员眼中数组的序号都是比正常少1的,因为数组从0开始,当然玩家不知道,我们也不可能在他玩之前先给他讲一遍数组的知识,所以委屈一下你啦,当玩家输入坐标后,如果坐标合理的话,即没有棋子且坐标处于合理范围,给到board的时候一定要进行-1,像这样board[row-1][col-1],如果坐标不合法要及时提醒,注意这可不是个循环,不能一直下,电脑玩家该砸电脑了。
void PlayerMove(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("玩家走:\n");
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] = '*';
}
else
{
printf("坐标非法\n");
}
}
else
{
printf("坐标非法,请重新输入\n");
}
}
然后是电脑玩家的设置,也是一样的传参,一样的声明(不想说了都)电脑玩家的话,就不用自行输入了,你还真想让电脑输啊,只需自动生成两个随机出即可,那么怎么生成呢?介绍一个新函数:rand,给大家看一下它的介绍
看不懂也没关系,我凑篇幅用的,大致意思就是利用时间戳随机产生一个数字,和srand函数共同使用,大家可以看到srand函数的返回值是void,参数是一个无符号整形的seed,如果我们想使用时间戳来帮我们随机生成数字的话我们还需要一个time函数,即根据现在的时间转换成一个16进制的数字,我们来看一下time函数的传参以及返回值情况
大家可以看到time返回的是一个time_t类型的变量,参数也是一个time_t的指针,我们并不传参,所以我们传一个NULL即可,因为空指针也是指针,相当于整形变量初始化的0,再把time的返回值强制转换为unsigned int即可
srand((unsigned int)time(NULL));
//这句话加在test函数里do while函数前即可
当然不要忘记引头文件,图片里有,记得要引在自己创建的头文件里,根据我上面发的rand函数图片下面的示例,加上这句话后就可以用rand生成随机数了,但是如何让它正好处在我棋盘可接受的范围呢?如果是10*10的棋盘那坐标一定是0~9,如果是100*100坐标一定是0~99,没错,我们可以使用生成的数字取余我们的ROW和COL,得到的数字就是棋盘可接受的数字,然后还是和Player Move相似的操作,判断,输入,只不过电脑玩家下的坐标都是合法的,如果不合法只能说明我们程序员写错了,所以不必判断范围
void ComputerMove(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("电脑走:\n");
x = rand() % ROW;
y = rand() % COL;
while (1)
{
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
真人和电脑都下完了,这下我们该判断的就是孰赢孰输,所以我们再设计一个IsWin函数,参数都是一样的,如果横三行竖三行两条对角线都一样了,代表有一方胜利,所以我们需要结束游戏然后宣布一方胜利,如果横三行满了但字符并不一样,就返回此行第一个字符,列也如此,对角线满的话,返回[1][1]的字符即可,如果满了返回Q,如果没满返回C,所以我们还需要一个函数来判断是否满IsFull,当满了需要它返回1,没满返回0,判断的方法,就是for循环里将board[x][y]挨个进行判断是否都等于'#' '*'
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;
else if (board[i][j] != ' ')
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[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';
}
好了回到我们的game_settings()函数
void game_settings()
{
int ret = 0;
//定义数组来存放一些棋盘信息
char board[ROW][COL] = { 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("玩家赢");
}
else if (ret == '#')
{
printf("电脑赢");
}
else if (ret == 'Q')
{
printf("平局");
}
}
简单的三子棋小游戏到这里就差不多了,大家可以先梳理一下思路然后自己再编译器上测试一下,我们下次再见