C语言三子棋

简介: 到这里我们的三字棋游戏就完成了,当然肯定还有很多不尽人如意的地方,比如电脑比较笨,总是赢不了,我们的判断部分也是固定写死地,那么如何能优化一下变成五子棋呢?这些处需要优化的地方推荐大家自己去尝试,如果以后有时间的话,我也会写出来让大家看一下,这篇花费的精力可是不少,喜欢的话就请点点赞收藏一下吧,偷偷告诉你:后续还会有扫雷哦!

@TOC

开篇语

今天我们要实现的是个C语言入门级别的小游戏,三子棋,相信大家应该小时候都玩过,但是这里还是要稍微解释一下,三子棋呢就是类比五子棋,但是有9个格子,玩家和电脑各下一颗棋子,谁先三个连成一条线,谁就可以获胜。

1、整体框架

首先我们还是要有一个整体的思路,先简单分析一下:
在这里插入图片描述
第一步肯定是先有一个整体的游戏框架,这个在前面的猜数字小游戏中,我们有详细解释,传送门请看这里猜数字小游戏详细解读,附源码,我们就不再过多赘述,直接看代码:

#define  _CRT_SECURE_NO_WARNINGS 1


#include<stdio.h>

void menu()
{
   
   
    printf("*****************************\n");
    printf("********     1.play    ******\n");
    printf("********     0.exit    ******\n");
    printf("*****************************\n");
}

void game()
{
   
   
    printf("三子棋\n");
}


void test()
{
   
   
    int input = 0;
    do
    {
   
   
        menu();
        scanf("%d",&input);
        switch (input)
        {
   
   
        case 1:
            game();
            break;
        case 0:
            printf("退出游戏\n");
            break;
        default:
            printf("输入非法,请重新输入\n");
            break;
        }

    } while (input);

}


int main()
{
   
   
    test();
    return 0;
}

然后我们再强调一遍,一定要写一部分测试一部分,千万不要一口气写完,到最后发现一堆bug,然后连改的心思都没有了,我们来看一下框架有没有问题:

在这里插入图片描述
看来我们的整体框架是没有什么问题的,接下来我们就要实现游戏的主体部分:

创建一个棋盘

这里有的小年轻可能一看,这创建一个棋盘,不就是一个3*3的数组嘛,直接这样写:

void game()
{
   
   
    char board[3][3];
}

到这里,就不得不提一下,因为我们今天要实现的是三字棋,这样没有问题,但是格局有点小,如果我们要把这个代码改成五子棋呢?是不是代码中的好多地方都要跟着改才行,所以我们最后要想一个办法把这些整个程序中经常出现的数据放到一起,前面我们已经了解到了#define定义宏,这里我们是不是就能用上了呢?

#define ROW 3
#define COL 3
#include<stdio.h>


void game()
{
   
   
    char board[ROW][COL];
}

这样我们的棋盘就有了;

初始化棋盘

当我们有了一个棋盘后,可以先把棋盘里每一个元素先放成空格,封装一个初始化棋盘的函数,代码如下:

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] = ' ';
        }
    }
}
void game()
{
   
   
    char board[ROW][COL];
    InitBoard(board,ROW,COL);

}

这里可能有小伙伴会有疑问,为什么还要传3和3呢,直接使用定义的标识符ROW,COL不就好了吗?可以是可以,但是我们说尽量不要过分依赖于这种全局的量,因为一旦离开了这些你的代码就完全不能用了,所以我们平时基本都不使用全局变量,就是为了代码的安全性。还有一个细节就是函数传参数的时候,我们接受的时候是直接用的数组的形式,从严格意义上来说我们是应该是用指针来接收的,但是由于我们目前学的知识受限,可以先写成这种形式,当然其实这种形式和指针本质上也是相同的。

打印棋盘

我们有了一个好的棋盘,接下来我们要打印出来看一下,所以我们需要再封装一个打印棋盘的函数,这里可千万不要简单想着直接把9个元素打印出来就行了,要知道我们打印出的是空格啊,是什么都看见的,我们要一个好看一点的棋盘,那么该怎么做呢?这里放上一个做好的图:
在这里插入图片描述
可以看到我们的棋盘是长这个样子的,那么我们该怎么来实现呢?我们首先想到的可能是:

void PrintBoard(char board[ROW][COL],int row,int col)
{
   
   
    int i = 0;
    for (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");
        }
    }
}

这样打印出来的棋盘看起来也是一模一样的,但是还是我们之前说过的,考虑到代码的通用性,如果我们打印5子棋的棋盘呢?所以我们还是要考虑用循环的方式来打印:

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

这是最容易想到的代码,但是效果距离我们想要的效果还差一些,
在这里插入图片描述
可以看到最右边和最下边是多出来一列,所以在此基础上我们还要优化一下:

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

这样就可以很好的打印出我们要的棋盘了,当这时候我们如果把ROW,和COL改成5,那么我们依旧可以打印出5*5的棋盘,
在这里插入图片描述
这就是好的代码,一定要综合考虑代码的可移植性,通用性,稳定性,安全性等等诸多因素。

思考游戏逻辑

我们在动手写代码时提前一定要想好,至少得有大致的思路:
我们要实现的游戏逻辑就是玩家下棋,电脑下棋,然后判断输赢(两次,玩家或者电脑下后每次都得判断),当我们想要去实现时,我可以这样想,
判断输赢:
如果玩家赢了我返回星号 *
如果电脑赢了我返回井号 #
如果是平局我返回 Q
如果上述都不是,游戏继续的话,我返回 C;
这样我们就有了一个整体的逻辑:
我们先来实现玩家下棋:

玩家下棋:

//玩家下棋

void PlayerMove(char board[ROW][COL],int row,int col)
{
   
   
    int x = 0;
    int y = 0;
    scanf("%d %d",&x,&y);
    board[x][y] = '*';

}

上述代码如果你认为正确的话,那你就大错特错了,绝对不会这么简单滴,你想一下,首先:你连个提示都没有,第二:小白玩家可不是我们程序员,看到数组就知道数组是从0开始的,他们可是简单的认为第一行就是1,第三:你是否考虑到玩家输入坐标的合法性,如果玩家输入一个100,100怎么办(当然也不太会出现),但是输入错误是很正常的事,第四:如果输入的坐标已经被占用了怎么办。所以这段代码可以说是漏洞百出。你想想要是面试写出这样的代码是多么恐怖的事,所以还是多培养编程思维,多多思考。那么我们来看正确的代码:

 //玩家下棋

void PlayerMove(char board[ROW][COL],int row,int col)
{
   
   
    int x = 0;
    int y = 0;
    printf("请输入要下棋的坐标:>");
    while (1)
    {
   
   
        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("坐标已被占用,请重新输入:>");
            }
        }
        //不合法
        else
        {
   
   
            printf("坐标非法,请重新输入:>");
        }
    }
}

当然了,我们这时候要测试一下代码到这里是否正确,在玩家每次下棋之后也要打印出棋盘,并且玩游戏下棋是要重复的动作,也要放到循环里面,所以我们的测试部分:

void game()
{
   
   
    char board[ROW][COL];
    InitBoard(board,ROW,COL);
    PrintBoard(board, ROW, COL);
    while (1)
    {
   
   
        PlayerMove(board, ROW, COL);
        PrintBoard(board, ROW, COL);
    }


}

在这里插入图片描述

这时候去测试一下我们之前写过的代码时没有任何问题的,那么接下来我们继续:

电脑下棋:

要实现电脑下棋就不得不提到随机数,因为电脑随机生成坐标去下棋,如果不明白随机数的还是去看上面我放到猜数字小游戏链接,里面有详细的随机数教程,电脑下棋和玩家下棋道理就非常相似了,只是多了一步随机数的生成,我们看代码:

//电脑下棋

void ComputerMove(char board[ROW][COL], int row, int col)
{
   
   
    printf("电脑下棋:>\n");
    while (1)
    {
   
   
        int x = rand() % row + 1;
        int y = rand() % col + 1;
        if (board[x][y] == ' ')
        {
   
   
            board[x][y] = '#';
            break;
        }
    }
}

我们只需要控制生成的随机数在0~2即可,然后判断此处坐标是否被占用。比起玩家下棋的部分甚至更简单。

判断是否获胜;

我们在每次下棋过后都需要判断是否有人获胜,我们还是把刚才我们思考的逻辑拿过来:
判断输赢:
如果玩家赢了我返回星号 *
如果电脑赢了我返回井号 #
如果是平局我返回 Q
如果上述都不是,游戏继续的话,我返回 C;
这样我们就有了一个整体的逻辑

这样我们就去想办法实现,我们先来封装一个判断输赢的函数,注意这时候我们要返回字符,返回值就不能写void了,判断输赢,赢的情况无非就三种,三行三列,两条对角线,如果你直接一堆if来判断当然也是可以的,但是尽量不要,如果实在想不出来,也可以考虑:

//判断输赢

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];
        }
        //判断三列
        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];
    }
    return 'C';
}

当然这里我们仅仅是写出了判断,在game函数里面也要补充上对应的判断函数和返回值部分:

void game()
{
   
   
    char board[ROW][COL];
    InitBoard(board,ROW,COL);
    PrintBoard(board, ROW, COL);
    char ret = 0;
    while (1)
    {
   
   
        PlayerMove(board, ROW, COL);
        PrintBoard(board, ROW, COL);
        ret=IsWin(board, ROW, COL);
        if (ret != 'C')
        {
   
   
            break;
        }
        ComputerMove(board,ROW,COL);
        PrintBoard(board, ROW, COL);
        ret=IsWin(board, ROW, COL);
        if (ret != 'C')
        {
   
   
            break;
        }
    }
    if (ret == '*')
    {
   
   
        printf("恭喜你获胜\n");
    }
    if (ret == '#')
    {
   
   
        printf("很遗憾,电脑赢了\n");
    }
    if (ret == 'Q')
    {
   
   
        printf("平局\n");
    }
}

这里我们还少了一种情况就是平局,所以在我们的判断部分还差一种情况,我们再封装一个函数看棋盘是否满了,如果棋盘满了则表示平局了,我们只需要将棋盘遍历一遍即可,只要有一个地方不是空格,就表示没满,否则就是满了,

//判断棋盘是否满了

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;
}

这样最终我们的三子棋就基本完成了,当然我们还可以加上一点小修饰,比如玩完一把后清空一下屏幕,我们来看最终的代码:

#define  _CRT_SECURE_NO_WARNINGS 1

#define ROW 3
#define COL 3
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#include<windows.h>

//初始化棋盘
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] = ' ';
        }
    }
}


//打印棋盘

//void PrintBoard(char board[ROW][COL],int row,int col)
//{
   
   
//    int i = 0;
//    for (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");
//        }
//    }
//}
//

//优化后

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

//玩家下棋

void PlayerMove(char board[ROW][COL],int row,int col)
{
   
   
    int x = 0;
    int y = 0;
    printf("请输入要下棋的坐标:>");
    while (1)
    {
   
   
        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("坐标已被占用,请重新输入:>");
            }
        }
        //不合法
        else
        {
   
   
            printf("坐标非法,请重新输入:>");
        }
    }
}

//电脑下棋

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 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];
        }
        //判断三列
        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';
}

void game()
{
   
   
    char board[ROW][COL];
    InitBoard(board,ROW,COL);
    PrintBoard(board, ROW, COL);
    char ret = 0;
    while (1)
    {
   
   
        PlayerMove(board, ROW, COL);
        PrintBoard(board, ROW, COL);
        ret=IsWin(board, ROW, COL);
        if (ret != 'C')
        {
   
   
            break;
        }
        ComputerMove(board,ROW,COL);
        PrintBoard(board, ROW, COL);
        ret=IsWin(board, ROW, COL);
        if (ret != 'C')
        {
   
   
            break;
        }
    }
    if (ret == '*')
    {
   
   
        printf("恭喜你获胜了\n");
        Sleep(2000);
        system("cls");
    }
    if (ret == '#')
    {
   
   
        printf("很遗憾,电脑赢了\n");
        Sleep(2000);
        system("cls");
    }
    if (ret == 'Q')
    {
   
   
        printf("平局\n");
        Sleep(2000);
        system("cls");
    }
}


void menu()
{
   
   
    printf("*****************************\n");
    printf("********     1.play    ******\n");
    printf("********     0.exit    ******\n");
    printf("*****************************\n");
}


void test()
{
   
   
    int input = 0;
    srand((unsigned int)time(NULL));
    do
    {
   
   
        menu();
        printf("请输入:>");
        scanf("%d",&input);
        switch (input)
        {
   
   
        case 1:
            game();
            break;
        case 0:
            printf("退出游戏\n");
            break;
        default:
            printf("输入非法,请重新输入\n");
            break;
        }

    } while (input);

}


int main()
{
   
   
    test();
    return 0;
}

到这里我们的三字棋游戏就完成了,当然肯定还有很多不尽人如意的地方,比如电脑比较笨,总是赢不了,我们的判断部分也是固定写死地,那么如何能优化一下变成五子棋呢?这些处需要优化的地方推荐大家自己去尝试,如果以后有时间的话,我也会写出来让大家看一下,这篇花费的精力可是不少,喜欢的话就请点点赞收藏一下吧,偷偷告诉你:后续还会有扫雷哦!!

相关文章
|
3天前
|
算法 C语言 C++
【C语言实战项目】三子棋游戏
【C语言实战项目】三子棋游戏
33 1
|
3天前
|
C语言
C语言之三子棋小游戏
C语言之三子棋小游戏
|
3天前
|
C语言
用c语言实现三子棋
用c语言实现三子棋
12 0
|
3天前
|
程序员 C语言
C语言设计三子棋
C语言设计三子棋
|
3天前
|
存储 小程序 编译器
C语言之三子棋小游戏的应用
C语言之三子棋小游戏的应用
|
3天前
|
C语言
C语言之详解数组【附三子棋和扫雷游戏实战】(二)
C语言之详解数组【附三子棋和扫雷游戏实战】(二)
|
5月前
|
C语言
三子棋真是太神奇啦~~~C语言三子棋小游戏详解,具体到每一步操作的解释说明,不信你学不会!
三子棋真是太神奇啦~~~C语言三子棋小游戏详解,具体到每一步操作的解释说明,不信你学不会!
43 2
|
3天前
|
C语言
三子棋c语言讲解
三子棋c语言讲解
|
3天前
|
C语言
C语言-三子棋
C语言-三子棋
24 1
|
3天前
|
存储 编译器 C语言
C语言之详解数组【附三子棋和扫雷游戏实战】(一)
C语言之详解数组【附三子棋和扫雷游戏实战】(一)