用 C 语言创建五子棋游戏

CBeginner
立即练习

介绍

在这个项目中,我们将使用 C 编程语言创建一个简单的基于文本的五子棋游戏。五子棋是一种两人策略棋盘游戏,目标是率先在水平、垂直或对角线上连续获得五颗棋子。我们将使用一个 15x15 的游戏棋盘来开发这个游戏。

👀 预览

五子棋游戏

🎯 任务

在这个项目中,你将学习:

  • 如何在 C 语言中使用二维数组设计和实现棋盘。
  • 如何创建一个主函数来控制游戏流程。
  • 如何实现初始化游戏、打印棋盘以及允许玩家轮流下棋的函数。
  • 如何开发一个函数来检查游戏中的获胜条件。
  • 如何编译和运行程序。

🏆 成果

完成这个项目后,你将能够:

  • 在 C 语言中使用二维数组。
  • 使用函数设计和实现游戏流程。
  • 检查游戏中的获胜条件。
  • 编译和运行 C 程序。

创建项目文件

首先,创建一个名为 gomoku.c 的新文件,并在你喜欢的代码编辑器中打开它。

cd ~/project
touch gomoku.c

设计棋盘

使用 'O' 和 'X' 表示棋子

首先,我们需要一个棋盘(15 * 15)来记录棋盘上每个位置的“状态”。因此,我们可以定义一个数组 chessboard[16][16]。为什么不是 [15][15] 呢?这是因为这样数组的坐标就能与棋盘的行和列精确对应,便于编写后续代码。

#include <stdio.h>

#define N 15

// 定义一个数组,并将每个元素初始化为 0。
int chessboard[N + 1][N + 1] = { 0 };

主函数

在开始编写主函数之前,让我们简要思考一下游戏的典型流程。首先,我们进入游戏的主界面,然后点击开始按钮进入游戏,接着显示游戏画面,判断胜负,最后结束游戏。那么,像五子棋这样的游戏流程是怎样的呢?

首先,我们进入游戏的欢迎界面,然后输入 Y 开始游戏(非 Y 则退出游戏),显示游戏棋盘,接着两位玩家轮流落子,之后判断胜负(是否有五个棋子连成一线)。

// 用于记录轮到玩家 1 还是玩家 2,奇数表示轮到玩家 1,偶数表示轮到玩家 2。
int whoseTurn = 0;

int main(void)
{
 // 一个自定义函数,用于初始化游戏,即显示欢迎界面并进入游戏显示棋盘。
 initGame();

 // 这个循环用于两位玩家轮流操作。
 while (1)
 {
  // 每次循环加 1,这样两人就能轮流操作。
  whoseTurn++;

  // 一个自定义函数,用于执行落子操作。
  playChess();
 }

 return 0;
}

初始化游戏函数

在这个函数中,我们要实现的功能有:

  • 显示一个简单的欢迎界面。
  • 要求输入 'Y',输入后显示棋盘。
void initGame(void)
{
 char c;

 printf("请输入'y'进入游戏:");
 c = getchar();
 if ('y'!= c && 'Y'!= c)
  exit(0);

 // 清屏
 system("clear");

 // 这里再次调用一个自定义函数,其功能是打印出棋盘。
 printChessboard();
}

initGame 函数中,我们使用了 exitsystem 函数,所以需要在程序顶部包含头文件 stdlib.h

#include <stdlib.h>

打印棋盘函数

在这个函数中,我们的目标是:

  • 打印出行号和列号,并打印出棋盘。
  • 如果数组元素的值为 0,打印一个星号(*),表示该位置为空。
  • 如果数组元素的值为 1,打印一个实心圆(X),代表玩家 1 的棋子。
  • 如果数组元素的值为 2,打印一个空心圆(O),代表玩家 2 的棋子。
void printChessboard(void)
{
 int i, j;

 for (i = 0; i <= N; i++)
 {
  for (j = 0; j <= N; j++)
  {
   if (0 == i)  // 这会打印出列号。
    printf("%3d", j);
   else if (j == 0) // 打印行号。
    printf("%3d", i);
   else if (1 == chessboard[i][j])
    printf("  X");
   else if (2 == chessboard[i][j])
    printf("  O");
   else
    printf("  *");
  }
  printf("\n");
 }
}

下棋函数

在这个函数中,我们想要实现以下几点:

  • 提示玩家输入放置棋子的位置。
  • 如果当前轮到玩家 1 行动,将数组中相应元素的值设为 1。
  • 如果当前轮到玩家 2 行动,将数组中相应元素的值设为 2。
  • 每一步棋之后,判断当前玩家是否获胜。
void playChess(void)
{
 int i, j, winner;

 // 判断是玩家 1 的回合还是玩家 2 的回合,然后将值赋给数组中的相应元素。
 if (1 == whoseTurn % 2)
 {
  printf("轮到玩家 1,请输入位置,格式为行号 + 空格 + 列号:");
  scanf("%d %d", &i, &j);
  chessboard[i][j] = 1;
 }
 else
 {
  printf("轮到玩家 2,请输入位置,格式为行号 + 空格 + 列号:");
  scanf("%d %d", &i, &j);
  chessboard[i][j] = 2;
 }

 // 再次打印棋盘。
 system("clear");
 printChessboard(); // 再次调用此函数。

 /*
 * 以下部分调用自定义函数(判断函数)。
 * 用于判断当前玩家这一步棋是否获胜。
 */
 if (judge(i, j, whoseTurn))
 {
  if (1 == whoseTurn % 2)
   printf("获胜者是玩家 1!\n");
  else
   printf("获胜者是玩家 2!\n");
 }
}

判断函数

函数参数:

  • x:当前落子的行号
  • y:当前落子的列号

返回值:

  • 1 或 0。1 表示当前玩家落子后获胜。
int judge(int x, int y)
{
    int i, j;
    int t = 2 - whoseTurn % 2;
    const int step[4][2]={{-1,0},{0,-1},{1,1},{1,0}};
    for(int i=0;i<4;++i)
    {
        const int d[2]={-1,1};
        int count=1;
        for(int j=0;j<2;++j)
            for(int k=1;k<=4;++k){
                int row=x+k*d[j]*step[i][0];
                int col=y+k*d[j]*step[i][1];
                if( row>=1 && row<=N && col>=1 && col<=N &&
                    chessboard[x][y]==chessboard[row][col])
                    count+=1;
                else
                    break;
            }
        if(count>=5)
            return 1;
    }
    return 0;
}

judge 函数中有 3 个嵌套的 for 循环,其目的是判断是否存在连续五个棋子的连线。

五个棋子的连线可以是水平、垂直或对角线方向。这里,我们将采用试错法,在水平、垂直和对角线方向上搜索连续的棋子。举个例子:

五子棋游戏

在上面的棋盘上,我们将根据坐标 (9, 10) 解释判断是否存在五个棋子连线的算法。

首先,我们检查从 (9, 10) 开始是否存在一条五个棋子的对角线。过程如下:

  • 从 (9, 10) 开始,我们向左上方搜索。满足条件的坐标是 (8, 9)、(7, 8) 和 (6, 7)。由于 (5, 6) 不满足条件,我们进入下一步。
  • 然后,我们向右下方搜索,找到 (10, 11),这是唯一满足条件的坐标。
  • 我们找到了一条直线上的五个点,所以玩家 2 获胜。

如果在对角线方向上没有五个棋子的连线,我们接着检查垂直和水平方向。如果它们都不满足获胜条件,这意味着当前玩家不能获胜,游戏将继续。

我的棋子被‘吃掉’了

我不知道大家有没有注意到,在我们的五子棋游戏中,即使某个位置已经被占用,我们在放置自己的棋子时仍然可以“吃掉”原来的棋子。

这是因为我们在编写 playChess 函数时,没有检查我们放置棋子的位置。我们可以像这样修改代码:

void playChess(void)
{
 int i, j, winner;
 if (1 == whoseTurn % 2)
 {
  printf("轮到玩家 1,请输入位置,格式为行号 + 空格 + 列号:");
  scanf("%d %d", &i, &j);
  //调试
  while(chessboard[i][j]!= 0)
  {
   printf("这个位置已经被占用,请重新输入位置:");
   scanf("%d %d",&i, &j);
  }
  //调试
  chessboard[i][j] = 1;
 }
 else
 {
  printf("轮到玩家 2,请输入位置,格式为行号 + 空格 + 列号:");
  scanf("%d %d", &i, &j);
  //调试
  while(chessboard[i][j]!= 0)
  {
   printf("这个位置已经被占用,请重新输入位置:");
   scanf("%d %d",&i, &j);
  }
  //调试
  chessboard[i][j] = 2;
 }

 system("clear");
 printChessboard();

 if (judge(i, j))
 {
  if (1 == whoseTurn % 2)
   printf("获胜者是玩家 1!\n");
  else
   printf("获胜者是玩家 2!\n");
 }
}

我们在函数中添加了一个循环,所以当位置已经被占用时,它会给出提示并要求输入新的位置。

为什么我永远赢不了?

当出现五个棋子连成一线,并提示“玩家 1”或“玩家 2”获胜时,接着又提示“轮到玩家 * 行动,请输入你的棋子位置……”。是不是很让人沮丧?实际上我们只需要添加一行代码!

 if (judge(i, j))
 {
  if (1 == whoseTurn % 2)
  {
   printf("获胜者是玩家 1!\n");
   exit(0); //调试
  }
  else
  {
   printf("获胜者是玩家 2!\n");
   exit(0); //调试
  }
 }
}

除了在玩家获胜后提示获胜者之外,使用 exit(0) 退出游戏。

编译与运行

执行 gcc 命令进行编译:

cd ~/project
gcc -o gomoku gomoku.c
./gomoku

五子棋游戏

总结

恭喜你!你已经使用 C 语言创建了一个简单的五子棋游戏。玩家可以轮流在一个 15x15 的棋盘上放置棋子,当有玩家连成五个连续的棋子时,程序将宣布其为获胜者。和你的朋友们尽情享受这个基于文本的游戏吧!

✨ 查看解决方案并练习✨ 查看解决方案并练习✨ 查看解决方案并练习✨ 查看解决方案并练习✨ 查看解决方案并练习✨ 查看解决方案并练习✨ 查看解决方案并练习✨ 查看解决方案并练习✨ 查看解决方案并练习✨ 查看解决方案并练习