1. 介绍
今天就带大家用C语言实现一下简洁版本的三子棋,三子棋也称为井字棋。
我们先看看三子棋的预期效果:
这里*代表玩家的棋子,#代表电脑的棋子。
我们通过观察发现,打印出的棋盘是一片空白,表面上看着没有数据,实际上打印的是字符数组,字符中存放的是空格。
注意:当我们想要完成一个比较大的项目时,可采取模块化处理。即:将函数的声明放在头文件中。将函数的定义和实现放在源文件里,不论该函数有多么小。
2. 准备工作
作者在写三子棋时将整个工程分为了三个部分:
- test.c
- game.c
- game.h
- test.c源文件用于测试三子棋代码,程序从test.c文件开始执行。
- game.c文件中存放与游戏实现相关函数的定义和实现
- game.h文件中存放与游戏实现相关的函数的声明、
3. 棋盘相关操作
通过前面我们了解到,所谓的下棋操作本质上是对字符数组中的元素进行操作,通过改变字符数组中的元素的值再将其打印出来,达到下棋效果。但是棋盘是含有横纵坐标的。如果我们定义一维字符数组,则无法方便的表示棋盘中的横纵坐标。综合考虑,二维字符数组比较符合要求。
因为棋盘是三行三列的布局,所以先创建一个二维字符数组:
char board[3][3] = {0};
但是经过思索,这里创建数组时,直接指定数组的行数和列数是不太稳妥的,因为万一我想实现一个6*6或其他参数类型的棋盘,再从这里修改就不太方便。所以,我们可以使用#define定义常量来控制数组的行数和列数,这样我们就可以很方便的修改棋盘的参数了。如下:
#define ROW 3//行 #define COL 3//列 char board[ROW][COL] = {0};//创建一个3*3的数组
通常而言,#define 定义常量这种语句一般放置于头文件中。
棋盘创建好了之后,首先将其每个元素初始化成空格。初始化成功之后,最好将棋盘打印出来,看是否初始化成功。
//初始化棋盘 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 DisplayBoard(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++) { printf("%c", board[i][j]); } printf("\n"); } }
但是这种方式的效果不直观,一片空白。我们可以为棋盘设计一个轮廓,使其可视化。先来看看棋盘的规律:
先看每一行的打印,整个棋盘有三行,每一行的打印是一组,整个棋盘按行就被分为了三组,每一组可用绿色和橙色部分表示,只不过最后一组的橙色部分没有打印而已。所以通过分析可以通过数组的行数控制分组数。
先对绿色部分进行分析:绿色部分先打印三个空格,再打印一个竖线,继续打印三个空格,再打印一个竖线,继续打印三个空格,最后一个空格不打印。
橙色部分:先打印三个减号,在打印一个竖线,继续打印三个减号,再打印一个竖线,继续打印三个减号,最后一个竖线不打印。
注意:每一组打印完之后要记得换行。
代码实现:
//打印棋盘 版本二 void DisplayBoard(char board[ROW][COL], int row, int col) { int i = 0; for (i = 0; i < row; i++)//控制行数 { printf(" %c | %c | %c ", board[i][0], board[i][1], board[i][2]); printf("\n"); if (i < row - 1) { printf("---|---|---"); printf("\n"); } } }
但是这样就算成功了吗?
我们将ROW和COL改成5试试呢:
我们发现,棋盘虽然变成5行,但是列数并没有发生改变。
同理,在之前的基础上,我们也可以对列数进行分析:
整个棋盘按每一行分,也能将每一行的打印分为三部分,每部分由绿色和橙色组成。因为这个棋盘有三列,所以每一行的打印可以分为三组,假设是五列,就可以分为五组,那么每一行的打印就可通过列数来控制了。
通过观察可以发现:每一行要打印的绿色部分(三个空格和三个减号)的次数就是该棋盘的列数,每一行要打印的橙色部分(竖线部分)的次数就是该棋盘的列数-1次。所以我们可以将代码再改进:
//打印棋盘 版本三 void DisplayBoard(char board[ROW][COL], int row, int col) { int i = 0; for (i = 0; i < row; i++)//控制行数 { //每一行的打印 %c | %c | %c 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"); } } }
4.下棋流程
棋盘打印好了就可以开始下棋了。
【玩家下棋】
首先让玩家下棋,玩家输入落子的坐标,再判断该坐标是否符合要求,符合要求就落子,若不符合要求就重新输入坐标。
注意:在玩家下棋时,由于不确定玩家是不是程序员,所以就不知道横纵坐标实际上是从0开始的。我们就设计成横纵坐标从1开始,在判断合法性时,稍作处理就好。在本游戏中x,y分别代表行号和列号,并不是严格的数学上的横纵坐标,例如3,6代表的是3行6列。
//玩家下棋 void Player(char board[ROW][COL], int row, int col) { int x = 0; int y = 0; printf("请玩家输入你的落子坐标>:(使用空格隔开)\n"); 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"); } } else { printf("你输入的坐标有误,请重新输入!\n"); continue; } } }
【电脑下棋】
电脑下棋同理,让电脑生成相应数组下标范围内的随机数,再判断该下标的元素是否为空格(即未被占用),为空格则在该坐标落子,若不为空格,重新生成随机数,直到生成的随机数满足要求为止。
void Computer(char board[ROW][COL], int row, int col)//电脑下棋 { int x = 0; int y = 0; while (1) { x = rand() % 3; y = rand() % 3; if (board[x][y] == ' ') { board[x][y] = '#'; break; } } }