【问题标题】:Code design for program in Scheme dealing with user inputScheme中处理用户输入的程序的代码设计
【发布时间】:2020-02-01 12:53:06
【问题描述】:

我正在尝试在 Scheme 中编写小程序,古老的游戏 Hamurabi(确切地说是诡计)。我想了解此类程序“设计”的首选方法,广泛处理用户输入。例如。使用循环、可变或不可变变量等。

我有一些我不太喜欢的工作变体。我相信我错过了一些更好的方法。以下是详细信息。 抱歉,解释太长了。

游戏本身是简单的“经济”模拟 - 我们有 3 个值,分别代表我们王国的人口、土地面积和粮食数量(也用作货币)。玩家规则数年,每年连续选择:

  • 购买或出售粮食的土地数量
  • 那么喂人用多少粮食
  • 最后用多少粮食播种

所以我们有代表年份的迭代的外部循环。在里面我们有三个步骤。首先改变面积和纹理的数量。第二 改变粮食和人口的数量。第三是改变粮食的数量(相对于可用土地和耕种田地的人数)。第四步(无需用户输入)确定我们收集了多少新作物以及老鼠吃了什么(即增加谷物量)。

这可以通过使用全局变量和(set! ...) 表单轻松完成。但是,我想知道如何以更“功能性”的方式对其进行编码。看来我需要使用几个相互递归(尾部优化)函数来表示步骤。并且每次都将更改的值作为参数传递。 Here is gist with this approach implemented with only step of buying/selling land。它的工作原理是这样的:

You have 100 people, 700 acres of land and 9600 bushels of grain.
Land trades at 24 bushels of grain for acre
How many acres to buy? -100
You have 100 people, 600 acres of land and 12000 bushels of grain.
Land trades at 21 bushels of grain for acre
How many acres to buy? 200

这不是很方便,因为会有很多小函数,并且大多数都需要所有变量,即使有些是通过的。除了popareagrain,我们还需要一些累加器(例如死于饥饿的总人数)。

所以我创建了两个函数来维护不可变的键值结构,例如

(list (cons 'pop 100) (cons 'area 1000) (cons 'grain 2800))

并将它们用作传递给每个函数的stateprop-get 从状态中按键获取值,而 prop-set 返回修改后的副本(我怀疑库中已经实现了一些类似的结构)。

(load "props.scm")

(define (one-year state)
    (map display
        (list "You have "
            (prop-get state 'pop) " people, "
            (prop-get state 'area) " acres of land and "
            (prop-get state 'grain) " bushels of grain."))
    (newline)
    (let ((state-upd (buy-land state)))
        (step-2 state-upd)))

(define (buy-land state)
    (let ((price (+ (random 10) 17))
            (area (prop-get state 'area))
            (grain (prop-get state 'grain)))
        (map display
            (list "Land trades at " price " bushels of grain for acre"))
        (newline)
        (display "How many acres to buy? ")
        (let ((b (read)))
            (prop-set (prop-set state 'area (+ area b)) 'grain (- grain (* price b))))))

Please here is the complete code in another gist.

这有点好,但完整的代码仍然有点冗长,包含所有这些 prop-gets、let 和相互递归。

这里还有哪些其他选择?我认为在可变全局变量和带有尾递归的不可变之间存在“中间”解决方案 - 比如使用命名 let 进行外部循环和一些可变结构将状态保存在局部变量中。但我觉得我可能会错过一些更简单和优雅的东西。

【问题讨论】:

    标签: functional-programming architecture scheme lisp


    【解决方案1】:

    如果您想使用纯函数式方法,您需要将状态变量从一个地方传递到另一个地方。 我们可以使用关联列表来存储元素,并且由于在汉谟拉比中游戏以 10 步完成,因此我们可以轻松地将状态变量用作日志,即游戏中发生的所有事件的日志。

    关联列表具有映射可以多次出现的属性,但只返回第一个匹配项。 所以基本上,如果状态是((population . 100) (population . 30)),那么这意味着当前人口是 100,而在上一回合是 30。我们将所有值存储在插槽中,这意味着我们可以对结果游戏进行尽可能多的统计想要。

    例如,初始状态为:

    (define initial-state '((population . 100)
                            (acres . 1000)
                            (grain . 3000)
                            (year . 0)))
    

    我们可以将具体的实现细节隐藏在辅助访问函数后面:

    (define (value state slot)
      (cdr (assoc slot state)))
    

    此外,我们可以使用一种有用的语法在一个状态下一次添加多个元素:

     (define (extend0 state key/values)
       (if (null? key/values)
           state
           (let ((key (car key/values))
                 (val (cadr key/values))
                 (tail (cddr key/values)))
             (extend0 (acons key val state) tail))))
    
     (define (extend state . key/values)
       (extend0 state key/values))
    

    因此,例如,您可以这样做:

    (extend initial-state 'grain 1000 'population 200)
    $1 = ((population . 200) (grain . 1000) (population . 100) (acres . 1000) (grain . 3000) (year . 0))
    

    我们还可以为公共插槽定义访问器:

    (define (getter slot)
      (lambda (state)
        (value state slot)))
    
    (define (setter slot)
      (lambda (state value)
        (acons slot value state)))
    
    (define population (getter 'population))
    (define set-population (setter 'population))
    
    (define acres (getter 'acres))
    (define set-acres (setter 'acres))
    
    (define grain (getter 'grain))
    (define set-grain (setter 'grain))
    
    (define price (getter 'price))
    (define set-price (setter 'price))
    
    (define year (getter 'year))
    (define set-year (setter 'year))
    

    您也可以使用宏来缩短上述内容。这里的方法是设计一些小的辅助函数,以确保我们编写的实际代码具有我们想要的表现力。

    另外,经常进行独立测试,这在不涉及内部状态时更容易进行。

    同时定义一个智能对象打印机:

    (define (echo items state)
      (if (list? items)
          (map (lambda (u)
                 (cond
                  ((null? u) (newline))
                  ((symbol? u) (display (value state u)))
                  ((procedure? u) (display (u)))
                  (else (display u))))
               items)
          (begin (display items) (newline)))
      state)
    

    ...和一个通用提示:

    (define (prompt state message tester setter)
      (echo message state)
      (let ((value (read)))
        (if (tester value)
            (setter state value)
            (prompt state message tester setter))))
    

    所有词汇都准备好后,您可以这样写buy-land

    (define (buy-land state)
      (let ((max-acres (floor/ (grain state) (price state))))
        (if (zero? max-acres)
            (echo "You cannot buy any acre." state)
            (prompt state
                    `("Land trades at " price " bushels of grain for acre." ()
                      "You have " grain " bushel(s) of grain." ()
                      "How many acres to buy (0-" ,max-acres ")? ")
                    (lambda (v) (and (integer? v) (<= 0 v max-acres)))
                    (lambda (state buy)
                      (extend state
                              'buy buy
                              'acres (+ (acres state) buy)
                              'grain (- (grain state)
                                        (* buy (price state)))))))))
    

    您可以将功能拆分成做事较少但组合更好的小功能:

    (define (random-events state)
      (let ((starve (random 20)))
        (extend state
                'starve starve
                'price (+ 17 (random 10))
                'population (max 0 (- (population state) starve)))))
    
    (define (game-step state)
      (if (= (year state) 10)
          (end-game state)
          (let ((state (set-year state (+ 1 (year state)))))
            (display-new-year-text state)
            (let ((state (random-events state)))
              (game-step (buy-land state))))))
    
    (define hammurabi
      (game-step initial-state))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-02-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-14
      • 1970-01-01
      相关资源
      最近更新 更多