- 一个菜单栏上有两个菜单:File和Help
- File菜单包含Start和Stop两个菜单项
- Help包含About菜单项
- 点击Start,程序将画出三个连在一起的空心小矩形,然后这三个小矩形同时向右移动
- 点击Stop,停止移动
好吧,我承认,这就是个贪食蛇的雏形。记得当年学习C#时也写了个最基本的贪食蛇游戏,现在算是二进宫了,轻车熟路。
在开始之前,需要先大致说明一下Racket的对象系统。
定义一个类:
- (class superclass-expr decl-or-expr ...)
- (class object%
- (init size) ; initialization argument
-
- (define current-size size) ; field
-
- (super-new) ; superclass initialization
-
- (define/public (get-size)
- current-size)
-
- (define/public (grow amt)
- (set! current-size (+ amt current-size)))
-
- (define/public (eat other-fish)
- (grow (send other-fish get-size))))
为了能够创建这个类对象而不需要每次都把上面这一大段写到代码里,可以用define把这个匿名类绑定到一个变量上,比如叫做fish%。那么需要创建一个fish%的对象就很简单:
- (new fish% (size 10))
需要注意的是,在Racket(也许其他的Scheme实现也一样)中,“{}”、“()”、“[]”是相同的,只不过必须匹配,如“{”必须匹配“}”。
为了调用一个类的函数,需要用以下两种形式之一:
- (send obj-expr method-id arg ...)
- (send obj-expr method-id arg ... . arg-list-expr)
如:
- (send (new fish% (size 10)) get-size)
看到这里你也许会感到很奇怪:为什么没有析构函数?早在Lisp诞生初期,它就包含了垃圾收集功能,因此,根本不需要你释放new得到的对象。过了许多年之后,许多包含垃圾收集功能的语言诞生了。
此外,结构体也是很有用的东西,它与类的区别,跟C++中类与结构体的区别差不多,但Racket结构体提供了很多辅助函数——当然是通过宏和闭包来提供这些函数。结构体是通过struct来定义的。——没猜错的话,struct应该也是一个宏——还没有细看Racket的代码。
- (struct node (x y) #:mutable)
- (node-x n) ; get x from a node n
- (set-node- n 10) ; set x to 10 of a node n
- (node? n) ; predicate, check if n is a node
这个应用的核心在于内嵌在canvas上的一个定时器:
- (define timer
- (new timer%
- [notify-callback
- (lambda ()
- (let ((dc (send this get-dc)))
- (send dc clear)
- (map (lambda (n)
- (send dc
- draw-rectangle (node-x n) (node-y n) 5 5))
- lst)
- (map (lambda (n)
- (set-node-x! n (+ (node-x n) 5)))
- lst)))
- ]
- [just-once? #f]))
每当超时时间发生时,notify-callback所绑定的回调函数就会被调用,完成在canvas上画图的功能,同时更新图形所在的位置,这样便形成了移动。
当然,现在这个程序还只是雏形而已,总代码量为101行。如果要完善成为一个贪食蛇游戏,还需要做很多工作,同时还需要进行一些设计,至少将Model、View和Controller分开吧。
从这里也可以看出,用Scheme来进行面向对象的开发也十分容易,并不需要用到Scheme的高级功能例如宏和续延等等。当然,如果能运用好这些高级功能,相信代码会更加简单。