20130829

用windows sdk写一个贪吃蛇

用windows sdk写一个贪吃蛇

这是上学期程序设计与实践3课程的一部分,用windows sdk写一个贪吃蛇。此项目由教师在课堂上演示、查SDK手册和解释、design和实现及解释代码,学生观摩。一共进行了五次课。持续五周,每周一次课,每次课90分钟。

1. 原由

之所以选用 windows sdk的原因如下。1.我的学生刚好学了一部分 windows sdk,虽然sdk更难,但是对于我的学生来说,比MFC却要简单。原因很明显,因为MFC无论有多么好,他们没学过。同理,.net也不如sdk适合。有的时候用户需求就是这样诡异,而你只能随着用户的知识结构调整自己,而不是反过来。2.java awt/swing同学们倒是学过,但是大家以后用java做gui的机会比较少。3.虽然不少人以后可能会做前端,但是用java script之类的也不合适,理由也是他们没学过。教师不能把所有的课程都变成 补基础知识的课程。只能因陋就简了。

选sdk的另方面的原因是希望通过贪吃蛇训练学生:1.即使没有学过某种知识(比如画图、音乐、刷新GDI等等),仍然能通过查手册建立技术原型;2.了解消息循环。

没有学习过,但是却可以自学,是一项重要的能力。假期的时候王师兄提到他遇到的一位女导师,似乎是用GTK从VC移植界面,花了两个 (?)小时就完成了。问她以前学过吗,她答:没,看看手册,然后就做出来了。我们希望培训学生这样的能力。我得承认,这很难,对教师比学生还要难。所以此贴谨供有此理想的教师和同学参考,并热烈期待指导和意见,而此贴本身绝非指导意见。

2. 流程

一般的软件工程 (和/或示范)都假设同学 (或工程师)已经具备和掌握了相关技术知识,对于底层机制的调用是routine类的工作。在本案例中,由于同学们对sdk能做些什么尚无完善的认识,因此大体的流程与通常的软件工程有所不同。我们先做简单的需求分析,估计有哪些功能是我们需要的,然后通过google和手册到sdk中找,找到了以后要做实验,证明这个功能是我们可以实现的,最后再把这些功能的代码在最后要用的项目中写一次。

这有点类似于快速原型法,但是动机不同。快速原型法的目的是确保软件工程师对用户的需求理解无误,用呈现出来的方式与用户确认。我们做原型的目的,是确保SDK这样的底层机制确实支持你要使用的功能。这对于初学者或者探索类的项目具有格外重要的意义。

3. 技术原型

把在需求非常粗糙的时候,先写出来的一些小程序,用于验证底层机制 (如SDK)支持这样的功能,这种小程序我们估且称为技术原型。

在课堂上,我们认为贪吃蛇有以下这样几个技术原型需要确认。

(1)画一些线,组成矩形; (2)把矩形擦掉; (3)把擦和重画结合起来,形成动画效果; (4)不阻塞地获取按键的状态,即程序不停,而在按键时做出某种影响。

前三种技术原型在写程序之前,先有"手动"的演示。教师在画图工具上,画出矩形,然后擦、画,形成动画效果,同时简单介绍视觉暂留现象。

按键获取这一技术原型,教师指出 getchar 或 scanf 这一类的函数不适合的原因,然后引入消息循环的概念。

4. 技术原型的补充

在开发的过程中,又发现需要 timer 的技术原型。把主项目停下来,写 timer技术原型,验证之后,把代码复制和修改到主项目中。

在开发中,发现当初考虑得不周到,有未尽的技术原型,这是正常的现象。除非你正完成的是与既有工作没大差别的项目,未计划周全是正常的。这也是教师在课堂写程序同时学生观摩的意义之一,使学生认识到"错误"不可避免,同时重要的是遇到"错误"如何修正。教师在课前把代码写好调好,课堂上按下F5就运行,同学们无法看到教师修正错误的过程,而这一过程会是他们写下最初100行时最需要的经验。

5. 对技术原型的再讨论

每个原型都在200行左右,除去由wizard生成的消息循环代码,就更短了。这在学生注意力和理解力可达的范围内。如果以整个工程的面目示人,则学生就需要从大的背景中找出某种技术对应的代码来,这就难多了,学生更容易产生挫败感。

每个原型都比较短,即使加上教师课堂上解释的时间,读文档的时间,一般地一到半次到两次课也能够完成。从无到有,虽然只是实现了一个小功能,但是如果学生事先就知道这一功能将出现在最后的工程中,这点小成就还是可以让他们小小地满足一下。

这些原型除了课堂演示,还要求同学们作为作业在当周完成。观摩教师写代码,和自己亲手历经各种困难把自己在课堂上已经见到效果的小项目重现出来,课程实践表明,难度上还是有很大差距的。事实上,全程能基本跟下来所有原型实验的同学,只有一名。

另,把技术原型中的代码抄到主工程中的时候,教师强调 业务逻辑与技术原型的分离。业务逻辑是尽可能与所依赖的平台和技术原型无关的。当然,类似于消息循环和按键消息响应这一类的技术原型,不可避免地与框架相关联。好在,这样的技术已经可以在相当程度上视为通用技术了,在很多平台上都类似。

6. 抽象

利用原型已经验证过的那些api,代码半抄半改,形成了整个贪吃蛇项目。当程序能跑起来没有bug的时候,同学们非常兴奋。我记得当时大家还鼓掌喊"oh yeah"了。

对于软件工程想要贯彻地教学目标而言,这才只是开始,如何抽象,比如用面向对象,比如把重构extract函数,这些才是真正的动机。而这些内容的教学,可以建立在上述刚刚由大家一起完成的贪吃蛇项目中。

在课堂中,我提出"需求变更",蛇头的颜色要求不同,碰撞的规则的变更,要求有更多的关卡等等。有些需求变更的满足,演示了extract函数,比如蛇头的颜色;有些要求重新设计数据结构 (结构体) ,变修改访问过这些结构体的函数;要求更多关卡这个,则演示了DSL的代码生成技术,我们又写了一个程序,专门用于把ASCII艺术画方式实现的关卡地图转换为C代码。比如下面几个关卡中,用"*"代表有障碍物的地方,"."代表空白处:

......
......
......
......*
.....**
......*
......*
......*
......*
.....***
------------------------------------
.
.
.
.
....*******
..........*
....*******
....*
....*******
-----------------------------------
.
.
.
.
....*******
..........*
....*******
..........*
....*******
-----------------------------------
.
.
.
.
....*.....*
....*.....*.
....*******
..........*
..........*
-----------------------------------
.
.
.
.
....*******
....*.....
....*******
..........*
....*******

上述关卡会由代码生成器转换成这样的代码: 
#define MAX_BLOCK_LENGTH 100
int block_length_stage[5] = {0, 10,23,23,13 };
int block_x_stage[5][MAX_BLOCK_LENGTH]={{0},
{6, 5, 6, 6, 6, 6, 6, 5, 6, 7},
{4, 5, 6, 7, 8, 9, 10, 10, 4, 5, 6, 7, 8, 9, 10, 4, 4, 5, 6, 7, 8, 9, 10},
{4, 5, 6, 7, 8, 9, 10, 10, 4, 5, 6, 7, 8, 9, 10, 10, 4, 5, 6, 7, 8, 9, 10},
{4, 10, 4, 10, 4, 5, 6, 7, 8, 9, 10, 10, 10}
};
int block_y_stage[5][MAX_BLOCK_LENGTH]={{0},
{3, 4, 4, 5, 6, 7, 8, 9, 9, 9},
{4, 4, 4, 4, 4, 4, 4, 5, 6, 6, 6, 6, 6, 6, 6, 7, 8, 8, 8, 8, 8, 8, 8},
{4, 4, 4, 4, 4, 4, 4, 5, 6, 6, 6, 6, 6, 6, 6, 7, 8, 8, 8, 8, 8, 8, 8},
{4, 4, 5, 5, 6, 6, 6, 6, 6, 6, 6, 7, 8}
};

关卡生成器也放在了附件中。

7. 未尽之事

最后的结果并非漂亮的设计,课堂中我也提到,真正的工程一般不会给我们机会做这么多地技术原型--它表明工程师的技术仍不成熟。但是,技术不成熟如果是事实的话,那么事实只能通过弥补来减弱影响,而不可能通过掩盖来避免负面效果。所以,如果学生或工程师技术不成熟,技术原型是一个帮助的工具。我们不能因为工具会暴露人类在体力上相对其他动物不足而拒绝使用。

代码也并非漂亮的代码,事实上,有很多不尽人意的地方。这些地方未进行修改的原因,一是我作为教师确实能力有限,对SDK的了解远不全面和深刻,二是它们超出了我原定的教学目的。

8. 感谢

我上课的时候犯了一个错误,没有先建立版本控制就开始写代码,每次课后备份的时候都覆盖了前次的代码。最后的结果固然还在,但是中间的过程也是我希望呈现的,不可或缺。

好在尤其同学备份了每次的代码,并非未如我一般愚蠢地操作,所以中间过程得以保留。所不完满者,尤其同学只备份了代码,而非整个工程,虽然保留了核心的代码,但是需要一些VC的使用知识才能编译。

9. 附件的目录树

|-- draw 绘制矩形的技术原型
|-- erase 擦除矩形的技术原型
|-- keyup 按键检测的技术原型
|-- from_YOUQI 尤其同学给出的有版本控制的代码
|   |-- week10
|   |   |-- draw
|   |   `-- erase
|   |-- week11
|   |   |-- keyup
|   |   `-- snake
|   |-- week12
|   |   |-- snak_business_logic_imp.h
|   |   `-- snake.cpp
|   |-- week13
|   |   |-- snak_business_logic_imp.h
|   |   `-- snake.cpp
|   `-- week14
|       |-- gamedesigner.cpp
|       |-- run.bat
|       |-- snak_business_logic_imp.h
|       |-- snake.cpp
|       `-- stage.txt
`-- snake
    |-- gamedesigner 关卡生成器
    |   |-- Debug
    |   |   |-- run.bat 脚本/批处理,用于把stage.txt转换为stage.h
    |   |   `-- stage.txt 关卡定义的源文件,关卡生成器的源文件
    |-- snake
    |   |-- snak_business_logic_imp.h 业务逻辑
    |   |-- snake.cpp 消息循环框架
    |   |-- stage.h 关卡生成器的输出文件


我无法通过GFW,因此不能向blogspot传附件,请参见[http://download.csdn.net/detail/younggift/6029671]

--------------------

博客会手工同步到以下地址:



No comments: