C语言| 如何搭建一个“#“字形游戏

一. 写在前面的话

众所周知,"#"字形游戏在几乎所有人的校园时光中都带来过一定的欢声笑语,那么,我们是否有可能用有限的技术在电脑上实现简单的一个“#“字形游戏呢?

可喜的是,在我们学完二维数组以后,就已经掌握了"#"字形游戏的核心技术,完全可以在终端上敲出这样的小程序了。

二. 项目构建流程

//注意:这里笔者用的软件是mac版本的VScode,具体流程在win上类似

1. 第一步 创建项目

我们首先需要先创立一个文件夹jingziqi,并在其中创立三个子项目文件:game.c game.h test.c

我们将在在game.h中定义一系列需要使用到的函数,引用需要的库文件,并define一些基本参数,方便后期修改,在game.c文件中我们将实现我们定义的函数,并在test.c中调用它们。

这样的做法可以使我们的代码的可阅读性大大提高,是相对合理的书写规范。

2. 第二步 文件互相引用

我们打开game.h 在这里我们需要引入我们需要的库文件和定义我们需要的变量的值

示例如下:

而以后我们便不再需要重复调用<stdio.h> <stdlib.h>等库文件,而只需要调用game.h便可以了

示例如下:

实际上,由于我们的game.c文件需要geme.h中的库和数值,test.c需要game.c和game.h中的函数和库,所以可以game.c调用game.h,而test.c调用game.c实现最简化调用。

这里还是有细节可以注意的,比如在引用同目录下文件时,我们往往用检索能力更强的双引号" ",而对于系统自带的库,我们用< >引用即可。

3. 搭建框架

3.1 test.c文件

首先,我们当然需要一个main()函数作为程序的主入口。

我们需要在test.c中实现这个入口,这也意味着以后的程序启动也是由test.c文件负责。

在main函数中,我们需要完成一个游戏的基本功能,即:输入一定的数值,然后根据输入判定游戏开始/退出/输入非法重新输入。

具体的实现逻辑,可以用一个menu菜单函数返回一个input类型的值,最后用switch判断input的值并执行相应的代码。

//test.c文件部分

int main(){
    int input;
    do{
    input = menu();
    switch (input)
    {
    case 1:
        printf("开始游戏!\n");
        game();
        break;
    case 0:
        printf("退出游戏\n");
        break;
    default:
        printf("输入非法,请重新输入!\n");
    }
    }while(input);
    return 0;
}

3.2 game.h文件

显而易见的是,我们需要在game.h文件中定义menu()函数,game()函数,并在game.c中实现它们的功能。

这是我们要在game.h中写的代码,注意末尾的;,具体实现我们需要放在game.c中

需要特别注意的时,不管是我们在test中直接调调用的函数menu() game()还是后续在game.c的game()函数内部调用的函数如InitBoard() Playerturn()等等,都需要在game.h中声明,这么做的目的是方便管理自写函数。

本程序所需手写的所有函数如下:

//game.h中定义函数部分

int menu();

void game();

void InitBoard(char board[ROW][COL], int row, int col);

void PriBorad(char board[ROW][COL],int row,int col);

void PlayerTurn(char board[ROW][COL],int row,int col);

void ComputerTurn(char board[ROW][COL], int row, int col);

char CheckBoard(char board[ROW][COL],int row,int col);

3.3 game.c文件

在game.c文件中,我们要填充在game.h文件里声明的所有函数,当然,在这个文件中也可以调用内部的函数,这正是我们在game()函数中做的。

menu()函数较为简单,我们可以顺手解决,然后集中精力完成game函数

以下是menu函数的具体内容,我们将在game.c中实现

int menu(){
    int input;
    printf("********************************\n");
    printf("*****  欢迎来到井字棋游戏  *****\n");
    printf("*****     1. 开始游戏      *****\n");
    printf("*****     0. 退出游戏      *****\n");
    printf("********************************\n");
    scanf("%d",&input);
    return input;
}

由此,游戏的框架已经搭建完毕,运行test.c文件已经初有模型,接下来,我们需要完成最复杂的game函数的实现。

4. game函数的实现

1. 创建和初始化棋盘InitBoard()

我们需要一个用于下棋的棋盘,这是简单的

char board[ROW][COL];

ROW 和 COL我们之前都已经define过,从这里开始可以直接调用

由于刚刚创建的board数组中没有储存确定的数据,因此直接打印到棋盘上会出现行列歪斜(效果图可看下文“试运行”),因此我们需要对棋盘进行全元素' '的初始化设定(空格)。利用for循环这是容易实现的,我们可以吧for循环装进InitBoard()函数:

void InitBoard(char board[ROW][COL], int row, int col)
{
    int i,j;
    for(i=0;i<row;i++){
        for(j=0;j<col;j++){
            board[i][j]=' ';
        }
    }
}

InitBoard()函数可以直接写在game()函数的上方

接下来便是在game()函数中调用这个函数了,注意传参

    //初始化棋盘 令其中元素均为' '
    char board[ROW][COL];
    InitBoard(board,ROW,COL);

由此,棋盘棋子初始化便完成了。

2. 打印棋盘函数PriBoard()

打印棋盘在游戏中几乎是必不可少的操作,接受玩家每一步指令之后我们几乎都要打印一次棋盘到屏幕上,实现相关功能以后,我们将频繁调用这个函数

我们的目标棋盘样式为:

这同样也是容易的,我们可以把   |   |   和---|---|---作为一组循环打印三次,只需要注意第三次打印的时候不把---|---|---打印上去即可,这样的功能我们通过if条件语句实现

void PriBorad(char board[ROW][COL],int row,int col)
{
    for(int i = 0; i < row; i++){
        printf(" %c | %c | %c \n",board[i][0],board[i][1],board[i][2]);
        if (i < row - 1)
        printf("---|---|---\n");
    }
}

这样一来,打印棋盘的功能就被实现了。我们在game()中调用它,作为开始游戏的第一个提醒,

与此同时,我们也需要给出其他提醒语句,示例如下:

    //打印棋盘
    PriBorad(board,ROW,COL);

    //提示语句
    printf("请玩家选择棋盘的横纵坐标落子\n");
    printf("(例如,如果想落在左上角处,请输入:1 1)\n");

3. 落子机制和判定机制

这几乎是整个游戏中最复杂的一段代码,但整体难度其实并不高。

游戏逻辑在于,在接收到系统的提醒语句之后,玩家会开始给出坐标信息,系统就此在先前初始化的board数组中填充"#",同时棋盘会被打印一次,接下来电脑通过算法会填充数组中的一个非空元素为"*",同时再次打印一次棋盘。循环往复。

分析这段逻辑之后,我们发现,每次电脑/玩家操作完之后,不仅需要立刻打印一次棋盘,同时也需要给出判定,判定玩家是否获胜/电脑是否获胜/数组是否被填满但未分出胜负(平棋)。

由此我们知道,实现这样的功能需要一个while循环嵌套三个新的函数PlayerTurn() CompuerTurn()和CheckBoard()

3.1 玩家落子函数PlayerTurn()

根据分析,玩家无法落子的情况只有两种,一种是输入的坐标函数数组越界,另外一种是输入的数组坐标已经被玩家/电脑 占用。我们可以用两个if语句实现相应情况的判断。

而显然的是,如果输入错误,玩家有机会返回重新输入,所以这些代码仍然是嵌套在一个while循环之中的,只有输入正确可以break跳出循环。

以下是代码的具体实现:

void PlayerTurn(char board[ROW][COL],int row,int col)
{
    printf("请玩家落子\n");
    while(1){
    int x,y;
    scanf("%d%d",&x,&y);
    if (x >= 4 || x < 1 || y >= 4 || y < 1)
    {
    printf("输入数字违法,请重新输入!");
    continue;
    }
    else if (board[x-1][y-1] == ' ')
    {
    board[x-1][y-1] = '#';
    PriBorad(board,row,col);
    break;
    }
    else {
    printf("输入处已被占用,请重新输入!\n");
    continue;
    }
  } 
}
3.2 电脑落子函数ComputerTurn()

电脑落子函数,其是可以算作是一个算法而细细研究。"#"字形游戏先手不输的结论已经不再是秘密,这里我们游戏中的电脑是后落子,我们可以设计一款算法,让电脑在先手情况下处于不输的地步,但是为了简单起见,我们暂时为电脑设计随机落子,后续知识充沛了再回来完善算法,让电脑更加聪明。

需要注意的是,由于我们取随机数时已经限制了范围,所以相比较于玩家落子的判定,少了一个if条件语句,其他逻辑几乎是一样的。

具体实现如下:

void ComputerTurn(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] = '*';
        PriBorad(board,ROW,COL);
        break;
        }
        else
        continue;
    }
}
3.3 判定胜负函数CheckBoard()

判定胜负的逻辑也是简单的

首先可以横着三行检查,若数组元素相等且不为' ',则相应的玩家或电脑获胜;

竖着三列同理;

接下来,是斜着×型检查;

最后,else检查board数组是否被填满,如果代码运行到这并判定填满,则显然平局

我们用大量的for循环实现上述伪代码:

char CheckBoard(char board[ROW][COL],int row,int col)
{   
    //判定胜负情况
    for(int i = 0; i <3; i++){
        if(board[i][0]==board[i][1] && board[i][2]==board[i][1] && board[i][1] != ' ')
        return board[i][0];
    }
    for(int i = 0; i <3; i++){
        if(board[0][i]==board[1][i] && board[2][i]==board[1][i] && board[1][i] != ' ')
        return board[0][i];
    }
    if (board[1][1]==board[0][0] && board[0][0]==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];
    //判定平局情况
    int s = 0;
    for(int i = 0; i < 3; i++){
        for(int j = 0; j<3; j++){
            if (board[i][j] == ' ')
            s++;
        }
    }
    if (s == 0)
    return 'Q';
    //否则则未分出胜负,返回'C'
    else
    return 'C';
}

值得一提的是,检查胜负函数并不是一个void类型的函数,我们需要创建相应的变量接受它不同情况的返回值,这决定了game()中何时跳出while循环。

4. 组装函数

我们至此终于完成了所有需要函数的实现,接下来就是在game()函数中一一调用它们。

void game(){
    //初始化棋盘 令其中元素均为' '
    char board[ROW][COL];
    InitBoard(board,ROW,COL);

    //打印棋盘
    PriBorad(board,ROW,COL);

    //提示语句
    printf("请玩家选择棋盘的横纵坐标落子\n");
    printf("(例如,如果想落在左上角处,请输入:1 1)\n");

    char index;
    while(1){
    //玩家落子
    PlayerTurn(board,ROW,COL);
    index = CheckBoard(board,ROW,COL);
    if (index == '#'){
        printf("玩家胜利!\n");
        break;
    }else if (index == '*'){
        printf("电脑胜利!\n");
        break;
    }else if (index == 'Q'){
        printf("平局\n");
        break;
    }

    //电脑落子
    ComputerTurn(board,ROW,COL);
    index = CheckBoard(board,ROW,COL);
    if (index == '#'){
        printf("玩家胜利!\n");
        break;
    }else if (index == '*'){
        printf("电脑胜利!\n");
        break;
    }else if (index == 'Q'){
        printf("平局\n");
        break;
    }
  }
}

由此,一个简易的"#"字形游戏就搭建完成了。

三. 试运行

写完代码之后当然要试运行一下,感受一下自己的劳动成果了。

这是我们最开始写的menu()函数

我们输入1开始游戏

这里可以看到,我们之前初始化的函数InitBoard和打印函数PriBoard()生效了,如果注释掉InitBoard()函数,我们也可以看看相应的效果:

由此便可见初始化的重要性了。

继续往下看,在玩家输入坐标1 1的时候棋盘被打印一次,电脑瞬间落子之后棋盘又被打印一次,显然Check()函数没有打断这里的while循环,接下来while循环到玩家继续落子。

如果输入非法,相应的效果也是不错的

输入占用情况和输入越界情况:

最后检查判定模块:

1

所有功能实现,小程序成功!

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值