;;; File:
;;;   dicegame.ss
;;; Version:
;;;   1.2 of March 2003
;;; Author:
;;;   Samuel A. Rebelsky
;;; Contents:
;;;   A CGI script that illustrates state variables (passed as hidden
;;;   fields in a form) and the random procedure.
;;; Organization:
;;;   Preparation - Stuff I need to do to get started.
;;;   Utilities - Other stuff I need.
;;;   Page Generators - Procedures for generating specific kinds of
;;;     pages (when given particular values)
;;;   Interface - Stuff to extract important values and then
;;;     call the page generators.  You'll find (page) here.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Preparation

;;; Look!  We're going to use Sam's CGI library.  What fun.
(load "/home/rebelsky/Web/Scheme/webutils.ss")

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Utilities

;; Procedure:
;;;   roll-a-die
;;; Parameters:
;;;   None
;;; Purpose:
;;;   To simulate the rolling of one six-sided die.
;;; Produces:
;;;   An integer between 1 and 6, inclusive.
;;; Preconditions:
;;;   None.
;;; Postconditions:
;;;   Returns an integer between 1 and 6, inclusive.
;;;   It should be difficult (or impossible) to predict which
;;;     number is produced.
;;; Citation:
;;;   Based on a procedure written by Henry Walker or John D. Stone
(define roll-a-die
  (lambda ()
    (+
     (random 6)    ; a value in the range [0 .. 5]
     1)))          ; now in the range [1 .. 6]

;;; Procedure:
;;;   pair-a-dice
;;; Parameters:
;;;   [None]
;;; Purpose:
;;;   Simulates the rolling of two dice.
;;; Produces:
;;;   dice, a list of two integers.
;;; Preconditions:
;;;   [None]
;;; Postconditions:
;;;   Each element of dice is an integer between 1 and 6, inclusive.
;;;   It is difficult to predict what values dice will contain.
;;;   Across many rolls, each value between 1 and 6 will appear 
;;;     an approximately equal number of times in each position.
(define pair-a-dice
  (lambda ()
    (list (roll-a-die) (roll-a-die))))

;;; Procedure:
;;;   play-even-odds
;;; Parameters:
;;;   dice, a list of two integers
;;; Purpose:
;;;   Determines an outcome based on the roll of the dice.
;;; Produces:
;;;   winnings, an integer
;;; Preconditions:
;;;   Each integer in dice is between 1 and 6, inclusive. [Unverified]
;;; Postconditions:
;;;   If the sum of the dice is even, winnings is 1.
;;;   If the sum of the dice is odd, winnings is -1.
(define play-evens-odds
  (lambda (dice)
    (let ((result (+ (car dice) (cadr dice))))
      (if (even? result) 1 -1))))

;;; Procedure:
;;;   report-result
;;; Parameters:
;;;   result, an integer
;;; Purpose:
;;;   Reports on the result as a string.
;;; Produces:
;;;   report, a string.
;;; Preconditions:
;;;   [Standard]
;;; Postconditions:
;;;   If result is positive, report is some variation of "You win!"
;;;   If result is negative, report is some variation of "I won!"
;;;   If result is 0, report is some variation of "We tie!"
(define report-result
  (lambda (result)
    (cond
      ((> result 0) (sap "You win $" (number->string result) "!"))
      ((< result 0) (sap "Ha ha!  You lose $" (number->string (- result)) "!"))
      (else "We tie!"))))

;;; Procedure:
;;;   sum-a-dice
;;; Parameters:
;;;   [None]
;;; Purpose:
;;;   Simulates the rolling of two dice.
;;; Produces:
;;;   dice-roll, an integer
;;; Preconditions:
;;;   [None]
;;; Postconditions:
;;;   dice-roll "simulates" the sum of two dice.
;;;   The particular value of dice-roll is unpredictable.
;;;   However, that value is between 2 and 12, inclusive.
;;;   The distribution of values of dice-roll should be similar
;;;     to the distribution of values of real dice.
(define sum-a-dice
  (lambda ()
    (+ (roll-a-die) (roll-a-die))))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Page Generators

(define sap string-append)

;;; Procedure
;;;   start-page
;;; Parameters:
;;;   [None]
;;; Purpose:
;;;   Generates an HTML page that starts the game.
;;; Produces:
;;;   A string that corresponds to that page.
;;; Preconditions:
;;;   [None]
;;; Postconditions:
;;;   The returned page is valid HTML.
(define start-page
  (lambda ()
    (make-page
     (head "A Dice Game")
     (body 
      (sap (heading 1 "Welcome to the Dice Game")
           (string #\newline)
           (paragraph 
            (sap "You start with $10.  "
                 "We repeatedly roll two dice.  "
                 "If their sum is even, you win $1.  "
                 "If their sum is odd, I win $1."))
            (string #\newline)
            (paragraph "Click below to play!")
            (string #\newline)
            (form "dicegame.cgi" "Roll!" 
                  (paragraph (hidden "account" "10")))
            valid)))))

;;; Procedure
;;;   game-page
;;; Parameters:
;;;   account, a number
;;; Purpose:
;;;   Generates an HTML page that plays one round of the game and
;;;   updates the account.
;;; Produces:
;;;   A string that corresponds to that page.
;;; Preconditions:
;;;   The account must be positive.
;;; Postconditions:
;;;   The returned page is valid HTML.
(define game-page
  (lambda (account)
    (let* ((roll (pair-a-dice))
           (result (play-evens-odds roll))
           (new-account (+ account result))
           (summary (report-result result)))
      (make-page
       (head summary)
       (body
        (sap
         (heading 1 "A Dice Game")
         (paragraph (sap "You had $" (number->string account) "."))
         (paragraph (sap "You rolled " 
                         (number->string (car roll))
                         " and "
                         (number->string (cadr roll))
                         "."))
         (paragraph summary)
         (paragraph (sap "You now have $" (number->string new-account) "."))
         (form "dicegame.cgi" "Roll"
               (paragraph 
                (hidden "account" (number->string new-account))))
         valid))))))

;;; Procedure
;;;   end-page
;;; Parameters:
;;;   [None]
;;; Purpose:
;;;   Generates an HTML page that ends the game.
;;; Produces:
;;;   A string that corresponds to that page.
;;; Preconditions:
;;;   [None]
;;; Postconditions:
;;;   The returned page is valid HTML.
(define end-page
  (lambda ()
    (make-page 
     (head "Loser!")
     (body 
      (string-append
       (heading 1 "You lost all of your money!")
       (string #\newline)
       (paragraph "You can't roll again; you ran out of money!")
       (string #\newline)
       (form "dicegame.cgi" "Start Again" "")
       valid)))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Interface

;;; Procedure:
;;;   page
;;; Parameters:
;;;   None
;;; Purpose:
;;;   Builds an HTML page according to specifications.
;;; Produces:
;;;   A string that corresponds to the HTML page.
;;; Preconditions:
;;;   greeting-page must be definedk, take one parameter, and generate a page.
;;; Postconditions:
;;;   The returned page is as valid as anything greeting-page produces.
(define page
  (lambda ()
    (let ((account (string->number
                    (get-nonempty-cgi-variable 'account "-1"))))
      (cond
        ((< account 0) (start-page))
        ((= account 0) (end-page))
        (else (game-page account))))))
