0

我正在使用 BSL 语言在 HtDP 第 4 章中工作。

我正在处理的问题是:

练习 136:如果你运行 main,按下空格键(开枪),然后等待很长一段时间,镜头从画布上消失。但是,当您关闭世界画布时,结果是仍然包含这个不可见镜头的世界。

设计一个替代的 tock 函数,它不仅在每个时钟周期移动一个像素,而且还消除了那些坐标将它们放在画布上方的镜头。提示:您可能希望考虑为递归 cond 子句设计一个辅助函数。

我想出的解决方案如下(在剧透中)。但是,我觉得我在做一些多余的事情。基本上我对辅助功能的应用不太正确。

(define (main w0)
  (big-bang w0
            (on-tick ticking)
            (on-key fire-key)
            (to-draw to-render)))

(define HEIGHT 100)
(define WIDTH 80)
(define TURRET-X-POS (/ WIDTH 2))
(define BKGRND (empty-scene WIDTH HEIGHT))
(define SHOT-IMG (triangle 4 "solid" "red"))

(define (to-render w0)
  (cond
    [(empty? w0) BKGRND]
    [else (place-image SHOT-IMG TURRET-X-POS (first w0) (to-render (rest w0)))]))

(define (fire-key w0 ke)
  (cond
    [(key=? ke " ") (cons HEIGHT w0)]
    [else w0]))

(define (ticking w0)
  (cond
   [(empty? w0) empty]
   [(empty? (only-inbound-shots w0)) empty]
   [else (cons (sub1    (first (only-inbound-shots w0))) 
               (ticking (rest  (only-inbound-shots w0))))]))

(define (only-inbound-shots w0)
  (cond      
    [(< (first w0) -4) (rest w0)]
    [else w0]))

更新:(
这比以前干净多了)

(define HEIGHT 100) ;height of scene
(define WIDTH 80)   ;width of scene
(define TURRET-X-POS (/ WIDTH 2)) ;position of turret, ie. shot's x-coordinate
(define BKGRND (empty-scene WIDTH HEIGHT)) ; scene itself
(define SHOT-IMG (triangle 4 "solid" "red")) ;image representing the shot
(define Y-BOUNDARY -4) ;y-coordinate where shot is no longer visible in scene

;List-of-numbers -> List-of-numbers
;renders all shots fired
(define (to-render w0)
  (cond
    [(empty? w0) BKGRND]
    [else (place-image SHOT-IMG TURRET-X-POS (first w0) 
                       (to-render (rest w0)))]))

;List-of-numbers, key event -> List-of-numbers
;only allows the space bar to fire a shot
;one space bar event produces one shot
(define (fire-key w0 ke)
  (cond
    [(key=? ke " ") (cons HEIGHT w0)]
    [else w0]))

;List-of-numbers -> List-of-numbers
;during each clock tick, the y-coordinate each of the shot 
;                                      in List-of-numbers is updated
;each y-coordinate decreases by -1
(define (ticking w0)
  (cond
    [(empty? w0) w0]
    [else (only-inbound-shots (update-shots w0) Y-BOUNDARY)]))

;List-of-numbers -> List-of-numbers
;does the actual updating of the shots in List-of-numbers
;each shot's value is decreased by -1
(define (update-shots w0)
  (cond
    [(empty? w0) w0]
    [else (cons (sub1 (first w0)) (update-shots (rest w0)))]))

;List-of-numbers -> List-of-numbers
;checks to see if the first shot in the List-of-numbers has gone past the Y-BOUNDARY
;if so then remove shot from the List-of-numbers and return the rest of the List
;otherwise return the List without change
(define (only-inbound-shots w0 y-boundary)
  (cond
    [(empty? w0) w0]
    [(< (first w0) y-boundary) (rest w0)]
    [else w0]))

;List-of-numbers -> List-of-numbers
;creates the world of shots
;seed value is empty, additional values created by space bar
(define (main w0)
  (big-bang w0
            (on-tick ticking)
            (on-key fire-key)
            (to-draw to-render)))

TESTS补充说:
我仍在进行测试。

(define test-shots
  (cons -6 (cons -5 (cons 10 empty))))

(define test-shots-2
  (cons -6 (cons 2 (cons 7 empty))))

(define test-shots-3
  (cons 4 (cons 9 (cons 10 empty))))

(check-expect (to-render test-shots) 
  (place-image SHOT-IMG TURRET-X-POS -6
    (place-image SHOT-IMG TURRET-X-POS -5
      (place-image SHOT-IMG TURRET-X-POS 10
        BKGRND))))


(check-expect (to-render test-shots-2) 
  (place-image SHOT-IMG TURRET-X-POS -6
    (place-image SHOT-IMG TURRET-X-POS 2
      (place-image SHOT-IMG TURRET-X-POS 7
        BKGRND))))

添加了世界功能的测试:

(define HEIGHT 1) ; makes test a little faster

(check-expect
  (fire-key 
    (ticking 
      (ticking 
        (ticking 
          (ticking 
            (fire-key 
              (ticking 
                (ticking 
                  (ticking 
                    (ticking (fire-key empty " "))))) 
            " "))))) 
    " ")
  (cons -3 (cons 1 empty))
4

2 回答 2

7
  • 关于缺失合同、目的声明和数据定义的常见评论适用于此。以及个别功能的测试;world.ss/universe.ss 是非常好的库的一个重要原因是它们使人们能够测试在概念上执行输入/输出的函数。

  • 我从代码中推断出很多关于您的数据定义的内容,但是(1.)您不应该将责任推给读者,并且(2.)这可能会导致我的推理出现错误。

  • 在我看来,您的定义明显偏离了模板ticking;它看起来不像我能想到的任何模板。类似的评论适用于only-inbound-shots

  • 您可能希望分解ticking为多个子例程,然后组合它们。

    我的意思的一个例子:如果你要创建一个函数来取一个数字列表的平均值,一个简单的方法是创建两个新函数:第一个产生数字的总和,然后第二个产生列表的长度;这些通过设计配方编写起来很简单。然后average是:

    ;; average : [Listof Number] -> Number
    ;; produces average value of input (x_1 x_2 ... x_n
    (define (average l)
      (/ (sum-of-list l) (length-of-list l)))
    

    但是,如果您尝试average在模板之后的单个定义中执行此操作,则在[Listof Number]获得正确答案时会遇到一些问题。(我认为不使用一两个蓄能器就无法正确完成。)

    分解成非常简单的子程序,然后在最后组合它们以获得所需的效果,这就是我所说的分解然后ticking组合碎片的意思。(如果你不解构你的输入,函数组合是一个完全有效的设计过程:见HtDP 3.1 节。)

  • 不过,更重要的是,我认为是对各个功能进行一些测试。特别是only-inbound-shots:我建议你自己考虑这个功能。

    • 假装你不知道谁会调用它,只知道他们会遵守它的契约(例如,他们只会传入一个 World,无论你定义为什么)。
    • 然后确保您为他们提供的任何可能的法律意见提供正确的答案。
    • 不要考虑如何在上面的其他代码中自己使用它,因为您不想同时将所有这些都放在脑海中。在这里概括起来实际上更简单only-inbound-shots,并考虑对任何可能的输入应该做什么。

为了给您提供一些关于测试问题的具体思考,这里有一些假设的图片,描述了您可能在测试中尝试处理的输入:

两个球 一个在上面, 三球二低,三个球,两个高


2013 年 2 月 28 日更新:

虽然我仍然建议为每个功能编写单独的单元测试,但端到端测试也很重要。在这种情况下,当前渲染的游戏不会告诉您是否有镜头位于场景之外(因为place-image与 say 不同overlay,它会自动从渲染中裁剪它们)。

因此,如果您想在游戏运行时对其进行调试,那么获取此类信息会很有用。就像在游戏顶部呈现的下拉文本一样(人们经常在视频游戏中看到这一点,以向您展示帧速率等内容)。因此,这是在游戏运行时获取该信息的一种策略:交换另一个渲染函数,该函数位于您现有的渲染函数之上,但会打印出有关 worldw0参数的其他信息。

(在这种情况下,查看它的长度可能很有用,尽管可以想象提取其他信息。)

;; List-of-numbers -> Image
;; Renders w0 via to-render, with a printout of shot count in top left corner.
(define (to-render-with-count w0)
  (place-image/align (text (number->string (length w0)) 30 'blue)
                     0 0 "left" "top"
                     (to-render w0)))

然后你加入to-render-with-count你的big-bang调用。减慢时钟滴答率也可能很有用,这样您就可以看到击键和时钟滴答混合时会发生什么,所以我也做了这个改变(在on-tick子句中):

(define (main w0)
  (big-bang w0
            (on-tick ticking 0.1)
            (on-key fire-key)
            (to-draw to-render-with-count)))

现在,我可以交互式地注意到有趣的趋势。产生这种情况的趋势:

148 个球,但它们在哪里?

为什么屏幕上有 148 个球,但只显示了 4 个?什么样的世界会发生这样的事?(如果你关闭由 创建的窗口big-bang,它会将当前世界返回到交互窗口,所以你会在那里确切地看到什么样的世界会发生这种情况。)

于 2013-02-27T09:55:01.397 回答
0

我把最终答案放在这里是因为最初的问题已经发生了很多事情。

(define HEIGHT 200) ;场景高度
 (define WIDTH 80) ;场景宽度
 (define TURRET-X-POS (/ WIDTH 2)) ;炮塔的位置,即。镜头的 x 坐标在哪里
 (define BKGRND (empty-scene WIDTH HEIGHT)) ; 场景本身
 (define SHOT-IMG (triangle 4 "solid" "red")) ;代表镜头的图像
 (define Y-BOUNDARY -4) ;镜头在场景中不再可见的y坐标
 
 ;数字列表 -> 数字列表
 ;渲染所有射击
 (定义(渲染 w0)
   (条件
     [(空?w0)BKGRND]
     [else (place-image SHOT-IMG TURRET-X-POS (first w0) (to-render (rest w0)))]))
 
 ;数字列表,关键事件 -> 数字列表
 ;只允许空格键开枪
 ;一个空格键事件产生一击
 (定义(火键 w0 ke)
   (条件
     [(key=?ke " ") (cons HEIGHT w0)]
     [否则 w0]))
 
 ;数字列表 -> 数字列表
 ;每时钟滴答更新世界状态
 (定义(勾选 w0)
   (条件
     [(空?w0)w0]
     [else (remove-outbound-shots (update-shots w0) Y-BOUNDARY)]))
 
 ;数字列表 -> 数字列表
 ;更新所有镜头
 (定义(更新镜头 w0)
   (条件
     [(空?w0)w0]
     [else (cons (sub1 (first w0)) (update-shots (rest w0)))]))
 
 ;数字列表 -> 数字列表
 ;从列表中删除所有超出 y 边界的镜头
 (define (remove-outbound-shots w0 y-boundary)
   (条件
     [(空?w0)w0]
[(< (first w0) y-boundary) (remove-outbound-shots (rest w0) y-boundary)] [else (cons (first w0) (remove-outbound-shots (rest w0) y-boundary))])) ;数字列表 -> 数字列表 ; 创造镜头世界 ;种子值为空,空格键创建的附加值 (定义(主 w0) (大爆炸 w0 (滴答滴答) (on-key fire-key) (绘制到渲染)))

测试:

(define test-shots-1
  (cons 1 (cons 4 (cons 10 (cons -6 (cons -5  (cons 1 (cons 4 (cons 10 (cons 10 (cons -6 (cons -9 empty))))))))))))


(define test-shots-4
  (cons 10 (cons -6 (cons -5  (cons 1 (cons 4 (cons 10 empty)))))))

(check-expect (remove-outbound-shots test-shots-4 -4) (list 10 1 4 10))
(check-expect (remove-outbound-shots test-shots-1 -4) (list 1 4 10 1 4 10 10))
于 2013-03-03T19:36:08.987 回答