今天,就让我们用C语言中的函数与二维数组的相关知识来写一个有意思的代码——三子棋的实现。
首先先介绍一下三子棋:三子棋是一款古老的民间传统游戏,又被称为黑白棋、圈圈叉叉棋、井字棋、一条龙、九宫棋等。游戏分为双方对战,双方依次在9宫格棋盘上摆放棋子,率先将自己的三个棋子连成一条线的一方则视为胜利者。
那我们应该如何用C语言来实现呢。首先确定一个大致的框架:可以将我们的代码分装在三个文件中,列如:
1.test.c——用来实现进入游戏前的一些操作
2.game.c——定义关于游戏的函数
3.game.h——声明关于游戏的函数
因此在test.c和game.c两个源文件中都应该包含game.h头文件,因此可以直接在game.h的头文件中包含所需调用函数的头文件即可
接下来就讲述代码该如何编写:
1.test.c
首先应该先打印一个菜单,以便玩家选择是否进行游戏,如以下代码:
void menu()//打印菜单
{
printf("******************************\n");
printf("********** 1.Play **********\n");
printf("********** 0.Exit **********\n");
printf("******************************\n");
}
之后通过switch循环来确定以下几种情况
1.输入1则开始游戏。
2.输入0则退出游戏。
3.输入其他数则报错,要求重新输入。
而又为了该游戏可以多次游玩,所以可以通过while循环来进行重复运行,具体代码如下:
int main()
{
srand((unsigned int)time(NULL));
int input = 1;
while(input)
{
menu();
printf("请输入选项: ");
scanf("%d", &input);
switch (input)
{
case 1:
printf("玩游戏\n");
break;
case 0:
printf("游戏结束\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
}
return 0;
}
代码运行后结果为
之后再开始写该游戏的主体。首先将main函数中的printf("玩游戏\n")改为自定义的游戏函数game(),此函数用来实现游戏的运行。之后先在该函数中定义一个二维数组充当三子棋棋盘。之后再在函数中调用game.c源文件中定义各种函数来实现游戏。
2.game.c
首先因为该游戏所需要的函数较多,所以可以先在game.h头文件中先声明所需要的各种函数,之后再在game.c文件中定义各个函数。
其次,我们可以通过宏定义来定义行长与列宽。这样做既可以保证后期对于棋盘的修改,也可以便于写代码途中对代码的测试。
初始化棋盘
游戏最开始前,为了便于操作,我们可以利用两层for循环的嵌套将棋盘都初始化成空格。其次此处数组传参也可以写成int* board或int board[ ][ ],但必须把行列数也给传过去便于初始化。代码如下:
//初始化棋盘
void Initboard(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++)
{
board[i][j] = ' ';//使数组每一个都初始化为空格
}
}
}
打印棋盘
由于这是一个二维数组,因此可以使用两层for循环的嵌套来实现。其次,为了是棋盘的美观,我们可以在没个空格间打印一些间隔符。实际效果如下
那我们该如何去实现呢?通过观察不难发现,其实这些是由一些“—”与“|”组成。因此我们可以再次使用for循环的嵌套来实现这样的操作。此外,要注意的是,为了使棋盘美观,棋盘的最后一列便可以不需要打印“|”,最后一行同样不需要打印“—”。因此,我们还需要运用到if判断语句,判断是否为最后一行或最后一列。代码如下:
//打印棋盘
void Printboard(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 (j != col-1) //此处用if语句来判断每列是需不需要打印“|”
{
printf(" %c |", board[i][j]);
}
else
{
printf(" %c ", board[i][j]);
}
}
printf("\n"); //每行打印完之后都要换行进入下一行的打印
if (i != row - 1) //判断是否为最后一行,若不是最后一行,则需要继续打印“---|”
{
//以下部分是作用为判断间隔行的每列需不需要打印“|”
int k = 0;
for (k = 0; k < col; k++)
{
if (k != col - 1)
{
printf("---|");
}
else
{
printf("---");
}
}
}
printf("\n"); //此处换行符的作用为打印完行与行之间的间隔之后的换行
}
}
双方下棋
首先,下棋分为两个步骤:一为玩家下棋,二为电脑下棋。
其次,下的每一步棋又应该考虑其合理性:一为下的棋是否在棋盘内,二为下棋的位置是否已经被下过。
玩家下棋:
1.判断落子是不是在棋盘内:由于在玩家的眼中该棋盘的首行首列均为第一行第一列,这与二维数组概念首行首列为零不符,因此,我们应该将玩家输入的行列数减一之后再判断是否在棋盘内。
2.判断落子位置是否已经被下过:此处只需要判断玩家落子的位置是否为空格,即可判断落子位置是否已经被下过。
因此,我们可以使用双层if语句的嵌套来判断以上所需要考虑的两种情况,只有两种情况都满足时才可以落子。又因为只有当玩家下完棋之后才可以进行后续代码的编译,因此,我们还需要一个while循环判断玩家是否已经下棋。代码如下:
//玩家下棋
void Playerdropsboard(char board[ROW][COL], int row, int col)
{
printf("请输入要落子的位置:");
int r = 0;
int c = 0;
while (1) //此处循环目的是确保玩家下完棋之后再让电脑下棋
{
scanf("%d %d", &r, &c);
if ((r >= 1 && r <= row) && (c >= 1 && c <= col)) //判断落子位置是否合理,即是否在棋盘内
{
if (board[r - 1][c - 1] != ' ') //判断落子位置是否已被下过
{
printf("该处已被下过,请重新落子\n");
}
else
{
board[r - 1][c - 1] = '#'; //玩家下棋为“#”
break; //玩家下完棋之后跳出循环,电脑再开始下棋
}
}
else
{
printf("选择位置错误,请重新落子\n");
}
}
}
电脑下棋:
为了是电脑为随机下棋,可以通过rand函数获得两个随机数作为落子的坐标。又因为通过rand获得的随机数的范围不确定,所以可以通过取余运算符%使的随机数一定是在0~3之间,即落子一定落在棋盘中。此外,要在主函数中加上srand((unsigned int)time(NULL))这一表达式,此表达式作用为每次重新编译时,使得通过rand获得的随机值不是唯一不变的,即重新设置rand的起点值。之后再通过if语句判断落子位置是否已经被下过。同时也需要循环语句来保证电脑一定会落子。代码如下:
//电脑下棋
void ComputerDropsboard(char board[ROW][COL], int row, int col)
{
int flag = 1; //此处该变量作用为确保电脑一定会落子
while (flag)
{
int r = rand() % 3;
int c = rand() % 3;
if (board[r][c] == ' ') //判断落子位置是否已被下过
{
board[r][c] = '*'; //电脑下棋为“*”
flag = 0; //当电脑落子是flag值为0则退出循环。若为落子则flag值为1,继续循环直到电脑落子为止
}
}
}
判断棋局
判断棋局主要分两大类情况:一为棋局已经出现了胜利者,二为棋局没有出现胜利者。而这两大类情况又需要分为几类小情况:
当棋局已经出现胜利者时:
1.胜利者为玩家。
2.胜利者为电脑。
当棋局还没有出现胜利者:
1.棋盘没有被下满,还需要继续下棋。
2.棋盘已经被下满,棋局结束,为平局。
之后,我们再对这几种小情况分别考虑。此处为了方便系统判断棋局情况,我们可以利用函数的返回值来体现棋局情况。因此我们可以返回‘#’(玩家下的棋)表示玩家胜利,返返回‘*’(电脑下的棋)表示电脑胜利,返回字符‘C’表示继续下棋,返回字符‘Q’表示平局。
首先判断棋局是否有胜利者:因为三子棋的规则为每行或每列或对角线上先连成三字一线者才为胜利。因此我们可以通过if判断语句与for循环语句来首先判断每行或每列是否有连成一线者,其次再判断对角线上是否有连成一线者。
其次,若没有出现胜利者,在来判断棋盘是否被下满。因此,我们需要通过两个for循环语句来遍历数组,查看数组中是否还有空格,若有,则证明棋盘还没有被下满,就返回‘C’,反之,就返回‘Q’。
代码如下:
char Judgeboard(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][1] == board[i][2]) &&(board[i][0] != ' ')) || ((board[0][i] == board[1][i]) && (board[1][i] == board[2][i]) && (board[0][i] != ' ')))
{
return board[i][0];//返回的即为赢家所用的字符,便于以后判断谁胜利
}
}
if ((board[0][0] == board[1][1]) && (board[1][1] == board[2][2])&&(board[1][1] != ' '))//判断主对角线上的三个数
{
return board[1][1];
}
else if ((board[0][2] == board[1][1]) && (board[1][1] == board[2][0]) && (board[1][1]!= ' '))//若主对角线上无法判断输赢在判断副对角线
{
return board[0][2];
}
//若前面有胜利者则直接通过return返回,不会进行后续代码,若无胜利者才编译后续代码
//首先判断棋盘有没有被下满
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
if (board[i][j] == ' ')
{
return 'C';//遍历数组当数组中还有' ',即棋盘还没有被下满要继续下棋
}
}
}
return 'Q';
}
此外,要注意的是,必须先判断棋局是否已经出现胜利者,在判断棋盘是否被下满。因为可能会有已经出现三棋一线但棋盘上还有空位没下的情况。此时若是先判断棋盘是否被下满,系统则会继续让玩家下棋,知道棋盘没有空位为止。
3.game.h
因为在调用函数前,都应该提前声明函数,因此在game.c中的每一个函数都应该在game.c中提前进行声明,因此game.c应如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//用宏定义定义棋盘的行列数,便于以后的棋盘的修改
#define ROW 3
#define COL 3
//初始化棋盘
void Initboard(char board[ROW][COL], int row,int col);
//此处数组传参也可以写成int* board或int board[][]
//但必须把行列数也给传过去便于初始化
//打印棋盘
void Printboard(char board[ROW][COL], int row, int col);
//下棋:
//1.需要考虑玩家下棋与机器下棋
//2.需要考虑落子的位置合理性与是否被下过
void Playerdropsboard(char board[ROW][COL], int row, int col);//玩家下棋
void ComputerDropsboard(char board[ROW][COL], int row, int col);//电脑下棋
//判断当前棋盘情况,如是否已经结束
//结束的情况中在判断玩家胜还是电脑胜还是平局
char Judgeboard(char board[ROW][COL], int row, int col);
最后,我们再对main函数中的game()函数在game.c的基础上进行编写。代码如下:
void game()
{
char Board[ROW][COL] = {0};
Initboard(Board,ROW,COL); //初始化棋盘
Printboard(Board, ROW, COL); //打印棋盘
char ret = 0;
while (1)
{
Playerdropsboard(Board, ROW, COL); //玩家下棋
Printboard(Board, ROW, COL); //玩家每下一次棋都应该打印一次棋盘便于玩家后续操作
ret = Judgeboard(Board, ROW, COL); //没当玩家或者电脑下完棋时都要判断一次棋盘情况。
if (ret != 'C') //如果返回‘C’则继续下棋,若返回其他值则直接跳出循环,之后再进行判断
break;
ComputerDropsboard(Board, ROW, COL); //电脑下棋
ret=Judgeboard(Board, ROW, COL);
if (ret != 'C') //同上
break;
Printboard(Board, ROW, COL); //电脑每下一次棋都应该打印一次棋盘便于玩家后续操作
}
//在循环之后判断是因为这样只需要判断一次,简便了代码
if (ret == '#')
printf("玩家胜利\n");
else if (ret == '*')
printf("电脑胜利\n");
else if (ret == 'Q')
printf("平局\n");
Printboard(Board, ROW, COL); //最后在打印一次棋盘,让玩家知道自己是怎么赢(输)的
}
综上,只需将这些代码结合起来,便是我们所需要的三字棋代码。这里就由我来帮助大家整合起来吧:
game.h
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//用宏定义定义棋盘的行列数,便于以后的棋盘的修改
#define ROW 3
#define COL 3
//初始化棋盘
void Initboard(char board[ROW][COL], int row,int col);
//此处数组传参也可以写成int* board或int board[][]
//但必须把行列数也给传过去便于初始化
//打印棋盘
void Printboard(char board[ROW][COL], int row, int col);
//下棋:
//1.需要考虑玩家下棋与机器下棋
//2.需要考虑落子的位置合理性与是否被下过
void Playerdropsboard(char board[ROW][COL], int row, int col);//玩家下棋
void ComputerDropsboard(char board[ROW][COL], int row, int col);//电脑下棋
//判断当前棋盘情况,如是否已经结束
//结束的情况中在判断玩家胜还是电脑胜还是平局
char Judgeboard(char board[ROW][COL], int row, int col);
test.c
#define _CRT_SECURE_NO_WARNINGS 1
//三子棋
#include "game.h"
void menu()//打印菜单
{
printf("******************************\n");
printf("********** 1.Play **********\n");
printf("********** 0.Exit **********\n");
printf("******************************\n");
}
void game()
{
char Board[ROW][COL] = {0};
Initboard(Board,ROW,COL); //初始化棋盘
Printboard(Board, ROW, COL); //打印棋盘
char ret = 0;
while (1)
{
Playerdropsboard(Board, ROW, COL); //玩家下棋
Printboard(Board, ROW, COL); //玩家每下一次棋都应该打印一次棋盘便于玩家后续操作
ret = Judgeboard(Board, ROW, COL); //没当玩家或者电脑下完棋时都要判断一次棋盘情况。
if (ret != 'C') //如果返回‘C’则继续下棋,若返回其他值则直接跳出循环,之后再进行判断
break;
ComputerDropsboard(Board, ROW, COL); //电脑下棋
ret=Judgeboard(Board, ROW, COL);
if (ret != 'C') //同上
break;
Printboard(Board, ROW, COL); //电脑每下一次棋都应该打印一次棋盘便于玩家后续操作
}
//在循环之后判断是因为这样只需要判断一次,简便了代码
if (ret == '#')
printf("玩家胜利\n");
else if (ret == '*')
printf("电脑胜利\n");
else if (ret == 'Q')
printf("平局\n");
Printboard(Board, ROW, COL); //最后在打印一次棋盘,让玩家知道自己是怎么赢(输)的
}
int main()
{
srand((unsigned int)time(NULL));
int input = 1;
while(input)
{
menu();
printf("请输入选项: ");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("游戏结束\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
}
return 0;
}
game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
//初始化棋盘
void Initboard(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++)
{
board[i][j] = ' ';//使数组每一个都初始化为空格
}
}
}
//打印棋盘
void Printboard(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 (j != col-1) //此处用if语句来判断每列是需不需要打印“|”
{
printf(" %c |", board[i][j]);
}
else
{
printf(" %c ", board[i][j]);
}
}
printf("\n"); //每行打印完之后都要换行进入下一行的打印
if (i != row - 1) //判断是否为最后一行,若不是最后一行,则需要继续打印“---|”
{
//以下部分是作用为判断间隔行的每列需不需要打印“|”
int k = 0;
for (k = 0; k < col; k++)
{
if (k != col - 1)
{
printf("---|");
}
else
{
printf("---");
}
}
}
printf("\n"); //此处换行符的作用为打印完行与行之间的间隔之后的换行
}
}
//玩家下棋
void Playerdropsboard(char board[ROW][COL], int row, int col)
{
printf("请输入要落子的位置:");
int r = 0;
int c = 0;
while (1) //此处循环目的是确保玩家下完棋之后再让电脑下棋
{
scanf("%d %d", &r, &c);
if ((r >= 1 && r <= row) && (c >= 1 && c <= col)) //判断落子位置是否合理,即是否在棋盘内
{
if (board[r - 1][c - 1] != ' ') //判断落子位置是否已被下过
{
printf("该处已被下过,请重新落子\n");
}
else
{
board[r - 1][c - 1] = '#'; //玩家下棋为“#”
break; //玩家下完棋之后跳出循环,电脑再开始下棋
}
}
else
{
printf("选择位置错误,请重新落子\n");
}
}
}
//电脑下棋
void ComputerDropsboard(char board[ROW][COL], int row, int col)
{
int flag = 1; //此处该变量作用为确保电脑一定会落子
while (flag)
{
int r = rand() % 3;
int c = rand() % 3;
if (board[r][c] == ' ') //判断落子位置是否已被下过
{
board[r][c] = '*'; //电脑下棋为“*”
flag = 0; //当电脑落子是flag值为0则退出循环。若为落子则flag值为1,继续循环直到电脑落子为止
}
}
}
char Judgeboard(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][1] == board[i][2]) &&(board[i][0] != ' ')) || ((board[0][i] == board[1][i]) && (board[1][i] == board[2][i]) && (board[0][i] != ' ')))
{
return board[i][0];//返回的即为赢家所用的字符,便于以后判断谁胜利
}
}
if ((board[0][0] == board[1][1]) && (board[1][1] == board[2][2])&&(board[1][1] != ' '))//判断主对角线上的三个数
{
return board[1][1];
}
else if ((board[0][2] == board[1][1]) && (board[1][1] == board[2][0]) && (board[1][1]!= ' '))//若主对角线上无法判断输赢在判断副对角线
{
return board[0][2];
}
//若前面有胜利者则直接通过return返回,不会进行后续代码,若无胜利者才编译后续代码
//首先判断棋盘有没有被下满
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
if (board[i][j] == ' ')
{
return 'C';//遍历数组当数组中还有' ',即棋盘还没有被下满要继续下棋
}
}
}
return 'Q';
}
运行之后应为这样:
由于截图距离有限,因此只能截下面一部分。
最后,感谢观看!