目录
写在前面
大家是否曾经和同桌玩过三子棋小游戏呢?
也许大家都玩过这个游戏,但是对它的称呼却并不相同,那首先带大家回忆一下玩法和规则
看到这些九宫格,有没有感觉到死去的记忆突然攻击你呢?
今天我们就试着用c语言来实现这个小游戏。
实现三子棋小游戏需要具备哪些知识呢?
1.分支与循环语句
2.函数
3.数组
没错,当我们能简单的使用上述知识,就可以来试着写一个三子棋小游戏了!
如果觉得上述知识没有简单掌握的话,请参照我之前的博客,里面有庖丁解牛式讲解。
游戏设计
游戏设计我们采用多文件的形式:
1.test.c //只包含main()、menu()、game()
2.game.c //用于定义游戏需要的各个函数
3.game.h //用于包含各种头文件和函数声明
1.main函数设计
首先,我们需要向玩家展示一份游戏菜单,如下图:
玩家此时可以选择
1.开始游戏
0.退出游戏
而且每结束一局游戏游戏后,都会再次打印菜单,所以此处需要一个循环。
那么三种循环语句我们该如何选择呢?
为了确保程序一运行之后,就能打印菜单,我们选择do......while循环
do
{
menu();//为了代码的整洁,我们设计一个单独的menu函数来打印菜单}while(...)
之后我们还需要定义一个变量input来接收玩家的选择
int input = 0;
printf("请选择:>");
scanf("%d", &input);
那么还有个问题,我们do......while该何时停止呢?
此时有个巧妙的设计,当玩家输入“0”来选择退出游戏是,循环就应该停止,那不妨就用input来控制循环,如下:
int main()
{
int input = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);.........
} while (input);
return 0;
}
接下来我们根据玩家输入的选择来实现菜单中的功能
switch (input)
{
case 1:
game();//进入游戏,我们用另外一个函数game()单独实现
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新输入\n");//对于选项以外的输入,给出提示
break;
}
但这里我们main函数的主体已经实现完毕。
来看看完整代码:
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新输入\n");
break;
}
} while (input);
return 0;
}
2.menu函数的设计
menu()函数,我们只需要用它将菜单的内容打印到屏幕上即可,所以它的实现很简单:
void menu()
{
printf("******三子棋*******\n");
printf("*******************\n");
printf("******1.start******\n");
printf("******0.exit ******\n");
printf("*******************\n");
printf("*******************\n");
}
3.game函数的设计
如图,玩家和电脑每走一步,都会将棋盘打印出来,因此,我们需要用一个3*3的二维数组来存放棋子。
char board[ROW][COL];//ROW为行数,COL为列数
由于在后面的设计中,我们会多次用到行数和列数,我们干脆在game.h中用define定义ROW与COL。今后如果想设计四子棋,五子棋,十子棋等等,我们只需改变ROW和COL即可。
#define ROW 3
#define COL 3
仔细观察棋盘,当某个位置没有落子时,它是空白的。所以我们首先得初始化一下二维数组board,使其内容都为空格。这里我们用一个函数InitBoard来实现数组的初始化。
3.1InitBoard函数
数组的初始化也很简单,通过for循环的嵌套遍历整个数组即可实现
//初始化棋盘
void InitBoard(char board[ROW][COL], const int row, const int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
board[i][j] = ' ' ;
}
}
}
InitBoard是设计游戏内容的函数,所以我们将它在game.h中声明,并在game.c中定义。
接着,玩家游戏开始前,我们最好先将棋盘打印一下,方便玩家选择坐标进行落子,
所以我们需要设计一个打印棋盘的函数。
3.2DisplayBoard函数
计算机里没有提供棋盘的图案,所以这需要我们手动来设计。
为了简便我们用' | '和' - '来“拼凑”一个棋盘大概长这个样子:
那么我们简单分析一下这个棋盘的组成
注意红色圈圈里的其实是空格,而最中间的红色圈圈就是将来要落子的位置。
因此棋盘就是数组元素与' | '和' - '组成的,其中2、4为分割线。那我们直接打印就好:
void DisplayBoard(char board[ROW][COL], const int row, const int col)
{
for (int 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");
}
}
这样我们就可以成功的打印3*3的棋盘了。但有一点小小的瑕疵就是,如果将来我们修改ROW和
COL的值,想要打印10*10的棋盘,显然此刻的 DisplayBoard 不能完成。我们可以试着改造一个
动态的、大小可变的棋盘打印函数,如下:
void DisplayBoard(char board[ROW][COL], const int row, const int col)
{
/*for (int 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");
}*/
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)
{
for (j = 0; j < col; j++)
{
printf("---");
if (j < col - 1)//防止最后一行打印分割线
printf("|");
}
}
printf("\n");
}
}
DisplayBoard是设计游戏内容的函数,所以我们将它在game.h中声明,并在game.c中定义。
到目前为止,我们的game()函数已经完善到这步:
void game()
{
char board[ROW][COL];
InitBoard(board, ROW, COL);//初始化棋盘
DisplayBoard(board, ROW, COL);//打印棋盘
//下棋
......
}
接下来就是最重要的环节,下棋的部分了。
我们默认让玩家先手,这是我们设计一个函数来实现玩家下棋。
3.3player_move函数
玩家下棋时,首先我们给出提示玩家该下棋了:
printf("玩家下棋:>\n");
下棋其实本质就是,将棋子的坐标保存到数组中,在通过DisplayBoard打印。
当然,在落子之前,我们必须得判断棋子的坐标是否合理。比如:
1.该坐标是不是已经被其他棋子占用
2.该坐标是否超出了棋盘的范围
当玩家不小心下到了不合理的位置,我们要给出提示,并让他重新选择下棋的坐标,直到下在正确
的位置,所以,此处应该运用到循环。
注意:大部分玩家并非程序员,所以他们可能并不知道数组的下标是从0开始的。所以我们需要将
玩家输入的坐标减一之后再使用。
void player_move(char board[ROW][COL], const int row, const int col)
{
int x = 0;
int y = 0;
printf("玩家下棋:>\n");
while (1)
{
printf("请输入坐标:>");
scanf("%d %d", &x,&y);
//判断下棋坐标是否超出棋盘范围
//如果合理,break结束循环
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");
}
}
}
3.4computer_move函数
玩家落一子后轮到电脑下棋了,电脑是不会自主思考的,这里我们就不要求AI的方式下棋了。
我们让电脑在一个随机的坐标下棋即可。
电脑下棋时,我们照样得判断它随机生成的坐标是否合法。但是却不用提醒它“你下错了”,我们就
让它不停的尝试,直到找到正确的位置。此处依旧需要一个while循环。
void computer_move(char board[ROW][COL], const int row, const int col)
{
printf("电脑下棋:>\n");
while (1)
{
int x = ...//该怎么赋值?
int y = ...
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
但这里我们怎么让电脑生成随机坐标的?
3.4.1随机坐标的生成
这里我们将认识一个函数 rand ,它的作用就是生成一个随机数。
我们这里不详细介绍它的原理,只是简单介绍一下使用方法:
使用rand函数之前,我们得包含stdlib.h的头文件。
最重要的一步,我们要在main()函数内部加上这句代码:
srand((unsigned int)time(NULL));
//别忘记包含time()函数所在的头文件 time.h
这样做之后,我们就可以使用rand函数了。
既然是随机数,我们就通过对随机数模3的操作,让它的值落在[0,3)的区间内。
int x = rand() % row;
int y = rand() % col;
然后仿照玩家下棋的逻辑,我们就顺利完成电脑下棋的函数。
void computer_move(char board[ROW][COL], const int row, const int col)
{
printf("电脑下棋:>\n");
while (1)
{
int x = rand() % row;
int y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
至此,玩家和电脑都会下棋了,我们只需要通过while循环让他俩一人一子即可,每落一子后将棋盘再展示一遍。
void game()
{
char ret = 0;
char board[ROW][COL];
InitBoard(board, ROW, COL);
DisplayBoard(board, ROW, COL);
//下棋
while (1)
{
player_move(board, ROW, COL);//玩家下棋
DisplayBoard(board, ROW, COL);
computer_move(board, ROW, COL);//电脑下棋
DisplayBoard(board, ROW, COL);
}}
但到这里还没有结束,因为我们会发现一会儿程序就会进入死循环,因为棋盘下满后,电脑一直在
生成随机数妄图找到正确的位置,却找不到。
按照正常的逻辑,当一方获胜后,游戏将不在进行,并宣布某一方获胜。
当棋盘下满后,无任何一方获胜,就宣布平局。
那我们该怎么实现这样的逻辑呢?
3.5is_win函数
当玩家或电脑每落下一子后,我们都应该判断一下,是否一方获胜,或者平局,若无任一情况,则
游戏继续。
如图所示,游戏获胜有四种情况:
1.任意一行三个棋子相同
2.任意一列三个棋子相同
3.主对角线三个棋子相同
4.次对角线三个棋子相同
若玩家获胜则返回' * ',否则返回' # '
若平局则返回'Q',
若都未发生则返回'c'
那么我们分别来进行判断:
//每一行各棋子是否相同
for (i = 0; i < row; i++)
{
int flag = 1;
for (j = 0; j < col - 1; j++)
{
if (board[i][j] != board[i][j + 1] || board[i][j] == ' ')
flag = 0;
}
if (flag == 1)
return board[i][0];
}
//每一列各棋子是否相同
for (i = 0; i < col; i++)
{
int flag = 1;
for (j = 0; j < row - 1; j++)
{
if (board[j][i] != board[j+1][i] || board[j][i] == ' ')
flag = 0;
}
if (flag == 1)
return board[0][i];
}
//主对角线各棋子是否相同
while (board[i][j]!=' '&&board[i][j] == board[i+1][j+1])
{
i++;
j++;
if (i == row-1 || j == col-1)
return board[0][0];
}
//次对角线个棋子是否相同
i = 0;
j = 0;
while (board[i][col-1-j] != ' ' && board[i][col-1-j] == board[i + 1][col-2-j])
{
i++;
j++;
if (i == row - 1 || j == col - 1)
return board[0][col-1];
}
四种获胜情况判断完之后,就要考虑是否平局了
那么我们通过函数is_full来判断棋盘是否下满
3.5.1is_is_full
又是一次简单的数组遍历
//判断棋盘是否下满
//如果下满就返回1,否则返回0
static int is_full(char board[ROW][COL], const int row, const 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;
}
//是否平局
if (is_full(board, ROW, COL)==1)
{
return 'Q';
}
当然上述内容都在is_win函数中现在来看一下完整的is_win函数:
char is_win(char board[ROW][COL], const int row, const int col)
{
int i = 0;
int j = 0;
//每一行各棋子是否相同
for (i = 0; i < row; i++)
{
int flag = 1;
for (j = 0; j < col - 1; j++)
{
if (board[i][j] != board[i][j + 1] || board[i][j] == ' ')
flag = 0;
}
if (flag == 1)
return board[i][0];//直接返回棋子的内容就知道是谁赢
}
//每一列各棋子是否相同
for (i = 0; i < col; i++)
{
int flag = 1;
for (j = 0; j < row - 1; j++)
{
if (board[j][i] != board[j+1][i] || board[j][i] == ' ')
flag = 0;
}
if (flag == 1)
return board[0][i];//直接返回棋子的内容就知道是谁赢
}
//主对角线各棋子是否相同
while (board[i][j]!=' '&&board[i][j] == board[i+1][j+1])
{
i++;
j++;
if (i == row-1 || j == col-1)
return board[0][0];//直接返回棋子的内容就知道是谁赢
}
//次对角线个棋子是否相同
i = 0;
j = 0;
while (board[i][col-1-j] != ' ' && board[i][col-1-j] == board[i + 1][col-2-j])
{
i++;
j++;
if (i == row - 1 || j == col - 1)
return board[0][col-1];//直接返回棋子的内容就知道是谁赢
}
//是否平局
if (is_full(board, ROW, COL)==1)
{
return 'Q';
}
return 'c';
}
到这里,我们在回去继续完善game()。
每落一子后,进行判断,若is_win返回'c'则游戏继续,否则意味着游戏结束,退出循环,之后就可
以判断是否任何一方获胜或者平局,我们这样实现这个逻辑:
void game()
{
char ret = 0;
char board[ROW][COL];
InitBoard(board, ROW, COL);
DisplayBoard(board, ROW, COL);
//下棋
while (1)
{
player_move(board, ROW, COL);
DisplayBoard(board, ROW, COL);
ret = is_win(board, ROW, COL);
if (ret != 'c')
break;
computer_move(board, ROW, COL);
DisplayBoard(board, ROW, COL);
ret = is_win(board, ROW, COL);
if (ret != 'c')
break;
}
if (ret == '*')
printf("玩家赢\n");
else if (ret == '#')
printf("电脑赢\n");
else if (ret == 'Q')
printf("平局\n");
DisplayBoard(board, ROW, COL);
}
至此,整个游戏的逻辑已经全部实现完毕!赶紧玩一下自己做的小游戏吧,并试着找找bug。
完整源码
//test.c
#define _CRT_SECURE_NO_DEPRECATE 1
#include"game.h"
void menu()
{
printf("******三子棋*******\n");
printf("*******************\n");
printf("******1.start******\n");
printf("******0.exit ******\n");
printf("*******************\n");
printf("*******************\n");
}
void game()
{
char ret = 0;
char board[ROW][COL];
InitBoard(board, ROW, COL);
DisplayBoard(board, ROW, COL);
//下棋
while (1)
{
player_move(board, ROW, COL);
DisplayBoard(board, ROW, COL);
ret = is_win(board, ROW, COL);
if (ret != 'c')
break;
computer_move(board, ROW, COL);
DisplayBoard(board, ROW, COL);
ret = is_win(board, ROW, COL);
if (ret != 'c')
break;
}
if (ret == '*')
printf("玩家赢\n");
else if (ret == '#')
printf("电脑赢\n");
else if (ret == 'Q')
printf("平局\n");
DisplayBoard(board, ROW, COL);
}
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新输入\n");
break;
}
} while (input);
return 0;
}
//game.c
#define _CRT_SECURE_NO_DEPRECATE 1
#include"game.h"
//初始化棋盘
void InitBoard(char board[ROW][COL], const int row, const int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
board[i][j] = ' ' ;
}
}
}
//打印棋盘
void DisplayBoard(char board[ROW][COL], const int row, const int col)
{
/*for (int 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");
}*/
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)
{
for (j = 0; j < col; j++)
{
printf("---");
if (j < col - 1)
printf("|");
}
}
printf("\n");
}
}
//玩家下棋
void player_move(char board[ROW][COL], const int row, const 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 computer_move(char board[ROW][COL], const int row, const int col)
{
printf("电脑下棋:>\n");
while (1)
{
int x = rand() % row;
int y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
//判断棋盘是否下满
static int is_full(char board[ROW][COL], const int row, const 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 is_win(char board[ROW][COL], const int row, const int col)
{
int i = 0;
int j = 0;
//每一行各棋子是否相同
for (i = 0; i < row; i++)
{
int flag = 1;
for (j = 0; j < col - 1; j++)
{
if (board[i][j] != board[i][j + 1] || board[i][j] == ' ')
flag = 0;
}
if (flag == 1)
return board[i][0];
}
//每一列各棋子是否相同
for (i = 0; i < col; i++)
{
int flag = 1;
for (j = 0; j < row - 1; j++)
{
if (board[j][i] != board[j+1][i] || board[j][i] == ' ')
flag = 0;
}
if (flag == 1)
return board[0][i];
}
//主对角线各棋子是否相同
while (board[i][j]!=' '&&board[i][j] == board[i+1][j+1])
{
i++;
j++;
if (i == row-1 || j == col-1)
return board[0][0];
}
//次对角线个棋子是否相同
i = 0;
j = 0;
while (board[i][col-1-j] != ' ' && board[i][col-1-j] == board[i + 1][col-2-j])
{
i++;
j++;
if (i == row - 1 || j == col - 1)
return board[0][col-1];
}
//是否平局
if (is_full(board, ROW, COL)==1)
{
return 'Q';
}
return 'c';
}
//game.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<string.h>
#define ROW 3
#define COL 3
//初始化棋盘
void InitBoard(char board[ROW][COL], const int row, const int col);
//打印棋盘
void DisplayBoard(char board[ROW][COL], const int row, const int col);
//玩家下棋
void player_move(char board[ROW][COL], const int row, const int col);
//电脑下棋
void computer_move(char board[ROW][COL], const int row, const int col);
//判断棋盘状态
char is_win(char board[ROW][COL], const int row, const int col);
感谢观看!