- 1. 前言
- 2. 游戏分工
- 3. 游戏菜单
- 4. 游戏过程
- 4.1 前提准备
- 4.2 棋盘的初始化
- 4.3 打印棋盘
- 4.4 对弈过程
- 玩家下棋
- 电脑下棋(简版,智能化下棋中会优化)
- 4.5 判断输赢
- 4.6 智能化下棋(※)
- 5. 完整展示
- game.h
- game.c
- test.c
- 6. 动画展示
- 7. 结语
三子棋,也称井字棋,该游戏进行起来十分方便,只需要一支笔和一张纸就可以进行游戏,是上课摸鱼的好游戏(bushi),也是我的童年回忆。
本篇博客将采用C语言来模拟实现简单的三子棋游戏。
人机大战,谁能更胜一筹?
2. 游戏分工该项目分为三个文件:
- test.c:游戏的逻辑
- game.h:函数声明,符号的定义
- game.c:游戏的实现
其中game.h,game.c为游戏模块,test.c为测试区域。
3. 游戏菜单在进入游戏前,我们需要让玩家通过菜单选择开始游戏或退出游戏。
游戏菜单应该实现三个功能:
- 进入游戏
- 退出游戏
- 非法输入的返回提示和说明
而对于游戏,我们至少需要玩一次,应该用do...while循环来完成游戏的重复游玩功能。对于游戏选项,则可以用switch语句完成。
对应代码:
void menu()
{
printf("**********************************n");
printf("************* 1.play *************n");
printf("************* 0.exit *************n");
printf("**********************************n");
}
void test()
{
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);
}
int main()
{
test();
return 0;
}
运行结果:
4. 游戏过程友情提示:
- 以下模块均用于实现游戏功能,对应的功能均在test.c的game()函数中调用、game.h中进行声明和相关常量的定义,函数功能在game.c中实现。
- 在.c文件中均需引自定义的头文件#include"game.h"。
- 对于函数的声明我会省略,以下模块展示的内容主要为在game.c文件中实现的游戏功能。
为了提高代码的专业性和方便对代码进行修改,我们将对于行和列进行定义(game.h).
对应代码:
#pragma once #include4.2 棋盘的初始化#define ROW 3 #define COL 3
既然是三子棋,那棋盘并不可少。在进行游戏前,我们需要将棋盘中的元素都置为空白,以便后续进行下棋的操作。
棋盘,无非就是一个3 * 3的二维数组,我们在game()函数中定义:
void game()
{
//棋盘的创建
char board[ROW][COL] = { 0 };
//初始化棋盘
init_board(board, ROW, COL);
}
对于棋盘的初始化,我们直接利用嵌套循环即可:
void init_board(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < col; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
board[i][j] = ' ';//置为空白
}
}
}
4.3 打印棋盘
在完成棋盘的初始化后,我们需要将初始化后的棋盘展示给玩家看,棋盘如图所示:
棋盘分为三块:
- 数据,一个数据表示为空格数据空格的形式.
- 分隔符|,分隔符的个数每行均为两个,边界不需要分隔符。
- 分隔线---,该分割线只存在于第一、二行数据的下方。
对于分隔符可以用列来进行限制,对于分隔线则是用行来限制:
void display_board(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf(" %c ", board[i][j]);
if (j < col - 1)//最后一列没有
printf("|");
}
printf("n");
if (i < row - 1)//最后一行没有
{
int j = 0;
for (j = 0; j < col; j++)
{
printf("---");
if (j < col - 1)
printf("|");
}
printf("n");
}
}
}
4.4 对弈过程
对弈过程不止一次,直到分出胜负为止,因此在game()函数中应用循环表示。
- 玩家下棋
思路:
- 玩家下棋的输入我们用坐标表示,但是需要明确一点:并不是所有玩家都了解C语言数组下标,因此玩家输入的坐标需要进行转化。
- 输入坐标的取值范围为:1~3,若不在范围内应提示输入错误。
- 玩家棋子用*表示,若选择坐标为空白,则将坐标置为*,并退出,让电脑进行后续操作,若选择坐标不是空白,则操作为悔棋操作,应提示重新输入。
对应代码:
void player_move(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
while (1)
{
printf("玩家下棋n");
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");
}
}
}
- 电脑下棋(简版,智能化下棋中会优化)
思路:
- 电脑为自动下棋,并不需要手动输入坐标。
- 自动随机值的提供需要利用时间戳来完成,和上次的猜数字小游戏相同,利用rand、srand、time函数来完成相关操作,srand函数在test中调用,rand函数在电脑下棋中用临时变量接收。
- 随机值取值范围应在0~2之间,由行和列的值都为3,直接让rand()%3即可(数值1模数值2结果为0~数值2-1)
- 电脑棋子用#表示,由于自动下棋并不需要人为操作,只要条件判断即可,不需要输入提示。
对应代码:
void computer_move(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("电脑下棋n");
while (1)
{
x = rand() % row;//0-2
y = rand() % col;//0-2
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;//注意退出
}
}
}
4.5 判断输赢
我们用以下方式判定游戏情况:
- 玩家赢 - *
- 电脑赢 - #
- 平局 - Q
- 继续 - c
思路:
- 赢取游戏,分为四种情况,行、列、主对角线、副对角线所占元素均相同且不为空。
- 在判断输赢后未得出结果,则说明游戏平局,我们可以封装一个函数判断棋盘是否满了,用返回值为1则说明平局,且该函数仅为判断输赢做辅助作用,并不需要声明,且不需要被其他文件所发现。
- 若输赢和平局并不满足,则说明游戏继续。
- 在game()中应对相应的返回结果做出判断,且在玩家和电脑下棋后均应该判断棋盘是否下满,下满则直接进入判断部分。
结合对弈过程和判断在game()函数的对应代码:
void game()
{
while (1)
{
//玩家下棋
player_move(board, ROW, COL);
//玩家下棋后展示棋盘
display_board(board, ROW, COL);
//判断输赢
ret = is_win(board, ROW, COL);
if (ret != 'C')
{
break;
}
//电脑下棋
computer_move(board, ROW, COL);
//电脑下棋后棋盘展示
display_board(board, ROW, COL);
ret = is_win(board, ROW, COL);
if (ret != 'C')
{
break;
}
}
if (ret == '*')
{
printf("玩家赢n");
}
else if (ret == '#')
{
printf("电脑赢了n");
}
else
{
printf("平局n");
}
}
game.c对应代码:
static int is_full(char board[ROW][COL], int row, int col)//static修饰为静态函数,只为is_win起辅助作用
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
if (board[i][j] == ' ')
{
return 0;
}
}
}
return 1;//下满了
}
char is_win(char board[ROW][COL], int row, int col)
{
//判断行
int i = 0;
for (i = 0; i < row; i++)
{
if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][i] != ' ')
{
return board[i][1];
}
}
//判断列
for (i = 0; i < col; i++)
{
if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[1][i] != ' ')
{
return board[1][i];
}
}
//判断主对角线
if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ')
{
return board[1][1];
}
//判断副对角线
if (board[2][0] == board[1][1] && board[1][1] == board[0][2] && board[1][1] != ' ')
{
return board[1][1];
}
if (is_full(board, row, col) == 1)
{
return 'Q';
}
return 'C';
}
4.6 智能化下棋(※)
对于电脑下棋,原先的代码使得在下棋过程中,电脑的操作过于呆板,没有挑战性。所以我们可以对电脑下棋进行升级。
智能化下棋分为两部分:
- 电脑判断是否能赢并下棋
- 电脑阻拦玩家下棋
思路:
我们封装一个函数,被computer_move函数所调用,函数中实现这两个功能。
这两个操作都是对于行、列、主副对角线进行判断,它们的实现逻辑是相同的,所以我们可以把它们在同一函数中实现,电脑赢棋的取决于一条路线上是否有两个#,阻拦玩家下棋则取决于一条路线上是否有两个*。
所以我们在传参时,只需要把元素传入,这样就可以将两部分在一个函数中实现。
对应代码:
static int intelligent_computer(char board[ROW][COL], int row, int col, char ch)//ch为'#'进行下棋,ch为'*'进行阻拦
{
//检查电脑能否获得胜利/阻拦玩家
int i = 0;
//行
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
//判断第一个和第二个的元素电脑是否已下棋,对第三个元素进行下棋/阻拦
if (board[i][0] == board[i][1] && board[i][0] == ch && board[i][2] == ' ')
{
board[i][2] = '#';
return 1;
}
//判断第一个和第三个的元素电脑是否已下棋,对第二个元素进行下棋/阻拦
if (board[i][0] == board[i][2] && board[i][0] == ch && board[i][1] == ' ')
{
board[i][1] = '#';
return 1;
}
//判断第二个和第三个的元素电脑是否已下棋,对第一个元素进行下棋/阻拦
if (board[i][1] == board[i][2] && board[i][1] == ch && board[i][0] == ' ')
{
board[i][0] = '#';
return 1;
}
}
}
//列
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
//第一和第二个已下,下/阻拦第三个
if (board[0][j] == board[1][j] && board[0][j] == ch && board[2][j] == ' ')
{
board[2][j] = '#';
return 1;
}
//第一和第三个已下,下/阻拦第二个
if (board[0][j] == board[2][j] && board[0][j] == ch && board[1][j] == ' ')
{
board[1][j] = '#';
return 1;
}
//第二和第三个已下,下/阻拦第一个
if (board[1][j] == board[2][j] && board[1][j] == ch && board[0][j] == ' ')
{
board[0][j] = '#';
return 1;
}
}
}
//主对角线
if (board[0][0] == board[1][1] && board[1][1] == ch && board[2][2] == ' ')//落子/阻拦主对角线第三个元素
{
board[2][2] = '#';
return 1;
}
if (board[0][0] == board[2][2] && board[0][0] == ch && board[1][1] == ' ')//落子/阻拦主对角线第二个元素
{
board[1][1] = '#';
return 1;
}
if (board[1][1] == board[2][2] && board[1][1] == ch && board[0][0] == ' ')//落子/阻拦主对角线第一个元素
{
board[0][0] = '#';
return 1;
}
//副对角线
if (board[0][2] == board[1][1] && board[0][2] == ch && board[2][0] == ' ')//落子/阻拦副对角线第三个元素
{
board[2][0] = '#';
return 1;
}
if (board[0][2] == board[2][0] && board[0][2] == ch && board[1][1] == ' ')//落子/阻拦副对角线第二个元素
{
board[1][1] = '#';
return 1;
}
if (board[1][1] == board[2][0] && board[1][1] == ch && board[0][2] == ' ')//落子/阻拦副对角线第一个元素
{
board[0][2] = '#';
return 1;
}
return 0;//若无法赢棋或无法阻拦,返回0
}
注:对于原先的computer_move函数也需要做出相应改进,改进内容在完整展示部分。
5. 完整展示 game.h#pragma once #includegame.c#include #include #include #define ROW 3 #define COL 3 //初始化棋盘 void init_board(char board[ROW][COL], int row, int col); //打印棋盘 void display_board(char board[ROW][COL], int row, int col); //玩家下棋 void player_move(char board[ROW][COL], int row, int col); //电脑下棋 void computer_move(char board[ROW][COL], int row, int col); //判断输赢 char is_win(char board[ROW][COL], int row, int col);
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void init_board(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < col; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
board[i][j] = ' ';
}
}
}
void display_board(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf(" %c ", board[i][j]);
if (j < col - 1)
printf("|");
}
printf("n");
if (i < row - 1)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf("---");
if (j < col - 1)
printf("|");
}
printf("n");
}
}
}
void player_move(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
while (1)
{
printf("玩家下棋n");
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");
}
}
}
static int intelligent_computer(char board[ROW][COL], int row, int col, char ch)//ch为'#'进行下棋,ch为'*'进行阻拦
{
//检查电脑能否获得胜利/阻拦玩家
int i = 0;
//行
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
//判断第一个和第二个的元素电脑是否已下棋,对第三个元素进行下棋/阻拦
if (board[i][0] == board[i][1] && board[i][0] == ch && board[i][2] == ' ')
{
board[i][2] = '#';
return 1;
}
//判断第一个和第三个的元素电脑是否已下棋,对第二个元素进行下棋/阻拦
if (board[i][0] == board[i][2] && board[i][0] == ch && board[i][1] == ' ')
{
board[i][1] = '#';
return 1;
}
//判断第二个和第三个的元素电脑是否已下棋,对第一个元素进行下棋/阻拦
if (board[i][1] == board[i][2] && board[i][1] == ch && board[i][0] == ' ')
{
board[i][0] = '#';
return 1;
}
}
}
//列
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
//第一和第二个已下,下/阻拦第三个
if (board[0][j] == board[1][j] && board[0][j] == ch && board[2][j] == ' ')
{
board[2][j] = '#';
return 1;
}
//第一和第三个已下,下/阻拦第二个
if (board[0][j] == board[2][j] && board[0][j] == ch && board[1][j] == ' ')
{
board[1][j] = '#';
return 1;
}
//第二和第三个已下,下/阻拦第一个
if (board[1][j] == board[2][j] && board[1][j] == ch && board[0][j] == ' ')
{
board[0][j] = '#';
return 1;
}
}
}
//主对角线
if (board[0][0] == board[1][1] && board[1][1] == ch && board[2][2] == ' ')//落子/阻拦主对角线第三个元素
{
board[2][2] = '#';
return 1;
}
if (board[0][0] == board[2][2] && board[0][0] == ch && board[1][1] == ' ')//落子/阻拦主对角线第二个元素
{
board[1][1] = '#';
return 1;
}
if (board[1][1] == board[2][2] && board[1][1] == ch && board[0][0] == ' ')//落子/阻拦主对角线第一个元素
{
board[0][0] = '#';
return 1;
}
//副对角线
if (board[0][2] == board[1][1] && board[0][2] == ch && board[2][0] == ' ')//落子/阻拦副对角线第三个元素
{
board[2][0] = '#';
return 1;
}
if (board[0][2] == board[2][0] && board[0][2] == ch && board[1][1] == ' ')//落子/阻拦副对角线第二个元素
{
board[1][1] = '#';
return 1;
}
if (board[1][1] == board[2][0] && board[1][1] == ch && board[0][2] == ' ')//落子/阻拦副对角线第一个元素
{
board[0][2] = '#';
return 1;
}
return 0;//若无法赢棋或无法阻拦,返回0
}
void computer_move(char board[ROW][COL], int row, int col)
{
printf("电脑下棋n");
int sign1 = 0;
int sign2 = 0;
sign1 = intelligent_computer(board, ROW, COL, '#');//电脑智能赢棋
if (sign1 == 0)//若电脑无法赢棋,则进行阻拦
{
sign2 = intelligent_computer(board, ROW, COL, '*');//阻拦玩家下棋
if (sign2 == 0)//若赢棋和阻拦都不满足,电脑自行下棋
{
int x = 0;
int y = 0;
while (1)
{
x = rand() % row;
y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
}
}
static int is_full(char board[ROW][COL], int row, int col)//判断是否满格
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
if (board[i][j] == ' ')
{
return 0;
}
}
}
return 1;
}
char is_win(char board[ROW][COL], int row, int col)
{
//判断行
int i = 0;
for (i = 0; i < row; i++)
{
if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][1] != ' ')
{
return board[i][1];
}
}
//判断列
for (i = 0; i < col; i++)
{
if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[1][i] != ' ')
{
return board[1][i];
}
}
//判断主对角线
if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ')
{
return board[1][1];
}
//判断副对角线
if (board[2][0] == board[1][1] && board[1][1] == board[0][2] && board[1][1] != ' ')
{
return board[1][1];
}
if (is_full(board, row, col) == 1)
{
return 'Q';
}
return 'C';
}
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 ret = 0;
char board[ROW][COL] = { 0 };
//初始化棋盘
init_board(board, ROW, COL);
//打印棋盘
system("cls");//清屏
display_board(board, ROW, COL);
while (1)
{
//玩家下棋
player_move(board, ROW, COL);
//玩家下棋后展示棋盘
display_board(board, ROW, COL);
//判断输赢
ret = is_win(board, ROW, COL);
if (ret != 'C')
{
break;
}
//电脑下棋
system("cls");//清屏
computer_move(board, ROW, COL);
//电脑下棋后棋盘展示
display_board(board, ROW, COL);
ret = is_win(board, ROW, COL);
if (ret != 'C')
{
break;
}
}
if (ret == '*')
{
printf("恭喜你赢得游戏!n");
}
else if (ret == '#')
{
printf("很遗憾,你输了,电脑赢了n");
}
else
{
printf("平局,再来一把吧!n");
}
}
void test()
{
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);
}
int main()
{
test();
return 0;
}
6. 动画展示
7. 结语
到这里,一个简易三子棋游戏也就实现成功了!
我们发现经过智能化的电脑下棋还是很厉害的嘛,我都输了好几次,不过经我挑战后,我赢的次数还是比电脑多的,所以本次人机大战,我宣布,由anduin我,获得胜利!
以上就是C语言实现三子棋的全部内容,如果觉得anduin写的还不错的话,还请一键三连!
我是anduin,一名C语言初学者,我们下期见!
(ps:如果大家觉得粘贴代码比较费劲的话,也可以从我的gitee打包下载源码:戳我得三子棋源码)



