前言
一个简单的三子棋游戏是怎样实现的呢?三子棋的实现过程究竟有我们想象的那样复杂吗?
其实,在我们学完C语言的函数和数组的知识后,我们完完全全就可以根据函数和数组的知识设计一个简单的三子棋游戏了!那么究竟要怎么实现呢?本篇文章就将为你逐步答疑解惑,快来看看吧!
1.实现思路
依照我们在初识C语言3中的猜数字游戏,我们可以想象,一个三子棋游戏的实现会有如下流程:
1.设计开始菜单(玩家选择是否进入游戏,1为开始游戏,0为退出游戏);
2.创建棋盘(创建一个3×3的棋盘);
3.初始化棋盘为空格(刚开始游戏,毋庸置疑要创建一个没有任何棋子的棋盘);
4.打印棋盘(向玩家呈现棋盘布局);
5.下棋(玩家和电脑分别落子);
6.判断胜负与平局;
7.游戏结束,再次显示菜单
2.模块化编程
2.1什么是模块化编程
模块化编程就是把我们的一整个项目,分装成很多独立的模块(比如写一个计算器的程序,我们就可以将其分为加法、减法、乘法、除法等模块)
我们就可以将每个功能分装成一个个函数一一去实现,然后再使用主函数引用我们已经定义完成的函数进行测试,如果程序出现bug,我们就可以直观地找到问题出现在哪个模块。
再将我们定义完成的函数放在实现文件game.c中,将测试功能的主函数放在测试文件test.c中,将程序需要调用的库函数、外部函数的声明存放在game.h中避免重复调用。
2.2为什么要使用模块化编程呢?
查看 1 中的实现流程,如果没有了解过模块化编程,我们可能会把所有代码都写在一个test.c文件里,以上的功能我都分装成一个个函数,一个文件从头写到尾实现以上所有功能,写完之后发现,我们为了完成这个小小的三子棋游戏竟然已经写了几百行代码了,甚至以后一个程序我们可能会写上千行、上万行的代码。
这时一经调试发现漏洞百出,几百行的程序竟然出现了50个bug!我们再一行行的找bug,因为代码太多太繁杂了,我们很难有一个清晰的分类,这就导致了代码太多太臃肿,并且也大大降低了我们代码的可读性。
如果这时候采用模块化编程,每一个函数只实现一个功能,每一个文件中存放相同作用的程序;(即:函数的声明放在game.h文件里,函数的定义放在game.c文件里,主函数以及函数的引用都放在test.c文件里)
模块化编程可以提高代码的可维护性、可移植性、可阅读性、可测试性;
3.功能实现(各个功能函数的实现)
3.1菜单函数
一个游戏的开始要有最基本的开始菜单栏,从菜单栏上可以令玩家了解到游戏的最基本信息以及让玩家选择是否要进入游戏,于是我们便可以这样设计菜单栏:
void menu() { printf("******************************\n"); printf("******** 1.开始游戏 ********\n"); printf("******** 0.退出游戏 ********\n"); printf("******************************\n"); }
3.2创建棋盘(创建一个3行3列的二维数组)
创建一个3×3的棋盘,实质上就是创建一个3行3列的二维数组,很简单,我们可以直接这样写:char board[3][3]={0};
这样写当然是对的,可是我们有没有想过,我们现在写的是三子棋,我们之后希望继续使用这个代码来写一个四子棋、五子棋......的游戏,这时候就需要我们把char board[3][3]={0};中的3改为4、5...这样来改就需要在几百行代码中找到所有的char board[3][3]={0};依次来改,这样未免也太繁琐了吧!
那么有没有更简便的方法呢?没错!答案当然是——有的!回顾往期文章,我们在初识C语言1——3.3.2中就提到过#define定义的标识符常量,这时候这个知识就派上用场了!
即:我们可以用ROW与COL分别代表行和列,创建char board[ROW][COL]={0}数组,然后使用宏定义#define定义ROW与COL为标识符常量,具体代码如下:
为了便于修改标识符常量,我们将其放在函数声明文件里(game.h):
#define ROW 3 #define COL 3
如上:此时的ROW与COL就代表3,之后改变棋盘大小(数组大小)直接修改数字即可
创建棋盘放在函数调用文件里(test.c):
char board[ROW][COL] = { 0 };//用二维数组创建棋盘
3.3初始化棋盘为空格(初始化函数)
刚进入游戏毋庸置疑要给玩家呈现一个没有任何棋子的棋盘,也就是初始化棋盘为空格,实现代码如下:
函数声明(game.h):
//初始化棋盘声明 void InitBoard(char board[ROW][COL], int row, int col);
函数定义(game.c):
//初始化棋盘函数的实现: //初始化棋盘为空格 void InitBoard(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++) { board[i][j] = ' '; } } }
3.4打印棋盘
不管是刚开始游戏还是玩家落子亦或是电脑落子,每一个环节结束之后我们当然都要输出已经完成的操作,展示棋盘,以此来帮助玩家判断局势,选择下一个落子坐标
可是我们已经初始化了棋盘为空格,如果直接输出你会发现我们的输出结果为这样:
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("%d", board[i][j]); } printf("\n"); } }
都是空白,看不到棋盘,这显然是不合理的,于是我们希望有如图一样的分割线来直观的帮助我们展示棋盘
具体代码如下:
函数声明(game.h):
//打印棋盘声明 void DisplayBoard(char board[ROW][COL], int row, int col);
函数定义(game.c):
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]); if (j < col - 1) { printf("|"); } } printf("\n"); if (i < col - 1) { int j = 0; for (j = 0; j < col; j++) { printf("---"); if (j < col - 1) { printf("|"); } } printf("\n"); } } }
3.5 下棋(落子函数)
就像玩五子棋一样,双方各执黑白颜色的棋子,我们设计的三子棋游戏也是同样的道理,所以在游戏开始之前我们先为玩家与电脑分配棋子
例如:我们使玩家落子为‘O’,电脑落子为‘X’
3.5.1玩家落子函数:
玩家下棋时,我们需要指引玩家输入棋盘对应的坐标来落子,可是我们知道,数组的首坐标是从0开始的,可是我们的玩家大多都不是程序员,当然习惯认为首坐标为1。
所以我们在设计程序时当然要考虑这个因素,具体代码实现如下:
函数声明(game.h):
//玩家下棋函数声明 void Player(char board[ROW][COL], int row, int col);
函数定义(game.c):
//玩家下棋函数的实现: void Player(char board[ROW][COL], int row, int col) { int x = 0; int y = 0; printf("玩家下棋:\n"); while (1) { printf("请输入你要落子的坐标(中间用空格隔开):\n"); 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] = 'O'; break; } else { printf("坐标已被占用,请重新输入:\n"); } } else { printf("坐标非法,请重新输入:\n"); } } }
3.5.2电脑落子函数:
电脑落子与玩家落子也是相同的道理,也需要电脑输入坐标,可是电脑要怎么来输入坐标呢?此时就不得不用到随机数的生成了,也就是让电脑生成两个随机数,然后再让此随机数充当电脑落子坐标。
(电脑生成随机数原理和方法在这里不再详细赘述,具体请看往期博文初识C语言3——函数(以猜数字游戏为例))
具体代码如下:
函数声明(game.h):
//电脑下棋函数声明 void Computer(char board[ROW][COL], int row, int col);
函数定义(game.c):
//电脑下棋函数的实现: void Computer(char board[ROW][COL], int row, int col) { int x = 0 ; int y = 0 ; printf("电脑落子:\n"); while (1) { x = rand() % row; y = rand() % col; if (board[x][y] == ' ')//判断坐标是否已被占用 { board[x][y] = 'X'; break; } } }
3.6判断胜负与平局
在函数内部判断输赢,然后返回一个字符。返回'O'则代表玩家赢,返回'X'代表电脑赢,然后'P'代表平局,返回'C'代表继续游戏。
在测试文件(test.c)中,只需要在调用判断输赢函数之后,接收返回值,判断返回值是什么,即可决定游戏结束还是继续下棋。
函数声明(game.h):
//判断谁输谁赢函数的声明 char IsWin(char board[ROW][COL], int row, int col);
函数定义(game.c):
//判断棋盘是否充满函数的实现: int IsFull(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 (board[i][j] == ' ') { return 0; } } } return 1; } //判断谁输谁赢函数的实现: char IsWin(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][0] == board[i][2] && board[i][0] != ' ') { return board[i][0]; } } //判断列 for (i = 0; i < col; i++) { if (board[0][i] == board[1][i] && board[0][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[0][0] != ' ') { return board[0][0]; } //判断另一条对角线 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 'P'; } return 'C'; }
基于以上叙述,思考一下,下棋的过程是一个交替往复的过程:玩家落子-电脑落子-玩家落子-电脑落子......直到test.c接收的返回值不为'C'时则游戏结束,所以这一过程必定要用到循环语句
具体代码如下:
函数调用(test.c):
char ret = 0 ; while (1) { Player(board, ROW, COL);//玩家下棋 DisplayBoard(board, ROW, COL); ret = IsWin(board, ROW, COL); if (ret != 'C') { break; } Computer(board, ROW, COL);//电脑下棋 DisplayBoard(board, ROW, COL); ret = IsWin(board, ROW, COL); if (ret != 'C') { break; } } if (ret == 'O') { printf("恭喜你,你赢了!\n"); } else if (ret == 'X') { printf("很遗憾,你输了-_-\n"); } else { printf("平局!\n"); } }
3.7测试调用(test.c)
#include "game.h"; void menu() { printf("******************************\n"); printf("******** 1.开始游戏 ********\n"); printf("******** 0.退出游戏 ********\n"); printf("******************************\n"); } void game() { char board[ROW][COL] = { 0 };//用二维数组创建棋盘 InitBoard(board, ROW, COL);//初始化棋盘(初始化二维数组) DisplayBoard(board, ROW, COL);//打印棋盘 char ret = 0 ; while (1) { Player(board, ROW, COL);//玩家下棋 DisplayBoard(board, ROW, COL); ret = IsWin(board, ROW, COL); if (ret != 'C') { break; } Computer(board, ROW, COL);//电脑下棋 DisplayBoard(board, ROW, COL); ret = IsWin(board, ROW, COL); if (ret != 'C') { break; } } if (ret == 'O') { printf("恭喜你,你赢了!\n"); } else if (ret == 'X') { printf("很遗憾,你输了-_-\n"); } else { printf("平局!\n"); } } int main() { int input = 0; srand((unsigned int) time (NULL)); do { menu(); printf("这是一个三子棋游戏,1为开始游戏,0为退出游戏,请选择:\n"); scanf("%d", &input); switch (input) { case 1: game(); break; case 0: printf("退出游戏\n"); break; default: printf("选择错误,请重新选择:\n"); break; } } while (input); return 0; }
4.全部代码整合
game.h:
#define ROW 3 #define COL 3 #include <stdio.h> #include <stdlib.h> #include <time.h> //初始化棋盘声明 void InitBoard(char board[ROW][COL], int row, int col); //打印棋盘声明 void DisplayBoard(char board[ROW][COL], int row, int col); //玩家下棋函数声明 void Player(char board[ROW][COL], int row, int col); //电脑下棋函数声明 void Computer(char board[ROW][COL], int row, int col); //判断谁输谁赢函数的声明 char IsWin(char board[ROW][COL], int row, int col);
game.c:
#include "game.h" //初始化棋盘函数的实现: //初始化棋盘为空格 void InitBoard(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++) { 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]); if (j < col - 1) { printf("|"); } } printf("\n"); if (i < col - 1) { int j = 0; for (j = 0; j < col; j++) { printf("---"); if (j < col - 1) { printf("|"); } } printf("\n"); } } } //玩家下棋函数的实现: void Player(char board[ROW][COL], int row, int col) { int x = 0; int y = 0; printf("玩家下棋:\n"); while (1) { printf("请输入你要落子的坐标(中间用空格隔开):\n"); 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] = 'O'; break; } else { printf("坐标已被占用,请重新输入:\n"); } } else { printf("坐标非法,请重新输入:\n"); } } } //电脑下棋函数的实现: void Computer(char board[ROW][COL], int row, int col) { int x = 0 ; int y = 0 ; printf("电脑落子:\n"); while (1) { x = rand() % row; y = rand() % col; if (board[x][y] == ' ') { board[x][y] = 'X'; break; } } } //判断棋盘是否充满函数的实现: int IsFull(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 (board[i][j] == ' ') { return 0; } } } return 1; } //判断谁输谁赢函数的实现: char IsWin(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][0] == board[i][2] && board[i][0] != ' ') { return board[i][0]; } } //判断列 for (i = 0; i < col; i++) { if (board[0][i] == board[1][i] && board[0][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[0][0] != ' ') { return board[0][0]; } //判断另一条对角线 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 'P'; } return 'C'; }
test.c:
#define _CRT_SECURE_NO_WARNINGS 1 #include "game.h"; void menu() { printf("******************************\n"); printf("******** 1.开始游戏 ********\n"); printf("******** 0.退出游戏 ********\n"); printf("******************************\n"); } void game() { char board[ROW][COL] = { 0 };//用二维数组创建棋盘 InitBoard(board, ROW, COL);//初始化棋盘(初始化二维数组) DisplayBoard(board, ROW, COL);//打印棋盘 char ret = 0 ; while (1) { Player(board, ROW, COL);//玩家下棋 DisplayBoard(board, ROW, COL); ret = IsWin(board, ROW, COL); if (ret != 'C') { break; } Computer(board, ROW, COL);//电脑下棋 DisplayBoard(board, ROW, COL); ret = IsWin(board, ROW, COL); if (ret != 'C') { break; } } if (ret == 'O') { printf("恭喜你,你赢了!\n"); } else if (ret == 'X') { printf("很遗憾,你输了-_-\n"); } else { printf("平局!\n"); } } int main() { int input = 0; srand((unsigned int) time (NULL)); do { menu(); printf("这是一个三子棋游戏,1为开始游戏,0为退出游戏,请选择:\n"); scanf("%d", &input); switch (input) { case 1: game(); break; case 0: printf("退出游戏\n"); break; default: printf("选择错误,请重新选择:\n"); break; } } while (input); return 0; }