在前面我们学习了C语言的一些基础知识,包括:选择、循环、函数体以及数组,在这些知识得到掌握后。我们要不断地练习与应用,今天我们结合之前所学内容,给大家写一个C语言的基础游戏项目,用来练习之前所学的知识;主要练习的内容包括:If选择语句、switch选择语句、for循环、while循环、do···while循环、数组、函数体的相关知识。这节我们会完整的带大家写这个代码,并且对每一步进行逻辑上的分析。
目录
一、游戏说明与分析
游戏说明:
需要构造一个3×3(或者更大的 )棋子,开始键盘为空,玩家和电脑依次往棋盘中下入棋子,当下入后需要判断是否获胜,获胜的条件是:一方的棋子连城横线或者竖线或者斜线,让棋盘填满还没构成获胜条件时,则判定为平局。
在明白了游戏的规则后,我们需要对游戏的整个思路做出分析,这里绘制了一个游戏的流程图,大家可以查看。
二、项目源码
首先,我们分析这段代码会很长,所以我们不能直接将所有的代码全部写在一个文件中,因为代码的功能很多,所以我们分别用不同的文件来存放里面的内容,在此,我们分三个文件来:
1、main.c主要存放主函数,其他函数的调用以及整个逻辑框架;
2、main.h主要存放函数的声明;
3、game.c主要存放不同功能函数的定义;
接着,我们将源码供上:
1、main.c文件
#include "main.h"//包含函数声明的文件
int main()//定义主函数
{
Is_play();//调用判断是否进入游戏
return 0;
}
void game ()//进入游戏
{
char b;
char board[ROW][COL]={0};//定义数组
Initialize_board(board,ROW,COL);//1、初始化棋盘;使棋盘内容为空;
Display_board(board,ROW,COL); //2、打印棋盘;
while(1)
{
Player_chess(board,ROW,COL);//3、玩家下棋;
Display_board(board,ROW,COL);//4、打印棋盘
b = Is_win(board,ROW,COL);//5、判断是否继续
if(b!='c')//判断是否赢了,或者平局的可能;
break;//有赢了,或者平局的可能,跳出循环;
Computer_chess(board,ROW,COL);//6、没有可能,计算机接着下棋
Display_board(board,ROW,COL);//7、打印棋盘
b = Is_win(board,ROW,COL); //8、判断输赢
if(b!='c')//判断是否赢了,或者平局的可能;
break;//有赢了,或者平局的可能,跳出循环;
}
if(b =='*')//根据判断输赢的条件得到的返回值,判断是谁赢;
{
printf("玩家赢!\n");//顽疾赢;
}
else if(b =='#')
{
printf("电脑赢!\n");//电脑赢;
}
else
{
printf("平局!\n");//平局;
}
2、main.h文件
#include <stdio.h>//包含头文件
#define ROW 3//定义常量,棋盘的行数
#define COL 3//定义常量,棋盘的列数
void User_interface();//声明主界面函数
void Is_play();//声明判断是否玩游戏函数
void game();//声明进入游戏函数
void Initialize_board(char board[ROW][COL],int x,int y);//声明棋盘初始化函数
void Display_board(char board[ROW][COL],int x,int y);//声明打印棋盘函数
void Player_chess(char board[ROW][COL],int x,int y);//声明玩家下棋函数
void Computer_chess(char board[ROW][COL],int x,int y);//声明电脑下棋函数
char Is_win(char board[ROW][COL],int x,int y);//声明判断是否取胜函数
int is_full(char board[ROW][COL],int x,int y);//声明是否棋盘下满函数
3、game.c文件
#include "main.h"//包含声明函数
//函数1、定义打印主界面函数
void User_interface()
{
printf("***************************\n");
printf("***** 1.play 0.exit*******\n");
printf("***************************\n");
}
//函数2、定义选择界面函数
void Is_play()
{
int a;
do
{
User_interface();//引用打印主界面函数
printf("请选择:");
scanf("%d",&a);//输入数字
switch(a)//根据输入的数字判断执行什么操作
{
case 1://第1种:进入游戏
{
game();
break;
}
case 0://第二种:退出游戏
{
printf("退出游戏\n");
break;
}
default://第三种输入错误
printf("输入错误,请重新输入\n");
break;
}
}
while (a);//一直循环
}
//函数3、定义棋盘初始化函数
void Initialize_board(char board[ROW][COL],int x,int y)//棋盘初始化是一个二维数组
{
int i ,j;
for(i=0;i<x;i++)
{
for(j=0;j<y;j++)
{
board[i][j]=' ';//让棋盘中的元素都为空字符
}
}
}
//函数4、定义打印棋盘函数
void Display_board(char board[ROW][COL],int x,int y)//打印棋盘函数是将棋盘的初始状态打印出来
{
int i,j;
for(i=0;i<x;i++)
{
for(j=0;j<y;j++)
{
printf(" %c ",board[i][j]);
if(j<y-1)
printf("|");
}
printf("\n");
if(i<x-1)
{
for(j=0;j<y;j++)
{
printf("---");
if(j<y-1)
printf("|");
}
printf("\n");
}
}
}
//函数5、定义玩家输入函数
void Player_chess(char board[ROW][COL],int x,int y)
{
int i ,j;
printf("玩家下棋>\n");
while(1)
{
printf("请输入坐标>\n");//玩家输入坐标
scanf("%d %d",&i,&j);
if(i>0&&i<=x&&j>0&&j<=y)//判断输入的坐标知否可行
{
if(board[i-1][j-1]==' ')
{
board[i-1][j-1]='*';
break;
}
printf("坐标已输入,请重新输入>\n");
}
else
printf("坐标非法,请重新输入>\n");
}
}
//函数6、定义计算机输入函数
void Computer_chess(char board[ROW][COL],int x,int y)
{
int i,j;
printf("电脑下棋>\n");
while(1)
{
i=rand()%x;//产生随机坐标的行数
j=rand()%y;//产生随机坐标的列数
if(board[i][j]==' ')//判断产生的坐标是否满足要求
{
board[i][j]='#';//满足下入
break;
}//不满足重新产生
}
}
//函数7、判断棋盘是否下满函数
int isfull(char board[ROW][COL],int x,int y)
{
int i=0,j=0;
for(i=0;i<x;i++)
{
for(j=0;j<y;j++)
{
if(board[i][j]==' ')
{
return 0;//棋盘没满,返回函数值0
}
}
}
return 1;//棋盘下满,返回函数值1
}
//函数8、定义判断是否取胜函数
char Is_win(char board[ROW][COL],int x,int y)
{
int i,j,a;
//8.1、判断取胜条件1:是否行相等;
for(i=0;i<x;i++)
{
for(j=0;j<y-1;j++)
{
if(board[i][j]==board[i][j+1]&&board[i][j]!=' ')
a=1;
else
{
a=0;
break;
}
}
if(a==1)
return board[i][j];
}
//8.2、判断取胜条件2:是否列相等;
for(j=0;j<y;j++)
{
for(i=0;i<x-1;i++)
{
if(board[i][j]==board[i+1][j]&&board[i][j]!=' ')
a=1;
else
{
a=0;
break;
}
}
if(a==1)
return board[i][j];
}
//8.3、判断取胜条件3:是否左斜线相等;
for(i=0;i<x-1;i++)
{
if(board[i][i]==board[i+1][i+1]&&board[i][i]!=' ')
a=1;
else
{
a=0;
break;
}
}
if(a==1)
return board[i][i];
//8.4、判断取胜条件4:是否右斜线相等;
for(i=0;i<x-1;i++)
{
if(board[i][x-1-i]==board[i+1][x-2-i]&&board[i][x-1-i]!=' ')
a=1;
else
{
a=0;
break;
}
}
if(a==1)
return board[i][x-1-i];
//8.5没有满足获胜条件,判断是否棋盘已满
if(isfull(board,x,y))//调用函数7、判断棋盘是否下满函数
{
return 'q' ;//已满,平局条件
}
//
return 'c';//不满,游戏继续
}
以上是实现游戏的代码,大家使用时,创建一个项目,在工程底下创建三个文件,分别为main.c、main.h、game.c,将上述代码依次复制进去即可运行。
三、运行结果
下面截图是匀性的结果:
四、分块详细说明
1、进入主界面并选择
在进入主界面后,我们有三种选项,分别是进入游戏,退出游戏以及输入输入错误选项;输入1进入游戏函数,输入0是退出游戏,输入其他是再次进入主界面重新选择,因此我们需要一个do··while循环,其作用就是,先打印依次主界面,然后,根据选择判断不同的情况,循环条件为死循环,只有在进入游戏或者退出游戏时,采用break跳出循环。
其中调用的打印主界面函数,也是比较简单,只需要打印三行代码,没有返回值,所以采用void空函数定义:
代码:根据选择判断
void Is_play()
{
int a;
do
{
User_interface();//引用打印主界面函数
printf("请选择:");
scanf("%d",&a);//输入数字
switch(a)//根据输入的数字判断执行什么操作
{
case 1://第1种:进入游戏
{
game();
break;
}
case 0://第二种:退出游戏
{
printf("退出游戏\n");
break;
}
default://第三种输入错误
printf("输入错误,请重新输入\n");
break;
}
}
while (a);//一直循环
代码:主界面
void User_interface()
{
printf("***************************\n");
printf("***** 1.play 0.exit*******\n");
printf("***************************\n");
}
2、初始化棋盘
在进入游戏后,主函数进入游戏函数模块game(),首先要进行数组初始化,这里的数组是一个ROW,COL的二位数字,ROW,COL是我们在main.h中定义棋盘行数与列数,因为我们要将下的棋子放在这个数组中去,所以我们首先要对这个二维数组的每个元素初始化,即让他们每一个元素为空格字符;
这个函数参数分别是主函数定义的二维数组的数组名,以及数组的行数与列数,利用两层for循环,完成对所有元素的初始化;
void Initialize_board(char board[ROW][COL],int x,int y)//棋盘初始化是一个二维数组
{
int i ,j;
for(i=0;i<x;i++)
{
for(j=0;j<y;j++)
{
board[i][j]=' ';//让棋盘中的元素都为空字符
}
}
}
3、打印棋盘
在初始化完成后,我们在下棋之前要先有一个棋盘的样子,如下图所示:
此处的难点在与如果构建这些下划线以及竖线。我们分析每一个元素底下都有一行下划线,除过最后一行元素,每一列的元素右边也都有一个下划线,除过最后一列元素,因此我们可以借助之前的二维数组,对棋盘进行打印。在上图中,二位数组只存在于其数行中间空格中。
void Display_board(char board[ROW][COL],int x,int y)//打印棋盘函数是将棋盘的初始状态打印出来
{
int i,j;
for(i=0;i<x;i++)
{
for(j=0;j<y;j++)
{
printf(" %c ",board[i][j]);//打印棋子行,注意二维数组元素前后各有一个空格。
if(j<y-1)
printf("|");//打印棋子右边的竖线
}
printf("\n");
if(i<x-1)
{
for(j=0;j<y;j++)
{
printf("---");//打印棋子下行的下划线
if(j<y-1)
printf("|");
}
printf("\n");
}
}
}
4、玩家下棋
在进行完棋盘初始化后,首先由玩家下棋,玩家只需要输入坐标即可,需要注意的是,坐标的行与列是要比数组下标的行与列大1,因为数组的下标是从0开始,因此,与要注意输入的坐标与数组的下标之间的差距,我们定义玩家下入棋子的符号为“*”。在玩家下入棋子后,我们需要做出以下判断,输入的坐标范围是否在棋盘的坐标范围内,如果不在,就需要重新进行输入,如果在范围内,还需要判断所输入的坐标内是否有棋子,如果没有棋子,为空格才能下入,如果有棋子还是需要重新输入坐标。
void Player_chess(char board[ROW][COL],int x,int y)
{
int i ,j;
printf("玩家下棋>\n");
while(1)
{
printf("请输入坐标>\n");//玩家输入坐标
scanf("%d %d",&i,&j);
if(i>0&&i<=x&&j>0&&j<=y)//判断输入的坐标知否可行
{
if(board[i-1][j-1]==' ')
{
board[i-1][j-1]='*';
break;
}
printf("坐标已输入,请重新输入>\n");
}
else
printf("坐标非法,请重新输入>\n");
}
}
在玩家下完棋后,对棋盘进行更新打印,需要进行判断,此时判断的是 ,玩家是否赢了,如果没赢,也没平局就继续由电脑下棋,反之,需要跳出循环,判断是赢了还是平局。
while(1)
{
Player_chess(board,ROW,COL);//3、玩家下棋;
Display_board(board,ROW,COL);//4、打印棋盘
b = Is_win(board,ROW,COL);//5、判断是否继续
if(b!='c')//判断是否赢了,或者平局的可能;
break;//有赢了,或者平局的可能,跳出循环;
Computer_chess(board,ROW,COL);//6、没有可能,计算机接着下棋
Display_board(board,ROW,COL);//7、打印棋盘
b = Is_win(board,ROW,COL); //8、判断输赢
if(b!='c')//判断是否赢了,或者平局的可能;
break;//有赢了,或者平局的可能,跳出循环;
}
5、电脑下棋
玩家下完棋后,并且判断没有平局和玩家赢后,就需要由电脑下棋,电脑下棋的方式与玩家基本一致,就是往空格数组中填入元素#,然而我们需要一个产生随机数的函数rand,让其%行数与列数,作为新的坐标填入数组中,除余后的数子肯定在棋盘的坐标范围内,我们只需要再次判断所输入的坐标内是否有棋子,如果没有棋子,为空格才能下入,如果有棋子还是需要重新产生随机数,随意此处也需要用一个循环。
void Computer_chess(char board[ROW][COL],int x,int y)
{
int i,j;
printf("电脑下棋>\n");
while(1)
{
i=rand()%x;//产生随机坐标的行数
j=rand()%y;//产生随机坐标的列数
if(board[i][j]==' ')//判断产生的坐标是否满足要求
{
board[i][j]='#';//满足下入
break;
}//不满足重新产生
}
}
6、判断是否获胜
在进行棋子填充后,我们需要对棋盘中的棋子进行判断,判断其是否满足获胜要求,根据前面所说,获胜的情况有1、有一行相同;2、有一列相同;3、左斜线相同;4、右斜线相同。所以在定义判断是否取胜函数,我们需要判断以下几种可能
6.1行相同
依次判断每一行,在每行中,每个元素是否相同并且不能等于空格,我们采用两两对比,如果有一个不相同,就跳出验证下一行,如果有一行相等,就给函数返回这一行的元素,后面根据返回的字符判断是谁赢了。
for(i=0;i<x;i++)
{
for(j=0;j<y-1;j++)
{
if(board[i][j]==board[i][j+1]&&board[i][j]!=' ')//依次判断两个元素是否相同
a=1;//如果相同a=1
else
{
a=0;//如果不相同a=1并跳出这一行
break;
}
}
if(a==1)//如果一行判断完,a始终为1,就返回其中相同的字符给函数
return board[i][j];
}
6.2行相同
依次判断每一列,在每列中,每个元素是否相同并且不能等于空格,我们采用两两对比,如果有一个不相同,就跳出验证下一列,如果有一列相等,就给函数返回这一列的元素,后面根据返回的字符判断是谁赢了。
for(j=0;j<y;j++)
{
for(i=0;i<x-1;i++)
{
if(board[i][j]==board[i+1][j]&&board[i][j]!=' ')//依次判断某列两个元素是否相同
a=1;//如果相同就令a=1
else
{
a=0;//不同就令a=0并且跳出这列判断
break;
}
}
if(a==1)//如果判断完某列a始终为1,就将相同的字符返回函数
return board[i][j];
}
6.3判断左斜线
依次判断左斜线上的元素,由于棋盘为方形,所以左斜线上元素的[][]下标是相同的,所以都用[i][i]来表示,如果有一个不相同,就跳出验证,如果全部相等,就给函数返回这一列的元素,后面根据返回的字符判断是谁赢了。
for(i=0;i<x-1;i++)
{
if(board[i][i]==board[i+1][i+1]&&board[i][i]!=' ')
a=1;
else
{
a=0;
break;
}
}
if(a==1)
return board[i][i];
6.4判断右斜线
依次判断右斜线上的元素,由于棋盘为方形,所以右斜线上元素的下标是用[i][y-j]来表示,如果有一个不相同,就跳出验证,如果全部相等,就给函数返回这一列的元素,后面根据返回的字符判断是谁赢了。
for(i=0;i<x-1;i++)
{
if(board[i][x-1-i]==board[i+1][x-2-i]&&board[i][x-1-i]!=' ')
a=1;
else
{
a=0;
break;
}
}
if(a==1)
return board[i][x-1-i];
6.5判断是否为平局或者游戏继续
当上述判断结束,并没有符合获胜的条件时,现在需要判断是否平局,如果不是平局,就证明游戏应当继续,而判断是否为平局,就是判断棋盘中的元素是否全部满了,如果满了就说明是平局,如果不满就说明不是平局。
依次判断棋盘中是否还有空格:
int isfull(char board[ROW][COL],int x,int y)
{
int i=0,j=0;
for(i=0;i<x;i++)
{
for(j=0;j<y;j++)
{
if(board[i][j]==' ')
{
return 0;//棋盘没满,返回函数值0
}
}
}
return 1;//棋盘下满,返回函数值1
}
根据是否有空格判断是平局还是游戏继续,返回给函数不同的字符;
if(isfull(board,x,y))//调用函数7、判断棋盘是否下满函数
{
return 'q' ;//已满,平局条件
}
//
return 'c';//不满,游戏继续
7、判断是谁获
根据第六步判断条件,我们可以得到不同的函数返回值(字符),根据不同的返回值,我们判断是谁最终获胜。
if(b =='*')//根据判断输赢的条件得到的返回值,判断是谁赢;
{
printf("玩家赢!\n");//顽疾赢;
}
else if(b =='#')
{
printf("电脑赢!\n");//电脑赢;
}
else
{
printf("平局!\n");//平局;
}
以上便是整个游戏代码的分块详细说明,能够对于整个 代码的框架与逻辑做出很好的分析。如果想运用到4、5···等更多行,只需要在头文件的定义中,改变行数与列数的定义即可;
写在后面:
这是一篇很长的博客,对于这个项目做出了很细致的讲解,你能够耐着性子看完这篇博客是值得点赞的,通过这篇的学习,我相信大家对于相关的知识(If选择语句、switch选择语句、for循环、while循环、do···while循环、数组、函数体等)有了不错的历练。当然,代码的写法不唯一,这种写法也可能不是最简便的,本人能力有限,还望大佬多多指正,如果有什么问题,欢迎大家一起讨论,共同进步。创作不易,还望大家多多点赞支持;