1.分模块实现
应用多文件模块化来处理,以适应日后工作
2.三子棋的实现
先定义上这两个模块所需要的源文件和头文件
创建出test.c和game.c文件以及game.h文件
2.1打印菜单
一上来给别人一点提示,打印下菜单吧,即写个菜单函数。
2.2选择
- 既然打印出菜单,就意味着要给别人输入的机会
int input = 0;
menu();
scanf("%d", &input);
-
输入之后,选择1玩游戏,选择0退出游戏。两种情况,分支语句swith走起
-
我们说玩了一把还要玩,那得继续输入,说明这是个循环的过程。
int main()
{
int input = 0;//input只能定义在do...while外面,否则判断部分无法使用
do
{
menu();
scanf("%d", &input);
switch (input)
{
case 1:
printf("玩游戏\n");
break;
case 0:
printf("退出\n");
break;
default:
printf("输入错误\n");
break;
}
} while (input);
return 0;
}
选择1,继续游戏,选择0退出,既不是1也不是0,重新输入。那判断部分正好写个input。此时可以测试一下程序的正确性
注意啊,虽然这里只是简单的输出了一句话,还欠一个game函数。但正确的写法就是一点点写,中间不断的测试代码的正确性,而不是一下子写完,报一堆错误。
2.3game的实现
2.3.1初始化棋盘和打印棋盘
- 下棋总得有个棋盘吧
- 没有下棋之前,棋盘里面放的是空格,那在创建的时候初始化棋盘为空格?那就必须写9个空格,还挺麻烦,干脆写个初始化棋盘的函数InintBoard。
我们说初始化棋盘和后面下棋这些函数都是三字棋这个游戏的一个模块,因此我们应该把它们统统放在一个函数里。
void game()
{
char board[3][3] = { 0 };
//初始化棋盘
InitBoard(board, 3, 3);//要操作里面的值,所以要传行和列
}
- game函数是整个游戏的整合,游戏的具体实现和声明我们得写在game.c和game.h里头。
- 把棋盘全部初始化为空格,无非就是把这个3*3的二维数组遍历一遍,全部改成空格
注意在运行前,test.c函数要引game.h头文件,因为声明和定义不再test.c里。如果不声明,找不到这个函数。
引自己创建的头文件用双引号 - 执行后,什么也没有发生,没有我们想要看到的棋盘啊。那是因为我们只是对这个数组进行了修改,但没有输出给自己看。所以还要再来一个打印棋盘的函数。
void game()
{
char board[3][3] = { 0 };
//初始化棋盘
InitBoard(board, 3, 3);
//打印棋盘
DisplayBoard(board, 3, 3);
}
为了能看见我先把棋盘初始化为x
写到这里有些人可能会报错,是因为没有引printf的头文件。难道这个时候又要在game.c里引一个头文件?
我们说现在要在game.c里引头文件,又要在test.c引头文件,那不挺啰嗦的吗,干脆直接在game.h引头文件,game.c和test.c都引一下game.h就可以了。
- 写到这里,又发现一个问题。那就是有一天,我不想玩3*3的棋盘了,难道重新再写吗?因此我们说,3*3就写死了。可以用#define定义的标识符常量来解决
此时把棋盘初始化,传参等等全部改成标识符常量。当哪天不想3*3的时候,改ROW,COL就全部改了。提高了代码的可复用性。比如这里改成了10,棋盘就变成了10
2.3.2打印分割线
- 上述代码只是为了测试逻辑写的,然而我们真正要实现的效果确实这样的
要在有这些分割线的棋盘上下棋,不然一堆空格,谁知道你把棋下哪里了。所以要做一个小小的优化 - 要在打印棋盘的时候就把分割线给打上去,也就是在DisplayBoard函数里加上打印分割线的代码。
而我们要达成这样一个效果
所以这里我们进行优化,对优化后的版本二直接打印看看
但是这样的写法也是有问题的,当把ROW和COL改了之后,就不再适用了。
我们说想要改成10*10的棋盘的时候,也能用。但是现在不行,说明我们这个代码还是有问题的,还需要在进行优化
3. 假设这个棋盘它后面还有|和分割线
接下来只要再在行循环的内部嵌套一套for循环,打印每一行里面的三组数据,并且把我们在最后加上的|和分割线去掉
这个时候,换成5也行。
这个是代码
void DisplayBoard(char board[ROW][COL], int row, int col)
{
//控制行,一共row行
//每行是一组数据和一组分割线
int i = 0;
for (i = 0; i < row; i++)
{
//控制列
int j = 0;
for (j = 0; j < col; j++)
{
//打印一行里的数据
printf(" %c ", board[i][j]);
if (j < col - 1)//少打印一个|
printf("|");
}
printf("\n");
if (i < row - 1)//少打印一行分割线
{
int j = 0;
for (j = 0; j < col; j++)
{
//打印一行里的分割线
printf("---");
if (j < col - 1)//少打印一个|
printf("|");
}
printf("\n");
}
}
}
2.3.3下棋
- 设置好棋盘之后,我们设想是玩家下棋*,电脑下棋#。而下棋无法就是把中间值改成*或者#,还是对这个二维数组操作。那就先来玩家下棋PlayerMove
- 玩家下棋,那就通过坐标下棋吧。给玩家输入坐标,根据坐标下棋。而要是这个坐标是(100, 100),棋盘压根没有这么大,所以要考虑玩家下棋的合法性,不能超过数组下标的范围。还有就是玩家看棋盘可不是从0行0列开始的,而是1行1列。
void PlayerMove(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("玩家下棋:>\n");
printf("请输入坐标,中间使用空格:>\n");
scanf("%d %d", &x, &y);
//坐标合法
if (x >= 1 && x <= 3 && y >= 1 && y <= 3)
{
}
//坐标非法
else
{
printf("坐标非法,重新输入\n");
}
}
输入非法要重新输入,所以这个过程应该是个循环。
while (1)
{
printf("请输入坐标,中间使用空格:>\n");
scanf("%d %d", &x, &y);
//坐标合法
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
}
//坐标非法
else
{
printf("坐标非法,重新输入\n");
}
}
- 就算坐标合法也要考虑有没有被落子过吧,要是被落子了坐标就被占用了,就重新输入。要是没有被落子就可以修改内容落子,并且打印一下棋盘给别人看到。
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] = '*';
break;
}
else
printf("坐标被占用\n");
}
//坐标非法
else
{
printf("坐标非法,重新输入\n");
}
}
}
这里写个死循环,只是为了测试下逻辑是否正确
- 玩家下完棋之后,就轮到电脑下棋了。电脑随机生成坐标下棋。
void ComputerMove(char board[ROW][COL], int row, int col)
{
int x = rand() % row;//0~row-1
int y = rand() % col;//0~col-1
printf("电脑下棋:>\n");
if (board[x][y] == ' ')
board[x][y] = '#';
}
但电脑生成的坐标如果非法,那就啥也不提示,悄悄的继续生成一个坐标。所以这是一个循环的过程。
void ComputerMove(char board[ROW][COL], int row, int col)
{
while (1)
{
int x = rand() % row;
int y = rand() % col;
printf("电脑下棋:>");
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
而只有下棋成功,才能退出循环。所以给个1死循环。
这时如果棋盘输满了就会死循环。因为还欠一个输赢的判断
2.3.4判断输赢
- 输赢在三字棋里无非就是这三种情况
所以只需要判断里面的值就好了。 - 写一个IsWin判断输赢,玩家赢了返回’*‘,电脑赢了返回’#‘,平局返回’Q’,继续返回’C’。
要注意ret的创建必须在while外面,否则就外面就使用不了ret - 判断每一行或者每一列和对角线是否相等就可以判断输赢了
char IsWin(char board[ROW][COL], int row, int col)
{
int i = 0;
//行
for (i = 0; i < row; i++)
{
if (board[i][0] == board[i][1] && board[i][0] == board[i][2] && board[i][0] != ' ')//注意要不能等于空格
return board[i][0];
}
//列
for (i = 0; i < col; i++)
{
if (board[0][i] == board[1][i] && board[0][i] == board[2][i] && board[0][i] != ' ')
return board[0][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];
if (IsFull(board, ROW, COL))
return 'Q';
//继续
return 'C';
}
- 而平局只需要每个格子都被落子,写个函数遍历一下数组就好了
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] == ' ')//只要有空格,说明没满,返回0
return 0;
}
}
return 1;
}
以上就是三字棋的代码了。后续还有进阶版。