C语言实现:9400字带你拿下三子棋

2023-10-19 15:50
文章标签 语言 实现 三子 拿下 9400

本文主要是介绍C语言实现:9400字带你拿下三子棋,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

项目要求:

三子棋的预览界面图如下:玩家和电脑下棋,每行相同或每列或对角线符号相同,则获胜。

思路:

我们首先在项目中创建三个文件:test.c(测试游戏逻辑) game.h(声明函数以及定义符号等) game.c(定义函数)
在test.c中,我们先把简单的菜单函数界面给弄出来,上去就干,直接来一个do while循环。然后分析,游戏要反反复复的玩,利用巧妙的设计,把输入值input作为while的判断部分,如果输入值为0则跳出循环,如果为其他值则继续执行循环。用switch语句进行选择。

test.c

void menu()	//菜单
{printf("****************************\n");printf("*******  1.开始游戏  *******\n");printf("*******  0.结束游戏  *******\n");printf("****************************\n");
}
void game()//游戏函数
{}
int main()
{int input;do{menu();//调用菜单函数scanf("%d",&input);switch (input){case 1:printf("请输入坐标排x 列y\n");game();//游戏函数break;case 0:printf("结束游戏\n");break;default:printf("输入错误,请重新输入\n");}} while (input);//如果输入0就为假就跳出循环,结束程序,如果输入1就继续游戏return 0;

然后我们运行,发现报错,没有引用头文件。为了方便,我们就把头文件放到game.h里面,然后在test.c里面引用它
game.h:

#include<stdio.h>

test.c:

#include "game.h" 自定义的头文件的调用使用””包含

然后我们来到game()函数部分,因为我们要输入的是两个坐标值,所以要定义一个二维数组来储存:

char board[ROW][COL];

我们可能要做的不止是三子棋,还可能是n子棋,所以不能把代码写死,所以用定义常量来实现一改全改
在game.h里面定义ROW和COL为3:

define ROW 3;
define COL 3;

有了数据存储以后,我们就可以格式化棋盘了,即将二维数组的数据都用空格来填充,以实现美观的效果,否则打印出来的效果会是乱码(随机值)。在game函数中把行,列,数组传过去:写个初始化函数

InitBoard(board,ROW,COL);

然后在game.h文件中声明函数

//初始化棋盘
void InitBoard(char board[ROW][COL],int row,int col);

声明完函数以后,就要去game.c中定义函数,将每个数据都存入空格,嵌套for循环就可以实现:

void InitBoard(char board[ROW][COL], int row, int col)//格式化棋盘
{
int i, j;
for (i = 0; i < row; i++)//数组是用下标来访问的,所以行的下标为0~row-1
{
for (j = 0; j < col; j++)//列同理
{
board[i][j] = ' ';//存入空格
}
}

格式化棋盘以后,我们要将棋盘打印出来,在game函数中调用打印函数

DisplayBoard(board, ROW, COL);

然后在game.h中声明它

//打印棋盘
void DisplayBoard(char board[ROW][COL], int row, int col);

然后在game.c中定义这个打印函数:
分析我们要的棋盘效果

    |  |
—|—|—
    |  |
—|—|—
    |  |
对于第一行,将打印(空格,数据,空格),然后打印一个’|’
打印完一行以后,然后换行,打印—|—|—

game.cvoid DisplayBoard(char board[ROW][COL], int row, int col)//打印棋盘
{int i,j;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");//打印分割线} 
这种方法虽然很直观,但是我们却把程序的行和列都写死了,如果想变成10子棋,列始终都只有三行,所以这样不妥,必须换一种写法。
我们只需要将%c拆开打印,再将分割线拆开打印即可
即把打印(空格,数据,空格),然后打印一个’|’这个操作看成一组数据,把打印—和打印|这个操作看成一组数据,分别打印。
void DisplayBoard(char board[ROW][COL], int row, int col)//打印棋盘
{int i,j;for (i = 0; i < row; i++){for (j = 0; j < col; j++){printf(" %c ",board[i][j]);//打印空格if(j<col-1)//最后一列是不需要打印|的printf("|");}printf("\n");//打印完以后换行,进入分割线部分if (i < row - 1)//我们将原来的打印内容拆成两部分,最后一行是不需要打印分割线的{for (j = 0; j < col; j++){printf("---");if(j<col-1)//最后一列是不需要打印|的printf("|");}printf("\n");}}
}

我们将ROW,COL改成10,10,运行结果如下:

这样就完美了属于是,既然棋盘打印出来了,那么接下来就是玩家输入一个坐标然后行动了
同时我们要实现的是玩家要能一直动,直到分出胜负,所以在game函数内给出一个死循环while(1),在这里面调用玩家行动函数PlayerMove(board, ROW, COL);玩家每次行动结束就打印一次棋盘,就要在后面加上打印函数DisplayBoard(board, ROW, COL);
然后在game.h中声明玩家行动函数
//玩家下棋

void PlayerMove(char board[ROW][COL], int row, int col);

然后在game.c中定义玩家行动函数

void PlayerMove(char board[ROW][COL], int row, int col)//玩家输入坐标,用*填充到数组内
{int x, y;//横坐标x与纵坐标yprintf("玩家动:\n");while (1){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] = '*';//若玩家输入3 3,则对应的下标应为2 2,数组是以下标形式访问的,所以应减1break;}else{printf("坐标被占用\n");}}else{printf("输入值不合法,请重新输入\n");}}
}
玩家动完电脑动,于是在game函数中调用电脑行动函数ComputerMove(board, ROW, COL);电脑动完再打印棋盘,调用打印函数DisplayBoard(board, ROW, COL);
然后在game.h中声明这个函数
//电脑下棋
void ComputerMove(char board[ROW][COL], int row, int col);
在game.c中定义这个函数
对于电脑移动,我们可以让它随机下在一个位置,直接给出限制范围,所以不需要判断电脑的值是否在范围内,但我们要判断值是否被占用了。然后这个随机的坐标,可以由rand函数来给出,rand函数我们在猜数字那个项目有介绍过,给出随机值。srand函数定义rand的起始部分,参数类型为无符号整型,它的函数头文件stdlib.h。以时间戳函数time()的返回值作为参数可以增大随机度,它的函数头文件time.h。srand配合时间戳使用,只定义一次才可以保证随机度最大,于是我们放到主函数里面定义srand。在主函数部分加上srand((unsigned int)time(NULL));

game.cvoid ComputerMove(char board[ROW][COL], int row, int col)//电脑移动
{printf("电脑动\n");while (1){int x = rand()%row;int y = rand()%col;//取余row得到的就是row-1,如果我们的ROW为3,那么得到的就刚好是下标的范围0~2了if (board[x][y] == ' ')//如果没被占用{board[x][y] = '#';break;}}
}
 

然后就只需要判断输赢了,需要讨论每个元素的位置情况,同样需要把二维数组传过去,对函数的返回值进行判断。判断一共会有四种情况:#电脑赢,*玩家赢,Q平局,C继续游戏
于是我们在game函数调用判断函数IsWin(board, ROW, COL);定义一个char类型的ret用于接受判断函数的返回值,在game函数里面进行判断。
在game.h声明判断函数:
//判断游戏是否有输赢

char IsWin(char board[ROW][COL], int row, int col);

在game.c中定义函数:

int IsFull(char board[ROW][COL], int row, int col)//判断元素是否填满:如果棋盘满了,返回1,不满返回0
{int i, j;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,j;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][0];}}for (i = 0; i < col; i++){if (board[0][i] == board[1][i]&&board[1][i]==board[2][i]&&board[0][i] != ' ')//列相等的情况{return board[0][i];}}//对角线相等的情况1if (board[0][0] == board[1][1]&&board[1][1] == board[2][2]&&board[0][0]!=' '){return board[0][0];}if (board[2][0] == board[1][1]&&board[1][1] == board[0][2]&&board[2][0]!=' '){return board[2][0];}//这样虽然可以完美运行三子棋,但其实这个代码写的很拉胯,因为写死了,而我们如果要实现n子棋,这代码根本就行不通,所以我们需要根据上面的代码,进行改进优化(等我技术变好了再回来优化//平局的情况:若所有元素全部被填充完了,全部填充完了也就是没有一个空格了,返回一个Qint ret = IsFull(board, row, col);//如果棋盘满了,返回1,不满返回0if (ret == 1){return 'Q';}//若非以上几种情况,则继续游戏,返回一个Creturn 'C';
}

在玩家跟电脑每次动完以后都执行一次判断

ret = IsWin(board, ROW, COL);if (ret != 'C')break;

跳出循环以后,说明结果已经出来了,就要进行讨论了:

if (ret == '*'){printf("玩家赢了\n");}else if(ret=='#'){printf("电脑赢了\n");}else{printf("平局\n");}

程序基本思路大致如此,然后我们发现判断输赢部分依然是写死了,无法实现10*10,而且电脑是随机下棋,电脑很难赢,这样的游戏玩起来就没意思了。等我技术变好了再回来改进。
全部源码如下:

test.c

//测试游戏的逻辑
#include "game.h"void menu()	//菜单
{printf("****************************\n");printf("*******  1.开始游戏  *******\n");printf("*******  0.结束游戏  *******\n");printf("****************************\n");
}void game()//游戏函数
{//用二维数组存储数据char board[ROW][COL];//我们可能要做的不止是三子棋,还可能是n子棋,所以不能把代码写死,所以用定义常量来实现一改全改char ret;//返回值储存器,接受游戏状态,判断输赢//格式化棋盘,即将每个数组的数据都用空格来填充,所以要知道它有多少行,多少列,然后把数组传过去InitBoard(board,ROW,COL);//打印棋盘DisplayBoard(board, ROW, COL);while (1){//玩家下棋:玩家输入坐标才可以动,然后要判断坐标是否合法,于是我们需要把二维数组行列传进去PlayerMove(board, ROW, COL);DisplayBoard(board, ROW, COL);//判断输赢:需要讨论每个元素的位置情况,同样需要把二维数组传过去,对函数的返回值进行判断//判断一共会有四种情况:#电脑赢,*玩家赢,Q平局,C继续游戏ret =IsWin(board, ROW, COL);if (ret != 'C')//如果返回值不是C,就跳出循环break;//电脑移动:电脑不需要输入坐标,但我们需要将二维数组的行列传进去对它进行限定赋值ComputerMove(board, ROW, COL);DisplayBoard(board, ROW, COL);system("cls");//移动完清空屏幕DisplayBoard(board, ROW, COL);printf("请输入排x 列y\n");//判断电脑是否赢得游戏ret = IsWin(board, ROW, COL);if (ret != 'C')break;}//跳出循环,说明ret!=C了if (ret == '*'){printf("玩家赢了\n");}else if(ret=='#'){printf("电脑赢了\n");}else{printf("平局\n");}
}
int main()
{int input;srand((unsigned int)time(NULL));do{menu();scanf("%d",&input);switch (input){case 1:printf("请输入坐标排x 列y\n");game();break;case 0:printf("结束游戏\n");break;default:printf("输入错误,请重新输入\n");}} while (input);//如果输入0就为假就跳出循环,结束程序,如果输入1就继续游戏return 0;
}

game.h

#define _CRT_SECURE_NO_WARNINGS 1
//头文件的包含
#include <stdio.h>
#include<stdlib.h>
#include<time.h>//符号的定义
#define ROW 10
#define COL 10//函数的声明
//二维数组的传参需要说清楚它有多少列,行可以省略//初始化棋盘
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);//判断游戏是否输赢
char IsWin(char board[ROW][COL], int row, int col);

game.c

#include "game.h"void InitBoard(char board[ROW][COL], int row, int col)//格式化棋盘
{int i, j;for (i = 0; i < row; i++)//数组是用下标来访问的,所以行的下标为0~row-1{for (j = 0; j < col; j++)//列同理{board[i][j] = ' ';}}
}//这是我们要实现的棋盘初始化效果//printf("    |   |   \n")
//printf("---|---|---\n");//printf("   |   |   \n");
//printf("---|---|---\n");//printf("   |   |   \n");
//分析这个初始化效果,以此打印棋盘:对于第一行是打印(空格,数据,空格),然后打印一个'|',同时我们发现,第三个数据的'|'是不需要打印的,于是我们需要将数据和|分开打印
//打印完这个以后,然后换行,打印---带上一个|,把---和|看成一个数据,我们发现,第三个数据的'|'也是不需要打印的
void DisplayBoard(char board[ROW][COL], int row, int col)//打印棋盘
{int i,j;/*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");} 这种方法虽然直观,但是我们却发现,写死了,如果想变成10子棋,列始终都只有三行,所以不妥,换一种写法。*///我们只需要把%c拆开打印即可,那么就可以定义行和列,拆开打印每一个数据。for (i = 0; i < row; i++){for (j = 0; j < col; j++){printf(" %c ",board[i][j]);//打印空格if(j<col-1)//最后一列是不需要打印|的printf("|");}printf("\n");//打印完以后换行,进入分割线部分if (i < row - 1)//我们将原来的打印内容拆成两部分,最后一行是不需要打印分割线的{for (j = 0; j < col; j++){printf("---");if(j<col-1)//最后一列是不需要打印|的printf("|");}printf("\n");}}
}void PlayerMove(char board[ROW][COL], int row, int col)//玩家输入坐标,用*填充到数组内
{int x, y;//横坐标x与纵坐标yprintf("玩家动:\n");while (1){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] = '*';//若玩家输入3 3,则对应的下标应为2 2,数组是以下标形式访问的,所以应减1break;}else{printf("坐标被占用\n");}}else{printf("输入值不合法,请重新输入\n");}}
}void ComputerMove(char board[ROW][COL], int row, int col)
{printf("电脑动\n");//对于电脑,我们可以让它随机下在一个位置,我们可以直接给出限制范围,所以不需要判断电脑的值是否在范围内,但我们要判断值是否被占用了while (1){int x = rand()%row;//rand函数我们在猜数字那个项目有介绍过,给出随机值。srand函数的参数类型为无符号整型,它的作用是定义rand的起始部分,函数头文件stdlib.h。以时间戳函数time()的返回值作为参数可以增大随机度,函数头文件time.h。srand配合时间戳使用,只定义一次才可以保证随机度最大,于是我们放到主函数里面定义srandint y = rand()%col;//取余row得到的就是row-1,如果我们的ROW为3,那么得到的就刚好是下标的范围0~2了if (board[x][y] == ' '){board[x][y] = '#';break;}}
}int IsFull(char board[ROW][COL], int row, int col)//判断元素是否填满:如果棋盘满了,返回1,不满返回0
{int i, j;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,j;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][0];}}for (i = 0; i < col; i++){if (board[0][i] == board[1][i]&&board[1][i]==board[2][i]&&board[0][i] != ' ')//列相等的情况{return board[0][i];}}//对角线相等的情况1if (board[0][0] == board[1][1]&&board[1][1] == board[2][2]&&board[0][0]!=' '){return board[0][0];}if (board[2][0] == board[1][1]&&board[1][1] == board[0][2]&&board[2][0]!=' '){return board[2][0];}//这样虽然可以完美运行三子棋,但其实这个代码写的很拉胯,因为写死了,而我们如果要实现n子棋,这代码根本就行不通,所以我们需要根据上面的代码,进行改进优化(等我技术变好了再回来优化//平局的情况:若所有元素全部被填充完了,全部填充完了也就是没有一个空格了,返回一个Qint ret = IsFull(board, row, col);//如果棋盘满了,返回1,不满返回0if (ret == 1){return 'Q';}//若非以上几种情况,则继续游戏,返回一个Creturn 'C';
}

这篇关于C语言实现:9400字带你拿下三子棋的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/240806

相关文章

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

科研绘图系列:R语言扩展物种堆积图(Extended Stacked Barplot)

介绍 R语言的扩展物种堆积图是一种数据可视化工具,它不仅展示了物种的堆积结果,还整合了不同样本分组之间的差异性分析结果。这种图形表示方法能够直观地比较不同物种在各个分组中的显著性差异,为研究者提供了一种有效的数据解读方式。 加载R包 knitr::opts_chunk$set(warning = F, message = F)library(tidyverse)library(phyl

透彻!驯服大型语言模型(LLMs)的五种方法,及具体方法选择思路

引言 随着时间的发展,大型语言模型不再停留在演示阶段而是逐步面向生产系统的应用,随着人们期望的不断增加,目标也发生了巨大的变化。在短短的几个月的时间里,人们对大模型的认识已经从对其zero-shot能力感到惊讶,转变为考虑改进模型质量、提高模型可用性。 「大语言模型(LLMs)其实就是利用高容量的模型架构(例如Transformer)对海量的、多种多样的数据分布进行建模得到,它包含了大量的先验

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略 1. 特权模式限制2. 宿主机资源隔离3. 用户和组管理4. 权限提升控制5. SELinux配置 💖The Begin💖点点关注,收藏不迷路💖 Kubernetes的PodSecurityPolicy(PSP)是一个关键的安全特性,它在Pod创建之前实施安全策略,确保P

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、