在学习二维数组后我们加以应用能写出三子棋这样一个小游戏。具体实现并不复杂。
游戏首先打印菜单,由我们选择单人游戏,双人游戏或退出游戏。
三子棋需要一个棋盘,我们可以通过循环打印来构造一个简单的3x3棋盘。
我们需要一个函数在每次下棋子后都打印一次来表示目前的棋盘,于是创建一个二维数组接收棋子后方可遍历打印。我们用 * 和 # 代表两种棋子。
游戏过程中玩家输入下标下棋后电脑下,直到决出胜负或平局。
直接看代码。
我们在写程序的时候要将函数模块化,不把所有函数都堆在一个源文件里。我们新建一个主体源文件main.c用于调用所有函数,新建一个game1.c文件用于函数的实现,即编写函数,新建一个game.h文件用于声明game1.c中的函数。
void menu()
{
printf("################################\n");
printf("######### 1.单人游戏 ##########\n");
printf("######### 2.双人游戏 ##########\n");
printf("######### 3.退出游戏 ##########\n");
printf("################################\n");
}
假如进入单人游戏,我们首先创建并初始化一个棋盘:
#define ROW 3
#define COL 3
char board[ROW][COL] = { 0 };
Initboard(board, ROW, COL);
我们用宏定义两个常量,ROW表示棋盘的行数,COL表示棋盘的列数。
这样做是方便我们一次性修改行数和列数。
void Initboard(char board[ROW][COL], int row, int col)
{
//char i = 0; char* p = board;
//while (i < ROW * COL)
//{
// *(p + i++) = ' ';
//}
//for (int i = 0;i < row;i++)
//{
// for (int j = 0;j < col;j++)
// {
// board[i][j] = ' ';
// }
//}
memset(board, ' ', row * col);
}
我们有三种初始化的方式:
void Displayboard(char board[ROW][COL], int row, int col)
{
for (int j = 0;j < col;j++)
{
printf("---|");
}
printf("\n");
for (int i = 0;i < row;i++)
{
for (int j = 0;j < col;j++)
{
printf(" %c ", board[i][j]);
printf("|");
}
printf("\n");
for (int j = 0;j < col;j++)
{
printf("---");
printf("|");
}
printf("\n");
}
}
棋盘如图
我们可以直接打印棋盘样式,采用循环是因为如果改变行和列数后棋盘无法正常显示。具体实现较为简易。
void Player_move(char board[ROW][COL], int row, int col)
{
int x = 0, y = 0;
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] = '*';
break;
}
else
{
printf("该位置已有棋子,重新输入\n");
continue;
}
}
else printf("输入错误,重新输入\n");
}
}
定义X和Y变量用于接收玩家输入的下标数。之后进行循环,假如成功下子才跳出循环。我们需要判断坐标是否已经有棋子,坐标是否合法,如果输入错误即再进行循环直到输入正确。需要注意的是在赋值时注意数组下标为 X-1,Y-1。例如玩家输入2 2,本意是在中间的位置下棋,但是数组读取的时候下标要-1,即传入数组中的数值应为1 1.
void Computer_move(char board[ROW][COL], int row, int col)
{
int x = 0, y = 0;
while (1)
{
x = rand() % row;
y = rand() % col;
simplealgorithm(board, row, col, &x, &y);
again: if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
else
{
x = rand() % row;
y = rand() % col;
goto again;
}
}
}
我们需要调用一个随机值来生成电脑下子的位置,于是调用rand函数,且要在main.c文件里先使用srand函数,将时间戳作为随机数种子。在主体函数里添加下列这句代码即可。
srand((unsigned int)time(NULL));
得到随机数后,将随机数%ROW,确保这个值的范围在0~ROW之间,不会越界后赋给X,作为随机的行坐标,同理得到随机的列坐标。之后调用一个极其简单的拦截函数,避免电脑看起来跟个人工智障似的。goto again是避免修改后的坐标因为无法通过判断语句后无法再取随机数后造成死循环。
void simplealgorithm(char board[ROW][COL], int row, int col,int *x,int *y)
{
for (int i = 0;i <= row;i++)//行
{
if ((board[i][0] == board[i][1] && board[i][0] == '*'))
{
*x = i;
*y = 2;
}
else if ((board[i][1] == board[i][2] && board[i][1] == '*'))
{
*x = i;
*y = 0;
}
}
for (int i = 0;i <= col;i++)//列
{
if ((board[0][i] == board[1][i] && board[0][i] == '*'))
{
*x = 2;
*y = i;
}
else if ((board[1][i] == board[2][i] && board[1][i] == '*'))
{
*x = 0;
*y = i;
}
}
for (int i = 0;i <= col;i++)//对角线
{
if ((board[i][i] == board[i+1][i+1] && board[i][i] == '*'))
{
if (i == 0)
{
*x = 2;
*y = 2;
}
else if (i == 1)
{
*x = 0;
*y = 0;
}
}
else if ((board[i][2] == board[i+1][i+1] && board[i][2] == '*'))
{
if (i == 0)
{
*x = 2;
*y = 0;
}
else if (i == 1)
{
*x = 0;
*y = 2;
}
}
}
}
这个函数只是简简单单的判断,并没有什么算法,功能也不齐全,在这不多作介绍,读者可自行研究。进入函数后进行判断,如果坐标需要修改即可修改坐标地址对应的值,所以我们要把函数里的X和Y传址过去。
char Win(char board[ROW][COL], int row, int col)
{
for (int 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 (int i = 0;i < col;i++)//判断列
{
if (board[0][i] == board[1][i] && board[2][i] == board[0][i] && board[0][i] != ' ')
return board[0][i];
}
//判断对角线
if (board[0][0] == board[1][1] && board[2][2] == board[0][0] && board[0][0] != ' ')
return board[0][0];
if (board[0][2] == board[1][1] && board[2][0] == board[0][2] && board[0][2] != ' ')
return board[0][2];
if (Full(board, row, col))
{
return 'F'; //Full
}
else return 'C';
}
int Full(char board[ROW][COL], int row, 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,可读性相较int更高。
C表示continue,对局尚未结束,继续游戏。
F表示full,棋盘已满,平局。
*表示 玩家胜,#表示电脑胜。
定义一个字符变量ret用于接收字符后进行判断。
我们通过循环判断行或列或对角线,如果出现三子相等且均不为空格即返回这一线上的任意一个棋子,即是 *或#
void body()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
menu();
scanf("%d", &input);
switch (input)
{
case 1:system("cls");game1();
break;
case 2:system("cls");
printf("先手是* 后手是#\n");
game2();
break;
case 3:printf("退出\n");input = 0;
break;
default:printf("输入错误,请重新选择\n");
break;
}
} while (input);
}
主体函数要做的是通过玩家的输入值判断进入哪个选项。在循环里,input作为判断条件,如果玩家输入的是3,即退出游戏,我们便把input设为0,方跳出循环,游戏终止。接下来我们看2、双人游戏
void game2()
{
char ret = 0;
char board[ROW][COL] = { 0 };
Initboard(board, ROW, COL);
while (1)
{
Displayboard(board, ROW, COL);
Player_move(board, ROW, COL);
Displayboard(board, ROW, COL);
ret = Win(board, ROW, COL);
if (ret != 'C')
break;
Player2_move(board, ROW, COL);
ret = Win(board, ROW, COL);
if (ret != 'C')
break;
}
if (ret == '#')
{
printf("玩家2赢了\n");
}
else if (ret == '*')
{
printf("玩家1赢了\n");
}
else if (ret == 'F')
{
printf("平局\n");
}
}
void Player2_move(char board[ROW][COL], int row, int col)
{
int x = 0, y = 0;
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] = '#';
break;
}
else
{
printf("该位置已有棋子,重新输入\n");
continue;
}
}
else printf("输入错误,重新输入\n");
}
}
这里很简单,我们只需要调用一个game2函数,再调用一个玩家2下子的函数后进行游戏即可。逻辑和单人游戏非常相似。
读者可以自行尝试优化代码,因为如果改变了行数和列数,即扩大棋盘,游戏的胜负判断函数和电脑的拦截函数将不可用。不过可以扩大棋盘后双人对战自行肉眼判断,甚至还能把棋盘扩的更大,来一盘快乐的五子棋~
(只是找下标可能是一个令人头疼的问题……)