208 lines
7.6 KiB
Scheme
208 lines
7.6 KiB
Scheme
;; Written by Don Felgar, edited by Greg Pettyjohn
|
|
;;
|
|
;; Multiple-choice quiz PLT servlet sample.
|
|
;;
|
|
;;
|
|
;; Question sexp interface:
|
|
;; Questions = (listof intro-text (listof question))
|
|
;; intro-text = string explaining what test is about
|
|
;; question = (question-text choices correct-answer explanation)
|
|
;; question-text = string, the actual question
|
|
;; choices = (listof string), possible answers to the question
|
|
;; correct-answer = integer, index into choices
|
|
;;
|
|
|
|
;; Configuration
|
|
(define *data-file*
|
|
(build-path (collection-path "web-server")
|
|
"default-web-root" "servlets" "examples" "english-measure-questions.ss"))
|
|
(define *questions-per-quiz* 5)
|
|
|
|
|
|
(require (lib "unitsig.ss")
|
|
(lib "servlet-sig.ss" "web-server")
|
|
(lib "list.ss")
|
|
(lib "etc.ss"))
|
|
|
|
(unit/sig () (import servlet^)
|
|
|
|
;; Accessors into question sexp's
|
|
(define question-text car)
|
|
(define question-choices cadr)
|
|
(define question-answer caddr)
|
|
(define question-explanation cadddr)
|
|
|
|
(define quiz (load *data-file*))
|
|
(define quiz-intro (car quiz))
|
|
(define all-questions (cadr quiz))
|
|
|
|
;; ask-question: question number number -> (listof (cons symbol string))
|
|
;; Page for asking quiz question.
|
|
;; result contains a binding for 'answer
|
|
(define (ask-question question-sexp question-number n-questions)
|
|
(request-bindings
|
|
(send/suspend
|
|
(lambda (k-url)
|
|
(let ((answer-num -1))
|
|
`(html
|
|
(head
|
|
(title "Quiz Servlet")
|
|
(body
|
|
(p ,(format "Question ~A of ~A" (add1 question-number)
|
|
n-questions))
|
|
(p ,(question-text question-sexp))
|
|
(form ((method "post") (action ,k-url))
|
|
,@(map (lambda (choice)
|
|
(set! answer-num (add1 answer-num))
|
|
`(p
|
|
,(choice-descriptor answer-num) ". "
|
|
(input ((type "radio")
|
|
(name "answer")
|
|
(value ,(number->string
|
|
answer-num))))
|
|
,choice))
|
|
(cadr question-sexp))
|
|
(input ((type "submit")
|
|
(value "Next"))))))))))))
|
|
|
|
;; ((listof question-sexp) size) -> (listof question-sexp)
|
|
;; Choose a subset (without duplicates) of given size from a given list
|
|
;; assume subset-size <= (length questions)
|
|
(define (random-question-set questions subset-size)
|
|
(let choose-questions ([questions questions]
|
|
[chosen '()])
|
|
(if (= (length chosen) subset-size)
|
|
chosen
|
|
(let ([qstn (list-ref questions (random (length questions)))])
|
|
(choose-questions
|
|
(filter
|
|
(lambda (q)
|
|
(not (eq? qstn q)))
|
|
questions)
|
|
(cons qstn chosen))))))
|
|
|
|
;; choice-descriptor: number -> character
|
|
;; Map 0 to "A", 1 to "B", etc
|
|
(define (choice-descriptor number)
|
|
(string (integer->char (+ (char->integer #\A) number))))
|
|
|
|
;; begin-quiz: -> request
|
|
;; request bindings are not currently used
|
|
(define (begin-quiz)
|
|
(send/suspend
|
|
(lambda (k-url)
|
|
`(html
|
|
(head
|
|
(title "Quiz Servlet"))
|
|
(body
|
|
(p ,quiz-intro)
|
|
(form ((method "post") (action ,k-url))
|
|
(input ((type "submit")
|
|
(value "Begin Quiz")))))))))
|
|
|
|
;; Compare list of questions to answers.
|
|
;; ((listof question-sexp) (listof integer|false)) -> (listof integer)
|
|
(define (score-quiz questions answers)
|
|
(foldr
|
|
(lambda (question answer csw)
|
|
(let ([correct-answer (question-answer question)])
|
|
(apply
|
|
(lambda (correct skipped wrong)
|
|
(cond
|
|
[(not answer)
|
|
(list correct (add1 skipped) wrong)]
|
|
[(= answer correct-answer)
|
|
(list (add1 correct) skipped wrong)]
|
|
[else
|
|
(list correct skipped (add1 wrong))]))
|
|
csw)))
|
|
(list 0 0 0) questions answers))
|
|
|
|
;; end-quiz: (listof question) (listof (or/c number false)) -> request
|
|
;; request bindings are not currently used.
|
|
(define (end-quiz questions answers)
|
|
(send/forward
|
|
(lambda (k-url)
|
|
(let* ((score (score-quiz questions answers))
|
|
(correct (car score))
|
|
(skipped (cadr score))
|
|
(wrong (caddr score))
|
|
(xml
|
|
`(html
|
|
(head
|
|
(title "Quiz Servlet"))
|
|
(body
|
|
(p ,(format "Your score: ~A/~A"
|
|
correct
|
|
(+ correct wrong skipped)))
|
|
(p ,(format "Correct: ~A" correct))
|
|
(p ,(format "Skipped: ~A" skipped))
|
|
(p ,(format "Wrong: ~A" wrong))
|
|
(table ((border "5"))
|
|
(tr (td "Question") (td "Correct Answer")
|
|
(td "Your Answer") (td "Explanation"))
|
|
,@(map
|
|
(lambda (q a)
|
|
`(tr
|
|
(td ,(question-text q))
|
|
(td ,(format "~A. ~A"
|
|
(choice-descriptor
|
|
(question-answer q))
|
|
(list-ref (question-choices q)
|
|
(question-answer q))))
|
|
(td ,(if a
|
|
(format "~A. ~A" (choice-descriptor a)
|
|
(list-ref
|
|
(question-choices q) a))
|
|
"Skipped"))
|
|
(td ,(question-explanation q))))
|
|
questions answers))
|
|
(form ((method "get")
|
|
(action ,k-url))
|
|
(input ((type "submit")
|
|
(value "New Quiz"))))))))
|
|
xml))))
|
|
|
|
;; Return the first value for key in bindings, if it at least one
|
|
;; exists, otherwise #f.
|
|
(define (binding-value key bindings)
|
|
(and (exists-binding? key bindings)
|
|
(extract-binding/single key bindings)))
|
|
|
|
;; run-quiz: -> void
|
|
;; run quizes until the student gets tired
|
|
(define (run-quiz)
|
|
(let ([*questions-per-quiz*
|
|
(if (> *questions-per-quiz* (length all-questions))
|
|
(begin
|
|
(display (format "~A ~A ~A ~A\n"
|
|
"Configuration error. *questions-per-quiz*:"
|
|
*questions-per-quiz*
|
|
"for a question list of size"
|
|
(length all-questions)))
|
|
(length all-questions))
|
|
*questions-per-quiz*)])
|
|
|
|
(let ([questions (random-question-set all-questions
|
|
*questions-per-quiz*)])
|
|
(begin-quiz)
|
|
(let ([answers
|
|
(build-list (length questions)
|
|
(lambda (question-number)
|
|
(let ([answer
|
|
(binding-value 'answer
|
|
(ask-question
|
|
(list-ref questions question-number)
|
|
question-number
|
|
*questions-per-quiz*))])
|
|
(and answer (string->number answer)))))])
|
|
(end-quiz questions answers))))
|
|
(run-quiz))
|
|
|
|
;; Entry point into servlet.
|
|
(run-quiz)
|
|
|
|
)
|
|
|
|
|