servlet.ss
(module servlet mzscheme
  (require (lib "servlet.ss" "web-server")
           (lib "list.ss")
           (lib "etc.ss")
           (lib "xml.ss" "xml")
           (lib "plt-match.ss"))
  (require (planet "list.ss" ("jaymccarthy" "mmss.plt" 1))
           (planet "maybe.ss" ("jaymccarthy" "mmss.plt" 1)))
  (require "SET.ss")
  (provide interface-version
           timeout
           start)
  
  (define interface-version 'v1)
  (define timeout (* 60 60 24))
  
  (define current-game (make-parameter #f))
  
  (define (card->image c)
    (format "/setcards/~a.gif" (card->number c)))
  (define (card->image/small c)
    `(img ([src ,(format "/setcards/small/~a.gif" (card->number c))]
           [width "47"]
           [height "31"])))
  
  (define (start-game)    
    (parameterize ([current-game (new-game)])
      (show-board)))
  
  (define show-board
    (opt-lambda ([message #f])
      (if (and (< (length (game-board (current-game))) 12)
               (>= (length (game-deck (current-game))) 3))
          (parameterize ([current-game (give-three (current-game))])
            (show-board message))
          (send/suspend/dispatch
           (lambda (embed/url)
             `(html (head (title "SET")
                          (style ([type "text/css"])
                                 "body { background-color: #F2F8C2; } "
                                 "div#info { float: right; width: 45%; text-align: center; } "
                                 "div#body { float: left; width: 35%; text-align: center; } "
                                 "table#board { width: 50%; text-align: center; } "
                                 "td.card { text-align: center; } "
                                 "a { text-decoration: none; color: blue; } "
                                 "a:hover { text-decoration: underline; } "
                                 "a.k-url:visited { color: blue; } "
                                 "tr#controls td { text-align: center; } "
                                 "div#footer { clear: left; width: 99%; } ")
                          (script ([type "text/javascript"])
                                  ,(make-cdata 
                                    (make-location 0 0 0) 
                                    (make-location 0 0 0)
                                    (string-append
                                     "function checkBoard() {"
                                     "var setform = document.getElementById(\"SET\");"
                                     "var count = 0;"
                                     "for ( var i = 0; i < setform.elements.length - 3; i++ ) {"
                                     " if ( setform.elements[i].checked ) { "
                                     "  count++;"
                                     " }"
                                     "}"
                                     "if ( count == 3 ) {"
                                     " return setform.elements[setform.elements.length-2].click();"
                                     "}"
                                     "};"
                                     "function checkCard(card) {"
                                     "var card = document.getElementById(card);"
                                     "card.checked = ! card.checked;"
                                     "};"))))
                    (body 
                     (div ([id "body"])
                          (form ([method "POST"]
                                 [id "SET"]
                                 [action ,(embed/url 
                                           (lambda (request)
                                             (let* ([bindings (request-bindings request)]
                                                    [no-set? (exists-binding? 'no-set bindings)]
                                                    [propose-set? (exists-binding? 'propose-set bindings)]
                                                    [start-over? (exists-binding? 'start-over bindings)])
                                               (cond 
                                                 [no-set?
                                                  (parameterize ([current-game (give-three (current-game))])
                                                    (show-board))]
                                                 [propose-set?
                                                  (let ([nums (map just-value
                                                                   (filter just?
                                                                           (map (lambda (b)
                                                                                  (match (symbol->string (car b))
                                                                                    [(regexp "^card-(.*?)$"
                                                                                             (list s n))
                                                                                     (make-just (string->number n))]
                                                                                    [_
                                                                                     (make-nothing)]))
                                                                                bindings)))])
                                                    (if (not (equal? (length nums) 3))
                                                        (show-board "Must supply three cards.")
                                                        (let ([cards (number->card* (game-board (current-game)) nums)])
                                                          (parameterize ([current-game (apply collect-set (current-game) cards)])
                                                            (show-board)))))]
                                                 [start-over?
                                                  (start-game)]))))])
                                (table ([id "board"])
                                       ,@(map (lambda (s)
                                                `(tr ,@s))
                                              (slices (map (lambda (c)
                                                             (let* ([n (card->number c)]
                                                                    [card-id (format "card-~a" n)])
                                                               `(td ([class "card"])
                                                                    (img ([src ,(card->image c)]
                                                                          [onClick ,(format "checkCard(\"~a\"); checkBoard();"
                                                                                            card-id)]))
                                                                    (br)
                                                                    (input ([type "checkbox"]
                                                                            [name ,card-id] [id ,card-id]
                                                                            [onClick "checkBoard();"])))))
                                                           (game-board (current-game)))
                                                      3 #t `(td nbsp)))
                                       (tr (td ([colspan "3"]) nbsp))
                                       (tr ([id "controls"]) (td (input ([type "submit"] [name "no-set"] [value "No Set"])))
                                           (td (input ([type "submit"] [name "propose-set"] [value "Propose Set"])))
                                           (td (input ([type "submit"] [name "start-over"] [value "Start Over"])))))))
                     (div ([id "info"])
                          ,@(if message
                                `((div ([id "message"]) ,message))
                                empty)
                          ,@(if (game-over? (current-game))
                                `((h2 "Game Over."))
                                empty)
                          (table ([id "stats"])
                                 (tr (td "Non-sets:")
                                     (td ,(number->string (stats-non-sets (game-stats (current-game))))))
                                 (tr (td "Useless Gives:")
                                     (td ,(number->string (stats-useless-gives (game-stats (current-game))))))
                                 (tr (td "Hasty SETs:")
                                     (td ,(number->string (stats-hasty-sets (game-stats (current-game))))))
                                 (tr (td "Total time:")
                                     (td ,(let ([st (stats-start-time (game-stats (current-game)))]
                                                [fts (stats-find-times (game-stats (current-game)))])
                                            (if (empty? fts)
                                                "0"
                                                (number->string 
                                                 (exact->inexact
                                                  (/ (- (first fts) st)
                                                     1000))))))))
                          (h2 "Completed SETs:")
                          (table ([id "sets"])
                                 ,@(map (match-lambda*
                                          [(list (list c1 c2 c3) time-diff)
                                           `(tr (td ,(format "~a"
                                                             (exact->inexact
                                                              (/ time-diff 
                                                                 1000))))
                                                (td ,(card->image/small c1))
                                                (td ,(card->image/small c2))
                                                (td ,(card->image/small c3)))])
                                        (game-sets (current-game))
                                        (stats-find-times/diff (game-stats (current-game))))))
                     (div ([id "footer"])
                          (p ([align "right"])
                             "Powered by " (a ([href "http://www.plt-scheme.org/"])
                                              (img ([width "53"] [height "19"] [src "/Defaults/documentation/plt-logo.gif"]))) 
                             (br)
                             (font ([size "2"]) "For more information on PLT Software, please follow the icon link."))))))))))
  
  (define (start initial-request)
    (start-game)))