目录
三子棋
1.整体框架test.c
编辑
显示菜单
玩家选择
2.游戏声明game.h
3.游戏逻辑geme.c
---初始化棋盘
---显示棋盘
---玩家下棋
---电脑下棋
---判断输赢
---打印结果
---游戏逻辑实现思路
4.思考
5.总结
我将三子棋拆分为两个模块,并将其分别写在3个文件当中:
---game.h :使用函数的声明
---game.c : 游戏逻辑的实现
---test.c :整体框架的实现
代码连接:https://gitee.com/zhy156/c-code-practice-warehouse.git
1.整体框架
2.游戏逻辑
---使用do while循环先让程序运行
---显示菜单
---玩家选择
调用函数打印出菜单
//菜单
void menu()
{
printf("***************************\n");
printf("************1.PLAY*********\n");
printf("************0.EXIT*********\n");
printf("***************************\n");
}
---使用scanf函数,将玩家输入的数字存储
---在使用switch语句,根据玩家输入的数字,来显示不同的效果:
玩家输入1:游戏进行
玩家输入0:游戏退出
玩家输入其它:提示输入错误,并配合do{} while(input);循环让他能够重新输入(玩家输入非0,循环会一直进行,直到玩家输入0,选择退出)
scanf("%d", &input);
switch (input)
{
case 1:
printf("玩游戏\n");//玩游戏
game();
break;
case 0:
printf("已退出\n");
break;
default:
printf("选择错误,请重新选择:\n");
break;
}
---根据游戏逻辑需要用到的函数对其进行声明
---使用对象式宏规定棋盘的行与列(方便修改)
定义一个二维数组来存放棋盘
---初始化棋盘
---显示棋盘
---玩家下棋
---电脑下棋
---判断输赢
---打印结果
这里许需要注意,当玩家或电脑下完一步棋就要对游戏结果进行判断因此:
---玩家下棋
---判断输赢
---显示棋盘
---电脑下棋
---判断输赢
---显示棋盘
定义一个二维数组来存放棋盘
char board[row][col]={0};
使用多重循环遍历二维数组(棋盘),让每一个元素都变为' '
使用函数将其模块化
void Init_board(char board[row][col])
{
int i, j;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
board[i][j] = ' ';
}
}
}
使用多重循环遍历棋盘的每一个元素并打印即可
但为了使棋盘跟具有识别性,我们对代码进行以下调整:
将其拆分为两行打印:1.元素行 2.分割行
---元素行:打印(" 元素 “),然后打印| 若列为第3列则不打印|
---分割行:打印("---"),然后打印| 若行为第3行则不打印,若列为第3列则不打印|
实现:通过遍历棋盘元素打印元素行,再加上限制条件来决定是否打印| (使用if条件语句)
打印玩元素行后再打印分割行,原理同上
代码如下:
//显示棋盘
void Display_board(char board[row][col])
{
int i, j;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)//遍历所有元素并打印
{
printf(" %c ", board[i][j]);
if (j < col - 1)//到了最后一列(col)选择不打印分割符‘|’
printf("|");
}
printf("\n");
if (i < row - 1)//到了最后一行选择不打印分割行
{
for (j = 0; j < col; j++)//在每一个元素下面打印分割行
{
printf("---");
if (j < col - 1)//到了最后一列(col)选择不打印分割符‘|’
printf("|");
}
printf("\n");
}
}
}
---设置X,Y来存放玩家输入的坐标(使用scanf函数)
---判断玩家输入坐标的合法性(限制在棋盘范围内)
---判断玩家输入的坐标是否被占用(判断是否为‘ ’)
---若坐标合法就将数组中对应元素替换:‘*’
---若坐标不合法就提醒玩家重新输入(这里写一个死循环,若数组中有元素被替换就使用break跳出循环)
注意:数组的下标是以0开始,但一般人默认以1开始,所以在替换数组元素时,将X,Y减1
实现
判断玩家输入坐标合法性:使用条件语句和操作符来实现 if((X>0&&X
0&&Y
判断玩家输入坐标是否被占用:if(board[X-1][Y-1]==' ')
坐标合法数组中元素替换:board[X-1][Y-1]='*'
//玩家下棋
void Play_move(char board[row][col])
{
int x, y;
printf("请输入你要下的坐标:");
while (1)
{
scanf("%d%d", &x, &y);
//判断坐标的合法性
if ((x > 0 && x < row + 1) && (y > 0 && y < col + 1))
{
//判断该位置是否被占用
if (board[x - 1][y - 1] == ' ')
{
board[x - 1][y - 1] = '*';
break;
}
else
{
printf("该坐标已被占用,请重新输入:");
}
}
else
{
printf("非法输入,请重新输入:");
}
}
}
电脑下棋和玩家下棋基本差不多,补充下面两点
---电脑随机下棋:使用时间戳来生成随机数(通过srand()设置起点并调用rand()函数生成)
---将生成的随机数限制在棋盘范围内:将得到的数字放入X,Y当中并使其%row,%col即可
例如3子棋,将得到的数字%row(3),将得到0,1,2三个数字,列同理
//电脑下棋
void Computer_move(char board[row][col])
{
while (1)
{
int x = rand() % 3;
int y = rand() % 3;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
我们先来思考:下棋在没出结果之前肯定是要一直下的,因此:
while(1)
{
//玩家下棋
//电脑下棋
}
那什么时候退出这个循环呢?我们设置一个Is_Wind()函数来判断,并让它返回一个结果给我们,我们先进行以下规定:
---玩家赢:*
---电脑赢:#
---平局:P
---继续:C
我们设置一个字符类型的ret来存放Is_Wind()函数的返回结果,如果返回的字符不是‘C’就一直循环下去:
while(1)
{
//玩家下棋
//ret = 判断输赢(我们设置char函数,使其判断后返回一个字符给我们并放在ret中)
if(ret !='C')
{
break;//说明不再继续,跳出循环
}
//显示棋盘(若未出结果,显示以下棋盘,方便继续下棋)
电脑下棋同理
}
那么游戏判断输赢的具体规则是什么呢?
棋盘个元素的表示:board[x][y]
三子棋的话只要有3个相同的元素在一条线上就能判断输赢,并且不能是' ',我们将它拆分为3块
---判断行:board[0][0]==board[0][1]==board[0][2]&&board[0][1]!=' '
接下来我们使用循环去判断3次即可
---判断列:board[0][0]==board[1][0]==board[2][0]&&board[1][0]!=' '
同上,使用循环判断3次
---判断对角线
左对角线:board[0][0]==board[1][1]==board[2][0]&&board[1][1]!=' '
右对角线:board[0][0]==board[1][1]==board[2][2]&&board[1][1]!=' '
---上面已经判断完赢的情况,接下来判断平局与继续的情况
继续:当棋盘元素中至少有一个元素为空
平局:当棋盘所有元素不为空的时候
我们设置一个判断是否为满的函数:Is_Full,在这个函数中我们去遍历棋盘的所有元素,
如果发现有一个为' ',就返回0,ret为'C'
如果遍历完发现没有元素为‘ ’,就返回1,ret为'P'
我们在外面根据返回的结果来决定ret 为‘P’还是'C'
判断棋盘是否满了:
int Is_Full(char board[row][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;
}
Is_Wind代码如下:
char Is_win(char board[row][col])
{
// 判断行
int i, j;
int num = 0;
char ret = 'C ';
for (i = 0; i < row; i++)
{
if (board[i][0]==board[i][1]==board[i][2]&&board[i][1]!=' ')
{
ret = board[i][j];
return ret;
}
}
// 判断列
for (j = 0; i < col; j++)
{
if (board[0][j]==board[1][j]==board[2][j]&&board[1][j]!=' ')
{
ret = board[i][j];
return ret;
}
}
// 判断对角线
if (board[0][0]==board[1][1]==board[2][0]&&board[1][1]!=' ')//左对角线
{
ret = board[i][j];
return ret;
}
if (board[0][0]==board[1][1]==board[2][2]&&board[1][1]!=' ')//右对角线
{
ret = board[i][j];
return ret;
}
//判断棋盘是否满了
num = Is_Full(board);//如果棋盘满了返回1,没满返回0
if (num == 1)
ret = 'P';//满了返回P
else
ret = 'C';//没满ret为'C',继续游戏
return ret;
}
此时游戏判断结果已出,跳出while循环,ret结果确定
我们定义一个函数,将ret传递过去,根据ret的结果来打印
//打印结果
void Print_ret(char ret)
{
if (ret == '*')
printf("玩家胜利:\n");
else if (ret == '#')
printf("电脑胜利\n");
else if (ret == 'P')
printf("平局\n");
else
{
printf("游戏继续\n");
}
}
代码能否进行优化?如果我想要实现N子棋,代码那部分可以修改?
---我们最开始使用了对象式宏去定义了棋盘的行与列,只需要修改其后面的数值就能实现N子棋的一部分
---我们显示棋盘的函数是将其拆分的,元素行与分割行,row,col有多大就会打印多大的棋盘
---需要优化的为Is_wind()函数:
我们仅仅写了三子棋的判断:
1.判断行2.判断列3.判断对角线
但我们写的是相对具体的下标,若要实现N子棋像之前那样写的话会造成冗余
因此:我们来找下规律
---判断在一条线上是否有N个元素相等,我们可以两两比较:
例如:判断a,b,c三个数是否相等,我们可以判断a与b是否相等,b与c是否相等若都相等说明a,b,c三个数相等
我们访问棋盘中的元素是通过下标来访问的
----因此我们可以使用多重循环遍历来改变下标从而得到:每一行上的元素,每一列上的元素,对角线上的元素
----我们引出计数器count=0;如果这相邻的两个元素相等,count就+1,直到count=N-1.
说明已经比较了N-1次,并且相邻两个元素都相等,就能说明这N个元素是相等的
对比上面:a,b,c 引出count,a和b相等,count+1,b和c相等,count+1;比较完后count=2(N-1),此时a,b,c三个数相等
注意:在Is_Wind函数中我们要进行多组判断(行,列,对角线)截至条件是count==N-1
因此在进行下一组判断时需要将count重置为0,并且保证相等的元素不为‘ ’
//判断输赢
char Is_win(char board[row][col])
{
// 判断行
int i, j;
int count = 0;
int num = 0;
char ret = 'C ';
for (i = 0; i < row; i++)
{
count = 0; // 每次判断一行时需要将 count 重置为 0
for (j = 0; j < col - 1; j++)
{
if (board[i][j] == board[i][j + 1] && board[i][j] != ' ')
{
count++;
if (count == row - 1)
{
ret = board[i][j];
return ret;
}
}
}
}
// 判断列
for (j = 0; j < col; j++)
{
count = 0; // 每次判断一列时需要将 count 重置为 0
for (i = 0; i < row - 1; i++)
{
if (board[i][j] == board[i + 1][j] && board[i][j] != ' ')
{
count++;
if (count == col - 1)
{
ret = board[i][j];
return ret;
}
}
}
}
// 判断对角线
count = 0; // 检查正对角线之前需要将 count 重置为 0
for (i = 0; i < row - 1; i++)
{
if (board[i][i] == board[i + 1][i + 1] && board[i][i] != ' ')
{
count++;
if (count == row - 1)
{
ret = board[i][i];
return ret;
}
}
}
count = 0; // 检查反对角线之前需要将 count 重置为 0
for (i = 0; i < row - 1; i++)
{
if (board[i][row - i - 1] == board[i + 1][row - i - 2] && board[i][row - i - 1] != ' ')
{
count++;
if (count == row - 1)
{
ret = board[i][row - i - 1];
return ret;
}
}
}
//平局
num = Is_Full(board);//如果棋盘满了返回1,没满返回0
if (num == 1)
ret = 'P';//满了返回P
else
ret = 'C';//没满ret为'C',继续游戏
return ret;
}
实现三子棋所运用到的知识:
-
---数组来存放棋盘 board[row][col]
---循环来读取数组中的元素 for,while,do__while
---使用函数来实现各个功能,实现模块化
---使用#define 定义常量,规定棋盘大小
---使用条件语句与操作符的搭配来达成限制条件 if(),==,!=,&&,||
---使用循环与break的搭配,可实现重复输入,达到条件后跳出循环
####整体框架test.c
---使用do{}while();循环来确保程序至少运行一次
---使用scanf()函数来读取用户所输入的数字
---使用选择语句:switch语句来对不同的输入值进行不同操作
注:do{}while();中while判断条件运用得当可以实现良好的逻辑
三子棋的实现当中,以玩家输入的input作为判断循环的截至条件:前面打印出的菜单给玩家提供选择,1.PLAY 0.EXIT 输入0循环结束退出,输入非0,循环一直运行下去,若输入非1,配合switch语句,提醒玩家重新输入,
####游戏逻辑game.c
//初始化棋盘
---通过多重循环遍历数组中的元素并将其赋为‘ ’
//玩家下棋-
--scanf()函数来读取玩家输入的坐标
--条件语句来限制输入范围(搭配关系运算符和逻辑运算符:==,!=,&&)
--while循环与break的使用(若玩家非法输入让其重新输入,直到坐标合法棋盘中有元素被替换)
//电脑下棋
---rand()函数生成随机数
//判断输赢
--使用函数模块化,多重循环读取数组元素,条件语句与操作符,
--条件语句搭配函数所返回的值来进行相关操作。
//打印结果
//使用函数,条件语句,所传递的参数来实现不同结果
####游戏声明game.h
---对要使用的函数进行声明,增加可读性,并在game.h中定义好row与col
---引用头文件,外部文件,一些函数写好后具有外部链接属性,想要使用它们需要对其引用