[原]重新开始学习Scheme(6):图形界面的小应用

4707阅读 0评论2012-05-18 fera
分类:Python/Ruby

在学习了一些Scheme基础之后,我写了一个小程序,其功能如下:
好吧,我承认,这就是个贪食蛇的雏形。记得当年学习C#时也写了个最基本的贪食蛇游戏,现在算是二进宫了,轻车熟路。

在开始之前,需要先大致说明一下Racket的对象系统。
定义一个类:
  1. (class superclass-expr decl-or-expr ...)
例如:
  1. (class object%
  2.   (init size) ; initialization argument
  3.  
  4.   (define current-size size) ; field
  5.  
  6.   (super-new) ; superclass initialization
  7.  
  8.   (define/public (get-size)
  9.     current-size)
  10.  
  11.   (define/public (grow amt)
  12.     (set! current-size (+ amt current-size)))
  13.  
  14.   (define/public (eat other-fish)
  15.     (grow (send other-fish get-size))))
这是一个匿名类,其基类为object%,初始化参数为size——类似于C++的初始化列表,接下来current-size指的是一个私有成员,其初始值由初始化参数size所指定。再之后是通过(super-new)对父类即object%类调用“构造函数”。之后是三个公有的成员函数。

为了能够创建这个类对象而不需要每次都把上面这一大段写到代码里,可以用define把这个匿名类绑定到一个变量上,比如叫做fish%。那么需要创建一个fish%的对象就很简单:
  1. (new fish% (size 10))
需要注意的是,在Racket(也许其他的Scheme实现也一样)中,“{}”、“()”、“[]”是相同的,只不过必须匹配,如“{”必须匹配“}”。

为了调用一个类的函数,需要用以下两种形式之一:
  1. (send obj-expr method-id arg ...)
  2. (send obj-expr method-id arg ... . arg-list-expr)
如:
  1. (send (new fish% (size 10)) get-size)
看到这里你也许会感到很奇怪:为什么没有析构函数?早在Lisp诞生初期,它就包含了垃圾收集功能,因此,根本不需要你释放new得到的对象。过了许多年之后,许多包含垃圾收集功能的语言诞生了。

此外,结构体也是很有用的东西,它与类的区别,跟C++中类与结构体的区别差不多,但Racket结构体提供了很多辅助函数——当然是通过宏和闭包来提供这些函数。结构体是通过struct来定义的。——没猜错的话,struct应该也是一个宏——还没有细看Racket的代码。
  1. (struct node (x y) #:mutable)
其使用如下所示:
  1. (node-x n) ; get x from a node n
  2. (set-node- n 10) ; set x to 10 of a node n
  3. (node? n) ; predicate, check if n is a node
还有其他的辅助函数,在此不一一列举。

这个应用的核心在于内嵌在canvas上的一个定时器:
  1. (define timer
  2.       (new timer%
  3.            [notify-callback
  4.             (lambda ()
  5.               (let ((dc (send this get-dc)))
  6.                 (send dc clear)
  7.                 (map (lambda (n)
  8.                        (send dc
  9.                              draw-rectangle (node-x n) (node-y n) 5 5))
  10.                      lst)
  11.                 (map (lambda (n)
  12.                        (set-node-x! n (+ (node-x n) 5)))
  13.                      lst)))
  14.             ]
  15.            [just-once? #f]))
每当超时时间发生时,notify-callback所绑定的回调函数就会被调用,完成在canvas上画图的功能,同时更新图形所在的位置,这样便形成了移动。

当然,现在这个程序还只是雏形而已,总代码量为101行。如果要完善成为一个贪食蛇游戏,还需要做很多工作,同时还需要进行一些设计,至少将Model、View和Controller分开吧。

从这里也可以看出,用Scheme来进行面向对象的开发也十分容易,并不需要用到Scheme的高级功能例如宏和续延等等。当然,如果能运用好这些高级功能,相信代码会更加简单。
上一篇:[原]重新开始学习Scheme(4):难学却重要的Scheme特性
下一篇:[原]重新开始学习Scheme(7):续延的例子