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

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

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

相关文章
|
8月前
|
C语言
C语言实现递归版多子棋的设计(下)
C语言实现递归版多子棋的设计
|
8月前
|
C语言
C语言实现递归版多子棋的设计(上)
C语言实现递归版多子棋的设计
|
算法 C语言
【C语言】三子棋游戏与多子棋 (保姆级的实现过程)
① 前言 三子棋,想必大家都有玩过吧。
153 0
【C语言】三子棋游戏与多子棋 (保姆级的实现过程)
|
6天前
|
存储 算法 C语言
【C语言程序设计——函数】素数判定(头歌实践教学平台习题)【合集】
本内容介绍了编写一个判断素数的子函数的任务,涵盖循环控制与跳转语句、算术运算符(%)、以及素数的概念。任务要求在主函数中输入整数并输出是否为素数的信息。相关知识包括 `for` 和 `while` 循环、`break` 和 `continue` 语句、取余运算符 `%` 的使用及素数定义、分布规律和应用场景。编程要求根据提示补充代码,测试说明提供了输入输出示例,最后给出通关代码和测试结果。 任务核心:编写判断素数的子函数并在主函数中调用,涉及循环结构和条件判断。
44 23
|
6天前
|
算法 C语言
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
本文档介绍了如何编写两个子函数,分别求任意两个整数的最大公约数和最小公倍数。内容涵盖循环控制与跳转语句的使用、最大公约数的求法(包括辗转相除法和更相减损术),以及基于最大公约数求最小公倍数的方法。通过示例代码和测试说明,帮助读者理解和实现相关算法。最终提供了完整的通关代码及测试结果,确保编程任务的成功完成。
33 15
|
6天前
|
C语言
【C语言程序设计——函数】亲密数判定(头歌实践教学平台习题)【合集】
本文介绍了通过编程实现打印3000以内的全部亲密数的任务。主要内容包括: 1. **任务描述**:实现函数打印3000以内的全部亲密数。 2. **相关知识**: - 循环控制和跳转语句(for、while循环,break、continue语句)的使用。 - 亲密数的概念及历史背景。 - 判断亲密数的方法:计算数A的因子和存于B,再计算B的因子和存于sum,最后比较sum与A是否相等。 3. **编程要求**:根据提示在指定区域内补充代码。 4. **测试说明**:平台对代码进行测试,预期输出如220和284是一组亲密数。 5. **通关代码**:提供了完整的C语言代码实现
45 24
|
2天前
|
存储 C语言
【C语言程序设计——函数】递归求斐波那契数列的前n项(头歌实践教学平台习题)【合集】
本关任务是编写递归函数求斐波那契数列的前n项。主要内容包括: 1. **递归的概念**:递归是一种函数直接或间接调用自身的编程技巧,通过“俄罗斯套娃”的方式解决问题。 2. **边界条件的确定**:边界条件是递归停止的条件,确保递归不会无限进行。例如,计算阶乘时,当n为0或1时返回1。 3. **循环控制与跳转语句**:介绍`for`、`while`循环及`break`、`continue`语句的使用方法。 编程要求是在右侧编辑器Begin--End之间补充代码,测试输入分别为3和5,预期输出为斐波那契数列的前几项。通关代码已给出,需确保正确实现递归逻辑并处理好边界条件,以避免栈溢出或结果
40 16
|
2天前
|
存储 编译器 C语言
【C语言程序设计——函数】分数数列求和2(头歌实践教学平台习题)【合集】
函数首部:按照 C 语言语法,函数的定义首部表明这是一个自定义函数,函数名为fun,它接收一个整型参数n,用于指定要求阶乘的那个数,并且函数的返回值类型为float(在实际中如果阶乘结果数值较大,用float可能会有精度损失,也可以考虑使用double等更合适的数据类型,这里以float为例)。例如:// 函数体代码将放在这里函数体内部变量定义:在函数体中,首先需要定义一些变量来辅助完成阶乘的计算。比如需要定义一个变量(通常为float或double类型,这里假设用float。
11 3
|
2天前
|
存储 算法 安全
【C语言程序设计——函数】分数数列求和1(头歌实践教学平台习题)【合集】
if 语句是最基础的形式,当条件为真时执行其内部的语句块;switch 语句则适用于针对一个表达式的多个固定值进行判断,根据表达式的值与各个 case 后的常量值匹配情况,执行相应 case 分支下的语句,直到遇到 break 语句跳出 switch 结构,若没有匹配值则执行 default 分支(可选)。例如,在判断一个数是否大于 10 的场景中,条件表达式为 “num> 10”,这里的 “num” 是程序中的变量,通过比较其值与 10 的大小关系来确定条件的真假。常量的值必须是唯一的,且在同一个。
7 2
|
6天前
|
存储 编译器 C语言
【C语言程序设计——函数】回文数判定(头歌实践教学平台习题)【合集】
算术运算于 C 语言仿若精密 “齿轮组”,驱动着数值处理流程。编写函数求区间[100,500]中所有的回文数,要求每行打印10个数。根据提示在右侧编辑器Begin--End之间的区域内补充必要的代码。如果操作数是浮点数,在 C 语言中是不允许直接进行。的结果是 -1,因为 -7 除以 3 商为 -2,余数为 -1;注意:每一个数据输出格式为 printf("%4d", i);的结果是 1,因为 7 除以 -3 商为 -2,余数为 1。取余运算要求两个操作数必须是整数类型,包括。开始你的任务吧,祝你成功!
31 1