我正在为编码挑战编写常见的 lisp 代码,这是一个 rpg 式的谜题,您需要计算战士造成的总杀伤力。因为我对普通 lisp 很陌生,所以我的代码可能很糟糕。请不要发布一般常见的 lisp 编码技巧,除非它们与错误相关。我计划在修复错误后将此代码发布到 codereview

代码运行良好,直到在 tick 内部(底部)条件 when (> overkill-damage 0) 为真。我正在使用 GNU Clisp 2.49 来运行此代码。

(defun timer (initialization-time interval)
    (list :init initialization-time :interval interval :ready nil :time-passed 0))

(defun tick-timer (timer)
    (let ((newtime (1+ (getf timer :time-passed))))
        (when (and (not (getf timer :ready)) (>= newtime (getf timer :init)))
            (setf (getf timer :ready) t))
        (setf (getf timer :time-passed) newtime)))

(defun timer-ready? (timer)
    (and
      (getf timer :ready)
      (= 0 (mod (getf timer :time-passed) (getf timer :interval)))))

(defun weapon (damage timer)
    (list :damage damage :timer timer))

(defun weapon-attack (weapon)
    (tick-timer (getf weapon :timer))
    (if (timer-ready? (getf weapon :timer))
        (getf weapon :damage)
        0))

(defun attack (character)
    (reduce #'(lambda (total weapon) (+ (weapon-attack weapon) total)) (getf character :weapons) :initial-value 0))

(defun attack-monster (monster damage)
    (- monster damage))

(defun calculate-overkill-damage (health)
    (if (> health 0)
        0
        (abs health)))

(defparameter *warrior* `(:weapons ,(list (weapon 35 (timer 0 4)))))
(defparameter *mage*    `(:weapons ,(list (weapon 80 (timer 2 8)))))
(defparameter *rogue*   `(:weapons ,(list (weapon 20 (timer 0 3))
                                  (weapon 30 (timer 0 4)))))

(defparameter *monsters* '(300 600 850 900 1100 3500))
(defparameter *current-monster* 0)
(defparameter *overkill* 0)
(defparameter *game-over* nil)

; I assume, for now, that when a monster dies, they will miss the rest of their attacks
(defun tick ()
  (sleep .1)
  (let* ((monster (nth *current-monster* *monsters*))
         (new-health (attack-monster monster (attack *warrior*)))
         (overkill-damage (calculate-overkill-damage new-health)))
    (format t "Attacking~%-------~%Attacking monster ~a, which has ~a health." *current-monster* monster)
    (format t "~%Dealt ~a overkill damage!" overkill-damage)
    (when (> overkill-damage 0)
        (do (format t "Dealt ~a overkill damage!" overkill-damage)
            (setf *overkill* (+ *overkill* overkill-damage))
            (format t "Total overkill damage is now ~a" *overkill*)
            (setf *current-monster* (1+ *current-monster*))
            (format t "Moving to next monster, ~a" *current-monster*)
            (when (= *current-monster* (1- (length *monsters*)))
                (setf *game-over* t))))
    (let* ((new-health (attack-monster monster (attack *mage*)))
           (new-health (attack-monster monster (attack *rogue*))))
      (setf (nth *current-monster* *monsters*) new-health)
      (format t "~%Monster is now at ~a health~%" (nth *current-monster* *monsters*)))))

(loop for x from 1 until (equal *game-over* t)
    do (tick))

最重要的部分在代码的底部,tick 函数。当此代码运行时,我收到错误 *** - LET: T is a constant, may not be used as a variable

这是执行时打印的内容:
TRUNCATED LOTS OF POINTLESS MESSAGES...
-------
Attacking monster 0, which has 10 health.
Dealt 0 overkill damage!
Monster is now at 10 health
Attacking
-------
Attacking monster 0, which has 10 health.
Dealt 25 overkill damage!
*** - LET: T is a constant, may not be used as a variable
The following restarts are available:
USE-VALUE      :R1      Input a value to be used instead.
ABORT          :R2      Abort main loop
Break 1 [18]> :w

<1/172> #<SPECIAL-OPERATOR LET>
[170] EVAL frame for form
(LET (FORMAT T "Dealt ~a overkill damage!" OVERKILL-DAMAGE)
 (TAGBODY #:LOOP-5923 (IF SETF (GO #:END-5924))
  (FORMAT T "Total overkill damage is now ~a" *OVERKILL*)
  (SETQ *CURRENT-MONSTER* (1+ *CURRENT-MONSTER*))
  (FORMAT T "Moving to next monster, ~a" *CURRENT-MONSTER*)
  (WHEN (= *CURRENT-MONSTER* (1- (LENGTH *MONSTERS*))) (SETQ *GAME-OVER* T))
  (PSETQ) (GO #:LOOP-5923) #:END-5924
  (RETURN-FROM NIL (PROGN *OVERKILL* (+ *OVERKILL* OVERKILL-DAMAGE)))))
Break 1 [18]>
:w 命令显示的代码甚至不存在,我真的不明白那里发生了什么。
即使我在 tick 上调用 macroexpand ,代码 (LET (FORMAT T "Dealt ~a overkill damage!" OVERKILL-DAMAGE)...... 也不会出现在任何地方。

有谁知道发生了什么?或者,如果您有任何 CLISP 调试技巧来帮助我查明错误,请告诉我!

最佳答案

好吧,我不明白代码应该做什么,但是您的错误来自 DO :http://www.lispworks.com/documentation/HyperSpec/Body/m_do_do.htm

正如文档所说,这是一个循环,其第一个参数是变量列表:

(do (format t "Dealt ~a overkill damage!" overkill-damage)

这会尝试使用 formatt"Dealt ~a overkill damage!"overkill-damage 作为变量。

如果您只想在 when 的主体中使用多个表单,则无需执行任何特殊操作。 when 支持开箱即用:
(when (> overkill-damage 0)
    (format t "Dealt ~a overkill damage!" overkill-damage)
    (setf *overkill* (+ *overkill* overkill-damage))
    (format t "Total overkill damage is now ~a" *overkill*)
    ...)

关于debugging - T 是常数,不能用作变量,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/32671866/

10-11 17:50