介绍
在这个项目中,我们将使用 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 函数中,我们使用了 exit 和 system 函数,所以需要在程序顶部包含头文件 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 的棋盘上放置棋子,当有玩家连成五个连续的棋子时,程序将宣布其为获胜者。和你的朋友们尽情享受这个基于文本的游戏吧!



