一、实验目标
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矩阵
3、结对编程过程
对所选题目进行再次分析之后,我组决定使用C++在VC6.0上编写代码。
经讨论,我们组决定每人承担部分功能,编写代码时,由写代码的人开启屏幕分享。
在编写print()函数时,我没有弄清楚i、j和width、height的对应关系,导致输出的矩阵大小与设定的大小不同,经杜蒙蒙同学提醒,将错误改正。同时注意到C++习惯于使用前置自增,而平时我们多使用后置自增。为更好符合代码规范,将原本写的后置自增改为前置自增。main()函数由杜蒙蒙同学编写,编写后运行无误,但是运行结果只能进行单次单步演化,为使功能更加完善,杜蒙蒙同学添加了switch,使得运行结果可以进行单次单步演化也可以进行多次单步演化。
4、主要功能模块
(1)随机初始化
① 实现代码
②运行结果
(2)下一时刻
①实现代码
②运行结果
(3)输出矩阵
①实现代码
②运行结果
如上图
5、提交到GitHub
此次试验GitHub仓库地址是https://github.com/cloudy-y/game_of_life
我和杜蒙蒙同学对各自负责的模块进行了多次commit
在其中一次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); }
在增加界面设计时,遇到了很多困难,主要是因为对于界面设计的知识掌握的不多。为了尽快解决问题,我与杜蒙蒙同学分别行动,各自查阅有关按钮的创建、窗口的建立以及网格的描画等资料,并在资料搜集结束后,开启屏幕分享对自己查阅到的信息进行交流,互相提问。在编写代码时,由于对有关界面的知识是速成的,掌握不牢固,所以多次互换角色。
编译0 error(s), 0 warning(s)后,连接时出了错
这是因为建工程的时候点错选项了,这个工程应该选Win32 Application,而我选的是Win32 Console Application。后面的入口函数是main函数,所以才说未解析的外部符号main。
改正后,代码可以成功运行,运行界面如下:
关于界面的颜色,经过多次对各种花里胡哨的颜色的尝试后,我与杜蒙蒙同学感觉到了深深的疲惫,最后还是选择了经典的黑白配。
成功运行后,将代码提交至GitHub
至此,本次结对编程实验结束。从一开始感觉这个实验很繁琐,担心两个人可能因为意见不合想法不同而导致种种问题,到现在感觉居然还很不错,整个过程说不上美妙,但是也是有一起脑阔疼一起大声笑的难忘回忆,亲身体验到了合作的魅力。