先重构,如前文打算提取了一些函数到功用.clp:

(deffunction 加一 (?数)
  (+ 1 ?数))

(deffunction 更新速度 (?距离 ?速度)
  (bind ?速度上限 2)
  (bind ?差值 (* 0.005 ?距离))
  (bind ?新速度 (+ ?速度 ?差值))
  (bind ?绝对值 (abs ?新速度))

  (if (> ?绝对值 ?速度上限)
    then (* ?速度上限 (/ ?绝对值 ?新速度))
    else ?新速度))

模版部分提取到数据结构.clp某种加了速度属性;上个版本里世界就加了个体总数量属性。

然后发现加载的时候各种符号还有不同含义的样子,颇有上世纪特色:

CLIPS> (batch* 加载.bat)
!!
%%%
*****
TRUE

每行对应一个源码文件。!表示函数定义(function),%为模版(template),*为规则。

业务部分六十行左右。之前木兰实现的业务部分大约一百五十行,当然还有一部分未实现:

(defrule 开始
  =>
【同前】
  (assert (某种 (长辈 0) (编号 0) (能量 0) (经历时间 0) (x (random 0 ?宽)) (速度 0))))

; 当无优先级声明时,规则编写顺序决定了执行的优先级!

(defrule 时间流逝
  ; 无食则停
  (食物)
  ?环境 <- (世界 (时间 ?时间))
  =>
  (modify ?环境 (时间 (加一 ?时间)))
)

(defrule 觅食
  (declare (salience 5))
  (世界 (时间 ?时间))
  ?个体 <- (某种 (编号 ?个体编号) (能量 ?能量) (x ?个体位置) (速度 ?当前速度) (经历时间 ?经历时间&:(< ?经历时间 ?时间)))
  ?食物 <- (食物 (编号 ?食物编号) (x ?食物位置))
  =>
  (modify ?个体 (经历时间 (加一 ?经历时间)))

  (bind ?距离 (- ?食物位置 ?个体位置))
  (if (< (abs ?距离) 2)
    then
      (retract ?食物)
      (modify ?个体 (能量 (加一 ?能量)))
      (println ?个体编号 "在" ?食物位置 "吃了" ?食物编号)
    else
      ; 靠近食物
      (bind ?新速度 (更新速度 ?距离 ?当前速度))
      (modify ?个体 (速度 ?新速度) (x (+ ?个体位置 ?新速度)))
  )
)

(defrule 繁衍
【同前】
  =>
  (assert (某种 (长辈 ?编号) (编号 ?个体量) (能量 0) (经历时间 ?时间) (x (加一 ?长辈位置)) (速度 0)))
  (modify ?世界 (数量 (加一 ?个体量)))
  (modify ?长辈 (能量 0))
)

; 待做:加开关
(defrule 显示详情
  (declare (salience 100))
  (某种 (编号 0) (速度 ?当前速度&:(neq ?当前速度 0)))
  =>
  (println ?当前速度)
)

在为了查看某个体的速度变化加最后一段时,感觉到和传统的C类代码开发的不同之处。之前这种情况,往往就找个地方塞这个print语句,比如在 觅食 的速度更新之后。CLIPS里虽然也可以这么干,不过也可以借助规则把这个「需求」另开一段代码组织,从阅读代码角度看,可以更少影响业务部分的顺畅可读性。

接下去先实现统计部分和狼、羊,再做二维地图。