实验二 结对编程(阶段二)

一、实验目标    

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 老李在实验过程中对我的帮助;前一段时间无心做实验,总是拖拖拉拉的,也是老李不厌其烦的提醒我(不过这个人确实很啰嗦),感谢感谢,你是最棒的小可爱~

posted @ 2020-03-30 19:18  小张不拖延  阅读(496)  评论(2编辑  收藏  举报