从零开始编写C语言五子棋程序
C语言程序是国科大计算机系本科生必修课程,我选修的是武成岗老师的C语言课程。除了课上回答问题,实验课competitive programming的成绩之外,最终的大作业五子棋程序也占据了相当重的分值。除了写出基本的显示棋盘、人人对战、人机对战的功能之外,武老师对人机对战的策略提出了更高的要求。期末时每个人所写的AI会进行1对1的比赛,五子棋作业的分数与比赛排名直接挂钩(卷,就嗯卷)。开这个坑的原因有两点,一是记录自己写这个程序的过程(可能是我目前写过的代码最长的程序),理清思路同大家分享,另一方面想熟悉一下刚学的Markdown排版。(试图摆脱折磨人的公众号内部编辑)今天要解决的是第一部分,五子棋棋盘的实现。
五子棋棋盘的实现
解决这个问题可以有两种思路:
- 利用双重循环首先构建出一个空棋盘,接着用户根据棋盘上显示的坐标实现下棋的操作,只需要把输入坐标所对应的字符替换成黑/白棋子即可。
- 构建三个棋盘:第一个棋盘用于存放空棋盘的模板,第二个棋盘是用于在终端显示的棋盘(交互棋盘),第三个棋盘是所谓“内部棋盘”,用户输入的坐标改变的是内部棋盘上的数值,这一数值的改变反映到交互棋盘上就是黑/白棋子。
两种思路其实都可以实现棋盘的功能,在这里,我为了减轻后期调试工作的工作量,选择采取第二种方式来构建棋盘。
构建棋盘的过程中,最关键的就是二维数组的运用。二维数组和内部棋盘上的点建立起了一一映射的关系,通过改变二维数组中元素的值,即可改变棋盘上的落子情况。二维数组的相关知识,详见多维数组。
有了这个基本思想,下面我们就可以开始书写代码了。
- 宏定义与全局变量的声明
#include #include #include #define SIZE 15#define CHARSIZE 2void initRecordBorard(void);void innerLayoutToDisplayArray(void);void displayBoard(void);//棋盘使用的是GBK编码,每一个中文字符占用2个字节。//空棋盘模板 char arrayForEmptyBoard[SIZE][SIZE*CHARSIZE+1] = { "┏┯┯┯┯┯┯┯┯┯┯┯┯┯┓", "┠┼┼┼┼┼┼┼┼┼┼┼┼┼┨", "┠┼┼┼┼┼┼┼┼┼┼┼┼┼┨", "┠┼┼┼┼┼┼┼┼┼┼┼┼┼┨", "┠┼┼┼┼┼┼┼┼┼┼┼┼┼┨", "┠┼┼┼┼┼┼┼┼┼┼┼┼┼┨", "┠┼┼┼┼┼┼┼┼┼┼┼┼┼┨", "┠┼┼┼┼┼┼┼┼┼┼┼┼┼┨", "┠┼┼┼┼┼┼┼┼┼┼┼┼┼┨", "┠┼┼┼┼┼┼┼┼┼┼┼┼┼┨", "┠┼┼┼┼┼┼┼┼┼┼┼┼┼┨", "┠┼┼┼┼┼┼┼┼┼┼┼┼┼┨", "┠┼┼┼┼┼┼┼┼┼┼┼┼┼┨", "┠┼┼┼┼┼┼┼┼┼┼┼┼┼┨", "┗┷┷┷┷┷┷┷┷┷┷┷┷┷┛"};//此数组存储用于显示的棋盘 char arrayForDisplayBoard[SIZE][SIZE*CHARSIZE+1];//此数组用于记录当前的棋盘的格局,即所谓“内部棋盘”int arrayForInnerBoardLayout[SIZE][SIZE];char play1Pic[]="●";//黑棋子;char play2Pic[]="◎";//白棋子;
- 主函数编写
int main(){ initRecordBorard(); //初始化一个空棋盘 arrayForInnerBoardLayout[0][0]=1; //在棋盘的左上角落一个黑色棋子 innerLayoutToDisplayArray(); //将心中的棋盘转成用于显示的棋盘 displayBoard(); //显示棋盘 getchar(); arrayForInnerBoardLayout[5][9]=2; innerLayoutToDisplayArray(); displayBoard(); getchar(); arrayForInnerBoardLayout[3][4]=2; innerLayoutToDisplayArray(); displayBoard(); getchar(); arrayForInnerBoardLayout[6][1]=1; innerLayoutToDisplayArray(); displayBoard(); getchar(); arrayForInnerBoardLayout[9][4]=2; innerLayoutToDisplayArray(); displayBoard(); getchar(); return 0;}//内部棋盘里的数组元素若为0,表示该位置为空,若为1,表示该位置落的是黑子,若为0,表示该位置落的是白子。//这是一个测试性的主函数(由于只是单纯想构建出棋盘来),整体操作分三步:落子——内部棋盘和交互棋盘的映射——显示出当前的交互棋盘棋盘的初始化
- 棋盘的初始化
//初始化一个空棋盘格局 void initRecordBorard(void){ //通过双重循环,将arrayForInnerBoardLayout清0 int i,j; for(i = 0;i<= SIZE-1 ;i++){ for(j = 0;j<= SIZE-1;j++) arrayForInnerBoardLayout[i][j] = 0; }}
- 内部棋盘和交互棋盘一一映射的构建
//将arrayForInnerBoardLayout中记录的棋子位置,转化到arrayForDisplayBoard中void innerLayoutToDisplayArray(void){ //第一步:将arrayForEmptyBoard中记录的空棋盘,复制到arrayForDisplayBoard中 int i,j; for(i = 0;i <= SIZE - 1;i++){ for(j = 0;j <=SIZE*CHARSIZE+1;j++) arrayForDisplayBoard[i][j] = arrayForEmptyBoard[i][j]; } //第二步:扫描arrayForInnerBoardLayout,当遇到非0的元素,将●或者◎复制到arrayForDisplayBoard的相应位置上 //注意:arrayForDisplayBoard所记录的字符是中文字符,每个字符占2个字节。●和◎也是中文字符,每个也占2个字节。 for( i = 0;i <= SIZE-1;i++){ for( j = 0; j <= SIZE-1;j++){ if(arrayForInnerBoardLayout[i][j]!=0){ if(arrayForInnerBoardLayout[i][j]==1){ arrayForDisplayBoard[i][2*j] = play1Pic[0]; arrayForDisplayBoard[i][2*j+1] = play1Pic[1]; }else if(arrayForInnerBoardLayout[i][j]==2){ arrayForDisplayBoard[i][2*j] = play2Pic[0]; arrayForDisplayBoard[i][2*j+1] = play2Pic[1]; } } } } }//这里涉及一个我之前没有接触过的知识点,将双字节字符赋值到字符数组里,要用连续的两个元素来进行赋值。
- 显示当前棋盘
//显示棋盘格局 void displayBoard(void){ int i; //第一步:清屏 system("clear"); //清屏 //第二步:将arrayForDisplayBoard输出到屏幕上 for(i=0;i<=SIZE-1;i++) printf("%3d %s\n",SIZE-i,arrayForDisplayBoard[i]); //第三步:输出最下面的一行字母A B .... printf(" "); for(i = 0;i<=SIZE-1;i++) printf("%2c",'A'+i); printf("\n");}
至此,我们已经完成了棋盘构建的全部工作,下面是程序的运行结果。
接下来的目标(周五前完成):
- 胜负的判定
- 先后手
- 记录上一次棋子的位置
- 输入quit退出游戏