失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > C语言实现三子棋游戏—可扩展到任意N子棋

C语言实现三子棋游戏—可扩展到任意N子棋

时间:2021-10-30 13:43:14

相关推荐

C语言实现三子棋游戏—可扩展到任意N子棋

C语言实现三子棋

游戏介绍游戏编程思路游戏代码详解主函数游戏菜单函数游戏逻辑函数初始化棋盘打印棋盘玩家下棋电脑下棋判断输赢 完整代码test.cgame_chess.cgame_chess.h 总结

游戏介绍

三子棋,又叫九宫棋、井字棋等。是一种在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类型,需要包含的头文件是<stdlib.h>,rand函数返回一个介于0到32767之间的伪随机整数。在调用rand之前,要使用srand函数为伪随机数生成器播种。

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,没满返回0int 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,没满返回0int 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';}

我们把棋盘设置成5x5的,运行结果如下:

完整代码

test.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;}

game_chess.c

#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,没满返回0int 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';}

game_chess.h

#include<stdio.h>#include<stdlib.h>#include<time.h>//符号的定义,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子棋,但也只实现了最基本的功能。还有很多可以改进的地方,比如,我们可以对电脑下棋的那部分代码进行优化,使其更智能。现在的代码是电脑随机产生一个坐标,改进后,当玩家有两个子连成一条直线时,电脑可以做出判断堵住玩家的路,玩家不会这么轻易地赢棋。文中若有错误,恳请各位及时指出,感谢。

如果觉得《C语言实现三子棋游戏—可扩展到任意N子棋》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。