【C语言】三子棋及多子棋

简介: C语言实现三子棋及多子棋

1. 三子棋

1.1 设计思路

首先说明本人使用的编译器是Visual Studio 2022。在本次设计三子棋及多子棋小游戏中,我准备将主体代码用一个头文件game.h和两个源文件game.c和test.c实现。其中game.h包含游戏函数的声明,game.c是游戏函数的实现,而test.c是用来测试游戏函数的。

我的设计思路如下:

(1)首先设计一个游戏菜单,用户可以通过游戏菜单的说明进行相应的游戏操作

(2)用二维数组创建一个棋盘,将棋盘初始化并且打印棋盘

(3)用二维数组记录每次玩家落子和电脑落子(用随机数解决)的状态

(4)每次玩家落子和电脑落子后,打印棋盘,判断是玩家赢电脑赢平局,还是继续游戏

1.2 具体实现

1.2.1 设计游戏菜单

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

我们在test.c文件中写一个menu()函数,使用printf()函数打印菜单,玩家输入1时开始游戏,输入0时退出游戏。

因为要使用printf()函数,所以我们在game.h中引入头文件stdio.h。

#include<stdio.h>

因为玩家玩好一局游戏后,可能还想继续玩。所以这里我采用了do-while结构,同时用了switch语句来实现用户做出相应的选择后执行相应的功能。

int main()
{
   
    int input = 0;
    do
    {
   
        menu();  //打印菜单
        printf("请选择:>");
        scanf("%d", &input); //用户输入相应选择
        switch (input)
        {
   
        case 1:
            printf("开始游戏\n");
            break;
        case 0:
            printf("退出游戏\n");
            break;
        default:
            printf("选择错误,重新选择!\n");
            break;
        }
    } while (input);
    return 0;
}

image.png

1.2.2 设计棋盘

1.2.2.1 设计棋盘大小

这里我们用二维数组创建一个棋盘,棋盘的大小(行和列)用宏定义实现。使用宏定义能改变棋盘的大小,也为后面实现多子棋做铺垫。

#define ROW 3
#define COL 3

我们将二维数组先初始化为0,在test.c文件中写一个game()函数,在game()函数中实现整个游戏功能。

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

同时我们对原先main()函数中的switch语句中的case 1:部分进行修改——将printf("开始游戏\n")更改成game()函数。

int main()
{
   
    int input = 0;
    do
    {
   
        menu();  //打印菜单
        printf("请选择:>");
        scanf("%d", &input); //用户输入相应选择
        switch (input)
        {
   
        case 1:
            game();
            break;
        case 0:
            printf("退出游戏\n");
            break;
        default:
            printf("选择错误,重新选择!\n");
            break;
        }
    } while (input);
    return 0;
}

我们之后就要不断扩展game()函数的功能。

1.2.2.2 初始化棋盘

我们将棋盘初始化为空格,提供落子空间。

在game.h文件中进行InitBoard()的函数声明:

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

在game.c文件中进行InitBoard()的函数实现:

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

同时在test.c文件的game()函数中增加相应内容:

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

1.2.2.3 打印棋盘

这里我们要设计一个棋盘,我的想法是实现一个如下图的棋盘。
image.png

要实现这样一个棋盘,我们可以把问题简化成打印数据打印分割线
image.png

首先我们在game.h文件中进行DisplayBoard()的函数声明:

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

在game.c文件中进行DisplayBoard()的函数实现:

void DisplayBoard(char board[ROW][COL], int row, int col)
{
   

    for (int i = 0; i < row; i++)
    {
   
        //1. 打印数据
        for (int j = 0; j < col; j++)
        {
   
            printf(" %c ", board[i][j]);
            if (j < col - 1)
                printf("|");
        }
        printf("\n");
        //2. 打印分割线
        if (i < row - 1)
        {
   
            for (int j = 0; j < col; j++)
            {
   
                printf("---");
                if (j < col - 1)
                    printf("|");
            }
            printf("\n");
        }
    }
}

image.png

当玩家选择1时就能看到如图的棋盘。

我们同时在test.c文件中的game()函数中增加相应内容:

void game()
{
   
    char board[ROW][COL] = {
    0 };
    InitBoard(board, ROW, COL);
    DisplayBoard(board, ROW, COL);
}

1.2.3 设计下棋步骤

1.2.3.1 玩家下棋

在game.h文件中进行PlayerMove()的函数声明:

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

玩家下棋时,落子在棋盘上,所以我们需要设置x,y两个变量表示玩家落子的位置。落子需要在二维数组不越界的条件下,所以我使用了if语句判断坐标是否合法,在坐标合法的情况下,如果棋盘对应位置没有落子(即为空格),玩家就可依落子。我们将玩家的落子标记为'*'。因为要贴近人们的习惯,所以我将左上角的坐标标成1 1而不是0 0。因为玩家要多次落子,所以我们在最外面加一个while(1)的循环,当玩家落子完后,我们就break退出循环。

在game.c文件中进行PlayerMove()的函数实现:

void PlayerMove(char board[ROW][COL], int row, int col)
{
   
    int x = 0;
    int y = 0;
    printf("玩家下棋>:\n");
    while (1)
    {
   
        printf("请输入下棋的坐标,中间使用空格>:");
        scanf("%d %d", &x, &y);
        //坐标合法
        if (x >= 1 && x <= row && y >= 1 && y <= col)
        {
   
            if (board[x - 1][y - 1] == ' ')//可以落子,
            {
                                  //这里用x-1.y-1是因为规则定义左上角为1 1,
                board[x - 1][y - 1] = '*'; //而数组存储的第一个元素下标是0 0,所以行和列都需减去1
                break;
            }
            else//不能落子
            {
   
                printf("坐标被占有,不能落子,重新输入坐标\n");
            }
        }
        else//非法
        {
   
            printf("坐标非法,重新输入\n");
        }
    }
}

同时我们想知道玩家落子完后棋盘的状态,我们可以在玩家每次落子后打印棋盘的状态。所以在test.c的game()函数中调用PlayerMove()函数和DisplayBoard()函数。

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

    PlayerMove(board, ROW, COL);  //玩家下棋
    DisplayBoard(board, ROW, COL); //打印棋盘
}

选择1后,玩家如果输入1 1,则在棋盘的左上角就会出现*f符号。

1.2.3.2 电脑下棋

在game.h文件中进行ComputerMove()的函数声明:

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

电脑下棋时,落子在棋盘上,所以我们也需要设置x,y两个变量表示玩电脑落子的位置。这里我使用了rand()随机数函数,通过rand()%row,获得0~row-1的值,通过rand()%col,获得0~col-1的值,从而确保落子的位置不越界,棋子合法落在棋盘上。从我们将玩家的落子标记为'#'。因为电脑要多次落子,所以我们在最外面加一个while(1)的循环,当电脑落子完后,我们就break退出循环。

因为要使用rand()函数,所以我们先简单了解下这个函数,这个函数的头文件是stdlib.h。
image.png

这个函数会返回一个范围在0~RAND_MAX(至少是32767)的伪随机整数。

在调用rand()函数产生随机数前,我们可以先使用srand()函数设置随机数种子(seed)。 如果没有设置随机数种子, rand()在调用时会自动设随机数种子为1。随机数种子相同,每次产生的随机数也会相同。

srand()函数也包含在stdlib.h头文件中。
image.png

我们还需要使用time()函数,该函数的头文件是time.h。
image.png

time()函数功能是获取当前时间,并将其转换为time_t类型的值。

其中,timet *time 是一个指向time t 类型变量的指针,用于存储获取到的时间值。如果t为NULL,则表示不需要存储时间值,只需要返回当前时间的time_t类型值。

因为我们的程序中需要使用rand()、srand()、time()这三个函数,所以我们要在game.h中引入相应的头文件。

#include<stdlib.h>
#include<time.h>
我们需要在test.c的main函数中添加代码:

srand((unsigned int)time(NULL));
/*用于生成随机数种子。
srand()函数接受一个unsigned int整型参数,它被用来初始化随机数生成器的种子。
time(NULL)函数返回当前时间的秒数,并将其转换为无符号整数值,然后作为种子传递给srand函数。
这样做的目的是使每次程序执行时都生成不同的随机序列。*/
接下来我们在game.c文件中进行DisplayBoard()的函数实现:

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

同时我们想知道电脑落子完后棋盘的状态,我们可以在电脑每次落子后打印棋盘的状态。所以在test.c的game()函数中调用PlayerMove()函数和DisplayBoard()函数。

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

    PlayerMove(board, ROW, COL);  //玩家下棋
    DisplayBoard(board, ROW, COL); //打印棋盘

    ComputerMove(board, ROW, COL);  //电脑下棋
    DisplayBoard(board, ROW, COL);  //打印棋盘
}

0d1f1ca844d4a7e4432ddfd1a662ff9d.jpg

1.2.3.3 设计判断输赢的函数

当玩家和电脑下完棋时,我们要判断输赢。这里我们使用'*'表示玩家赢,'#'表示电脑赢,'Q'表示平局,'C'表示继续游戏。
在game.h文件中进行IsWin()的函数声明:

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

三子棋游戏的目标是在一个3x3的棋盘上先将自己的棋子连成一条直线的玩家获胜。在判断输赢时,需要检查横向、纵向和对角线上是否存在连续的三个相同棋子。如果以上三种情况都不存在,而且棋盘已经放满,那么游戏结束,平局。

我们先在game.h中声明判断棋盘是否放满的IsFull()函数:

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

要判断棋盘是否放满,非常简单,我们只要使用两层for循环遍历一下棋盘,如果有棋盘格的状态为空格,这说明没有放。当完整遍历完后,如果所有棋盘格的状态都不会空格,说明放满了。

我们在game.c文件中实现IsFull()函数:

int IsFull(char board[ROW][COL], int row, int col)
{
   
    for (int i = 0; i < row; i++)
    {
   
        for (int j = 0; j < col; j++)
        {
   
            if (board[i][j] == ' ')
                return 0;
        }
    }
    return 1;
}

我们通过for循环分别遍历行和列判断是否有三个棋子连成一条直线,同时通过if语句判断主对角线和副对角线是否有三个棋子连成一条直线。若以上都没有,我们检查一下棋盘是否放满,若放满则是平局,否则我们继续游戏。

在game.c文件中实现判断输赢的函数IsWin():

char IsWin(char board[ROW][COL], int row, int col)
{
   
    //赢
    //行
    for (int 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];
    }
    //列
    for (int i = 0; i < col; i++)
    {
   
        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))
    {
   
        return 'Q';
    }
    //继续
    return 'C';
}

因为玩家和电脑要依次下棋直到分出输赢,所以我们在外层要加一个while(1)的循环。同时我们需要使用一个变量ret来记录游戏进程和游戏结果,如果ret值为'C'表示继续游戏,否则退出游戏。通过最后ret的值判断输赢。如果ret值为'*'表示玩家赢,ret值为'#'表示电脑赢,ret值为'Q'表示平局。

所以t我们在test.c文件中实现game()函数:

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

36978321b14739188e1b1365ab6e784a.jpg

1.3 完整代码

1.3.1 game.h

#pragma once

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

#define ROW 3
#define COL 3


//初始化棋盘
void InitBoard(char board[ROW][COL], int row, int col);
//打印棋盘
void DisplayBoard(char board[ROW][COL], int row, int col);
//玩家下棋
void PlayerMove(char board[ROW][COL], int row, int col);
//电脑随机下棋
void ComputerMove(char board[ROW][COL], int row, int col);
//判断棋盘是否放满
int IsFull(char board[ROW][COL], int row, int col);
//判断输赢
//玩家赢 - '*'
//电脑赢 - '#'
//平局 - 'Q'
//继续 - 'C
char IsWin(char board[ROW][COL], int row, int col);

1.3.2 game.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"game.h"

//初始化棋盘为空格
void InitBoard(char board[ROW][COL], int row, int col)
{
   
    for (int i = 0; i < row; i++)
        for (int j = 0; j < col; j++)
            board[i][j] = ' ';

}

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

    for (int i = 0; i < row; i++)
    {
   
        //1. 打印数据
        for (int j = 0; j < col; j++)
        {
   
            printf(" %c ", board[i][j]);
            if (j < col - 1)
                printf("|");
        }
        printf("\n");
        //2. 打印分割线
        if (i < row - 1)
        {
   
            for (int 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("玩家下棋>:\n");
    while (1)
    {
   
        printf("请输入下棋的坐标,中间使用空格>:");
        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("坐标被占有,不能落子,重新输入坐标\n");
            }
        }
        else//非法
        {
   
            printf("坐标非法,重新输入\n");
        }
    }
}

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

//判断棋盘是否放满
int IsFull(char board[ROW][COL], int row, int col)
{
   
    for (int i = 0; i < row; i++)
    {
   
        for (int j = 0; j < col; j++)
        {
   
            if (board[i][j] == ' ')
                return 0;
        }
    }
    return 1;
}

//判断输赢
char IsWin(char board[ROW][COL], int row, int col)
{
   
    //赢
    //行
    for (int 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];
    }
    //列
    for (int i = 0; i < col; i++)  
    {
   
        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))
    {
   
        return 'Q';
    }
    //继续
    return 'C';
}

1.3.3 test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"game.h"

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

void game()
{
   
    char board[ROW][COL] = {
    0 };
    InitBoard(board, ROW, COL);
    //打印棋盘
    DisplayBoard(board, ROW, COL);
    char ret = 0;
    while (1)
    {
   
        //下棋
        //玩家下棋
        PlayerMove(board, ROW, COL);
        DisplayBoard(board, ROW, COL);
        //判断输赢
        ret = IsWin(board, ROW, COL);
        if (ret != 'C')
            break;
        //电脑随机下棋
        ComputerMove(board, ROW, COL);
        DisplayBoard(board, ROW, COL);
        //判断输赢
        ret = IsWin(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("请选择:>");
        scanf("%d", &input);
        switch (input)
        {
   
        case 1:
            game();
            break;
        case 0:
            printf("退出游戏\n");
            break;
        default:
            printf("选择错误,重新选择!\n");
            break;
        }
    } while (input);
    return 0;
}

2. 多子棋

2.1 思路分析和代码改进

前面实现三子棋的判断输赢中我们使用了如下代码:

char IsWin(char board[ROW][COL], int row, int col)
{
   
    //赢
    //行
    for (int 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];
    }
    //列
    for (int i = 0; i < col; i++)
    {
   
        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))
    {
   
        return 'Q';
    }
    //继续
    return 'C';
}

我们发现这段代码有一定的局限性,当是n子棋(n>3)时,上述代码就不适用了,所以我们需要进行改进。

(1)对于行的判断,我们可以用i和j两个变量进行两重for循环,外层用i遍历行数,内层用j从0遍历到列数-1,两两判断一行中两个相邻的数组元素,如果不相等或者有没放棋子的棋盘格,我们使用break退出。每次遍历完一行时,进行判断,如果j==列数-1,说明这一行有n个棋子相连。

(2)对于列的判断,我们可以用j和i两个变量进行两重for循环,外层用j遍历列数,内层用i从0遍历到行数-1,两两判断一列中两个相邻的数组元素,如果不相等或者有没放棋子的棋盘格,我们使用break退出。每次遍历完一列时,进行判断,如果i==行数-1,说明这一列有n个棋子相连。

(3)对于主对角线的判断,我们可以用i和j两个变量进行两重for循环,外层用i遍历从0到行数-1,内层用j从0遍历到1,两两判断主对角线上两个相邻的数组元素,如果不相等或者有没放棋子的棋盘格,我们使用break退出。当遍历完主对角线时,进行判断,如果j==列数-1,说明主对角线有n个棋子相连。

(4)对于副对角线的判断,我们可以用i和j两个变量进行两重for循环,外层用i遍历从0到行数-1,内层用j从列数-1遍历到1,两两判断副对角线上两个相邻的数组元素,如果不相等或者有没放棋子的棋盘格,我们使用break退出。当遍历完副对角线时,进行判断,如果i==行数-1,说明副对角线有n个棋子相连。

char IsWin(char board[ROW][COL], int row, int col)
{
   
    //赢
    //行
    int i = 0;
    int j = 0;
    for (i = 0; i < row; i++)
    {
   
        j = 0;
        for (j = 0; j < col - 1; j++)
        {
   
            if (board[i][j] != board[i][j + 1] || board[i][j] == ' ')
                break;
        }
        if (j == col - 1)
            return board[i][j];
    }
    //列
    i = 0;
    j = 0;
    for (j = 0; j < col; j++)
    {
   
        i = 0;
        for (i = 0; i < row - 1; i++)
        {
   
            if (board[i][j] != board[i + 1][j] || board[i][j] == ' ')
                break;
        }
        if (i == row - 1)
            return board[i][j];
    }
    //主对角线
    i = 0;
    j = 0;
    for (i = 0; i < row - 1; i++)
    {
   
        j = 0;
        for (j = 0; j < col - 1; j++)
        {
   
            if (board[i][j] != board[i + 1][j + 1] || board[i][j] == ' ')
                break;
        }
        if (j == col - 1)
            return board[i][j];
    }

    //副对角线
    i = 0;
    j = 0;
    for (i = 0; i < row - 1; i++)
    {
   
        j = 0;
        for (j = col - 1; j > 0; j--)
        {
   
            if (board[i][j] != board[i + 1][j - 1] || board[i][j] == ' ')
                break;
        }
        if (i == row - 1)
            return board[i][j];
    }
    //平局
    if (IsFull(board, row, col))
    {
   
        return 'Q';
    }
    //继续
    return 'C';
}

为了方便测试,我们此时宏定义ROW为5,COL为5

#define ROW 5
#define COL 5

24f04d6e4cdd12f288ed43afa86adba5.png
f993ebf3440a5aa4fb09b599ee56f67c.png

2.2 完整代码

2.2.1 game.h

#pragma once

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

#define ROW 5
#define COL 5

//初始化棋盘
void InitBoard(char board[ROW][COL], int row, int col);
//打印棋盘
void DisplayBoard(char board[ROW][COL], int row, int col);
//玩家下棋
void PlayerMove(char board[ROW][COL], int row, int col);
//电脑随机下棋
void ComputerMove(char board[ROW][COL], int row, int col);
//判断棋盘是否放满
int IsFull(char board[ROW][COL], int row, int col);
//判断输赢
//玩家赢 - '*'
//电脑赢 - '#'
//平局 - 'Q'
//继续 - 'C
char IsWin(char board[ROW][COL], int row, int col);

2.2.2 game.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"game.h"

//初始化棋盘为空格
void InitBoard(char board[ROW][COL], int row, int col)
{
   
    for (int i = 0; i < row; i++)
        for (int j = 0; j < col; j++)
            board[i][j] = ' ';
}

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

    for (int i = 0; i < row; i++)
    {
   
        //1. 打印数据
        for (int j = 0; j < col; j++)
        {
   
            printf(" %c ", board[i][j]);
            if (j < col - 1)
                printf("|");
        }
        printf("\n");
        //2. 打印分割线
        if (i < row - 1)
        {
   
            for (int 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("玩家下棋>:\n");
    while (1)
    {
   
        printf("请输入下棋的坐标,中间使用空格>:");
        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("坐标被占有,不能落子,重新输入坐标\n");
            }
        }
        else//非法
        {
   
            printf("坐标非法,重新输入\n");
        }
    }
}

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


int IsFull(char board[ROW][COL], int row, int col)
{
   
    for (int i = 0; i < row; i++)
    {
   
        for (int 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;
    int j = 0;
    for (i = 0; i < row; i++)
    {
   
        j = 0;
        for (j = 0; j < col - 1; j++)
        {
   
            if (board[i][j] != board[i][j + 1] || board[i][j] == ' ')
                break;
        }
        if (j == col - 1)
            return board[i][j];
    }
    //列
    i = 0;
    j = 0;
    for (j = 0; j < col; j++)
    {
   
        i = 0;
        for (i = 0; i < row - 1; i++)
        {
   
            if (board[i][j] != board[i + 1][j] || board[i][j] == ' ')
                break;
        }
        if (i == row - 1)
            return board[i][j];
    }
    //主对角线
    i = 0;
    j = 0;
    for (i = 0; i < row - 1; i++)
    {
   
        j = 0;
        for (j = 0; j < col - 1; j++)
        {
   
            if (board[i][j] != board[i + 1][j + 1] || board[i][j] == ' ')
                break;
        }
        if (j == col - 1)
            return board[i][j];
    }

    //副对角线
    i = 0;
    j = 0;
    for (i = 0; i < row - 1; i++)
    {
   
        j = 0;
        for (j = col - 1; j > 0; j--)
        {
   
            if (board[i][j] != board[i + 1][j - 1] || board[i][j] == ' ')
                break;
        }
        if (i == row - 1)
            return board[i][j];
    }
    //平局
    if (IsFull(board, row, col))
    {
   
        return 'Q';
    }
    //继续
    return 'C';
}

2.2.3 test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"game.h"

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

void game()
{
   
    char board[ROW][COL] = {
    0 };
    InitBoard(board, ROW, COL);
    //打印棋盘
    DisplayBoard(board, ROW, COL);
    char ret = 0;
    while (1)
    {
   
        //下棋
        //玩家下棋
        PlayerMove(board, ROW, COL);
        DisplayBoard(board, ROW, COL);
        //判断输赢
        ret = IsWin(board, ROW, COL);
        if (ret != 'C')
            break;
        //电脑下棋
        ComputerMove(board, ROW, COL);
        DisplayBoard(board, ROW, COL);
        //判断输赢
        ret = IsWin(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("请选择:>");
        scanf("%d", &input);
        switch (input)
        {
   
        case 1:
            game();
            break;
        case 0:
            printf("退出游戏\n");
            break;
        default:
            printf("选择错误,重新选择!\n");
            break;
        }
    } while (input);
    return 0;
}

3. 总结

到这里,我们的三子棋及多子棋小游戏的设计就结束了,通过设计该游戏,我增加了对二维数组和循环结构的理解,也熟悉了随机数函数的使用,学习使用了宏定义,提高了自定义函数的编写能力和分析问题的能力。

希望通过后续学习,不断提高代码能力和分析解决问题的能力。由于本人是大一学生,知识水平有限,本文如果有错误和不足之处,还望各位大佬多多指出并提出建议!

相关文章
|
5月前
|
C语言
C语言实现递归版多子棋的设计(下)
C语言实现递归版多子棋的设计
|
5月前
|
C语言
C语言实现递归版多子棋的设计(上)
C语言实现递归版多子棋的设计
|
算法 C语言
【C语言】三子棋游戏与多子棋 (保姆级的实现过程)
① 前言 三子棋,想必大家都有玩过吧。
134 0
【C语言】三子棋游戏与多子棋 (保姆级的实现过程)
|
22天前
|
存储 Serverless C语言
【C语言基础考研向】11 gets函数与puts函数及str系列字符串操作函数
本文介绍了C语言中的`gets`和`puts`函数,`gets`用于从标准输入读取字符串直至换行符,并自动添加字符串结束标志`\0`。`puts`则用于向标准输出打印字符串并自动换行。此外,文章还详细讲解了`str`系列字符串操作函数,包括统计字符串长度的`strlen`、复制字符串的`strcpy`、比较字符串的`strcmp`以及拼接字符串的`strcat`。通过示例代码展示了这些函数的具体应用及注意事项。
|
25天前
|
存储 C语言
C语言程序设计核心详解 第十章:位运算和c语言文件操作详解_文件操作函数
本文详细介绍了C语言中的位运算和文件操作。位运算包括按位与、或、异或、取反、左移和右移等六种运算符及其复合赋值运算符,每种运算符的功能和应用场景都有具体说明。文件操作部分则涵盖了文件的概念、分类、文件类型指针、文件的打开与关闭、读写操作及当前读写位置的调整等内容,提供了丰富的示例帮助理解。通过对本文的学习,读者可以全面掌握C语言中的位运算和文件处理技术。
|
25天前
|
存储 C语言
C语言程序设计核心详解 第七章 函数和预编译命令
本章介绍C语言中的函数定义与使用,以及预编译命令。主要内容包括函数的定义格式、调用方式和示例分析。C程序结构分为`main()`单框架或多子函数框架。函数不能嵌套定义但可互相调用。变量具有类型、作用范围和存储类别三种属性,其中作用范围分为局部和全局。预编译命令包括文件包含和宏定义,宏定义分为无参和带参两种形式。此外,还介绍了变量的存储类别及其特点。通过实例详细解析了函数调用过程及宏定义的应用。
|
1月前
|
Linux C语言
C语言 多进程编程(三)信号处理方式和自定义处理函数
本文详细介绍了Linux系统中进程间通信的关键机制——信号。首先解释了信号作为一种异步通知机制的特点及其主要来源,接着列举了常见的信号类型及其定义。文章进一步探讨了信号的处理流程和Linux中处理信号的方式,包括忽略信号、捕捉信号以及执行默认操作。此外,通过具体示例演示了如何创建子进程并通过信号进行控制。最后,讲解了如何通过`signal`函数自定义信号处理函数,并提供了完整的示例代码,展示了父子进程之间通过信号进行通信的过程。
|
1月前
|
C语言
C语言 字符串操作函数
本文档详细介绍了多个常用的字符串操作函数,包括 `strlen`、`strcpy`、`strncpy`、`strcat`、`strncat`、`strcmp`、`strncpy`、`sprintf`、`itoa`、`strchr`、`strspn`、`strcspn`、`strstr` 和 `strtok`。每个函数均提供了语法说明、参数解释、返回值描述及示例代码。此外,还给出了部分函数的自实现版本,帮助读者深入理解其工作原理。通过这些函数,可以轻松地进行字符串长度计算、复制、连接、比较等操作。
|
1月前
|
SQL 关系型数据库 C语言
PostgreSQL SQL扩展 ---- C语言函数(三)
可以用C(或者与C兼容,比如C++)语言编写用户自定义函数(User-defined functions)。这些函数被编译到动态可加载目标文件(也称为共享库)中并被守护进程加载到服务中。“C语言函数”与“内部函数”的区别就在于动态加载这个特性,二者的实际编码约定本质上是相同的(因此,标准的内部函数库为用户自定义C语言函数提供了丰富的示例代码)
|
2月前
|
C语言
【C语言】字符串及其函数速览
【C语言】字符串及其函数速览
26 4