Hello,大家好!!!这里是小周为您带来的呕心沥血之作------C语言秘籍!!
C语言秘籍分为初阶和高阶两部!!跟着小周学定会让你C语言功力大成,称霸武林,话不多说,我们接着上回继续开讲!!!!!
一、实现逻辑
1、游戏不退出,继续玩下一把(循环)
2、应用多文件的形式写代码
二、代码分布
test.c -- 测试游戏的(main)
game.c -- 游戏的实现 game.h -- 游戏函数的声明
三、代码实现
1、首先需要把这个游戏的工程创建好

2、写出main主函数,设置好本游戏的菜单,我设计的菜单有两个选项(1、play 0、exit)接着为了实现游戏不退出,可以接着玩下一把,我们就要采用循环的方法,那究竟用那种循环比较好呢?结合当今的游戏我们发现,进去肯定是要有界面的,所以综合考虑用do while循环较好

3、我们还应该考虑到,用户在输入1的时候要开始玩游戏,输入0的时候退出,输入其他选项输出“选择错误,重新选择”,同时我们发现输入1的时候,用户在玩游戏,输入0则退出,所以我们不妨将while后的判断条件直接设置为input

4、我们运行一下看看

发现存在了一些错误,将它们修改后再次运行

再次修改

这回把所有的问题全部解决,预期结果和运行后的结果完全一致
那问大家一个问题,我为什么要将input的定义放在do while循环外面呢?放里面可不可以呢
因为在这个程序中,将 input 的定义放在 do while 循环外部是为了确保在整个 main 函数的作用域内 input 都是可见的,并且在循环条件判断和 switch 语句中都可以使用它,如果放到循环里面,那么循环的判断条件就会出现问题
5、接下来要对游戏的功能设计实现
(1)我们需要一个3×3的棋盘,其次玩家在输入下棋坐标后,电脑也会对应下棋,二者用不同的图案对应,游戏在走的过程中要进行数据的存储,可以使用3×3的二维数组,比如
char board[3][3];
(2)没进行游戏之前数组中存放空格(可以封装在函数中实现哦)
(3)同时在前面我们提到要将游戏相关的代码放到game.c和game.h中

我们发现有好多3,如果未来游戏升级优化,变成五子棋,十字棋,棋盘越来越大,挨个改数字很麻烦,有没有什么更好的方法呢?

我们可以这么做,使用预处理指令,定义宏常量
将宏 ROW 定义为整数值 3在程序中,可以通过 ROW 来代替 3 使用,COL也是如此,因为我们将宏定义在了头文件,所以在别的文件如果使用的话,需要引用头文件,引用自己的文件则是
#include"game.h"
(4)接着实现初始化函数
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] = ' ';
}
}
}
此段代码是游戏功能的实现,放在game.c中,同样需要在引用头文件game.h
补充:我们可以将每个文件都涉及到的相同库函数等的头文件都放在game.h中,这样会很方便
(5)我们再设计一个打印函数来看看初始化后的效果
//打印版本1
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]);
}
printf("\n");
}
}
我们运行一下主函数看一下

发现并不能看见棋盘在哪里,由此可见这个版本1的打印代码有点low,我们来优化一下
我们将每一个区域分的清楚一点,像这样

每个区域中间是对应游戏下棋时候的符号,符号两边用空格隔开,每列之间用竖线隔开,”---“这个是每行的分割线,可以将一行数据和分割线看成一组,最后一组的分割线不打印
//打印版本2
void DisplayBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
//1、打印数据
printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
//2、打印分割线
if (i < row - 1)
{
printf("---|---\---\n");
}
}
}
运行看一下,

好像是我们要的效果,如果换成10×10的棋盘呢?

我们发现代码还是存在问题的,并没有打印出10×10的棋盘,因为输出的3列定死了,再次优化
//打印版本3
void DisplayBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
//1、打印数据
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)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf("---");
if (j < col - 1)
{
printf("|");
}
}
printf("\n");
}
}
}

这回运行后的棋盘才是完美符合要求的,不会再有问题
(6)玩家下棋功能的实现
在这里需要注意,玩家认为棋盘的行数和列数都是从1开始,而二维数组的行数和列数都是从0开始的,同时还要注意坐标输入是否合法,由于输入坐标的不合法还需要重新输入所以要考虑使用循环,还存在坐标被占有的情况
//玩家下棋
void PlayerMove(char board[ROW][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");
}
}
}
我们修改一下game函数代码,让玩家一直下棋,测试这个下棋的逻辑
void game()
{
char board[ROW][COL] = { 0 };
InitBoard(board,ROW,COL);//要有函数封装的思想,同时此函数需要将二维数组的行数和列数同样作为参数传递
//打印棋盘
DisplayBoard(board, ROW, COL);
//下棋
while (1)
{
//玩家下棋
PlayerMove(board, ROW, COL);
DisplayBoard(board, ROW, COL);
//电脑下棋
}
}

(7)电脑下棋模块的实现
注意一下是随机生成,rand的使用在之前提到过,所以这里就不再赘述
//电脑随机下棋
void ComputerMove(char board[ROW][COL], int row, int col)
{
int x = 0;//0~row-1
int y = 0;//0~col-1
printf("电脑下棋:>\n");
while (1)
{
x = rand() % row;
y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
接着测试代码
//test.c
void game()
{
char board[ROW][COL] = { 0 };
InitBoard(board,ROW,COL);//要有函数封装的思想,同时此函数需要将二维数组的行数和列数同样作为参数传递
//打印棋盘
DisplayBoard(board, ROW, COL);
while (1)
{
//下棋
//玩家下棋
PlayerMove(board, ROW, COL);
DisplayBoard(board, ROW, COL);
//电脑下棋
ComputerMove(board, ROW, COL);
DisplayBoard(board, ROW, COL);
}
}

运行后发现并没有什么问题(测试时候不看输赢,因为还有后续的代码)
(8)判断输赢
//判断输赢
//玩家赢 - '*'
//电脑赢 - '#'
//平局 - 'Q'
//继续 - 'C'
//同时主函数要在每次玩家下棋和电脑下棋后都追加判断一次,不是继续的情况就要break
//因为我规定了每种输赢的返回值
//所以game函数如下
void game()
{
char board[ROW][COL] = { 0 };
InitBoard(board,ROW,COL);//要有函数封装的思想,同时此函数需要将二维数组的行数和列数同样作为参数传递
//打印棋盘
DisplayBoard(board, ROW, COL);
char ret = 0;
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("玩家赢\n");
}
else if (ret == '#')
{
printf("电脑赢\n");
}
else
{
printf("平局\n");
}
}
还需注意赢的条件是三行的某一行或者三列的某一列或者对角线,有同样的符号,因为我们是对数组直接比对,是程序员的角度,所以二维数组的行和列都是从0开始的
//平局的情况---判断棋盘是否已满
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;//没有空格,已满
}
//判断输赢
//玩家赢 - '*'
//电脑赢 - '#'
//平局 - 'Q'
//继续 - 'C'
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][0] != ' ')
{
return board[i][0];//这就是为什么玩家赢和电脑赢对应符号是*和#的原因,简洁省去一些没有必要的判断
}
}
//列
for (i = 0; i < col; i++)
{
if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][i] != ' ')
{
return board[0][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];
}
//平局---就是看棋盘满没满
if (IsFull(board, ROW, COL) == 1)
{
return 'Q';
}
//继续
return 'C';
}
到这里,这个游戏的代码就完全结束了,接下来我们来验证一下

非常棒!没有任何问题,可以实现的情况也全部展现出来了!给自己数个大拇指吧!
3177

被折叠的 条评论
为什么被折叠?



