【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. 总结

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

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

相关文章
|
6月前
|
C语言
C语言实现递归版多子棋的设计(下)
C语言实现递归版多子棋的设计
|
6月前
|
C语言
C语言实现递归版多子棋的设计(上)
C语言实现递归版多子棋的设计
|
算法 C语言
【C语言】三子棋游戏与多子棋 (保姆级的实现过程)
① 前言 三子棋,想必大家都有玩过吧。
144 0
【C语言】三子棋游戏与多子棋 (保姆级的实现过程)
|
1月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
34 3
|
10天前
|
C语言
c语言调用的函数的声明
被调用的函数的声明: 一个函数调用另一个函数需具备的条件: 首先被调用的函数必须是已经存在的函数,即头文件中存在或已经定义过; 如果使用库函数,一般应该在本文件开头用#include命令将调用有关库函数时在所需要用到的信息“包含”到本文件中。.h文件是头文件所用的后缀。 如果使用用户自己定义的函数,而且该函数与使用它的函数在同一个文件中,一般还应该在主调函数中对被调用的函数做声明。 如果被调用的函数定义出现在主调函数之前可以不必声明。 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必多所调用的函数在做声明
27 6
|
30天前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
36 10
|
23天前
|
存储 算法 程序员
C语言:库函数
C语言的库函数是预定义的函数,用于执行常见的编程任务,如输入输出、字符串处理、数学运算等。使用库函数可以简化编程工作,提高开发效率。C标准库提供了丰富的函数,满足各种需求。
|
29天前
|
机器学习/深度学习 C语言
【c语言】一篇文章搞懂函数递归
本文详细介绍了函数递归的概念、思想及其限制条件,并通过求阶乘、打印整数每一位和求斐波那契数等实例,展示了递归的应用。递归的核心在于将大问题分解为小问题,但需注意递归可能导致效率低下和栈溢出的问题。文章最后总结了递归的优缺点,提醒读者在实际编程中合理使用递归。
57 7
|
29天前
|
存储 编译器 程序员
【c语言】函数
本文介绍了C语言中函数的基本概念,包括库函数和自定义函数的定义、使用及示例。库函数如`printf`和`scanf`,通过包含相应的头文件即可使用。自定义函数需指定返回类型、函数名、形式参数等。文中还探讨了函数的调用、形参与实参的区别、return语句的用法、函数嵌套调用、链式访问以及static关键字对变量和函数的影响,强调了static如何改变变量的生命周期和作用域,以及函数的可见性。
30 4
|
1月前
|
存储 编译器 C语言
C语言函数的定义与函数的声明的区别
C语言中,函数的定义包含函数的实现,即具体执行的代码块;而函数的声明仅描述函数的名称、返回类型和参数列表,用于告知编译器函数的存在,但不包含实现细节。声明通常放在头文件中,定义则在源文件中。