实验二 结对编程(阶段二)
一、实验目标
1、体验敏捷开发中的两人合作。
2、进一步提高个人编程技巧与实践。
二 、实验内容
1、根据以下问题描述,练习结对编程(pair programming)实践;
2、要求学生两人一组,自由组合。每组使用一台计算机,二人共同编码,完成实验要求。
3、要求在结对编程工作期间,两人的角色至少切换 4 次;
4、编程语言不限,版本不限。建议使用 Python 或 JAVA 进行编程。
三、实验过程
1、代码规范
👉 参考了几篇相关博客,总结出适用于本次编程的部分c语言规范;
(1)函数定义
每个函数的定义和说明应该从第一列开始书写。函数名(包括参数表)和函数体的花括号应该各占一行。在函数体结尾的括号后面可以加上注释,注释应该包括函数名,这样比较方便进行括号的配对检查,也可以清晰地看出函数是否结束。
(2)空格与空行的使用
空行:空行起着分隔程序段落的作用,可以使程序的布局更加清晰;两个相对独立的程序块、变量说明之后必须要加空行;
空格:函数的变量说明与执行语句之间加上空行,每个函数内的主要功能块之间加空行表示区隔;
(3)缩进与折行
缩进:根据语句间的层次关系采用缩进格式书写程序,每进一层,往后缩进一层;有使用Tab键和采用4个空格两种方式,但要注意统一,不要混用;
折行:每行的长度不要超过80个字符,当程序行太长时,应该分行书写;当把1行的内容分成几行写时,操作符号应该放在行末且分行时应该按照自然的逻辑关系进行;
(4)嵌套语句(语句块)的格式
对于嵌套式的语句--即语句块(如,if、while、for、switch等)应该包括在花括号中,花括号的左括号应单独占一行,并与关键字对齐。建议即使语句块中只有一条语句,也应该使用花括号包括,这样可以使程该序结构更清晰,也可以避免出错。建议对比较长的块,在末尾的花括号后加上注释以表明该语言块结束;
(5)注释
C语言中使用一组(/* … */)作为注释界定符,注释内容尽量用英语方式表述,应该出现在要说明的内容之前,而不应该出现在其后;而且除了说明变量的用途和语言块末尾使用的注释,尽量不使用行末的注释方式。注意:虽然注释有助于理解代码,但注意不可过多地使用注释。
(6)命名规范
常量、变量命名:可以选择有意义的英文(小写字母)组成变量名,使人看到该变量就能大致清楚其含义;不要使用人名、地名和汉语拼音。如果使用缩写,应该使用那些约定俗成的,而不是自己编造的。多个单词组成的变量名,除第一个单词外的其他单词首字母应该大写;如果符号常量由多个单词构成,两个不同的单词之间可以用下划线连接;符号常量的命名用大写字母表示;
函数命名:对于一个比较长的程序段落,应该加注释予以说明。如果设计文档中有流程图,则程序中对应的位置应该加注释予以说明。如果程序中使用了某个复杂的算法,建议注明其出处。如果在调试中发现某段落容易出现错误,应该注明;
参考链接:https://www.cnblogs.com/lynnwayne/archive/2007/07/11/814673.html
2、程序的总体设计
我和队友在开始编程的时候,提出了两种大方案:一个是仅关注细胞本身,计算活细胞周围活细胞的数量,从而打印出 n 代后的活细胞状态;另一个是将整个地图上的方格用二维数组表示,每一个方格都是一个细胞,或生或死;经讨论,决定使用方案二;
(1)读取初始地图并打印
事先用文本文件设置一个地图文本文件,在文本文件中,* 号表示活细胞,其余符号数字均表示死细胞;这一部分主要是进入该文本文档,读取初始地图再将该地图初始化为用方格表示的地图形式;
- Readmap()
- Initshowmap()
(2)计算新的地图
每输入一次指定字符,地图更新一次;第一个地图为当前地图,以当前地图为基准,计算下一个地图的状态(这里的计算很复杂,分普通情况和特殊情况);
- Run()
- Calculate()
(3)更新并打印新的地图
计算出下一个地图的状态后,就调用Renewmap()函数对地图进行更新,并将该更新后的地图打印出来。此处需要用到Windows API的知识,也就是对控制台上坐标的修改,后面会详细对它进行说明;
- Pos()
- Renewmap()
- Print()
3、程序结对编程过程及功能实现情况
(1)part 1
第一次交互,我写代码,老李负责审查,我们利用的QQ上的屏幕分享,在我写代码的时候,老李就通过语音对我进行指导。第一步,我就简单的写了函数的声明,两个人大致将生命游戏分了模块。实时进行屏幕分享,我写的代码含义老李都知道,也避免了我手动敲很多的注释说明;
我和老李提前约定好了每一个人提交的commit,统一标记为xxx的第x次commit,方便后续的查看;但是我们也忽略了一方面:这样统一了commit 的标记,没有实质性的将自己对代码的修改体现出来,最后也没办法清楚的看到哪个人对代码做出的修改;
(2)part 2
这一部分,前一阶段李宏丽负责写代码,我负责审查;后一阶段我负责编写,李宏丽负责审查,通讯工具还是qq的屏幕分享(好用!)。在这一阶段,我们开始实实在在的编写每个模块的具体功能了,先从第一部分开始进行编写;在这一部分,用到了readmap()函数读取地图,地图采用的是一个文本文件,刚开始设定的地图大小是20x20,所以在文本文件中是有数字和符号*组成的20x20的矩阵,*代表活细胞,数字代表死细胞;在这个函数中,先读取指针打开初始地图文本文件,利用两个循环读取文件的内容,利用两个数组判定某位置的细胞是否为生,生为1,死为0;
initshowmap()函数作用是初始化地图,根据上面的二维数组的内容,0为白色方块,1为黑色方块;
代码差不多完成后,我俩发现总是会报错,打不开我们提前准备好的文本文件,编译运行时显示异常;所以又加上了异常处理,判断文本文件是否存在;
这里用到了 access 函数,该函数的作用是确定文件或文件夹的访问权限。即,检查某个文件的存取方式,比如说是只读方式、只写方式等。如果指定的存取方式有效,则函数返回0,否则函数返回-1。
👉 Readmap()
int ReadMap() { FILE *fp; //利用指针打开事先准备好的文本文件 int i = 0,j = 0; char ch = '\0'; //先判断地图文件是否存在,存在则继续,否则直接返回-1 if(access("LifeGameMap.txt",0)) { printf("地图文件不存在\n"); return -1; } fp = fopen("LifeGameMap.txt","r"); for(i = 0;i < M;i++) { //读完一整行 for(j = 0;j < N;j++) { fscanf(fp,"%c",&ch); if('*' != ch) { Map1[i][j] = 0;//nowmap[][] Map2[i][j] = 0;//lastmap[][] } else {//设置为1,方便后期计算周围活细胞个数(直接累加就行) Map1[i][j] = 1; Map2[i][j] = 1; } } //一行最后有一个换行符 fscanf(fp,"%c",&ch); } return 0; }
👉 Initshowmap()
void InitShowMap(int thisMap[M][N]) { int i = 0,j = 0; for(i = 0;i < M;i++) { for(j = 0;j < N;j++) { if(0 == thisMap[i][j]) printf("□"); else printf("■"); } printf("\n"); } printf("请按Y(y)继续进化:"); }
(3)part 3
这一部分我和老李也相互转变了几次身份,主要原因就是计算周围活细胞的Calculate()太难编写了。
👉 规则:一个细胞周围共有8个细胞
a. 如果一个细胞周围有3个细胞为生,则该细胞为生(即该细胞若原先为死,则转为生,若原先为生,则保持不变) 。
b. 如果一个细胞周围有2个细胞为生,则该细胞的生死状态保持不变;
c. 在其它情况下,该细胞为死(即该细胞若原先为生,则转为死,若原先为死,则保持不变)
👉 我们的设想:
我们事先将活细胞赋值为1,死细胞赋值为0。count 的值是周围八个细胞的值之和,这样一来,有n个活细胞,count 的值为 n ,直接用二维数组计算就行;
这个设想的不足是没有考虑到特殊情况,直接对遍历细胞周围的八个细胞,却没考虑到有的细胞周围没有八个细胞。
👉 改良后:
void Calculate(int nowMap[M][N],int lastMap[M][N]) { int i = 0,j = 0,count = 0; for(i = 0;i < M;i++) { for(j = 0;j < N;j++) { count = 0; //特殊情况(第一排、最后一排、左边中间、右边中间、正中间)特殊计算,否则计算四周 if(0 == i) { //在第一排 if(0 == j) { count = nowMap[i][j+1]+nowMap[i+1][j]+nowMap[i+1][j+1]; } else if(j == N-1) { count = nowMap[i][j-1]+nowMap[i+1][j]+nowMap[i+1][j-1]; } else { count = nowMap[i][j-1]+nowMap[i+1][j-1]+nowMap[i][j+1]+nowMap[i+1][j]+nowMap[i+1][j+1]; } } else if(M-1 == i) { if(0 == j) { count = nowMap[i][j+1]+nowMap[i-1][j]+nowMap[i-1][j+1]; } else if(j == N-1) { count = nowMap[i][j-1]+nowMap[i-1][j]+nowMap[i-1][j-1]; } else { count = nowMap[i][j-1]+nowMap[i-1][j-1]+nowMap[i][j+1]+nowMap[i-1][j]+nowMap[i-1][j+1]; } } else if(0 == j) //左边中间的情况 count = nowMap[i-1][j]+nowMap[i-1][j+1]+nowMap[i][j+1]+nowMap[i+1][j+1]+nowMap[i+1][j]; else if(N-1 == j) count = nowMap[i-1][j]+nowMap[i-1][j-1]+nowMap[i][j-1]+nowMap[i+1][j-1]+nowMap[i+1][j]; else count = nowMap[i-1][j]+nowMap[i-1][j-1]+nowMap[i][j-1]+nowMap[i+1][j-1]+nowMap[i+1][j]+nowMap[i-1][j+1]+nowMap[i][j+1]+nowMap[i+1][j+1]; //根据count的结果来判断新的地图中细胞的生死 if(3 == count) lastMap[i][j] = 1; else if(2 == count) lastMap[i][j] = nowMap[i][j]; else lastMap[i][j] = 0; } } }
(3)part 4
在这一部分我们编写了三个函数,其中 pos()函数参考了某个博主贪吃蛇的代码;Renewmap()函数的作用是更新地图。接收到我们输入的y字符,就更新地图。我们需要两个地图,对前一个地图的细胞周围活细胞进行计算,根据计算所得到的结果决定下一个地图是什么样的,根据当前地图计算下一个地图的状态,首先我们要判断某地图是否为当前地图,这里我们用flag来表示,以第一个地图为基准,计算第二个地图的状态,这时第二个地图变成了当前地图,flag就要做出相应的改变。计算好下一个状态的地图后用Print()函数打印输出;
👉 Renewmap()
void RenewMap() { if(1 == thisFlag)//nowmap { Calculate(Map1,Map2); thisFlag = 2; } else { Calculate(Map2,Map1); thisFlag = 1; } }
(5)part 5
这一部分我们对已经编写好的代码进行了规范处理,对部分难理解的语句进行注释,修改了一些小问题;
(6)运行结果截图
4、项目github地址
地址:https://github.com/xxx-y/life-game
5、实验总结
(1)贴两张我俩交互的截图,纪念一下本次合作 🎉
(2)对于GitHub操作:通过本次实验,我再次学习了Github的相关知识(其实是距离上一次的学习太久了,忘得差不多了 🐷)第二次的学习,是理论加上实践同时进行的,让我注意到了许多理论学习时没有注意到的地方,上一次学习时写的博客也给了我很大的帮助,毕竟是自己编写的还是有点印象的。特别时后面两人合作的相关操作,不结合实践,根本不能注意细节上的操作;在这里贴一下操作过程我总是出错的地方;
❓ 添加远程库时,出现“remote: Permission to Kill/GitWork.git denied to Kill.”
👉 提交对象弄错,把提交地址写错,应该是git方式,误写成htts方式了。正确的指令:$ git remote add origin xxxx.git
❓ 输入$ git push origin master 提示出错信息:error:failed to push som refs to .......
👉
(3)对于本次实验编写的代码:没有实现细胞自动生成,需要手动输入字符,只能控制细胞一代一代的衍生;而且,最终的实验结果界面不是很美观,比起一个完备的小游戏还差的很远,希望以后有机会能够将代码继续完善;
(4)对于结对编程:在实验过程中,两个人合作确实能更有效率,沟通交流真的很重要。十分感谢我的 partner 老李在实验过程中对我的帮助;前一段时间无心做实验,总是拖拖拉拉的,也是老李不厌其烦的提醒我(不过这个人确实很啰嗦),感谢感谢,你是最棒的小可爱~