Recursive Scheme Function Value Error

2019-08-07 04:21发布

问题:

I am writing a small hangman game in scheme and am getting a very weird issue that almost seems like a language specific one.

In my game I have a variable that holds the number of mistakes allowed and on each recursive call of my game loop, I "let" the value to a new one if it needs to be changed. Here is some code to help visualize how I run the game loop.

guessed_list - a list of string characters containing old guesses and one new guess (ex. '("a" "x" "b") where "a" is the new guess)

game_word - '("a" "b" "c")

display_word - a list of string characters containing letters I have matched and hyphens for those I haven't up to this iteration of my game loop (ex '("" "b" "") where "a" from the guessed_list is going to be evaluated this loop iteration)

mistakes_left - The number of mistakes I have before my game should end because of wrong guessed. Initially this starts at 6, but in my current example should be 5 because 1 letter, "x", was guessed incorrectly.

     ;; Game Loop.
  (define (game-loop guessed_list display_word mistakes_left)
    (let ((n_mistakes_left 
            (- mistakes_left (if (contains? game_word (car guessed_list))
                                 0 1))))   
      (if (= n_mistakes_left 0)
          (display n_mistakes_left);; End game output
          (let ((display_word (fill-in-guess (list (car guessed_list)) 
                                             game_word display_word))
                (guessed_list (sort guessed_list string<?)))

            (display "You have guessed: ")
            (display-list guessed_list ", ")
            (display "\n\n")
            (draw-hangman n_mistakes_left)
            (display "\n\nWord: ")
            (display-list display_word " ")

            (cond ((contains? display_word "_")
                     (display "\n\nEnter a letter to guess: ")
                     (game-loop (append (list (symbol->string (read))) guessed_list)
                                display_word n_mistakes_left))
                  (else (display "\n\nYou Won!")))))))

I can post my helper methods contains?, fill-in-guess, display-list, draw-hangman if necessary, but all of them work as they should and do not change values of my mistakes_left variable for their functionality.

The problem I am running into is my mistakes_left variable starts out at 6 and passes through fine on the first call of game-loop, but on subsequent calls, gets smaller even when guessing a correct value. I have taken every piece individually, tested it and mistakes_left comes out with the right value until I recurse.

I suspect it has to do with the recurse and "let"ing my variable, but I would like a difinitive answer if anyone could or point out the most likely simple error I am missing!

EDIT:

Here is the rest of the code to test, I still get the issue. I think append worked because it appends the second list to the first, so in that sense cons and append gave me the same input.

  (define zero_wrong "
  |---------

  |        |

  |      

  |        

  |         

  |         

  |         

  |         

  |         

  |_______________")
  (define one_wrong "
  |---------

  |        |
          ___
  |      |. .|
          ---
  |        

  |         

  |         

  |         

  |         

  |         

  |_______________")
  (define two_wrong "
  |---------

  |        |
          ___
  |      |. .|
          ---
  |        |
           |
  |        |
           |
  |         

  |         

  |         

  |         

  |_______________")
  (define three_wrong "
  |---------

  |        |
          ___
  |      |. .|
          ---
  |        |
           |----
  |        |
           |
  |         

  |         

  |         

  |         

  |_______________")
  (define four_wrong "
  |---------

  |        |
          ___
  |      |. .|
          ---
  |        |
       ----|----
  |        |
           |
  |         

  |         

  |         

  |         

  |_______________")
  (define five_wrong "|---------

  |        |
          ___
  |      |. .|
          ---
  |        |
       ----|----
  |        |
           |
  |         \\
             \\
  |         

  |         

  |         

  |_______________")
  (define six_wrong "|---------

  |        |
          ___
  |      |x x|
          ---
  |        |
       ----|----
  |        |
           |
  |       / \\
         /   \\
  |         

  |         

  |         

  |_______________")

  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; Read list value at x.
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  (define (get-str-at x str_lst)
     (cond ((equal? x 0)
        (car str_lst))
     (else
        (get-str-at (- x 1) (cdr str_lst))
     )
     )
  )

  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; Car operation for strings.
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  (define (string-car str)
     (substring str 0 1)
  )

  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; Cdr operation for strings.
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  (define (string-cdr str)
     (substring str 1 (string-length str))
  )

  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; Converts a string into a 
  ;; list of character strings.
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

  (define (string-to-char-string-list str)
     (cond 
        ((equal? (string-cdr str) "")
           (list str) 
        )
        (
           (append (list (string-car str)) (string-to-char-string-list (string-cdr str)))
        )
     )
  )

  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; Tests if a list contains a spefified object.
  ;;
  ;; Method code from:
  ;; http://stackoverflow.com/questions/1869116/scheme-built-in-to-check-list-containment
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  (define (contains? list item)
    (if (empty? list)
           #f
        (or (eq? (first list) item)
           (contains? (rest list) item)
        )
     )
  )

  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; Displays a list with the 
  ;; given separater.
  ;;
  ;; Base code from:
  ;; ftp://ftp.cs.utexas.edu/pub/garbage/cs345/schintro-v13/schintro_99.html
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

  (define (display-list a_list separater)
     (if (null? a_list)
        (display "")
        (begin 
           (display (car a_list))
           (if (null? (cdr a_list))
              (display "")
              (display separater))
           (display-list (cdr a_list) separater)
        )
     )
  )


  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; Gets the Xth word in the 
  ;; provided file.
  ;; 
  ;; Does not check for eof
  ;; condition, so x must be 
  ;; within range of the file.
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  (define (get-word x file)
     (cond 
        ((= 1 x)
           (read file))
        (else
           (read file)
           (get-word (- x 1) file)
        )
     )
  )


  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; Returns a list of blanks
  ;; equal to the number of
  ;; letters in provided word.
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

  (define (init-display-word game_word)
     (cond 
        ((null? game_word)
           (list))
        (else
           (append (init-display-word (cdr game_word)) '("_"))
        )
     )
  )


  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; Fills in the blank spaces
  ;; in the display word with
  ;; the letter that matches
  ;; those positions in the
  ;; game word.
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

  (define (fill-in-guess letter game_word display_word)
     (cond
        ((null? game_word)
           (list)
        )
     (else
     (cond 
        ((equal? letter (list (car game_word)))
           (append letter (fill-in-guess letter (cdr game_word) (cdr display_word)))
        )
        (else
           (append (list (car display_word)) (fill-in-guess letter (cdr game_word) (cdr display_word)))
        )
     )
     )
     )
  )

  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; Draws the hanging man.
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

  (define (draw-hangman guesses_left)
      (cond ((equal? guesses_left 6)
                (display zero_wrong))
      (else (cond ((equal? guesses_left 5)
                (display one_wrong))
      (else (cond ((equal? guesses_left 4)
                (display two_wrong))
      (else (cond ((equal? guesses_left 3)
                (display three_wrong))
      (else (cond ((equal? guesses_left 2)
                (display four_wrong))
      (else (cond ((equal? guesses_left 1)
                (display five_wrong))
      (else (display six_wrong))
      )))))))))))
  )

回答1:

I have made several modifications to your code. I have marked my changes with comments above the function, explaining them. You problem was that you sorted guessed_list. There is no need to do so. I have tested it and it works. Keep in mind, that if you call game-loop with an empty list of guesses it will error. To fix that you need to test if guessed_list is null, and also return 0 for the subtraction. I will leave that to you. Also in many places in your code, you had nested conds That is not necessary. Read here: cond

(define game_word '("a" "b" "c"))

 (define zero_wrong "
  |---------

  |        |

  |      

  |        

  |         

  |         

  |         

  |         

  |         

  |_______________")
  (define one_wrong "
  |---------

  |        |
          ___
  |      |. .|
          ---
  |        

  |         

  |         

  |         

  |         

  |         

  |_______________")
  (define two_wrong "
  |---------

  |        |
          ___
  |      |. .|
          ---
  |        |
           |
  |        |
           |
  |         

  |         

  |         

  |         

  |_______________")
  (define three_wrong "
  |---------

  |        |
          ___
  |      |. .|
          ---
  |        |
           |----
  |        |
           |
  |         

  |         

  |         

  |         

  |_______________")
  (define four_wrong "
  |---------

  |        |
          ___
  |      |. .|
          ---
  |        |
       ----|----
  |        |
           |
  |         

  |         

  |         

  |         

  |_______________")
  (define five_wrong "|---------

  |        |
          ___
  |      |. .|
          ---
  |        |
       ----|----
  |        |
           |
  |         \\
             \\
  |         

  |         

  |         

  |_______________")
  (define six_wrong "|---------

  |        |
          ___
  |      |x x|
          ---
  |        |
       ----|----
  |        |
           |
  |       / \\
         /   \\
  |         

  |         

  |         

  |_______________")

  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; Read list value at x.
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (get-str-at x str_lst)
  (cond ((equal? x 0)
         (car str_lst))
        (else
         (get-str-at (- x 1) (cdr str_lst)))))

  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; Car operation for strings.
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (string-car str)
  (substring str 0 1))

  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; Cdr operation for strings.
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; *** This is enough.
(define (string-cdr str)
  (substring str 1))

  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; Converts a string into a 
  ;; list of character strings.
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(define (string-to-char-string-list str)
  (cond 
    ((equal? (string-cdr str) "")
     (list str))
    ((append (list (string-car str)) (string-to-char-string-list (string-cdr str))))))

  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; Tests if a list contains a spefified object.
  ;;
  ;; Method code from:
  ;; http://stackoverflow.com/questions/1869116/scheme-built-in-to-check-list-containment
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (contains? list item)
  (if (empty? list)
      #f
      (or (string=? (first list) item)
          (contains? (rest list) item))))

  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; Displays a list with the 
  ;; given separater.
  ;;
  ;; Base code from:
  ;; ftp://ftp.cs.utexas.edu/pub/garbage/cs345/schintro-v13/schintro_99.html
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(define (display-list a_list separater)
  (if (null? a_list)
      (display "")
      (begin 
        (display (car a_list))
        (if (null? (cdr a_list))
            (display "")
            (display separater))
        (display-list (cdr a_list) separater))))


  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; Gets the Xth word in the 
  ;; provided file.
  ;; 
  ;; Does not check for eof
  ;; condition, so x must be 
  ;; within range of the file.
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (get-word x file)
  (cond 
    ((= 1 x)
     (read file))
    (else
     (read file)
     (get-word (- x 1) file))))


  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; Returns a list of blanks
  ;; equal to the number of
  ;; letters in provided word.
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(define (init-display-word game_word)
  (cond 
    ((null? game_word)
     (list))
    (else
     (append (init-display-word (cdr game_word)) '("_")))))


  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; Fills in the blank spaces
  ;; in the display word with
  ;; the letter that matches
  ;; those positions in the
  ;; game word.
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; No need for append here. Just use cons when adding to the start of the list. 
; No need to nest conds
(define (fill-in-guess letter game_word display_word)
  (cond
    ((null? game_word)
     '())
    ((equal? letter (car game_word))
     (cons letter (fill-in-guess letter (cdr game_word) (cdr display_word))))
    (else
     (cons (car display_word)
           (fill-in-guess letter (cdr game_word) (cdr display_word))))))

  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; Draws the hanging man.
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; You used cond as an if/else statement. You can have multiple clauses in a cond.
; You only need one final else cluase.
(define (draw-hangman guesses_left)
  (cond ((equal? guesses_left 6)
         (display zero_wrong))
        ((equal? guesses_left 5)
         (display one_wrong))
        ((equal? guesses_left 4)
         (display two_wrong))   
        ((equal? guesses_left 3)
         (display three_wrong))
        ((equal? guesses_left 2)
         (display four_wrong))
        ((equal? guesses_left 1)
         (display five_wrong))
        (else (display six_wrong))))

; Don't sort the guessed-list. 
; You had display when guess left was 0. Not draw_hagman
(define (game-loop guessed_list display_word mistakes_left)
  (let ((n_mistakes_left 
         (- mistakes_left (if (contains? game_word (car guessed_list))
                              0 1))))   
    (if (= n_mistakes_left 0)
        (draw-hangman n_mistakes_left);; End game output
        (let ((display_word (fill-in-guess (car guessed_list) 
                                           game_word display_word)))

          (display "You have guessed: ")
            (display-list guessed_list ", ")
          (display "\n\n")
          (draw-hangman n_mistakes_left)
          (display "\n\nWord: ")
          (display-list display_word " ")

          (cond ((contains? display_word "_")
                 (display "\n\nEnter a letter to guess: ")
                 (game-loop (cons (symbol->string (read)) guessed_list)
                            display_word n_mistakes_left))
                (else (display "\n\nYou Won!")))))))


回答2:

I generalize the question to not focus on the hangman aspect of it. There was an immediate answer to the problem and comments further explained the reasoning for the error. You can find all of that information in this post: Scheme Recursion Loop Incorrect Values and Variable Binding.