实验二 结对编程--第二阶段(更新)

一、实验目标
  1)体验敏捷开发中的两人合作。

  2)进一步提高个人编程技巧与实践。

 

二 、实验内容
  1)根据以下问题描述,练习结对编程(pair programming)实践;

  2)要求学生两人一组,自由组合。每组使用一台计算机,二人共同编码,完成实验要求。

  3)要求在结对编程工作期间,两人的角色至少切换 4 次;

  4)编程语言不限,版本不限。建议使用 Python 或 JAVA 进行编程。

 

三、实验过程

1、代码规范

(1)函数命名、 变量命名、 文件命名要有描述性,少用缩写,尽量做到见名知意。

(2)文件名要全部小写,可以包含下划线 (_) 或连字符 (-),依照项目的约定.。如果没有约定,,那么 “_” 更好。

(3)一般来说,函数名的每个单词首字母大写 (即 “驼峰变量名” 或 “帕斯卡变量名”), 没有下划线.。对于首字母缩写的单词, 更倾向于将它们视作一个单词进行首字母大写 。

(4)使用前置自增。不考虑返回值的话,前置自增 (++i) 通常要比后置自增 (i++) 效率更高。因为后置自增 (或自减) 需要对表达式的值 i 进行一次拷贝.。如果 i 是迭代器或其他非数值类型, 拷贝的代价是比较大的。

(5)不使用流(除非是日志接口需要),使用 printf 之类的代替。

(6)注释:// 或 /* */ 都可以,但 //  常用。要在如何注释及注释风格上确保统一。

(7)函数注释:基本上每个函数声明处前都应当加上注释,描述函数的功能和用途。只有在函数的功能简单而明显时才能省略这些注释。注释使用叙述式 (“Opens the file”) 而非指令式 (“Open the file”); 注释只是为了描述函数,而不是命令函数做什么。通常,注释不会描述函数如何工作,那是函数定义部分的事情。

 

2、总体设计

代码有四个主要功能模块:随机初始化、清屏、下一时刻以及输出0/1矩阵

实验二 结对编程--第二阶段(更新)_第1张图片

 

 各功能模块的实现如下图所示:
实验二 结对编程--第二阶段(更新)_第2张图片

 

 

实验二 结对编程--第二阶段(更新)_第3张图片

 

 

3、结对编程过程

对所选题目进行再次分析之后,我组决定使用C++在VC6.0上编写代码。

经讨论,我们组决定每人承担部分功能,编写代码时,由写代码的人开启屏幕分享。

实验二 结对编程--第二阶段(更新)_第4张图片

 

在编写print()函数时,我没有弄清楚i、j和width、height的对应关系,导致输出的矩阵大小与设定的大小不同,经杜蒙蒙同学提醒,将错误改正。同时注意到C++习惯于使用前置自增,而平时我们多使用后置自增。为更好符合代码规范,将原本写的后置自增改为前置自增。main()函数由杜蒙蒙同学编写,编写后运行无误,但是运行结果只能进行单次单步演化,为使功能更加完善,杜蒙蒙同学添加了switch,使得运行结果可以进行单次单步演化也可以进行多次单步演化。

 

 

4、主要功能模块

(1)随机初始化

    ① 实现代码

        实验二 结对编程--第二阶段(更新)_第5张图片

    ②运行结果

   实验二 结对编程--第二阶段(更新)_第6张图片

 

(2)下一时刻

      ①实现代码

        实验二 结对编程--第二阶段(更新)_第7张图片                                                                                                                                       

②运行结果

 实验二 结对编程--第二阶段(更新)_第8张图片

(3)输出矩阵

      ①实现代码

         实验二 结对编程--第二阶段(更新)_第9张图片

      ②运行结果

          如上图

 

5、提交到GitHub

此次试验GitHub仓库地址是https://github.com/cloudy-y/game_of_life

实验二 结对编程--第二阶段(更新)_第10张图片

 

我和杜蒙蒙同学对各自负责的模块进行了多次commit

实验二 结对编程--第二阶段(更新)_第11张图片

 

在其中一次pull request 时,发生了冲突,无法进行merge。经查阅资料发现,这是因为我们在相近的时间修改了相同的一段代码,导致在github中不能够自动merge。此时合并过程发生冲突, git 会把修改记录直接保存在文件中,让开发者判断文件如何解决合并。要解决冲突,只需要将发生冲突的文件中不需要的内容删掉,再分别执行①git commit -a②git checkout master③git merge 小伙伴-master即可。

 

四、实验小结

1、通过此次结对编程实验,加强了自己对于问题看法的表达能力,同时在小组讨论时思维更加集中、跳跃,效率得到很大提升,体会到了结对编程的好处。

2、对于git的操作更加熟练,也发现了GitHub的强大之处。

 

增加界面设计(更新)

我与杜蒙蒙同学提交在GitHub上的原始代码的输出结果,只能输出细胞的0/1矩阵,细胞的演化不够直观也不够美观。经讨论,决定增加界面。关于界面设计的主要代码如下

/* 消息回调函数 */
LRESULT CALLBACK WindowProc(HWND   hwnd,
    UINT   msg,
    WPARAM wParam,
    LPARAM lParam)
{
    //存储用户窗口的宽和高
    static int cxClient, cyClient;
    //界面字体宽高
    static int cxChar, cyChar;

    //用于创建后备缓冲
    static HDC      hdcBackBuffer;
    static HBITMAP  hBitmap;
    static HBITMAP  hOldBitmap;

    switch (msg)
    {
    case WM_CREATE:
        {
        RECT rect;

        GetClientRect(hwnd, &rect);

        cxClient = rect.right;
        cyClient = rect.bottom;

        //将窗口移动到屏幕中央
        int scrWidth, scrHeight;
        scrWidth = GetSystemMetrics(SM_CXSCREEN);
        scrHeight = GetSystemMetrics(SM_CYSCREEN);
        GetWindowRect(hwnd, &rect);
        MoveWindow(hwnd, (scrWidth - rect.right) / 2, (scrHeight - rect.bottom) / 2, rect.right - rect.left, rect.bottom - rect.top, FALSE);

        //创建世界和生物
        g_world = new CWorld(WORLD_WIDTH, WORLD_HEIGHT);
        DrawWorld(g_world, WORLD_WIDTH, WORLD_HEIGHT, GetDC(hwnd));
        CreateButton(hwnd, ((LPCREATESTRUCT)lParam)->hInstance);
        EnableWindow(GetDlgItem(hwnd, START_BTN_ID), TRUE); //启用开始按钮
        EnableWindow(GetDlgItem(hwnd, PAUSE_BTN_ID), FALSE);    //禁用暂停按钮
        EnableWindow(GetDlgItem(hwnd, NEXT_BTN_ID), TRUE);  //启用下一步按钮
        EnableWindow(GetDlgItem(hwnd, KILL_ALL_BTN_ID), TRUE);  //启用杀死所有按钮

        //后备缓冲区相关处理
        hdcBackBuffer = CreateCompatibleDC(NULL);
        HDC hdc = GetDC(hwnd);
        hBitmap = CreateCompatibleBitmap(hdc, cxClient, cyClient);
        hOldBitmap = (HBITMAP)SelectObject(hdcBackBuffer, hBitmap);
        //销毁处理
        ReleaseDC(hwnd, hdc);

        }
        break;
    case WM_COMMAND:    //按钮被按下后的响应
    {
        int button_id = LOWORD(wParam);
        switch (button_id)
        {
        case RANDOM_BTN_ID: //随机初始化按钮按下
            g_world->InitMap();
            DrawWorld(g_world, WORLD_WIDTH, WORLD_HEIGHT, GetDC(hwnd));
            break;
        case START_BTN_ID:  //开始按钮按下
            SetTimer(hwnd, WORLD_TIMER_ID, WORLD_TIMER_ELAPSE, WorldTimerCallBack); //启动计时器
            EnableWindow(GetDlgItem(hwnd, RANDOM_BTN_ID), FALSE);   //禁用随机生成按钮
            EnableWindow(GetDlgItem(hwnd, START_BTN_ID), FALSE);    //禁用开始按钮
            EnableWindow(GetDlgItem(hwnd, PAUSE_BTN_ID), TRUE); //启用暂停按钮
            EnableWindow(GetDlgItem(hwnd, NEXT_BTN_ID), FALSE); //禁用下一步按钮
            EnableWindow(GetDlgItem(hwnd, KILL_ALL_BTN_ID), FALSE); //禁用杀死所有按钮
            break;
        case PAUSE_BTN_ID:  //暂停按钮按下
            KillTimer(hwnd, WORLD_TIMER_ID);    //销毁计时器
            EnableWindow(GetDlgItem(hwnd, RANDOM_BTN_ID), TRUE);    //启用随机生成按钮
            EnableWindow(GetDlgItem(hwnd, START_BTN_ID), TRUE); //启用开始按钮
            EnableWindow(GetDlgItem(hwnd, PAUSE_BTN_ID), FALSE);    //禁用暂停按钮
            EnableWindow(GetDlgItem(hwnd, NEXT_BTN_ID), TRUE);  //启用下一步按钮
            EnableWindow(GetDlgItem(hwnd, KILL_ALL_BTN_ID), TRUE);  //启用杀死所有按钮
            break;
        case NEXT_BTN_ID:   //下一步按钮按下
            g_world->nextStep();
            DrawWorld(g_world, WORLD_WIDTH, WORLD_HEIGHT, GetDC(hwnd));
            break;
        case KILL_ALL_BTN_ID:   //杀死所有细胞按钮按下
            g_world->killAll();
            DrawWorld(g_world, WORLD_WIDTH, WORLD_HEIGHT, GetDC(hwnd));
            break;
        default:
            break;
        }
    }
        break;
    case WM_KEYUP:
        //按下Esc退出
        switch (wParam)
        {
        case VK_ESCAPE:
            PostQuitMessage(0);
            break;
        }
        break;
    case WM_PAINT:
        PAINTSTRUCT ps;
        BeginPaint(hwnd, &ps);
        //将后备缓冲区涂上黑色背景
        BitBlt(hdcBackBuffer,
                0,
                0,
                cxClient,
                cyClient,
                NULL,
                NULL,
                NULL,
                BLACKNESS);

        //描画世界
        DrawGrid(hdcBackBuffer, WORLD_WIDTH, WORLD_HEIGHT);
        DrawCell(g_world, hdcBackBuffer);
        BitBlt(ps.hdc, 0, 0, cxClient, cyClient, hdcBackBuffer, 0, 0, SRCCOPY);
        EndPaint(hwnd, &ps);
        break;
    case WM_SIZE:                                                  //(不要)
    {
        //变更窗口大小时的处理
        cxClient = LOWORD(lParam);
        cyClient = HIWORD(lParam);

        SelectObject(hdcBackBuffer, hOldBitmap);
        DeleteObject(hBitmap);
        HDC hdc = GetDC(hwnd);
        hBitmap = CreateCompatibleBitmap(hdc,
            cxClient,
            cyClient);

        ReleaseDC(hwnd, hdc);
        SelectObject(hdcBackBuffer, hBitmap);
    }
    break;
    case WM_DESTROY:
        //销毁世界
        delete g_world;

        //清除并销毁后备缓冲区
        SelectObject(hdcBackBuffer, hOldBitmap);
        DeleteDC(hdcBackBuffer);
        DeleteObject(hBitmap);

        //终了程序,发送WM_QUIT消息  
        PostQuitMessage(0);
        break;
    }

    return DefWindowProc(hwnd, msg, wParam, lParam);
}


int WINAPI WinMain(HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR     szCmdLine,
    int       iCmdShow)
{
    HWND hWnd;  //窗口句柄
    WNDCLASSEX winclass;    //创建窗口类对象

                            //窗口类对象的初始化
  //WNDCLASSEX是window的数据类
    winclass.cbSize = sizeof(WNDCLASSEX);  //WNDCLASSEX的大小,可以用sizeof(WNDCLASSEX)来获得准确的值
    winclass.style = CS_HREDRAW | CS_VREDRAW; //从这个窗口类派生的窗口具有的风格。您可以用“or”操作符来把几个风格或到一起
    
    winclass.lpfnWndProc = WindowProc;//窗口处理函数的指针(windowproc是自己写的函数,当用户点击按钮时,win会调用该函数)
    
    winclass.cbClsExtra = 0;//指定紧跟在窗口类结构后的附加字节数
    winclass.cbWndExtra = 0;//指定紧跟在窗口实例的附加字节数。如果一个应用程序在资源中用CLASS伪指令注册一个对话框类时,则必须把这个成员设成DLGWINDOWEXTRA
    
    winclass.hInstance = hInstance;//本模块的实例句柄(winmain实参的第一个参数)
    
    winclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);//图标的句柄
    
    winclass.hCursor = LoadCursor(NULL, IDC_ARROW);//鼠标的句柄,这里是win提供的默认标准箭头
    
    winclass.hbrBackground = NULL;//背景画刷的句柄,即设置窗口的背景颜色
    
    winclass.lpszMenuName = NULL;//指向菜单的指针,用于加载和选用窗口
    
    winclass.lpszClassName = g_szWindowClassName;//为你的WNDCLASSEX类取一个别名
    winclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);//和窗口类关联的小图标。如果该值为NULL。则把hIcon中的图标转换成大小合适的小图标。

    //注册窗口类,把新建的窗口类注册上去
    if (!RegisterClassEx(&winclass))//此处为出现ERROR的消息弹框
    {
        MessageBox(NULL, "Registration Failed!", "Error", 0);
        return 0;
    }

    //创建窗口  
    hWnd = CreateWindowEx(NULL,                 // extended style,窗口的扩展风格
        g_szWindowClassName,  // window class name,指向注册类名的指针
        g_szApplicationName,  // window caption,指向窗口名称的指针
        WS_OVERLAPPEDWINDOW,  // window style,窗口风格
        0,                    // initial x position,窗口的水平位置
        0,                    // initial y position,窗口的垂直位置
        WINDOW_WIDTH,         // initial x size
        WINDOW_HEIGHT,        // initial y size
        NULL,                 // parent window handle,父窗口的句柄
        NULL,                 // window menu handle,菜单的句柄或是子窗口的标识符
        hInstance,            // program instance handle,应用程序的实例句柄
        NULL);                // creation parameters,指向窗口的创建数据

                              //容错处理
    if (!hWnd)
    {
        MessageBox(NULL, "CreateWindowEx Failed!", "Error!", 0);
        return 0;
    }

    //显示窗口
    ShowWindow(hWnd, iCmdShow);
    UpdateWindow(hWnd);

    MSG msg;

    //不断从事件队列里取到消息(默认)
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);//将虚拟消息转化为字符消息
        DispatchMessage(&msg);//可调用回调函数,即windowproc()处理
    }

    return msg.wParam;
}

//超时后回调函数
void CALLBACK WorldTimerCallBack(HWND hwnd, UINT message, UINT iTimerID, DWORD dwTime)
{
    g_world->nextStep();
    DrawWorld(g_world, WORLD_WIDTH, WORLD_HEIGHT, GetDC(hwnd));
}

//描画整个世界
void DrawWorld(CWorld * world, int world_w, int world_h, HDC hdc)
{
    CleanWorld(hdc);
    DrawGrid(hdc, world_w, world_h);
    DrawCell(world, hdc);
}

//将世界涂成黑色(背景色)
void CleanWorld(HDC hdc)
{
    HPEN WhitePen = CreatePen(PS_SOLID, 1, RGB(0,0,0));//建立画笔
    HBRUSH WhiteBrush = CreateSolidBrush(RGB(0,0,0));//建立阴影画刷
    SelectObject(hdc, WhitePen);//选用GDI对象
    SelectObject(hdc, WhiteBrush);

    //画矩形
    Rectangle(hdc, 0, 0, WORLD_WIDTH * CELL_SIZE, WORLD_HEIGHT * CELL_SIZE);
    DeleteObject(WhitePen);//删除GDI对象
    DeleteObject(WhiteBrush);
}

//描画所有细胞
void DrawCell(CWorld* world, HDC hdc)
{
    HPEN BluePen = CreatePen(PS_SOLID, 1, RGB(255,250,240));
    HBRUSH BlueBrush = CreateSolidBrush(RGB(255,250,240));
    SelectObject(hdc, BluePen);
    SelectObject(hdc, BlueBrush);
    for (int i = 0; i < world->getWidth(); ++i)
    {
        for (int j = 0; j < world->getHeight(); ++j)
        {
            if (world->getCellAlive(i, j) == 1)
            {
                Rectangle(hdc, i * CELL_SIZE, j * CELL_SIZE, i * CELL_SIZE + CELL_SIZE, j * CELL_SIZE + CELL_SIZE);
            }
        }
    }

    DeleteObject(BluePen);
    DeleteObject(BlueBrush);
}

//描画网格
void DrawGrid(HDC hdc, int w, int h)
{
    HPEN GrayPen = CreatePen(PS_SOLID, 1, RGB(128, 128, 128));
    SelectObject(hdc, GrayPen);
    for (int i = 0; i <= w; ++i)
    {
        MoveToEx(hdc, i * CELL_SIZE, 0, NULL);//移动目标
        LineTo(hdc, i * CELL_SIZE, h * CELL_SIZE);//画线
    }
    for (i = 0; i <= h; ++i)
    {
        MoveToEx(hdc, 0, i * CELL_SIZE, NULL);
        LineTo(hdc, w * CELL_SIZE, i * CELL_SIZE);
    }

    DeleteObject(GrayPen);
}

void CreateButton(HWND hwnd, HINSTANCE hInstance)
{
    RECT rect;
    GetClientRect(hwnd, &rect);
    int cxClient = rect.right;//整个窗口最右边的x的值

    /* 建立按钮 */
    int cxChar = LOWORD(GetDialogBaseUnits());//返回值为水平对话框的基本单位
    int cyChar = HIWORD(GetDialogBaseUnits());
    //开始按钮
    int button_w = cxChar * 12;//按钮的长
    int button_h = cyChar * 2;//按钮的宽
    int button_y = 400+ cyChar * 3;//按钮位置的x值
    int random_btn_x = cxChar * 3;//按钮位置的y值
    int start_btn_x = random_btn_x + button_w + cxChar*5;
    int pause_btn_x = start_btn_x + button_w + cxChar*5;
    int next_btn_x = pause_btn_x + button_w+ cxChar*5;
    int kill_all_btn_x = next_btn_x + button_w+ cxChar*5;

    CreateWindow(TEXT("Button"), TEXT("随机初始化"),//按钮控件的类名
        WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,/*扁平样式*/
        random_btn_x,button_y , button_w, button_h,// /*X坐标*/, /*Y坐标*/, /*宽度*/, /*高度*/,
        hwnd, (HMENU)RANDOM_BTN_ID,//后者是控件唯一标识符
        hInstance, NULL);
    CreateWindow(TEXT("Button"), TEXT("开始"),
        WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
        start_btn_x,button_y , button_w, button_h,
        hwnd, (HMENU)START_BTN_ID,
        hInstance, NULL);
    CreateWindow(TEXT("Button"), TEXT("暂停"),
        WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
        pause_btn_x,button_y , button_w, button_h,
        hwnd, (HMENU)PAUSE_BTN_ID,
        hInstance, NULL);
    CreateWindow(TEXT("Button"), TEXT("下一步"),
        WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
        next_btn_x, button_y, button_w, button_h,
        hwnd, (HMENU)NEXT_BTN_ID,
        hInstance, NULL);
    CreateWindow(TEXT("Button"), TEXT("清屏"),
        WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
        kill_all_btn_x, button_y, button_w, button_h,
        hwnd, (HMENU)KILL_ALL_BTN_ID,
        hInstance, NULL);
}

 

在增加界面设计时,遇到了很多困难,主要是因为对于界面设计的知识掌握的不多。为了尽快解决问题,我与杜蒙蒙同学分别行动,各自查阅有关按钮的创建、窗口的建立以及网格的描画等资料,并在资料搜集结束后,开启屏幕分享对自己查阅到的信息进行交流,互相提问。在编写代码时,由于对有关界面的知识是速成的,掌握不牢固,所以多次互换角色。

 

实验二 结对编程--第二阶段(更新)_第12张图片

 

编译0 error(s), 0 warning(s)后,连接时出了错

这是因为建工程的时候点错选项了这个工程应该选Win32 Application,而我选的是Win32 Console Application。后面的入口函数是main函数,所以才说未解析的外部符号main。

改正后,代码可以成功运行,运行界面如下:


实验二 结对编程--第二阶段(更新)_第13张图片

 

 

关于界面的颜色,经过多次对各种花里胡哨的颜色的尝试后,我与杜蒙蒙同学感觉到了深深的疲惫,最后还是选择了经典的黑白配。

成功运行后,将代码提交至GitHub

 

实验二 结对编程--第二阶段(更新)_第14张图片

 

至此,本次结对编程实验结束。从一开始感觉这个实验很繁琐,担心两个人可能因为意见不合想法不同而导致种种问题,到现在感觉居然还很不错,整个过程说不上美妙,但是也是有一起脑阔疼一起大声笑的难忘回忆,亲身体验到了合作的魅力。

 

你可能感兴趣的:(实验二 结对编程--第二阶段(更新))