0


三子棋游戏(利用基础语法实现人工智能)

通过基础的C语言知识,来实现三子棋的人工智能,让电脑可以实现自由落子、堵棋以及赢棋的功能。整体代码650行左右,适合刚接触编程的同学,在实现程序的同时,增加自己的编程知识,并且锻炼用逻辑思维思考的能力。

整体结构组成

游戏代码主要由三部分组成,text.c存放主函数,game.c存放游戏实现部分的函数,game.h声明在game.c中定义的函数。

text.c(菜单、主函数)

game.c(游戏内容)

game.h(声明游戏内容中所包含的函数)

大致框架

为了避免有输入错误的情况,以及玩完一遍游戏还可以玩第二遍,这里整个游戏框架用do while循环,保证整个流程至少可以运行一次。

#include"game.h"

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

int main()
{
    int input = 0;
    do
    {
        menu();
        printf("请选择(1/0):>");
        scanf("%d", &input);
    } while (input);
    return 0;
}

随后,针对输入的值不同,来判定是进行游戏还是退出游戏,或是输入错误。这里可以用switch 选择语句完成该部分的判断。

switch (input)
        {
        case 0:
            printf("退出游戏\n");
            break;
        case 1:
            printf("开始游戏\n");
            game();//游戏
            Sleep(500);//等500毫秒
            system("cls");//清空屏幕
            break;
        default:
            printf("输入错误,请重新输入\n");
        }

当我们完成一次游戏后,为了方便进行下一次玩,用**system("cls")将我们的屏幕清空,但cls清空的速度非常快,这里用Sleep(500)让系统休眠500毫秒,方便我们看到游戏的结果。system和Sleep的使用均需要包含头文件<windows.h>**。

当完成了这部分的内容,整个游戏的大致框架就完成了,将game()先打上注释,这里测试一下能否正常运行。

游戏部分

游戏的部分整体流程如下:

生成棋盘

  • 定义数组

三子棋的棋盘一共三行三列,共有九个空可以下棋,这用一个三行三列的数组来存放每个空的数据。

为了代码的灵活性,这里不要把行和列的值写死,用define定义的常量来写,方便以后代码的更新。

//game.h

#define ROW 3
#define COL 3

//text.c

#include"game.h"

void game()
{
    char board[ROW][COL] = { 0 };
}
  • 初始化

定义完数组后,为了更加贴合棋盘的样式,我们需要对数组进行初始化,将空格赋给每个元素。这里定义一个初始化棋盘的函数InitBoard,将数组,行和列的参数传给函数,这个函数的部分拿到game.c中完成,并且在game.h中声明一下。

//text.c

#include"game2.h"

void game()
{
    char board[ROW][COL] = { 0 };
    InitBoard(board, ROW, COL);//初始化

}

//game.h

//初始化部分
void InitBoard(char board[ROW][COL], int row, int col);

//game.c

void InitBoard(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++)
        {
            board[i][j] = ' ';//将空格赋给每个元素
        }
    }
}
  • 打印棋盘

打印棋盘时注意也不要把棋盘写死,这里我们定义DisPaly_Board函数,用for循环来打印棋盘,打印“ | | | ”时,将“ |”设定为一组,遍历到最后一列时,只打印“ ”;同理,打印“---|---|---”时,将“---|”设定为一组进行打印,遍历到最后一列时,只打印“---”,遍历到最后一行时都不打印。

//text.c

void game()
{
    char board[ROW][COL] = {0};
    //初始化
    InitBoard(board, ROW, COL);
    //打印棋盘
    DisPaly_Board(board, ROW, COL);
}

//game.h

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

//game.c

void DisPaly_Board(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++)
        {
            printf(" %c ", board[i][j]);
            if (j < col - 1)
                printf("|");
        }
        printf("\n");

        if (i < row - 1)
        {
            int j = 0;
            for (j = 0; j < col; j++)
            {
                printf("---");
                if (j < col - 1)
                {
                    printf("|");
                }
            }
            printf("\n");
        }
    }
}

这样写的好处是:当我们改变define定义的常量时,棋盘的大小也会随之改变。

例如将ROW和COL都改成10:

玩家下棋

在玩家下棋这部分中,我们定义玩家通过输入坐标来进行下棋。需要注意的是:数组所定义的下标是从0开始的,而玩家认知的坐标是从1开始的(例如:玩家想输入第一行第一列的坐标是,输入的是(1,1),而不是(0,0)),这里需要将玩家输入的横、纵坐标减1,转换成数组的坐标。

在玩家输入完坐标后,我们还需要判断该坐标是否合法,如果玩家输入的坐标超限,或者该坐标已经下过棋了,那么需要重新输入坐标。

这里玩家下的棋,用 * 表示。定义PlayerMove用来实现玩家下棋的这部分。

//text.c

void game()
{
    int ret = 0;
    char board[ROW][COL] = {0};
    //初始化
    InitBoard(board, ROW, COL);
    //打印棋盘
    DisPaly_Board(board, ROW, COL);
    while (1)
    {
        //玩家下棋
        PlayerMove(board, ROW, COL);
        //打印棋盘
        DisPaly_Board(board, ROW, COL);
    }

}

//game.h

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

//game.c

void PlayerMove(char board[ROW][COL], int row, int col)
{
    int x = 0;
    int y = 0;
    while (1)
    {
        printf("玩家下棋:>\n");
        scanf("%d%d", &x, &y);
        if (x > 0 && x <= row && y > 0 && y <= col)
        {
            if (board[x - 1][y - 1] == ' ')
            {
                board[x - 1][y - 1] = '*';
                break;
            }
        }
        printf("坐标非法,请重新输入\n");
    }
}

当玩家下完棋后,我们需要再将棋盘打印一下。并且下棋是一个有来有回的过程,这里用一个循环将整个下棋的过程包含起来,直到分出胜负或平局跳出循环。

电脑下棋(智能下棋)

电脑下棋分为三个部分:赢棋堵棋自由落子

这里的优先级是赢棋>堵棋>自由落子,在写代码的时候要遵循:能赢则赢,不能赢则堵,以上情况都不符合的时候下新棋的原则。(自由落子不等于下废棋,不能彻底随机)

定义三个函数:

赢棋函数:TryWin(如果函数执行并落子,返回0;否则返回1)

堵棋函数:Cut(如果函数执行并落子,返回0;否则返回1)

自由落子函数:ComputerMove(构建整体框架,根据前两个函数的返回值来判断是否落子)

  • 自由落子

需要注意的是,在下新棋的时候,为了不让该棋变成废棋,需要判定电脑落子附近的8个单元格内至少有一个棋子是玩家的落子。

具体方法如下:先让电脑随机生成一个坐标,然后判定该坐标是否同时满足条件1和条件2,如果满足就落子,如果不满足则重新生成新的坐标,所以这里需要用到while循环,根据优先度的排序,while循环的表达式则是Cut函数的返回值。

条件1:该位置是空地。

条件2:(x,y)相邻的8个元素中,至少有一个格子有玩家的落子。(条件2是为了防止电脑随机下棋下到角落里,该棋便成了废棋)

ComputerMove代码如下:

//game.h
//电脑下棋
void ComputerMove(char board[ROW][COL], int row, int col);

//电脑尝试三连
int TryWin(char board[ROW][COL], int row, int col);

//堵棋
int Cut(char board[ROW][COL], int row, int col);

//text.c
srand((unsigned int)time(NULL));//使用rand()需要在主函数中用srand修饰,提供随机标准。

//game.c
//电脑下棋
void ComputerMove(char board[ROW][COL], int row, int col)
{
    printf("电脑下棋:>\n");
    if (TryWin(board, ROW, COL)==1)//先尝试三连
    {
        while (Cut(board, ROW, COL))//不能三连尝试堵棋
        {
            int x = rand() % row;
            int y = rand() % col;
            //控制范围,不要下的太偏了,新棋下的位置要在玩家落子的位置附近,以免变成废棋
            if (board[x][y] == ' ' && (board[x - 1][y - 1] == '*' || board[x - 1][y] == '*' || board[x - 1][y - 1] == '*' || board[x][y - 1] == '*' || board[x][y + 1] == '*' || board[x + 1][y - 1] == '*' || board[x + 1][y] == '*' || board[x + 1][y + 1] == '*'))
            {
                board[x][y] = '#';
                break;
            }
        }
    }
    
}
  • 堵棋

堵棋的优先度在下新棋之上,在赢棋的优先度之下。

堵棋共有四种堵法:横着堵竖着堵左斜堵右斜堵

将堵棋的大致框架写出,并且为了代码的适用性,这里的代码也不要写死,既要适用三行三列的棋盘,也要同样适用于更大的棋盘。(五行五列、十行十列等等)

当棋盘中不需要堵棋时,返回1;而只要当其中一种情况成立,电脑便落子堵棋,返回0。

堵棋框架:

//game.c
//堵棋

int Cut(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++)
        {
            //堵横着
    
            //堵竖着的

            //右斜(左上右下)

            //左斜(左下右上)
                
        }
    }
    return 1;
}

横着堵:

横着堵有四种情况:XXO、OXX,XOX,以及OXXO(在大棋盘中)。

这里采用for循环遍历来寻找棋盘中是否存在以上四种情况,针对不同的情况,坐标的搜索范围也不一致,这里注意数组的访问不要超出限制。

//堵横着

if (j < col - 2)//XOX型
{
    if (board[i][j] == board[i][j + 2] && board[i][j + 1] == ' ' && board[i][j] == '*')
    {
        board[i][j + 1] = '#';
        return 0;
    }
}
if (j < col - 1)//XXO或OXX型
{
    if (board[i][j] == board[i][j + 1] && board[i][j] == '*')
    {
        if (j < col - 2 && board[i][j + 2] == ' ' && board[i][j - 1] == ' ')//两种情况都符合
        {
            int ch = rand() % 2;//两种情况都符合时,随机下
            switch (ch)
            {
            case 0:
                board[i][j + 2] = '#';
                return 0;
            case 1:
                board[i][j - 1] = '#';
                return 0;
            }
        }
        //只符合其中一种情况
        if (j < col - 2 && board[i][j + 2] == ' ')//XXO
        {
            board[i][j + 2] = '#';
            return 0;
        }
        if (board[i][j - 1] == ' ')//OXX
        {
            board[i][j - 1] = '#';
            return 0;
        }
    }
}

竖着堵:

竖着堵与横着堵的情况一致,也是四种情况,将横纵坐标的要求对调一下即可。

//堵竖着的
                //X
                //0
                //X型
if (i < row - 2)
{
    if (board[i][j] == board[i + 2][j] && board[i + 1][j] == ' ' && board[i][j] == '#')
    {
        board[i + 1][j] = '#';
        return 0;
    }
}
//X      O
//X      X
//O型 或 X型
if (i < row - 1)
{
    if (board[i][j] == board[i + 1][j] && board[i][j] == '#')
    {
        if (i < row - 2 && board[i + 2][j] == ' ' && board[i - 1][j] == ' ')//符合两种情况随机堵
        {
            int ch = rand() % 2;
            switch (ch)
            {
            case 0:
                board[i + 2][j] = '#';
                return 0;

            case 1:
                board[i - 1][j] = '#';
                return 0;

            }
        }
        if (i < row - 2 && board[i + 2][j] == ' ')//只有下方可以堵
        {
            board[i + 2][j] = '#';
            return 0;
        }
        if (board[i - 1][j] == ' ')//只有上方可以堵
        {
            board[i - 1][j] = '#';
            return 0;
        }
    }
}

左斜堵:

左斜堵的情况也是四种,但是需要注意的是,这里符合条件的横纵坐标范围与右斜有很大区别。

//堵斜着(左下右上)
            //    X
            //  0
            //X    型
if (i < row - 2 && j > 1)
{
    if (board[i][j] == board[i + 2][j - 2] && board[i + 1][j - 1] == ' ' && board[i][j] == '#')
    {
        board[i + 1][j - 1] = '#';
        return 0;
    }
}
//    X                O
//  X                X
//O     型   或    X      型
if (i < row - 1 && j > 0)
{
    if (board[i][j] == board[i + 1][j - 1] && board[i][j] == '#')
    {
        if (i < row - 2 && j > 1 && board[i + 2][j - 2] == ' ' && board[i - 1][j + 1] == ' ')//符合两种情况随机堵
        {
            int ch = rand() % 2;
            switch (ch)
            {
            case 0:
                board[i + 2][j - 2] = '#';
                return 0;
            case 1:
                board[i - 1][j + 1] = '#';
                return 0;
            }
        }
        if (i < row - 2 && j > 1 && board[i + 2][j - 2] == ' ')//只有下方可以堵
        {
            board[i + 2][j - 2] = '#';
            return 0;
        }
        if (board[i - 1][j + 1] == ' ')//只有上方可以堵
        {
            board[i - 1][j + 1] = '#';
            return 0;
        }
    }
}

右斜堵:

//堵斜着(左上右下)

            //X
            // 0
            //  X型
if (i < row - 2 && j < col - 2)
{
    if (board[i][j] == board[i + 2][j + 2] && board[i + 1][j + 1] == ' ' && board[i][j] == '#')
    {
        board[i + 1][j + 1] = '#';
        return 0;
    }
}
// X         O
//  X          X
//   O型   或    X型
if (i < row - 1 && j < col - 1)
{
    if (board[i][j] == board[i + 1][j + 1] && board[i][j] == '#')
    {
        if (i < row - 2 && j < col - 2 && board[i + 2][j + 2] == ' ' && board[i - 1][j - 1] == ' ')//符合两种情况随机堵
        {
            int ch = rand() % 2;
            switch (ch)
            {
            case 0:
                board[i + 2][j + 2] = '#';
                return 0;
            case 1:
                board[i - 1][j - 1] = '#';
                return 0;
            }
        }
        if (i < row - 2 && j < col - 2 && board[i + 2][j + 2] == ' ')//只有下方可以堵
        {
            board[i + 2][j + 2] = '#';
            return 0;
        }

        if (board[i - 1][j - 1] == ' ')//只有上方可以堵
        {
            board[i - 1][j - 1] = '#';
            return 0;
        }
    }
}

当我们把上述共十六种的情况都罗列出来后,电脑便会逢棋必堵,并且堵棋函数中所运用到的知识和方法都是C语言初阶的内容,并没有很高深的内容。

  • 赢棋

为了让我们的电脑更进一步,可以抓住玩家露出的破绽,我们再赋予电脑一个赢棋的函数。

而赢棋的优先级也是最高的,毕竟如果游戏都赢了,那就没有堵棋和下棋的事儿了。

赢棋的函数其实和堵棋的函数十分类似,可以说是99.9%都是一致的。

想象一下:在判定是否需要堵棋时,我们其实判定的是玩家是否会赢,如果出现玩家赢的可能,那么我们就要执行堵棋。我们只需要改变一下条件,改成判定电脑是否会赢,如果出现电脑赢的可能,那么就执行赢棋。

例如:

//堵棋

if (j < col - 2)//XOX型
{
    if (board[i][j] == board[i][j + 2] && board[i][j + 1] == ' ' && board[i][j] == '*')
    {
        board[i][j + 1] = '#';
        return 0;
    }
}

//赢棋
if (j < col - 2)//XOX型
{
    if (board[i][j] == board[i][j + 2] && board[i][j + 1] == ' ' && board[i][j] == '#')
    {
        board[i][j + 1] = '#';
        return 0;
    }
}

将堵棋的代码完全拷贝下来,并将表达式中的*改成#即可。

判断输赢

三子棋的输赢判定方式很简单,横、竖、斜三棋相连,为了不把代码写死,这里将分别对横、竖、斜(左下右上)、斜(右下左上)这四种进行分别判定。

将框架写好,随后在for循环的内部加入判定部分即可。

//game.c

int Judgment(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 (i < row  && j < col - 2)//三子棋,所以靠边界的两行没必要判断
            {
                if (board[i][j] == board[i][j + 1] && board[i][j + 1] == board[i][j + 2] && board[i][j] && board[i][j] != ' ')
                {
                    return board[i][j];
                }
            }
  • 判断竖三连

竖着三个相等且不等于空格,将第一个元素返回。

    if (i < row - 2 && j < col)
            {
                if (board[i][j] == board[i + 1][j] && board[i + 1][j] == board[i + 2][j] && board[i][j] != ' ')
                {
                    return board[i][j];
                }
            }
  • 判断斜三连(左下右上)

斜三连的情况稍微复杂一点,正斜和反斜针对i和j的取值范围不同,并且坐标的变换也不一样。

    if (i < row-2 && j > 1)  
            {
                if (board[i][j] == board[i + 1][j - 1] && board[i + 1][j - 1] == board[i + 2][j - 2] && board[i][j] != ' ')
                {
                    return board[i][j];
                }
            }
  • 判断斜三连(左下右上)

if (i < row-2 && j < col-2 )
            {
                if (board[i][j] == board[i + 1][j + 1] && board[i + 1][j + 1] == board[i + 2][j + 2] && board[i][j] != ' ')
                {
                    return board[i][j];
                }
            }
  • 判断平局

除了输赢,还会有平局的情况发生,判断平局的方法很简单,遍历一遍整个数组,当发现没有‘ ’时,即平局了。这里如果平局则返回q,没有平局则返回c,判断平局的部分要放在判断输赢的后面。


//判断平局
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;
}

if (IsFull(board, row, col))
    {
        return 'q';
    }
    return 'c';

测试

当完成了所有代码后,这里我们用5×5的棋盘可以更好的测试一下。

当玩家先手下棋后,电脑在堵截玩家这方面还是咬的很死的,现在电脑有一个三连的机会,我们卖一个破绽,看看电脑能否抓住。

电脑不负众望抓住了机会,踏上了人工智能面向世界的第一步。我们这里设定的是玩家先手,玩家的优势稍微大一点。

如果想要设置电脑先手的话,还需要对电脑下棋部分的代码再加一点东西:即当整个棋盘是空的时候,电脑在中心区域随机下棋。(我们之前的代码一直是电脑负责对玩家的落子进行围追堵截,没有先手的情况

改进

其实整个代码还是有很大的优化空间的:

1、受到三子棋的限制,在3×3的棋盘太过无趣,但当三子棋在大棋盘中进行游玩时,先手一方必赢。

这里可以将三子棋改成五子棋,本质框架不变,但是需要堵棋的情况会比三子棋要多出一些。

2、电脑在堵棋时,如果是10×10的大棋盘,那么会出现可以两头堵的情况,在最初设定时,我们设置的是两头随机堵,但是现实中这样往往会错失良机。电脑的下棋也是如此,我们只是设置了一个随机坐标,保证在玩家落子的附近,但不能保证该坐标就是最优的选择。那么如何改进呢?

我们可以对整个棋盘设置权重P

当该坐标周围一圈附近8个元素中每多一颗电脑的棋子,P+10;

附近8个元素中每多一颗玩家的棋子,P+5;

该坐标周围第二圈附近16个元素中每多一颗电脑的棋子,P+5;

该坐标周围第二圈附近16个元素中每多一颗玩家的棋子,P+2。

以此类推,离坐标越远权重越低,每次电脑下棋前,遍历一遍棋盘,选择权重最高的方法下,如果有权重相同的情况,再选择周围空位最多的坐标。

除以上两点之外,其实还有更多细节之处可以优化,有兴趣的小伙伴可以自己尝试一下。

整体代码展示

这里为了方便大家的测试与改进,我把全部的代码放在下面。如果有不足之处,还请大家多多指点。

text.c

//三子棋
#include"game.h"

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

void game()
{
    int ret = 0;
    char board[ROW][COL] = {0};
    //初始化
    InitBoard(board, ROW, COL);
    //打印棋盘
    DisPaly_Board(board, ROW, COL);
    while (1)
    {
        //玩家下棋
        PlayerMove(board, ROW, COL);
        //打印棋盘
        DisPaly_Board(board, ROW, COL);
        //判断输赢
        ret = Judgment(board, ROW, COL);
        if (ret != 'c')
        {
            break;
        }
        //电脑下棋
        ComputerMove(board, ROW, COL);
        //打印棋盘
        DisPaly_Board(board, ROW, COL);
        //判断输赢
        ret = Judgment(board, ROW, COL);
        if (ret != 'c')
        {
            break;
        }
    }
    if (ret == '*')
    {
        printf("玩家赢了\n");
    }
    else if (ret == '#')
    {
        printf("电脑赢了\n");
    }
    else
        printf("平局\n");
}

int main()
{
    int input = 0;
    srand((unsigned int)time(NULL));
    do
    {
        menu();
        printf("请选择(1/0):>");
        scanf("%d", &input);
        switch (input)
        {
        case 0:
            printf("退出游戏\n");
            break;
        case 1:
            printf("开始游戏\n");
            game();//游戏
            Sleep(500);
            system("cls");//清空屏幕
            break;
        default:
            printf("输入错误,请重新输入\n");
        }
    } while (input);
    return 0;
}

game.h

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<windows.h>

#define ROW 5//在测试的时候,建议将行和列设置稍微大一点
#define COL 5

//初始化
void InitBoard(char board[ROW][COL], int row,int col);

//打印棋盘
void DisPaly_Board(char board[ROW][COL], int row, int col);

//玩家下棋
void PlayerMove(char board[ROW][COL], int row, int col);

//电脑尝试三连
int TryWin(char board[ROW][COL], int row, int col);

//堵棋
int Cut(char board[ROW][COL], int row, int col);

//电脑下棋
void ComputerMove(char board[ROW][COL], int row, int col);

//判断输赢
int Judgment(char board[ROW][COL], int row, int col);

game.c

#include"game.h"

//初始化
void InitBoard(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++)
        {
            board[i][j] = ' ';
        }
    }
}

//打印棋盘
void DisPaly_Board(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++)
        {
            printf(" %c ", board[i][j]);
            if (j < col - 1)
                printf("|");
        }
        printf("\n");

        if (i < row - 1)
        {
            int j = 0;
            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;
    while (1)
    {
        printf("玩家下棋:>\n");
        scanf("%d%d", &x, &y);
        if (x > 0 && x <= row && y > 0 && y <= col)
        {
            if (board[x - 1][y - 1] == ' ')
            {
                board[x - 1][y - 1] = '*';
                break;
            }
        }
        printf("坐标非法,请重新输入\n");
    }
}

//电脑下棋(堵棋)
int Cut(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 (j < col - 2)//XOX型
            {
                if (board[i][j] == board[i][j + 2] && board[i][j + 1] == ' ' && board[i][j] == '*')
                {
                    board[i][j + 1] = '#';
                    return 0;
                }
            }
            if (j < col - 1)//XXO或OXX型
            {
                if (board[i][j] == board[i][j + 1] && board[i][j] == '*')
                {
                    if (j < col - 2 && board[i][j + 2] == ' ' && board[i][j - 1] == ' ')//两种情况都符合
                    {
                        int ch = rand() % 2;//两种情况都符合时,随机下
                        switch (ch)
                        {
                        case 0:
                            board[i][j + 2] = '#';
                            return 0;
                        case 1:
                            board[i][j - 1] = '#';
                            return 0;
                        }
                    }
                    //只符合其中一种情况
                    if (j < col - 2 && board[i][j + 2] == ' ')//XXO
                    {
                        board[i][j + 2] = '#';
                        return 0;
                    }
                    if (board[i][j - 1] == ' ')//OXX
                    {
                        board[i][j - 1] = '#';
                        return 0;
                    }
                }
            }

            //堵竖着的
                //X
                //0
                //X型
            if (i < row - 2)
            {
                if (board[i][j] == board[i + 2][j] && board[i + 1][j] == ' ' && board[i][j] == '*')
                {
                    board[i + 1][j] = '#';
                    return 0;
                }
            }
            //X      O
            //X      X
            //O型 或 X型
            if (i < row - 1)
            {
                if (board[i][j] == board[i + 1][j] && board[i][j] == '*')
                {
                    if (i < row - 2 && board[i + 2][j] == ' ' && board[i - 1][j] == ' ')//符合两种情况随机堵
                    {
                        int ch = rand() % 2;
                        switch (ch)
                        {
                        case 0:
                            board[i + 2][j] = '#';
                            return 0;

                        case 1:
                            board[i - 1][j] = '#';
                            return 0;
                        }
                    }
                    if (i < row - 2 && board[i + 2][j] == ' ')//只有下方可以堵
                    {
                        board[i + 2][j] = '#';
                        return 0;
                    }
                    if (board[i - 1][j] == ' ')//只有上方可以堵
                    {
                        board[i - 1][j] = '#';
                        return 0;
                    }
                }
            }
            //堵斜着(左上右下)
            //X
            // 0
            //  X型
            if (i < row - 2 && j < col - 2)
            {
                if (board[i][j] == board[i + 2][j + 2] && board[i + 1][j + 1] == ' ' && board[i][j] == '*')
                {
                    board[i + 1][j + 1] = '#';
                    return 0;
                }
            }
            // X         O
            //  X          X
            //   O型   或    X型
            if (i < row - 1 && j < col - 1)
            {
                if (board[i][j] == board[i + 1][j + 1] && board[i][j] == '*')
                {
                    if (i < row - 2 && j < col - 2 && board[i + 2][j + 2] == ' ' && board[i - 1][j - 1] == ' ')//符合两种情况随机堵
                    {
                        int ch = rand() % 2;
                        switch (ch)
                        {
                        case 0:
                            board[i + 2][j + 2] = '#';
                            return 0;
                        case 1:
                            board[i - 1][j - 1] = '#';
                            return 0;
                        }
                    }
                    if (i < row - 2 && j < col - 2 && board[i + 2][j + 2] == ' ')//只有下方可以堵
                    {
                        board[i + 2][j + 2] = '#';
                        return 0;
                    }

                    if (board[i - 1][j - 1] == ' ')//只有上方可以堵
                    {
                        board[i - 1][j - 1] = '#';
                        return 0;
                    }
                }
            }

            //堵斜着(左下右上)
            //    X
            //  0
            //X    型
            if (i < row - 2 && j > 1)
            {
                if (board[i][j] == board[i + 2][j - 2] && board[i + 1][j - 1] == ' ' && board[i][j] == '*')
                {
                    board[i + 1][j - 1] = '#';
                    return 0;
                }
            }
            //    X                O
            //  X                X
            //O     型   或    X      型
            if (i < row - 1 && j > 0)
            {
                if (board[i][j] == board[i + 1][j - 1] && board[i][j] == '*')
                {
                    if (i < row - 2 && j > 1 && board[i + 2][j - 2] == ' ' && board[i - 1][j + 1] == ' ')//符合两种情况随机堵
                    {
                        int ch = rand() % 2;
                        switch (ch)
                        {
                        case 0:
                            board[i + 2][j - 2] = '#';
                            return 0;
                        case 1:
                            board[i - 1][j + 1] = '#';
                            return 0;
                        }
                    }
                    if (i < row - 2 && j > 1 && board[i + 2][j - 2] == ' ')//只有下方可以堵
                    {
                        board[i + 2][j - 2] = '#';
                        return 0;
                    }
                    if (board[i - 1][j + 1] == ' ')//只有上方可以堵
                    {
                        board[i - 1][j + 1] = '#';
                        return 0;
                    }

                }
            }
        }
    }
    return 1;
}

//电脑下棋(尝试三连)
int TryWin(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 (j < col - 2)
            {
                if (board[i][j] == board[i][j + 2] && board[i][j + 1] == ' ' && board[i][j] == '#')//XOX型
                {
                    board[i][j + 1] = '#';
                    return 0;
                }
            }
            if (j < col - 1)//XXO或OXX型
            {
                if (board[i][j] == board[i][j + 1] && board[i][j] == '#')
                {
                    if (j < col - 2 && board[i][j + 2] == ' ' && board[i][j - 1] == ' ')//两种情况都符合
                    {
                        int ch = rand() % 2;
                        switch (ch)
                        {
                        case 0:
                            board[i][j + 2] = '#';
                            return 0;
                        case 1:
                            board[i][j - 1] = '#';
                            return 0;
                        }
                    }
                    if (j < col - 2 && board[i][j + 2] == ' ')
                    {
                        board[i][j + 2] = '#';
                        return 0;
                    }
                    if (board[i][j - 1] == ' ')
                    {
                        board[i][j - 1] = '#';
                        return 0;
                    }
                }
            }

            //堵竖着的
                //X
                //0
                //X型
            if (i < row - 2)
            {
                if (board[i][j] == board[i + 2][j] && board[i + 1][j] == ' ' && board[i][j] == '#')
                {
                    board[i + 1][j] = '#';
                    return 0;
                }
            }
            //X      O
            //X      X
            //O型 或 X型
            if (i < row - 1)
            {
                if (board[i][j] == board[i + 1][j] && board[i][j] == '#')
                {
                    if (i < row - 2 && board[i + 2][j] == ' ' && board[i - 1][j] == ' ')//符合两种情况随机堵
                    {
                        int ch = rand() % 2;
                        switch (ch)
                        {
                        case 0:
                            board[i + 2][j] = '#';
                            return 0;

                        case 1:
                            board[i - 1][j] = '#';
                            return 0;

                        }
                    }
                    if (i < row - 2 && board[i + 2][j] == ' ')//只有下方可以堵
                    {
                        board[i + 2][j] = '#';
                        return 0;
                    }
                    if (board[i - 1][j] == ' ')//只有上方可以堵
                    {
                        board[i - 1][j] = '#';
                        return 0;
                    }
                }
            }
            //堵斜着(左上右下)
            //X
            // 0
            //  X型
            if (i < row - 2 && j < col - 2)
            {
                if (board[i][j] == board[i + 2][j + 2] && board[i + 1][j + 1] == ' ' && board[i][j] == '#')
                {
                    board[i + 1][j + 1] = '#';
                    return 0;
                }
            }
            // X         O
            //  X          X
            //   O型   或    X型
            if (i < row - 1 && j < col - 1)
            {
                if (board[i][j] == board[i + 1][j + 1] && board[i][j] == '#')
                {
                    if (i < row - 2 && j < col - 2 && board[i + 2][j + 2] == ' ' && board[i - 1][j - 1] == ' ')//符合两种情况随机堵
                    {
                        int ch = rand() % 2;
                        switch (ch)
                        {
                        case 0:
                            board[i + 2][j + 2] = '#';
                            return 0;
                        case 1:
                            board[i - 1][j - 1] = '#';
                            return 0;
                        }
                    }
                    if (i < row - 2 && j < col - 2 && board[i + 2][j + 2] == ' ')//只有下方可以堵
                    {
                        board[i + 2][j + 2] = '#';
                        return 0;
                    }

                    if (board[i - 1][j - 1] == ' ')//只有上方可以堵
                    {
                        board[i - 1][j - 1] = '#';
                        return 0;
                    }
                }
            }

            //堵斜着(左下右上)
            //    X
            //  0
            //X    型
            if (i < row - 2 && j > 1)
            {
                if (board[i][j] == board[i + 2][j - 2] && board[i + 1][j - 1] == ' ' && board[i][j] == '#')
                {
                    board[i + 1][j - 1] = '#';
                    return 0;
                }
            }
            //    X                O
            //  X                X
            //O     型   或    X      型
            if (i < row - 1 && j > 0)
            {
                if (board[i][j] == board[i + 1][j - 1] && board[i][j] == '#')
                {
                    if (i < row - 2 && j > 1 && board[i + 2][j - 2] == ' ' && board[i - 1][j + 1] == ' ')//符合两种情况随机堵
                    {
                        int ch = rand() % 2;
                        switch (ch)
                        {
                        case 0:
                            board[i + 2][j - 2] = '#';
                            return 0;
                        case 1:
                            board[i - 1][j + 1] = '#';
                            return 0;
                        }
                    }
                    if (i < row - 2 && j > 1 && board[i + 2][j - 2] == ' ')//只有下方可以堵
                    {
                        board[i + 2][j - 2] = '#';
                        return 0;
                    }
                    if (board[i - 1][j + 1] == ' ')//只有上方可以堵
                    {
                        board[i - 1][j + 1] = '#';
                        return 0;
                    }
                }
            }
        }
    }
    return 1;
}

//电脑下棋
void ComputerMove(char board[ROW][COL], int row, int col)
{
    printf("电脑下棋:>\n");
    if (TryWin(board, ROW, COL) == 1)//先尝试三连
    {
        while (Cut(board, ROW, COL))//不能三连尝试堵棋
        {
            int x = rand() % row;
            int y = rand() % col;
            //控制范围,不要下的太偏了
            if (board[x][y] == ' ' && (board[x - 1][y - 1] == '*' || board[x - 1][y] == '*' || board[x - 1][y - 1] == '*' || board[x][y - 1] == '*' || board[x][y + 1] == '*' || board[x + 1][y - 1] == '*' || board[x + 1][y] == '*' || board[x + 1][y + 1] == '*'))
            {
                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;
}

//判断输赢
int Judgment(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 (i < row && j < col - 2)//三子棋,所以靠边界的两行没必要判断
            {
                if (board[i][j] == board[i][j + 1] && board[i][j + 1] == board[i][j + 2] && board[i][j] && board[i][j] != ' ')
                {
                    return board[i][j];
                }
            }
            //判断竖行是否相连
            if (i < row - 2 && j < col)
            {
                if (board[i][j] == board[i + 1][j] && board[i + 1][j] == board[i + 2][j] && board[i][j] != ' ')
                {
                    return board[i][j];
                }
            }
            //判断斜行是否相连(左下右上)
            if (i < row - 2 && j > 1)
            {
                if (board[i][j] == board[i + 1][j - 1] && board[i + 1][j - 1] == board[i + 2][j - 2] && board[i][j] != ' ')
                {
                    return board[i][j];
                }
            }
            //判断斜行是否相连(右下左上)
            if (i < row - 2 && j < col - 2)
            {
                if (board[i][j] == board[i + 1][j + 1] && board[i + 1][j + 1] == board[i + 2][j + 2] && board[i][j] != ' ')
                {
                    return board[i][j];
                }
            }
        }
    }
    if (IsFull(board, row, col))//判断平局
    {
        return 'q';
    }
    return 'c';
}

本文转载自: https://blog.csdn.net/why1472587/article/details/124931701
版权归原作者 是王久久阿 所有, 如有侵权,请联系我们删除。

“三子棋游戏(利用基础语法实现人工智能)”的评论:

还没有评论