2015-09-19 23 views
3

我正在爲編碼挑戰編寫常見的lisp代碼,這是一個RPG式的難題,您需要計算戰士處理的總體過度殺傷傷害。因爲我對通用的lisp很陌生,所以我的代碼可能很糟糕。請不要發佈一般的通用lisp編碼技巧,除非它們與錯誤有關。我計劃在發佈此代碼後,在錯誤得到修復後,我們將codereviewT是一個常量,不能用作變量

代碼運行良好,直到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上調用宏展開,代碼(LET (FORMAT T "Dealt ~a overkill damage!" OVERKILL-DAMAGE)......也不會出現在任何地方。

有誰知道發生了什麼事?或者,如果您有任何CLISP調試提示可幫助我找出錯誤,請告訴我!

回答

6

好了,我不明白是什麼代碼是應該做的,但你的錯誤來自DOhttp://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*) 
    ...) 
2

do的第一個參數定義了循環的局部變量,如let所示;你正在使用它作爲身體的開始。

+0

噢好吧,我似乎錯了。我認爲只要順序地執行它的正文中的每個陳述。什麼功能*做*然後呢? – Azeirah

+1

@Azeirah ['progn'](http://www.lispworks.com/documentation/HyperSpec/Body/s_progn.htm#progn),但在這種情況下你甚至不需要這樣做。看到我的答案。 – melpomene

5

DO是Common Lisp中的宏。它是Lisp中較老的控制結構之一,如DOLISTDOTIMES

由於它是一個宏,它可能很難調試。尤其是當DO宏本身不執行任何語法檢查時。

對於在Lisp中進行調試,我們可以使用編譯器和解釋器。首先,讓我們使用編譯器:

[1]> (defun test() (do (format t "hello world") (read))) 
TEST 
[2]> (compile 'test) 

** - Continuable Error 
in TEST : Illegal syntax in LET/LET*: "hello world" 

編譯器提供有關非法語法錯誤消息。所以有一個語法錯誤,但它沒有說明它來自哪裏。由於代碼中沒有LETLET*,它必須來自某種語法轉換 - >宏。DEFUNDO是宏。

(macro-function 'do) -> #<COMPILED-FUNCTION DO> 

下一步是看DO形式的宏展開:

[4]> (macroexpand-1 '(do (format t "hello world") (read))) 
(BLOCK NIL 
(LET (FORMAT T "hello world") 
    (TAGBODY #:LOOP-3239 (IF READ (GO #:END-3240)) (PSETQ) (GO #:LOOP-3239) #:END-3240 
    (RETURN-FROM NIL (PROGN))))) ; 
T 

在上面的表格我們可以看到LET,我們可以看到綁定是錯誤的。所以DO表格可能是錯誤的。

這現在是檢查do語法的最佳時間:請參閱DO的HyperSpec條目。通常這應該足夠清楚,以查找語法錯誤。

,我們還可以通過例如使用CLISP解釋和步:

[5]> (step (test)) 
step 1 --> (TEST) 
Step 1 [6]> step 
step 2 --> (BLOCK NIL (LET (FORMAT T "hello world") (TAGBODY #:LOOP-3210 # # ...))) 
Step 2 [7]> step 
step 3 --> (LET (FORMAT T "hello world") (TAGBODY #:LOOP-3210 (IF READ #) (PSETQ) ...)) 
Step 3 [8]> step 

*** - LET: T is a constant, may not be used as a variable 

基本上我們看到在每一步完成的代碼轉換。

由於Common Lisp有很多實現,所以有一些更好的錯誤消息。例如SBCL:

* (defun test() (do (format t "hello world") (read))) 
; in: DEFUN TEST 
;  (DO (FORMAT 
;   T 
;   "hello world") 
;   (READ)) 
; 
; caught ERROR: 
; during macroexpansion of 
; (DO (FORMAT 
;  T 
;  "hello world") 
;  (READ)). 
; Use *BREAK-ON-SIGNALS* to intercept. 
; 
; "hello world" is an illegal form for a DO varlist. 
; 
; compilation unit finished 
; caught 1 ERROR condition 

這樣比較好。