项目要求:

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

思路:

我们首先在项目中创建三个文件: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.c 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拆开打印,再将分割线拆开打印即可
即把打印(空格,数据,空格),然后打印一个’|’这个操作看成一组数据,把打印—和打印|这个操作看成一组数据,分别打印。 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与纵坐标y printf("玩家动:\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,数组是以下标形式访问的,所以应减1 break; } 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.c void 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]; } } //对角线相等的情况1 if (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子棋,这代码根本就行不通,所以我们需要根据上面的代码,进行改进优化(等我技术变好了再回来优化
//平局的情况:若所有元素全部被填充完了,全部填充完了也就是没有一个空格了,返回一个Q int ret = IsFull(board, row,
col);//如果棋盘满了,返回1,不满返回0 if (ret == 1) { return 'Q'; } //若非以上几种情况,则继续游戏,返回一个C
return '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与纵坐标y printf("玩家动:\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,数组是以下标形式访问的,所以应减1 break; } 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配合时间戳使用,只定义一次才可以保证随机度最大,于是我们放到主函数里面定义srand
int 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]; } } //对角线相等的情况1 if (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子棋,这代码根本就行不通,所以我们需要根据上面的代码,进行改进优化(等我技术变好了再回来优化
//平局的情况:若所有元素全部被填充完了,全部填充完了也就是没有一个空格了,返回一个Q int ret = IsFull(board, row,
col);//如果棋盘满了,返回1,不满返回0 if (ret == 1) { return 'Q'; } //若非以上几种情况,则继续游戏,返回一个C
return 'C'; }

技术
下载桌面版
GitHub
Gitee
SourceForge
百度网盘(提取码:draw)
云服务器优惠
华为云优惠券
腾讯云优惠券
阿里云优惠券
Vultr优惠券
站点信息
问题反馈
邮箱:[email protected]
吐槽一下
QQ群:766591547
关注微信