三子棋,又叫九宫棋、井字棋等。是一种在3*3格子上进行的连珠游戏,和五子棋类似,由于棋盘一般不画边框,格线排成井字故得名。游戏需要的工具仅为纸和笔,然后由分别代表O和X的两个游戏者轮流在格子里留下标记(一般来说先手者为X),任意三个标记形成一条直线,则为获胜。但是,有很多时候会出现和棋的情况。
游戏玩完一把不过瘾,可以继续玩。选择do…while循环结构,无论是否玩游戏,都会进入游戏菜单。
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请输入:>");
scanf("%d", &input);
switch (input)
{
case 2:
printf("1.玩家落子为'x',电脑落子为'o'。\n2.任意三个标记形成一条直线(横线,竖线,对角线),则为获胜。\n\
3.棋盘满了,仍未出现三个标记形成一条直线的情况,则为平局。\n");
break;
case 1:
game();
break;
case 0:
printf("退出游戏!\n");
break;
default:
printf("输入错误,请重新输入!\n");
break;
}
}while (input);
return 0;
}
打印菜单,调用menu函数。游戏菜单代码如下:
void menu()
{
printf("*************三子棋游戏*************\n");
printf("********* 2.游戏规则 *********\n");
printf("********* 1.开始游戏 *********\n");
printf("********* 0.退出游戏 *********\n");
printf("***********************************\n");
}
运行结果如下:
根据菜单上的数字,选择进入相应的程序去执行。
选择2,显示游戏规则。
选择1,开始游戏。
选择0,退出游戏。
选择其他数字,提示输入错误。
为了实现三子棋游戏,封装成函数game,game函数中搭建了实现游戏的相关逻辑。
void game()
{
//存储数据
char board[ROW][COL];
char ret = 0;
//初始化棋盘
InitBoard(board, ROW, COL);
//打印棋盘
DisplayBoard(board, ROW, COL);
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 == 'x')
{
printf("恭喜你,玩家赢了!\n");
}
if (ret == 'o')
{
printf("很遗憾,电脑赢了!\n");
}
if (ret == 'Q')
{
printf("打成平局!\n");
}
DisplayBoard(board, ROW, COL);
}
玩家或者电脑在落子后能够记录下来,如果存放数据,用一个3x3的二维数组。没有落子,显示空格。玩家落子,显示x。电脑落子,显示o。存放的都是字符,选择创建一个char类型的数组。
直接创建一个3x3的二维数组,局限性太大。如果有天我们想玩5x5或者10x10的多子棋,我们需要把程序中所有3x3数组的地方都修改了,太麻烦了。我们在game.h中对数组的大小进行符号定义,如果要修改只需要在头文件中进行修改即可。此时我们定义的是3x3的棋盘。
//符号的定义,N*N的棋盘
#define ROW 3
#define COL 3
下面具体介绍各个函数的功能。
初始化棋盘,调用InitBoard函数。初始化为空格,也就是数组的每个元素都是空格,遍历数组,每个元素赋值为空格。
void InitBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
board[i][j] = ' ';
}
}
}
初始化棋盘后,我们想把棋盘打印出来看看。打印棋盘,调用DisplayBoard函数。打印棋盘的本质就是在打印数组的内容,把数组每个元素打印出来。
void DisplayBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
printf(" --- --- ---\n");
for (i = 0; i < row; i++)
{
printf("| %c | %c | %c |\n",board[i][0],board[i][1],board[i][2]);
if (i < row - 1)
{
printf("|---|---|---|\n");
}
}
printf(" --- --- ---\n");
}
运行结果如下:
如果没有初始化棋盘,棋盘里放的都是随机值,也就是数组没有初始化,里面的元素都是随机值,打印出来的是下面的结果:
由此可见初始化棋盘很有必要。
如果我们想要打印出5x5的棋盘,打印出来是这个样子。
这好像跟我们需要的不太一样,原因是我们上面写的打印棋盘的函数DisplayBoard不够通用,只适用于3行3列的情况,可以修改一下代码。
//打印棋盘,将棋盘打印成N*N方格
void DisplayBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < col; i++)
{
printf(" ---");
}
printf("\n");
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf("|");
printf(" %c ", board[i][j]);
}
printf("|");
printf("\n");
if (i < row - 1)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf("|");
printf("---");
}
printf("|");
printf("\n");
}
}
for (i = 0; i < col; i++)
{
printf(" ---");
}
printf("\n");
}
打印出来的结果如下:
3x3的棋盘我们可以直接看出要落子位置的坐标,如果是10x10的呢?有些地方的坐标还需要我们去数,这时候我们可以考虑给棋盘加上坐标,这样一眼就可以看出要落子的位置坐标,对打印棋盘函数DisplayBoard再进行修改。
void DisplayBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
printf(" ");
for (i = 0; i < col; i++)
{
printf("%4d", i + 1);
}
printf("\n");
printf(" ");
for (i = 0; i < col; i++)
{
printf(" ---");
}
printf("\n");
for (i = 0; i < row; i++)
{
int j = 0;
printf("%2d", i + 1);
for (j = 0; j < col; j++)
{
printf("|");
printf(" %c ", board[i][j]);
}
printf("|");
printf("\n");
if (i < row - 1)
{
int j = 0;
printf(" ");
for (j = 0; j < col; j++)
{
printf("|");
printf("---");
}
printf("|");
printf("\n");
}
}
printf(" ");
for (i = 0; i < col; i++)
{
printf(" ---");
}
printf("\n");
}
运行结果如下:
我们以3x3的棋盘为例解释一下上面的代码。分为三个部分。
第一部分,"---- ---- ---- " 的打印,即为红色框起来的部分,上下各打印一次,有几列就打印几次"----"。
第二部分,"| | | |" 的打印,即为黄色框起来的部分,有几行就打印几组,有几列就打印几个"| ",最后一个黄色框起来的部分 " | " 单独打印。
第三部分,"|----|----|----|" 的打印,即为蓝色框起来的部分,打印的组数比行数少1,有几列就打印几个"|----",最后一个蓝色框起来的部分”|“单独打印。
玩家下棋,调用PlayerMove函数。玩家要先输入落子的坐标,然后对坐标的合法性进行判断,玩家不一定是程序员,他们认为第一行第一列的坐标为(1,1),实际在数组中该点的坐标为(0,0)。若坐标是非法的,提示重新输入。最后,判断坐标是否被占用,如果坐标的位置是空格,说明没被占用,可以落子在此处,落子成功后跳出循环,如果坐标被占用,输出提示信息。玩家下完棋后再打印一下棋盘。
//玩家下棋,落子为x
//玩家要先输入坐标,然后判断坐标的合法性,最后再判断坐标是否被占用
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] = 'x';
break;
}
else
{
printf("坐标已被占用,请重新输入!\n");
}
}
else
{
printf("输入非法坐标,请重新输入!\n");
}
}
}
运行结果如下:
这时再输入(1,1),会显示如下结果:
此时是3x3的棋盘,如果输入(4,4),会显示如下结果:
电脑下棋,调用ComputerMove函数。电脑要随机生成落子的坐标,用到rand函数。我们可以看到rand函数的功能是生成伪随机数,返回值是int类型,需要包含的头文件是
srand函数用来设置随机起点,参数是随机数生成种子,为了使生成的随机数每次都不一样且更随机一些,选择用时间戳作为随机数生成种子,时间是一直在变化的,time函数的参数可以是NULL。
电脑落子之前也要检查随机产生的坐标位置是否被占用,如果没被占用,电脑落子,跳出循环。如果被占用,重新再生成随机坐标。电脑落子后,也要将棋盘打印出来。
//电脑下棋,落子为o
//电脑随机产生一个坐标,判断该坐标是否被占用
void ComputerMove(char board[ROW][COL], int row, int col)
{
printf("电脑走:>\n");
while (1)
{
int x = rand() % row;//随机产生的坐标值在0~row-1之间
int y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = 'o';
break;
}
}
}
rand函数返回一个介于0到32767之间的伪随机整数,3x3的棋盘,我们需要产生的随机数为0,1,2,所以我们用产生的随机数模3。rand() % row会产生一个0~row-1之间的随机值。
判断输赢,调用IsWin函数,IsWin函数可以用来判断游戏的状态。在游戏进行的过程中一共有四种状态:玩家赢了,返回x。电脑赢了,返回o。平局,返回Q。游戏继续,返回C。
//判断棋盘是否满了,满了返回1,没满返回0
int IsFull(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (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++)
{
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[0][2] == board[1][1] && board[1][1] == board[2][0] && board[1][1] != ' ')
{
return board[1][1];
}
int ret = 0;
ret = IsFull(board, ROW, COL);
if (ret == 1)
{
return 'Q';
}
return 'C';
}
运行结果如下:
我们不难发现,上面这段判断输赢的代码有一定的局限性,只能判断3行3列的情况。如果棋盘是5x5或者10x10的呢?这里对判断输赢的函数进行改进,使其通用性更高。
//判断棋盘是否满了,满了返回1,没满返回0
int IsFull(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (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++)
{
int count1 = 0;
int j = 0;
for (j = 0; j < col - 1; j++)
{
if (board[i][j] != ' ' && board[i][j] == board[i][j + 1])
{
count1++;
}
if (count1 == ROW - 1)
{
return board[i][j];
}
}
}
//竖列
for (j = 0; j < col; j++)
{
int count2 = 0;
for (i = 0; i < row - 1; i++)
{
if (board[i][j] != ' ' && board[i][j] == board[i + 1][j])
{
count2++;
}
if (count2 == ROW - 1)
{
return board[i][j];
}
}
}
//对角线
int count3 = 0;
for (i = 0; i < row-1; i++)
{
if (board[i][i] != ' ' && board[i][i] == board[i + 1][i + 1])
{
count3++;
}
if (count3 == ROW - 1)
{
return board[i][i];
}
}
int count4 = 0;
for (i = 0; i < row-1 ; i++)
{
if (board[i][row-1-i] != ' ' && board[i][row-1-i] == board[i+1][row-2-i])
{
count4 ++;
}
if (count4 == ROW - 1)
{
return board[i][row-1-i];
}
}
int ret = 0;
ret = IsFull(board, ROW, COL);
if (ret == 1)
{
return 'Q';
}
return 'C';
}
#include"game_chess.h"
void menu()
{
printf("*************三子棋游戏*************\n");
printf("********* 2.游戏规则 *********\n");
printf("********* 1.开始游戏 *********\n");
printf("********* 0.退出游戏 *********\n");
printf("************************************\n");
}
void game()
{
//存储数据
char board[ROW][COL];
char ret = 0;
//初始化棋盘
InitBoard(board, ROW, COL);
//打印棋盘
DisplayBoard(board, ROW, COL);
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 == 'x')
{
printf("恭喜你,玩家赢了!\n");
}
if (ret == 'o')
{
printf("很遗憾,电脑赢了!\n");
}
if (ret == 'Q')
{
printf("打成平局!\n");
}
DisplayBoard(board, ROW, COL);
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请输入:>");
scanf("%d", &input);
switch (input)
{
case 2:
printf("1.玩家落子为'x',电脑落子为'o'。\n2.任意三个标记形成一条直线(横线,竖线,对角线),则为获胜。\n\
3.棋盘满了,仍未出现三个标记形成一条直线的情况,则为平局。\n");
break;
case 1:
game();
break;
case 0:
printf("退出游戏!\n");
break;
default:
printf("输入错误,请重新输入!\n");
break;
}
}while (input);
return 0;
}
#include"game_chess.h"
//初始化棋盘,将棋盘中所有要落子的地方初始化为空格
void InitBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
board[i][j] = ' ';
}
}
}
//打印棋盘,将棋盘打印成N*N方格
//void DisplayBoard(char board[ROW][COL], int row, int col)
//{
// int i = 0;
// printf(" --- --- ---\n");
// for (i = 0; i < row; i++)
// {
// printf("| %c | %c | %c |\n",board[i][0],board[i][1],board[i][2]);
// if (i < row - 1)
// {
// printf("|---|---|---|\n");
// }
// }
// printf(" --- --- ---\n");
//}
void DisplayBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
printf(" ");
for (i = 0; i < col; i++)
{
printf("%4d", i + 1);
}
printf("\n");
printf(" ");
for (i = 0; i < col; i++)
{
printf(" ---");
}
printf("\n");
for (i = 0; i < row; i++)
{
int j = 0;
printf("%2d", i + 1);
for (j = 0; j < col; j++)
{
printf("|");
printf(" %c ", board[i][j]);
}
printf("|");
printf("\n");
if (i < row - 1)
{
int j = 0;
printf(" ");
for (j = 0; j < col; j++)
{
printf("|");
printf("---");
}
printf("|");
printf("\n");
}
}
printf(" ");
for (i = 0; i < col; i++)
{
printf(" ---");
}
printf("\n");
}
//玩家下棋,落子为x
//玩家要先输入坐标,然后判断坐标的合法性,最后再判断坐标是否被占用
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] = 'x';
break;
}
else
{
printf("坐标已被占用,请重新输入!\n");
}
}
else
{
printf("输入非法坐标,请重新输入!\n");
}
}
}
//电脑下棋,落子为o
//电脑随机产生一个坐标,判断该坐标是否被占用
void ComputerMove(char board[ROW][COL], int row, int col)
{
printf("电脑走:>\n");
while (1)
{
int x = rand() % row;//随机产生的坐标值在0~row-1之间
int y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = 'o';
break;
}
}
}
//判断棋盘是否满了,满了返回1,没满返回0
int IsFull(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (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++)
//{
// 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[0][2] == board[1][1] && board[1][1] == board[2][0] && board[1][1] != ' ')
//{
// return board[1][1];
//}
//判断多子棋
//横行
for (i = 0; i < row; i++)
{
int count1 = 0;
int j = 0;
for (j = 0; j < col - 1; j++)
{
if (board[i][j] != ' ' && board[i][j] == board[i][j + 1])
{
count1++;
}
if (count1 == ROW - 1)
{
return board[i][j];
}
}
}
//竖列
for (j = 0; j < col; j++)
{
int count2 = 0;
for (i = 0; i < row - 1; i++)
{
if (board[i][j] != ' ' && board[i][j] == board[i + 1][j])
{
count2++;
}
if (count2 == ROW - 1)
{
return board[i][j];
}
}
}
int count3 = 0;
for (i = 0; i < row-1; i++)
{
if (board[i][i] != ' ' && board[i][i] == board[i + 1][i + 1])
{
count3++;
}
if (count3 == ROW - 1)
{
return board[i][i];
}
}
int count4 = 0;
for (i = 0; i < row-1 ; i++)
{
if (board[i][row-1-i] != ' ' && board[i][row-1-i] == board[i+1][row-2-i])
{
count4 ++;
}
if (count4 == ROW - 1)
{
return board[i][row-1-i];
}
}
int ret = 0;
ret = IsFull(board, ROW, COL);
if (ret == 1)
{
return 'Q';
}
return 'C';
}
#include
#include
#include
//符号的定义,N*N的棋盘
#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);
//返回‘x’--玩家赢
//返回 'o' --电脑赢
//返回‘Q’--平局
//返回 'C' --游戏继续
char IsWin(char board[ROW][COL], int row, int col);
本文在三子棋的基础上可以扩展到N子棋,但也只实现了最基本的功能。还有很多可以改进的地方,比如,我们可以对电脑下棋的那部分代码进行优化,使其更智能。现在的代码是电脑随机产生一个坐标,改进后,当玩家有两个子连成一条直线时,电脑可以做出判断堵住玩家的路,玩家不会这么轻易地赢棋。文中若有错误,恳请各位及时指出,感谢。