Wednesday 4 May 2016

Know Your Tables?


The RANDOM function has hundreds of uses in programming. Suppose, for example, you wanted a program to teach your eight-year-old son or brother his 9-times table. You could set it up like this:

(defun table-guess ()
   "Let the boy guesses the table."
   (let ((a 0))
     (format *terminal-io* "What is 1 times 9? ")
     (clear-input *terminal-io*)
     (setq a (read *terminal-io*)
     (when (= a 9)
       (format *terminal-io* "Correct~%"))
     (format *terminal-io* "What is 2 times 9? ")
     (clear-input *terminal-io*)
     (setq a (read *terminal-io*)
     (when (= a 18)
       (format *terminal-io* "Correct~%"))
    ...



...and so on. But this would give you a very long program - without even solving the problem of how the computer responds if one of this answer is wrong!
    Use the RANDOM function, however, and you can produce a much more compact program that will not only ask all the right questions, but ask them in random order a much better way of teaching, as well as of programming.
    Particularly when you are learning programming, it is always best to work out of the 'core' of a program before adding the frills. So first try these forms:


(let ((n (1+ (random 12)))
  (a 0))
  (format *terminal-io* "What is ~a times 9? " n)
  (clear-input *terminal-io*)
  (setq a (read *terminal-io*))
  (when (= a (* n 9))
    (format *terminal-io* "Correct~%")))

this form is using RANDOM function in the same way as in the guessing game. The LET macro first set up two variables: one for the random number the computer selects and the other for user guessing. Then, with the same LET form, you tell the computer to pick a number - any integer number - between 1 and 12. (The 1+ function is necessary because the random numbers starts at 0 - a number not wanted for this job).
    In the first FORMAT form you ask the player to multiply by 9 whichever number the computer has chosen this time. The SETQ special form actually take the console input and stores in the a variable. The WHEN macro warns the computer to multiply this number by 9, compare this with the player's answer and - if the latter is correct - print "Correct".
    Execute this form and you will find that it works - once. You could make it continuous by modifying it as follows:

(loop named guess-table do
  (let ((n (1+ (random 12)))
        (a 0))
    (format *terminal-io* "What is ~a times 9? " n)
    (clear-input *terminal-io*)
    (setq a (read *terminal-io*))
    (when (= a (* n 9))(
      (format *terminal-io* "Correct~%"))))

...but why not do the job properly, as here:


(defun guess-tables ()
  (let ((player-name nil)
        (n nil)
        (a nil))
    (format *terminal-io* "Hello. What is your name? ")
    (clear-input *terminal-io*)
    (setq player-name (read *terminal-io*))
    (format *terminal-io* "Hello, ~a, I have some questions for you~%" player-name)
    (sleep 2)
    (loop named game-loop do
      (setq n (1+ (random 12)))
      (loop named wrong-answer-loop do
          (format *terminal-io* "What is ~a times 9? " n)
          (clear-input *terminal-io*)
          (setq a (read *terminal-io*))
        if (= (* n 9) a) do
          (format *terminal-io* "Well done, ~a, here is the next one~%" player-name)
          (return-from wrong-answer-loop)
        else do
          (format *terminal-io* "Sorry, please try again~%"))
        (sleep 1))))

the SLEEP function simply waste time, by making the computer 'count up to' a certain number before springing the next question on your unsuspecting infant. (How it does this is in Common Lisp Programming 2).
    From the viewpoint of your eight-year-old, this function has one terrible disadvantage: it goes on and on without stopping. You can release him from this misery, like this: push CTRL, then C and when the interpreter drops in the debugger press Q.
    From your viewpoint, on the oder hand, the program has a bonus feature: simply by changing the 9s in the function to 5s, 6s, 7s or whatever, you can test him on all his tables.
    And because the computer does all the computations, you do not need to know any of the answers yourself!
    Anyway to avoid the program to be changed for every table, let render the GUESS-TABLES function more general:

(defun guess-tables (base-number)
  (let ((player-name nil)
        (n nil)
        (a nil))
    (format *terminal-io* "Hello. What is your name? ")
    (clear-input *terminal-io*)
    (setq player-name (read *terminal-io*))
    (format *terminal-io* "Hello, ~a, I have some questions for you~%" player-name)
    (sleep 2)
    (loop named game-loop do
        (setq n (1+ (random 12)))
        (loop named wrong-answer-loop do
           (format *terminal-io* "What is ~a times ~a? " n base-number)
           (clear-input *terminal-io*)
           (setq a (read *terminal-io*))
          if (= (* n base-number) a) do
           (format *terminal-io* "Well done, ~a, here is the next one~%" player-name)
           (return-from wrong-answer-loop)
          else do
           (format *terminal-io* "Sorry, please try again~%"))
        (sleep 1))))

Now would be fine if the computer could track down the number of guesses vs. the player's right answers:


(defun guess-tables (base-number)
  (let ((player-name nil)
        (correct-guesses 0)
        (n nil)
        (a nil))
    (format *terminal-io*
            "Hello. What is your name? ")
    (clear-input *terminal-io*)
    (setq player-name (read *terminal-io*))
    (format *terminal-io*  
            "Hello, ~a, I have some questions for you~%" 
            player-name)
    (sleep 2)
    (loop named game-loop
          for guess-count from
          do
            (format *terminal-io*  
                    "~a correct guesses on ~a. Question #~a "
                    correct-guesses 
                    (1- guess-count) 
                    guess-count)
        (setq n (1+ (random 12)))
        (loop named wrong-answer-loop 
            do
                (format *terminal-io* 
                        "What is ~a times ~a? " 
                        n
                        base-number)
                (clear-input *terminal-io*)
                (setq a (read *terminal-io*))
            if (= (* n base-number) a)  
            do
                (format *terminal-io* 
                        "Well done, ~a, here is the next one~%"
                        player-name)
                (incf correct-guesses)
                (return-from wrong-answer-loop)
            else
            do
                (format *terminal-io*
                        "Sorry, please try again~%"))
           (sleep 1))))


(guess-tables 2)

As you can see to advance towards the next question, the player must enter the correct answer, this is not the case. In fact if the player needs to end the game he/she has to enter CTRL+C and the Q keys. Let's modify again to prevent this:


(defun guess-tables (base-number)
  (let ((player-name nil)
        (correct-guesses 0)
        (n nil)
        (a nil))
    (format *terminal-io* 
            "Hello. What is your name? ")
    (clear-input *terminal-io*)
    (setq player-name (read *terminal-io*))
    (format *terminal-io*  
            "Hello, ~a, I have some questions for you~%"
            player-name)
    (sleep 2)
    (loop 
        named game-loop
        for guess-count from 1
        do
            (format *terminal-io*  
                    "~a correct guesses on ~a. Question #~a "
                    correct-guesses
                    (1- guess-count)
                    guess-count)
            (setq n (1+ (random 12)))
            (format *terminal-io* 
                    "What is ~a times ~a? "
                    n
                    base-number)
            (clear-input *terminal-io*)
            (setq a (read *terminal-io*))
            (if (= (* n base-number) a)
                (progn
                    (format *terminal-io*
                            "Well done, ~a, here is the next one~%"
                            player-name)
                    (incf correct-guesses))
                (format *terminal-io*
                        "Sorry, wrong answer.~%"))
            (sleep 1)
        unless (yes-or-no-p "Another game? ")
        do
            (format *terminal-io*
                    "Bye ~a, you scored ~a on ~a correct answers.~%"
                    player-name
                    correct-guesses
                    guess-count)
            (return-from game-loop))))

(guess-tables 3)