- The program exemplifies a sound approach to naming. Debates can rage on what the best names are: different and perhaps better or worse names are possible.
- The program does an excellent job of treating constants as constants rather than variables.
num-message is a step toward modularity.
- Whether by accident or intuition the idea of
for points toward a very useful and idiomatic abstraction often found in scheme/racket/Lisp programs: streams. Treating the guesses as a stream to be processed is useful.
Areas for improvement
- Modularity: User interface is still mingled with control flow in
- Iteration: Recursion is the idiomatic method for repeating a process. List comprehensions are an available alternative for programming in the fuctional style.
The code demonstrates some different [maybe better, maybe worse] techniques.
The key abstraction is three streams:
- An input stream of guesses.
- An output stream of messages.
- An internal stream of clock ticks. The clock ticks up rather than down so that we can talk about the first guess and the first message or write timestamped guesses and messages to a log. It starts with 1 because that maps better to the idea of "first, second...".
Declaring the constants as functions allows for redefiing their behavior without impacting the main control flow. Providing for variable argument arity allows for experimenting with the semantics of the main loop independently of the function implementation or dispatching based on the number of arguments.
#| Data Types |#
(struct game-message (start win lose too-high too-low))
#| User Interaction |#
(game-message "Please make your first guess: "
"\nWinner Winner Chicken Dinner!\n"
"\nAll your guesses are belong to us :(\n"
"\nYour guess is too high. Please Guess again: "
"\nYour guess is too low. Please Guess again: "))
(define (get-target . args)
(define (get-guesses . args)
(define (get-guess . args)
(define (inform-user message)
A Higher Order Function
make-game takes a target and a number of guesses as input and returns a function that iterates over the
clock stream, taking a guess from the stream of guesses and outputing a message on the stream of messages.
#| Constructor |#
(define (make-game target guesses)
(define (game clock)
(let ((guess (get-guess)))
[(equal? guess target)
(inform-user (game-message-win messages))]
[(= clock guesses)
(inform-user (game-message-lose messages))]
[(< guess target)
(inform-user (game-message-too-low messages))
(game (add1 clock))]
[(> guess target)
(inform-user (game-message-too-high messages))
(game (add1 clock))])))
A submodule named "main" has special status when called by racket from the command line: it will execute automatically.
Game execution begins by calling the function returned by
make-game as a closure.
#| Play game |#
(module* main #f
((make-game (get-target)(get-guesses)) 1))
Note that this is run from the operating system shell, not the Racket REPL.
$ racket number-game.rkt
"Please make your first guess: "
Your guess is too low. Please Guess again: 6
Your guess is too low. Please Guess again: 9
Your guess is too low. Please Guess again: 99
Your guess is too high. Please Guess again: 55
Your guess is too high. Please Guess again: 18
All your guesses are belong to us :(