2.因为主函数只是大概框架,所以游戏逻辑与自定义函数实现可以另开一个源文件(game.c),这里就包括绝大部分的功能是如何实现的,但是因此游戏逻辑(game.c)与主函数文件(test.c)有多个共同的头文件需要引用,则再开个头文件(game.h)来包含所有的头文件,这样子就可以模块化你的代码,什么东西该在什么地方你都知道
3.模块化虽然有好处,但必须这么干吗?
是的!在公司里,你负责的可能就是其中一个小小的功能,而不是所有东西,所以你写出来的东西不能是和主框架混在一起的东西,现在养成模块化的习惯,才可以在以后更快适应公司需求
二.1.作为一个简单的游戏,应该有个主菜单来给玩家选择玩还是不玩,所以这里用menu函数来打印菜单,然后用switch语句对应菜单上的提示玩家选择玩还是不玩,这里的主函数与menu函数可以通用于大部分游戏,这也是模块化的好处之一
int main()
{
srand((unsigned)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");
}
} while (input);
return 0;
}
菜单函数如下 ,1为玩,0为不玩,玩家选错要提醒并重来,且0,1的设计刚好对应switch语句括号内表达式的真假
void menu()
{
printf("**************************\n");
printf("******** 1.play **********\n");
printf("******** 0.exit **********\n");
printf("**************************\n");
}
因为第一个源文件test.c是要写出主逻辑的,也就是游戏的流程,所以应该把game的实现(即游戏的流程)写出来,不然就写个菜单也太浪费了,
2.好的,现在开始讲游戏流程了,作为三子棋,首先就要有棋盘,于是我们定义了一个二维数组board当做棋盘,这里ROW为行,COL为列,
为什么可以这样做呢,因为我这里用标识符常量定义了ROW,COL为3 3,所以并不是算变长数 组(ROW,COL不是变量),定义就都放在头文件里,到时候引用一下就行了。
ok现在讲为什么要这么做,因为要增强棋盘的通用性,一开始就把棋盘写死了就是3×3的二维数 组,到时候想改成五子棋时不是到处改,但是这样做的话只需要在标识符常量定义里把 ROW,COL的值改一次,代码里的对应行列的值就都改了
3.继续,因为棋盘里一开始都是空的,所以要初始化棋盘为空格,函数同样放在game.c里完成,这里只是写流程,调用一下函数就行了
4.打印棋盘,既然你有棋盘,自然是要给别人看得,但是你单单创建个二维数组人是看不到棋盘的,所以还要打印出来给人看,同理,函数逻辑放在game.c里完成
5.现在有了棋盘,就要开始下棋了,我们推演一下这个过程,无非就是你下棋,我下棋,没分出胜负就继续你下棋,我下棋,如此循环,所以这里应该写个循环,但是我们们在小小的循环括号表达式里很难写出具体什么时候结束这个循环(因为很复杂,一个式子不好说清楚),干脆写个死循环while(1),然后每次下棋后判断是否分出胜负,分出胜负则跳出,我这里是分为电脑和玩家对决,所以下棋就分为PlayerMove与ComputerMove两个函数来代表玩家和电脑下棋,
6.判断是否分出胜就用is_win来实现,同理前面就是个外壳,然后每次下完一步棋还要再打印一次棋盘给别人看咋下的,写到这里小小感叹一下自定义函数真的好用,一次定义,多次调用,写好一个调用就可以了,接下来我们就要看啥时候会结束了,(因为结束也是游戏的流程之一,所以也要写在这里),我们创建个变量ret用来获取游戏状态,这里我们把游戏状态分为四种 //1.玩家赢了 - * //2.电脑赢了 - # //3.平局 - Q //4.游戏继续 - C 如果iswin返回的不是游戏继续的‘c’,就说明分出胜负了,然后再打印出到底谁赢了,并且打印出棋盘,让人赢(输)得明白。
7.这里就很直观看得出模块化与自定义函数的好处了,到现在我们都是按流程来写,然后每个功能都放在对应得函数里实现,一步一步攻克难题,让人可以明确知道现在该干什么,下一步该干什么,思路明确是写一个复杂项目很重要的东西
void game()
{
char board[ROW][COL];
//初始化棋盘 -空格
Initboard(board, ROW, COL);
//打印棋盘 本质是打印数组的内容
DisplayBoard(board,ROW,COL);
char ret = 0;//获取游戏状态
//下棋
while (1)
{
//玩家玩
PlayerMove(board, ROW, COL);
DisplayBoard(board, ROW, COL);
printf("\n");
//判断玩家是否输赢
ret = Is_Win(board,ROW,COL);
if (ret != 'C')
break;
//电脑走
ComputerMove(board, ROW, COL);
DisplayBoard(board, ROW, COL);
printf("\n");
//判断电脑是否输赢
char ret = Is_Win(board, ROW, COL);
if (ret != 'C')
break;
}
if (ret == '*')
{
printf("玩家赢了\n");
}
else if (ret == '#')
{
printf("电脑赢了\n");
}
else
{
printf("平局\n");
}
DisplayBoard(board, ROW, COL);
}
三.1.终于,要到了我们的自定义函数部分了,前面的都是开胃菜,现在到游戏真正的逻辑实现
首先初始化棋盘
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初始化一下就行了。
2.现在就要打印棋盘了,但是棋盘单单是数据,还有横竖交错的分割线,如
看得出来,如果将数据与横分割线看做一组,最下面的分割线是不打印的,同理右面的竖分割线一样,所以这里需要对分割线判断一下,必须是1,2组才打印分割线
void DisplayBoard(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");
}
}
}
3.接下来就是玩家下棋,因为是玩家,下棋时还需要判断是否是合法坐标,同时也要判断这个坐标是不是被下过了,又因为玩家不一定是程序员,人家以为默认是由1 1开始的,所以要把玩家的坐标都减一
void PlayerMove(char board[][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("玩家走>\n");
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");
}
}
else
{
printf("坐标非法,请重新输入\n");
}
}
}
现在到电脑走了,因为电脑可以限制,不用考虑坐标非法,,我们现在水平有限,就让电脑随机下吧,那牵扯到随机值那肯定用时间戳啊,因为srand只要用一次所以放在了主函数里{srand((unsigned)time(NULL));}代码如下
void ComputerMove(char board[ROW][COL], int row, int col)
{
printf("电脑走>\n");
while (1)
{
int x = rand() % row;
int y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
现在到最重要的判断胜负,因为还有一个平局状态,所以我们写一个判断棋盘是否满的状态,只要棋盘里还有空格,就是没满,所以这里开始遍历数组,找有没有空格 写完这个函数就要来判断是否分出胜负,三子棋一共有四种赢法,三行,三列,两个对角线 我们把下的棋子当做谁赢的标志,这样处理可以在判断胜负时直接返回其中的棋子,这样就不用电脑写一个判断胜负,玩家方面还要写个判断胜负,更加简洁,判断完之后返回刚刚约定好的状态标志,这样就可以啦
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 Is_Win(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];
}
//判断平局
//如果棋盘满了返回1,不满返回0
int ret = isFull(board,row,col);
if (ret == 1)
{
return 'Q';
}
//继续
return 'C';
}
最后,我们把函数的申明和头文件的包含,标识符常量定义都放在game.h里,只需要在两个文件里 #include"game.h" 就可以了,其中内容如下
#define _CRT_SECURE_NO_WARNINGS 1
#define ROW 3
#define COL 3
//头文件的包含
#include<stdio.h>
#include<stdlib.h>
//函数的申明
void Initboard(char board[ROW][COL], int row, int col);
//打印棋盘函数
void DisplayBoard(char board[ROW][COL], int row,int col);
//玩家下棋
void PlayerMove(char board[][COL],int row,int col);
//电脑下棋
void ComputerMove(char board[ROW][COL], int row , int col);
//1.玩家赢了 - *
//2.电脑赢了 - #
//3.平局 - Q
//4.游戏继续 - C
//判断是否有人赢
char Is_Win(char board[ROW][COL], int row, int col);
最后的最后是我的gitee链接,有兴趣可以看我的代码哦