6
\$\begingroup\$

Ok, it's not completely like Simon Says, since it's text-based. There is nothing about memorizing based on visual cues and/or sound here, just memorizing long sequences of numbers. I wanted to do it in the language I write the least code in, Scheme (w/ Chicken), and see what happens.

(use posix)
(use (srfi 1))

(define button-count 4)

(define (clear-screen)
  (system "cls"))

(define (make-delay)
  (sleep 2))

(define (show-sequence sequence)
  (if (not (null? sequence))
    (begin
      (clear-screen)
      (make-delay)
      (write (car sequence))
      (make-delay)
      (show-sequence (cdr sequence)))
    (begin
      (clear-screen)
      (write-line "GO!"))))

(define (show-score score)
  (format #t "Your score: ~A~%" score))

(define (get-player-input)
  (read-line))

(define (make-sequence length)
  (map random (make-list length button-count)))

(define (player-sequence-matches? target-sequence)
  (cond
    [(null? target-sequence) #t]
    [(string=? (get-player-input) (number->string (car target-sequence)))
     (player-sequence-matches? (cdr target-sequence))]
    [else #f]))

(define (simon-says difficulty)
  (let [(current-sequence (make-sequence difficulty))]
    (show-sequence current-sequence)
    (if (player-sequence-matches? current-sequence)
      (+ 1 (simon-says (+ difficulty 1)))
      0)))

(define (play-simon-says)
  (show-score (simon-says 1)))

If you want to run it on something Unix, you'll need to change (system "cls") to (system "clear").

\$\endgroup\$
3
\$\begingroup\$
  1. Ideally I'd like to use termcap or terminfo or the like to clear the terminal without shelling out. Alas, it seems Chicken doesn't have an egg for either termcap or terminfo. (It does have an egg for curses, but I consider that to be too heavyweight for just clearing a terminal.)

  2. I'd probably use for-each for iterating through the items in show-sequence, rather than the recursive approach you have:

    (define (show-sequence seq)
      (for-each (lambda (x)
                  (clear-screen)
                  (make-delay)
                  (display x)    ;; not write
                  (make-delay))
                seq)
      (clear-screen)
      (display "GO!\n"))
    
  3. Not a huge fan of making a get-player-input function that's exactly the same as read-line. I'd just call read-line directly.

  4. Your definition of make-sequence is quite clever, but it's not very idiomatic. Since you have SRFI 1 loaded, I'd just use list-tabulate:

    (define (make-sequence len)  ;; not length
      (list-tabulate len (lambda (_) (random button-count))))
    
  5. You can simplify player-sequence-matches? by using SRFI 1's every:

    (define (player-sequence-matches? seq)
      (every (lambda (x)
               (= (string->number (read-line)) x))
             seq))
    

    And since Chicken has cut builtin, you can simplify even further:

    (define (player-sequence-matches? seq)
      (every (cut = (string->number (read-line)) <>) seq))
    
  6. For calculating the total score, I'd rather accumulate the score, rather than use recursion to hold the score. Like so:

    (define (simon-says difficulty)
      (let loop ((difficulty difficulty)
                 (score 0))
        (define seq (make-sequence difficulty))
        (show-sequence seq)
        (if (player-sequence-matches? seq)
            (loop (add1 difficulty) (add1 score))
            score)))
    

    This way, the function is totally tail-recursive.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy