大家好,上节课,我为大家讲解了有关数组的基本知识,那么这节课,我就带着大家来一起实现一个叫《三子棋》的小游戏,会用到我们之前学过的数组,循环等相关知识,也是通过这个游戏,希望大家能够了解C语言实现游戏及其他项目的基本逻辑,那我们话不多说,现在开始吧!
1. 准备工作
在开始写代码之前,我们肯定要在项目中创建一些文件。本次游戏中我们用到的文件有main.c, game.c, game.h。main.c 是主函数,主要是展示给用户看的,包含菜单等这个游戏所用到的一些逻辑,game.h用来声明这些逻辑(函数),game.c中则是对这些函数进行实现。
2. 游戏规则
我们这个三子棋的规则是让用户和电脑进行下棋,用户作为先手,用户下的棋用 * 代替,电脑下的棋用#代替,当某一列,某一行,或者对角线上的三个位置都是相同的字符时,其中一方获胜。
3. main.c的主要代码内容
既然我们想做一个三子棋的小游戏,那么,我们首先就要写一个菜单,菜单中应该包括开始游戏和退出游戏两个选项,我们用1和0代替,又因为用户可能要进行多次游戏,所以我们应如何实现呢?没错,我们应该把这个菜单函数放到循环里,当用户输入0时,代表用户不想继续玩了,这时循环退出,游戏结束,当用户输入为1时,代表用户还想继续玩,那么循环继续,当用户输入其他值时,我们可以提示用户输入错误,请他重新输入一次。
以下就是我们的代码实现
// main.c
void menu()
{
printf("*******************************\n");
printf("********** 1. play **********\n");
printf("********** 0. exit **********\n");
printf("*******************************\n");
printf("*******************************\n");
printf("*******************************\n");
}
int main()
{
int choice = 0; // 选择
while(1)
{
menu();
printf("请选择 ->");
scanf("%d", &choice);
switch (choice)
{
case 1:
printf("开始游戏\n");
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,重新选择\n");
system("cls");
break;
}
}
return 0;
}
让我们编译代码,来看一下菜单给用户的展示效果
怎么样,是不是还有美感的?(hhh)
4. 游戏棋盘的实现
有了菜单,我们应该如何展示进行三子棋游戏的过程呢?由于我们用 * 和 # 来代表棋子,那么毋庸置疑,我们应该创建一个长度为3 X 3 的二维字符数组。
那么首先,第一步,我们应该对棋盘进行初始化,将棋盘中的每一个字符都初始化为一个空格。
我们用函数InitBoard实现,在game.h中进行函数声明,在game.c中进行函数实现,在main.c中进行函数调用,下面我们讲解game.c中的具体函数实现过程
用两层循环,直接全部初始化空格就行,很简单
void InitBoard(char board[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
board[i][j] = ' ';
}
}
}
5. 打印棋盘
当我们初始化棋盘之后,我们发现,打印出来的棋盘全部都是空格,棋盘与棋盘之间没有分界线,因此,玩家就难以分辨自己在哪个位置下棋。
所以,我们要对我们设计的棋盘进行优化。
说是优化,无非就是在棋盘中间加上若干条分界线,因为我们的游戏名称叫做《三子棋》,所以在这里,我提供一种优化方法。
void DisplayBoard(char board[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
printf(" %c ", board[i][j]);
if(j < col - 1)
printf("|");
}
printf("\n");
if (i < row - 1)
{
for (int j = 0; j < col; j++)
{
printf("---");
if (j < col - 1)
printf("|");
}
}
printf("\n");
}
}
这回,让我们再看一次我们设计的棋盘。
怎么样,这次我们设计的棋盘是不是清晰明了了呢?
6. 玩家下棋
设计好棋盘之后,我们接下来应该做什么呢?应该是让玩家进行下棋吧,我们设计一个函数PlayerBoard来进行实现
void PlayerMove(char board[ROW][COL], int row, int col)
{
printf("玩家下棋>:\n");
while (1)
{
printf("请输入下棋的坐标,中间使用空格>:\n");
int x, y = 0;
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");
}
}
}
在上面的代码中,首先提示玩家输入两个坐标,分别作为棋盘的行和列,然后首先应该判断玩家输入的坐标是否合法,如果不合法,应该让玩家重新输入,所以,我们应该把这段代码放入循环里。如果玩家输入的坐标合法,我们继续分两种情况,如果用户输入的这个坐标之前已经有棋了,那么用户也应该重新输入,如果之前没有放置棋,也就是这个位置的字符为 '0',那么提示用户输入成功,游戏继续。
7. 电脑下棋
同理,在我们写完玩家下棋的代码之后,我们写电脑下棋的代码就更加轻松了。由于电脑下棋我们是让程序随机生成两个1 - 3之间的问题,所以不存在坐标不合法的情况,但是仍然存在生成的坐标位置之前已经放置了棋,所以我们依然要判断生成坐标的合理性,代码如下
void ComputerMove(char board[ROW][COL], int row, int col)
{
int x, y = 0;
printf("电脑下棋:>\n");
while (1)
{
x = rand() % row;
y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
8. 判断输赢
在玩家和电脑你一次我一次的进行博弈的过程中,一定有一个时刻会分出胜负,或者棋盘走满仍未分出胜负的情况,我们称之为平手。
所以,一共有四种情况:
1. 玩家赢 我们让函数返回 *
2. 电脑赢 我们让函数返回 #
3. 打成平手 我们让函数返回 Q
4. 游戏继续 我们让函数返回 C
那么问题来了,什么情况下某一方获胜呢?
其实答案很简单,因为我们设计的是三子棋,所以期盼的某一行,某一列,或者对角线上放置的字符都相同时,相应的哪一方就获胜
由于我们把玩家下的棋用 * 来代替,把电脑下的棋用 # 来代替,而当玩家或者电脑获胜的时候,我们又让函数返回 * 或者 # ,所以我们可以直接返回棋盘上棋子所对应的内容,部分代码如下
for (int i = 0; i < row; i++)
{
if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][0] != ' ')
{
return board[i][0]; // 直接返回
}
}
for (int i = 0; i < row; i++)
{
if (board[0][i] == board[1][i] && board[1][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];
以上就是某一方获胜的代码,但是我们知道,一局比赛,玩家和电脑还有可能打成平手。
我们上文说了,平手的意思是:棋盘下满了,但是没有某一行,某一列,或者对角线上放置的字符都相同。
所以,我们可以再写一个函数IsFull,来判断棋盘是否已经满了,不能再下棋了
int IsFull(char board[ROW][COL], int row, int col)
{
for(int i = 0; i < row; i ++)
for (int j = 0; j < col; j++)
{
if (board[i][j] == ' ')
{
return 0;
break;
}
}
return 1;
}
所以,平手的代码就很好写了
// 平局
if (IsFull(board, row, col) == 1) return 'Q';
因为函数的执行是从上到下的,所以如果某一方获胜,平手的情况都没发生,我们直接返回C就好了,让游戏继续进行
所以,判断输赢函数的所有代码如下
char IsWin(char board[ROW][COL], int row, int col)
{
// 赢
for (int i = 0; i < row; i++)
{
if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][0] != ' ')
{
return board[i][0]; // 直接返回
}
}
for (int i = 0; i < row; i++)
{
if (board[0][i] == board[1][i] && board[1][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) == 1) return 'Q';
// 继续
return 'C';
}
10. 完善game代码
最后,让我们来捋一下整个函数的逻辑
1. 初始化棋盘,打印棋盘
2. 玩家下棋,电脑下棋
那么问题来了,我们该在什么时候判断输赢呢,是不是应该在每一个对方下完棋之后,就应该判断一次输赢呢?
所以,我们应该优化一下game代码
1. 初始化棋盘,打印棋盘
2. 玩家下棋,判断输赢
3. 电脑下棋,判断输赢
4. 游戏继续或结束
game的代码如下
void game()
{
char board[ROW][COL] = { 0 };
InitBoard(board, ROW, COL); // 有关游戏的操作
// 打印棋盘
DisplayBoard(board, ROW, COL);
// 下棋
char ret = 0;
while (1)
{
// 1.玩家下棋
PlayerMove(board, ROW, COL);
DisplayBoard(board, ROW, COL);
// 判断输赢
ret = IsWin(board, ROW, COL);
if (ret != 'C') break;
// 2.电脑下棋
ComputerMove(board, ROW, COL);
DisplayBoard(board, ROW, COL);
ret = IsWin(board, ROW, COL);
if (ret != 'C') break;
}
// 只判断一次
if (ret == '*') printf("玩家赢\n");
else if (ret == '#') printf("电脑赢\n");
else printf("平局");
}
11. 运行代码
大家可以看到,我们玩家就赢了电脑,因为第一行的棋子全部都是 * 。
那么,我们三子棋的讲解到这里就结束了,同学们学会了吗,快和你们的小伙伴们一起玩起来吧!
希望通过这个三子棋的例子,能让大家更好的理解数组,函数等概念,以及对C语言的逻辑有了初步的了解。
好了,今天的分享到这里就结束了,感谢大家的点赞,分享,与转发~
下期,我会给大家带来另外一个小游戏《扫雷》,有兴趣的小伙伴千万不要错过呦!
我们下期再见,拜拜~