>>11
This is version 2, save the Americans edition, get it while supplies last!
;;; DATA-STRUCTURE
(defun my/make-question (prompt response required-time)
(list prompt response required-time))
(defun my/required-time (question) (nth 2 question))
(defun my/response (question) (nth 1 question))
(defun my/prompt (question) (nth 0 question))
;;; MAIN LOOP
(defun my/quiz-round (randomp prompts)
(let (incorrect timelyp q-and-a attempt)
(while prompts
(unwind-protect
(setq q-and-a (if randomp (seq-random-elt prompts) (car prompts))
prompts (delete q-and-a prompts)
timelyp (let ((time (my/required-time q-and-a)))
(or (not time) (run-with-timer time nil #'(lambda () (setq timelyp)))))
attempt (read-number (format "Evaluate: %s\n" (my/prompt q-and-a))))
(when (timerp timelyp) (cancel-timer timelyp)))
(cond ((/= attempt (my/response q-and-a))
(message (format "You answered %d instead of %d." attempt (my/response q-and-a)))
(push q-and-a incorrect)
(sleep-for 0.5))
((not timelyp)
(message "You answered too slowly.")
(push q-and-a incorrect))
(t (message "Correct!")))
(sleep-for 0.5))
incorrect))
(defun my/quiz (randomp prompts)
(while (setq prompts (my/quiz-round randomp prompts))
;; (prin1 prompts)
(message "New Round!")
(sleep-for 2)))
;;; USER-INTERFACE
(defmacro my/time-check (required-time default)
`(cond ((not ,required-time) (setq ,required-time ,default))
((not (floatp ,required-time))
(error ,(concat (symbol-name required-time) " must be a float.")))
((<= ,required-time 0)
(error ,(concat (symbol-name required-time) " must be positive.")))))
(defun my/beginner-multiplication-quiz (required-time)
(interactive "P")
(my/time-check required-time 2)
(my/quiz t (my/basic-times-table required-time)))
(defun my/intermediate-multiplication-problems-quiz (digit-time)
(interactive "P")
(my/time-check digit-time 4)
(my/quiz nil (my/random-multiplication-problems 10 digit-time '(1000 100000) '(2 10))))
(defun my/advanced-multiplication-problems-quiz (digit-time)
(interactive "P")
(my/time-check digit-time 4)
(my/quiz nil (my/random-multiplication-problems 10 digit-time '(1000 100000) '(100 1000))))
(defun my/intermediate-division-problems-quiz (digit-time)
(interactive "P")
(my/time-check digit-time 4)
(my/quiz nil (my/random-division-problems 10 digit-time '(1000 100000) '(2 10))))
(defun my/advanced-division-problems-quiz (digit-time)
(interactive "P")
(my/time-check digit-time 4)
(my/quiz nil (my/random-division-problems 10 digit-time '(1000 100000) '(100 1000))))
;;; DATA-SETS
(defun my/basic-times-table (required-time)
(cl-mapcan
#'(lambda (a)
(mapcar
#'(lambda (b)
(my/make-question (format "%d * %d = ?" a b) (* a b) required-time))
(number-sequence a 9)))
(number-sequence 2 9)))
(defun my/bounded-random (min max)
(+ (mod (random) (- max min)) min))
(defmacro my/generator (number fun)
(let ((prompts (gensym))
(i (gensym)))
`(let (,prompts)
(dotimes (,i ,number ,prompts)
(push (,fun ,i) ,prompts)))))
(defmacro my/defun-random-arithmetic (name bindings fun)
`(defun ,name (number digit-time bounds1 bounds2)
(my/generator
number
(lambda (_)
((lambda ,bindings ,fun)
(my/bounded-random (cadr bounds1) (car bounds1))
(my/bounded-random (cadr bounds2) (car bounds2)))))))
(my/defun-random-arithmetic
my/random-multiplication-problems (a b)
(my/make-question (format "%d * %d = ?" a b) (* a b) (* digit-time (ceiling (log10 (* a b))))))
(my/defun-random-arithmetic
my/random-division-problems (a b)
(my/make-question (format "%d / %d = ?" (* a b) b) a (* digit-time (ceiling (log10 (* a b))))))