homework-06
现代程序设计作业6
吐槽
注:下面这些话不是写给助教的,话语偏激请见谅。
这次作业是要补写一个围棋程序。该程序用c#编写,实现了两个人在同一程序 下的对弈功能,并且可以向前向后浏览棋局每步的历史记录,或从文件 中读取棋局。但是程序缺少了关键的“后退”函数,需要补写,并进行改进。
不得不说这个程序写的实在是够垃圾。一个功能如此简单的程序,用c#这 样相当高级的语言写,竟然写了1500行,令人叹为观止。而且程序的逻辑 相当混乱,好像是故意要迷惑人一样,有相当多的完全没有用处的废话。 让人不禁一愣:这句话这么写是个什么意思?想了好久发现:哦,完全是 作者脑残而已。
当然原作者还是很良心的加了许多注释,但有的人就是闲的蛋疼,非要 把注释去掉,还是煞费苦心的按字符替换掉,我真服了。我想有不少同学 提出过作业量的问题。我也看到了您的回复,看起来您可不想跟什么别 的水课一样随便搞搞得了,而是一定要高端大气,高贵冷艳。给出的理由 让外人乍一看还真是那么回事!不多练习怎么提高?呵呵,这话真是对。 可是阿,您老是自己随便yy个什么东西就拿来让我们做。从作业1到作业8, 有几次不是随便敷衍过去的?我想问问,这8次作业,几个是您能做的? 你自己都做不了的话,如何拿来教我们?退一步说,您并不一定要完全 会做,毕竟您也不是专门搞技术的嘛。但您开了这么一门名为“现代程 序设计”的听起来非常大气的课,怎么也要对一个问题有个基本的认识吧。 我想您没有做到,请您好好想想。
您的作业并不难,比这难的作业多了去了,可是,我并不想浪费时间。
可我还是花了很多时间做了,真是悲哀。
playPrev方法的实现
由于时间原因注释部分我就直接写在博客中了,见谅。
既然有playPrev,那应该也会有playNext。我的实现主要是根据playNext 来的。先来分析一下playNext的代码:
- 参数只有一个:GoMove类型的gm,根据名字应该就是下一步。
- 取出gm的point,color,容易想到这是gm这一步下的位置和颜色。
- clearLabelsAndMarksOnBoard,这个完全不知道在做什么,程序 中没有任何地方体现了这句话的作用,先略过。
- repaintOneSpotNow,这是比较重要的一个方法,用来去除参数那个 点的高亮标志,具体实现用到了invalidate方法,对此我不是很了解, 姑且认为是调用了某个绘图方法。
- bDrawMark应该是指是否画高亮,在repaintOneSpotNow中将其先 设为false,然后绘制,从而去掉上一步的高亮。
- setStone明显就是将gm这一步的位置下上对应颜色的棋子。
- m_gmLastMove是比较重要的属性,表示最后走的一步,这里将其设 为gm。
- setLabelsOnBoard与setMarksOnBoard作用依然未知,注释掉也没有影响
- doDeadGroup是去掉gm这一步吃掉的棋子,如果有吃掉,设m_fAnyKill 为true。
- appendDeadGroup是将吃掉的棋子放入一个arraylist。而这个if语句 比较令人费解:如果gm这一步吃掉了棋子,将它们收集到gm.DeadGroup中; 否则,查看是否有gm这一步颜色的棋子被吃掉,如果有则把他们收集起来。 在调试过程中我发现一个bug,即如果一个子下完之后是没气的,那么 这个子直接消失,颜色变为对手颜色,相当于这一步没有下。正常情况下 应该不能下这样的一步。这个bug对应上面的代码就可以发现,这是原作者 故意设置的。勉强来说也可以不算bug。
- optRepaint,重绘,注意只有被标记为updated的区域才会重绘。这 里的区域是以格点为中心边长为一个格子边长的正方形区域。这是局部 重绘的单位。
- 将颜色设为对手颜色,表示这一步走完。
- 清空文本框,内容设为新的注释。
至此基本了解了主要方法的含义,可以写playPrev了。
public void PlayPrevious(GoMove move)
{
if (m_gmLastMove != null)
repaintOneSpotNow(m_gmLastMove.Point);
m_colorToPlay = move.Color;
Grid[move.Point.X,move.Point.Y].RemoveStone();
if (null != move.DeadGroup)
{
System.Collections.IEnumerator ie = move.DeadGroup.GetEnumerator();
Point deadp;
while (ie.MoveNext())
{
deadp = (Point)ie.Current;
Grid[deadp.X, deadp.Y].SetStone(move.DeadGroupColor);
}
}
bDrawMark = true;
m_gmLastMove = gameTree.PeekPrevious();
if(null != m_gmLastMove)
Grid[m_gmLastMove.Point.X, m_gmLastMove.Point.Y].SetUpdated();
optRepaint();
textBox1.Clear();
textBox1.AppendText(move.Comment);
}
这是做完代码分析之后的代码,变量名有所改变。
- 注意参数move是要去掉的一步,这一点能从GoTree的实现中看出。 说实话这个GoTree的可读性非常差,++操作符确实方便,但在没有 注释的情况下就是灾难。
- repaintOneSpotNow仍是去掉高亮,现在去掉的是move这一步的。
- 设置颜色为move的颜色,去掉move这一步后颜色就应该是move 的颜色。
- 去掉move这步的棋子。
- 如果move这步吃掉了棋子,则将其恢复。这里用到了DeadGroup的 迭代器。
- 设置m_gmLastMove = gameTree.PeekPrevious()注意是peekprevious 而不是doprevious。两者的区别是do会使当前步退回去,而peek只是 取出那一步。最后一步应该是去掉move后的,也就是move的前一步。这里 比较乱,主要还是因为GoTree的实现很脑残。
- 设置最后一步的高亮,这里只需要将最后一步的point位置设为updated即可。 这一点很难看出,这中设计真是脑残。
- 调用重绘。
- 重设文本框。
点评
这程序写的非常烂,小学生水平,我真的不想再说了,因为槽点实在 太多,多到无从吐起。
有相当多的关键方法意义不明,封装的也很不好,参数的传递也做的不好, 很多地方让人非常迷惑。我想只要是看了这程序的人都能感受到这无处 不在的蛋疼。
文件处理写的太麻烦。
ui还不错,连棋盘的阴影都还单独绘制了。
至于labels和marks的用途我至今仍未弄明白,也不想去做了,目前没有 影响到任何功能。
至于code analysis,我尝试着做了一下,把warning从160+个减少到了 76个,我尽力了。熟悉这个功能人应该清楚,有很多warning实际上 是根本不应该改的(比如string str他会提醒你str这个名字不够好,求你 说一个更好的?)。我想邹老师说的把他们全部解决是一件非常坑的事。 还有一部分建议涉比较好但及代码的设计层面,我实在没有时间大改了。
选择题
这个程序从功能上需要改进的地方有很多。首先是我前面说过的bug: 在不合理位置下棋会直接消失。比较好的做法是如果我要在一个不合理 的位置下棋,不予响应就是了。这个算是比较容易实现的。
这个程序不支持悔棋。他不是可以退回吗?是的,但如果你试一下先退回 再随意下一步,再尝试前进与后退,会发现实际上他把你后来改的一步 插在最后了,而不是插在你悔棋的部分。可能作者本身的意思就是不允许 悔棋,但实际上你却可以退回后再下新的步,这是很不合理的。有两种 方式改进,一是真正不允许悔棋,一是允许悔棋。完全不允许悔棋比较 简单,而要想实现悔棋功能需要改GoTree的实现,比较麻烦(其实是 他写的比较蛋疼,自己写的话其实比较简单)。
关于邹老师给出的a和b,我的回答是完全不用变。
C++作业
计算“Hello World!”中
字母‘e’的个数
字母‘l’的个数
#include<iostream> #include<algorithm> using namespace std; string str; int count(char c) { int n = 0; for_each (str.begin(),str.end(),[&n,c](char x){if (x == c) n += 1;}); return n ; } int main(void) { cin >> str; cout<< count('l')<<endl; cout<< count('e')<<endl; return 0; }
打印“Hello World!”循环右移n位的结果
#include<stdio.h> #include<string.h> int main(void) { int i,r,l; char s[255]; scanf("%d\n",&r); fgets(s,255,stdin); l = strlen(s)-1; for (i = 0;i<l;i++) putchar(s[(i - r + l)%l]); return 0; }