C语言实现三子棋
Posted 夏.冬
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言实现三子棋相关的知识,希望对你有一定的参考价值。
利用C语言实现三子棋(井字棋)
相信大家在小时候都用纸和笔与小伙伴们玩过一个经典的游戏——井字棋,即三子棋,看完这篇文章后你就可以用C语言写出属于自己的三子棋了。
实现三子棋用到的C语言知识点
实现该游戏写出的代码行数相比猜数字游戏是较多的,大概有两百多行,因此可以分为三个文件,一个头文件,两个源文件,头文件中对函数进行声明,源文件中对函数进行定义。
此次用到的知识点与上篇猜数字游戏的知识点大致相同,又新加了二维数组,如果你对二维数组还不了解,建议先学习二维数组的相关知识后再阅读本篇文章。
此次代码并不复杂,只是较为繁琐,对逻辑思维能力有一定的要求,写代码时要逻辑清晰,以免被绕进去。其中大多是对自定义函数的利用,并无太多难点。
鄙人也只是一个C语言初学者,写了很多遍才能够独立写出这个游戏的代码,如果有不足或者错误的地方,还请大家指正。
具体的代码实现
首先创建出一个头文件,两个源文件,10_30.c文件用来存放主函数、菜单、游戏步骤等内容,game.1.c文件用来写具体的游戏代码,game.1.h是头文件,用来对函数进行声明。
这里的主函数及菜单写法与上篇的猜数字游戏写法完全相同,有不懂的地方可参考上篇内容。
#include<stdio.h>
void menu()
{
printf("*****************************************\\n");
printf("***** 1 .play 0. exit *****\\n");
printf("*****************************************\\n");
}
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;
}
完成后,再编写game函数内部的代码
一、定义二维数组 - 存放棋盘信息
首先,我们要定义一个二维数组,用来存放走出的棋盘信息。
void game()
{
//数组 - 存放走出的棋盘信息
char board[ROW][COL] = { 0 };
}
其中,ROW为行数,COL为列数,那么这里你可能会有疑问,为什么不直接将行数和列数写成3呢?不要着急,我们先把ROW和COL的用法搞清楚。
这时切换到头文件中
这两行代码的意思是将ROW和COL的值定义为3,这样的好处是如果以后要改棋盘的大小,只需要在头文件中修改ROW和COL的值即可,就无需修改每个二维数组括号中的值了。
既然ROW和COL的值是在头文件中定义的,那么我们就要引用一下头文件。
#include "game.1.h"即为引用我们的头文件。
这里涉及到了一个知识点:引用自带的头文件是用尖括号<>,而引用我们自己创建的头文件要用双引号" " 。
将这个问题搞清楚后,我们继续往下写代码。
二、初始化棋盘
将数组即我们的棋盘创建后,应该初始化棋盘,即将数组中的内容全部设为空格,目的是打印棋盘时会打印出一个空棋盘。如果数组中的内容不全为空格,棋盘中就会打印出内容(如果数组的内容全为0,打印时也不会打印出内容)。
//游戏的整个算法实现
void game()
{
//数组 - 存放走出的棋盘信息
char board[ROW][COL] = { 0 };
//初始化棋盘
InitBoard(board, ROW, COL);
}
初始化棋盘时我们需要自定义一个函数,函数传参时需要传三个内容,数组名,数组的行数和列数。
既然初始化棋盘是游戏的一部分,那我们就在game.1.c文件中实现函数。
首先要在头文件中声明一下函数,该函数无需返回值,所以为void。
声明后,我们在game.1.c文件中定义函数。(将声明函数的代码去掉分号)
我们在该文件中用了头文件中声明的函数,那么我们也要引用一下头文件。
初始化棋盘即把数组中每个元素的内容都定义为空格。
#include "game.1.h"
void InitBoard(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++)
{
board[i][j] = ' ';
}
}
}
注:既然两个源文件都要引用头文件stdio.h,那就可以只在头文件中引用stdio.h,其他两个源文件引用了头文件game.1.h也就相当于引用了头文件stdio.h。
三、打印棋盘
初始化棋盘完成后,我们进行下一步,打印棋盘。
//打印棋盘
// | |
//---|---|---
// | |
//---|---|---
// | |
DisplayBoard(board, ROW, COL);
打印棋盘的效果如图所示,我们同样需要自定义一个函数,在头文件中声明,并在game.1.c文件中实现。
那么我们该如何美观的打印出棋盘呢?如果将行数与列数改变,又如何让打印出的棋盘大小相应改变呢?
我们不妨将棋盘拆解一下,我们再来回顾一下棋盘的样子。
//打印棋盘
// | |
//---|---|---
// | |
//---|---|---
// | |
我们可将一行数据与一行分割行视为一个整体部分,为了方便下面的讲解,把这个整体部分暂命名为小黑。
即:
// | |
//---|---|---
每打印一次打印出一个小黑,打印三次就可以打印出整个棋盘。
void DisplayBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0;i < row;i++)
{
//1. 打印一行的数据
int j = 0;
for (j = 0;j < col;j++)
{
printf(" %c ", board[i][j]);
if (j < col - 1)
{
printf("|");
}//end of if
}//end of for
printf("\\n");//每打印完一行换行
}//end of for
}
使用如上代码我们就完成了一行数据的打印。
看到这里你肯定还存有疑惑,我们再对这串代码解析一遍。
printf(" %c ",arr[i][j]);
//这里的意思是打印空格 字符 空格,其中的字符就是我们的棋子,加上两个空格是为了让我们的棋盘更加美观
if (j < col - 1)
{
printf("|");
}
//这里的意思是最后一列是不需要打印"|"的,所以加上一个if语句进行选择
//将如上两个打印的内容加起来即可以打印出" 棋子 | 棋子 | 棋子 "的内容
这串代码只是打印出小黑的一半内容,我们还需要打印一行分割行,即打印出"—"。
void DisplayBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0;i < row;i++)
{
//1. 打印一行的数据
int j = 0;
for (j = 0;j < col;j++)
{
printf(" %c ", board[i][j]);
if (j < col - 1)
{
printf("|");
}//end of if
}//end of for
printf("\\n");//每打印完一行换行
//2. 打印分割行
if (i < row - 1)//最后一行不需要打印分割行
{
for (j = 0;j < col;j++)
{
printf("---");
if (j < col - 1)//最后一列不需要打印"|
"
{
printf("|");
}//end of if
}//end of for
}//end of if
printf("\\n");//每打印完一行换行
}//end of for
}
使用如上代码便可打印出完整的棋盘。
运行结果如下图
四、下棋
打印完棋盘后我们就可以下棋了,下棋又分为玩家下棋与电脑下棋两部分,我们这里假设玩家先下。
下棋肯定是要双方下多次的,不可能玩家下一次电脑下一次游戏就结束了,因此这里用while循环。
下完一次棋后打印棋盘,显示出下的棋子的位置。这里再次创建一个自定义函数,依然要在头文件中声明,在game.1.c文件中使用。
我们将x,y作为玩家要下的坐标,我们知道数组第一个元素的下标为0,第一行第一个元素的下标为0 0,但我们不能要求每个玩家都是程序员,在任何一个非程序员的玩家的认知中,第一行第一个数的下标都+++++是1 1。因此如果玩家下的坐标为x,y,那我们就要输入x-1,y-1。
其次,玩家并不会关心你的棋盘有多大,所以玩家输入的坐标就很有可能超出棋盘范围,因此我们要判断玩家所输坐标的合法性,若坐标不合法,给出提示,并让玩家重新输入,因此此处要用到循环。同时玩家可能下完一个坐标以后再下相同的坐标,因此也要判断并给出提示,如果该处坐标等于空格,则说明此处并未被占用,若不等于空格,则说明此处坐标已被占用。
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);
//判断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三个库函数,生成随机数的具体方法上篇已具体介绍,这里不再讲解。
电脑下棋无需判断坐标是否非法,只需判断坐标是否被占用。
void ComputerMove(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的随机值
//此处x、y的值已符合下标范围,所以无需-1
if (board[x][y] == ' ')
{
board[x][y] = '#';//设电脑下的棋子用#表示
break;
}
}
}
运行结果如下图:
将以上代码完成后,还需要判断玩家和电脑的输赢,玩家每下完一步就要判断输赢,同样,电脑每下完一步也要判断输赢。
这里同样也需要一个自定义函数,创建并初始化一个变量ret,用来存储函数IsWin()的返回值。
我们在头文件里声明函数,并注释一下各种情况下函数的返回值,在game.1.c文件中实现函数。
IsWin函数的返回类型为char,所以要定义为char。
//下棋
while (1)
{
//玩家下棋
PlayerMove(board, ROW, COL);
DisplayBoard(board, ROW, COL);
//判断玩家是否赢
ret = IsWin(board, ROW, COL);
//等于C则说明游戏要继续,不等于C则说明游戏已分出胜负,跳出循环,到while循环下的选择语句中判断结果
if (ret != 'C')
{
break;
}
//电脑下棋
ComputerMove(board, ROW, COL);
DisplayBoard(board, ROW, COL);
//判断电脑是否赢
ret = IsWin(board, ROW, COL);
if (ret != 'C')
{
break;
}
}//end of while
if (ret = '*')
{
printf("玩家赢\\n");
}
else if (ret = '#')
{
printf("电脑赢\\n");
}
else
{
printf("平局\\n");
}
接下来再在game.1.c文件中完善IsWin函数的内容。
胜利有三种情况,横三行连成一线,竖三列连成一线,以及两条对角线连成一线。
除了判谁胜谁负,还要判断是否平局,以及判断游戏是否继续。
char IsWin(char board[ROW][COL], int row, int col)
{
int i = 0;
//横三行
for (i = 0;i < row;i++)
{
//判断一行三个棋子是否相等
//注意不能写成xx==xx==xx的形成,要用&&连接两个等式
//board[1][1]!=' '是为了一开始棋盘都为空格时,不会直接被判断为跳出循环
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 (1 == IsFull(board, ROW, COL))
{
return 'Q';
}
//若以上条件都不满足,则说明结果还没出,游戏继续
return 'C';
}
判断是否平局时,依然需要自定义一个函数IsFull。
IsFull函数的实现代码如下:
//返回1表示棋盘满了
//返回0表示棋盘没满
int IsFull(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;
}//end of if
}//end of for
}//end of for
return 1;
}
上述所有代码写完,说明这个游戏就做完啦,运行结果如下图:
如果你能看到这里,那么恭喜你已经可以写出自己的三子棋了,一定要多写几遍,直到能把这个代码完整独立的写下来,对自己的逻辑能力的锻炼真的很大。
完整代码如下:
//10_30.c
#include "game.1.h"
void menu()
{
printf("*****************************************\\n");
printf("***** 1 .play 0. exit *****\\n");
printf("*****************************************\\n");
}
//游戏的整个算法实现
void game()
{
char ret = 0;
//数组 - 存放走出的棋盘信息
char board[ROW][COL] = 以上是关于C语言实现三子棋的主要内容,如果未能解决你的问题,请参考以下文章