distributed server game

This commit is contained in:
Matthias Felleisen 2012-12-21 21:10:47 -05:00
parent a354195ae4
commit a567de9a48
5 changed files with 480 additions and 0 deletions

View File

@ -0,0 +1,148 @@
#lang racket
;; the client for distributed Guess my Number
(require 2htdp/image 2htdp/universe "shared.rkt")
(provide launch-guess-client)
;
;
;
;
;
; ;;;;;; ;
; ; ; ;
; ; ; ;;;; ;;;;;; ;;;;
; ; ; ; ; ; ; ;
; ; ; ; ; ;
; ; ; ;;;;;; ; ;;;;;;
; ; ; ; ; ; ; ;
; ; ; ; ;; ; ; ; ;;
; ;;;;;; ;;;; ;; ;;;; ;;;; ;;
;
;
;
;
;; ClientState = String
(define ClientState0 "no guess available")
;; Constants
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define TEXT-SIZE 11)
(define HELP-TEXT
(text "↑ for larger numbers, ↓ for smaller ones"
TEXT-SIZE
"blue"))
(define HELP-TEXT2
(text "Press = when your number is guessed; q to quit."
TEXT-SIZE
"blue"))
(define WIDTH (+ (image-width HELP-TEXT2) 10))
(define HEIGHT 150)
(define COLOR "red")
(define SIZE 72)
(define TEXT-X 3)
(define TEXT-UPPER-Y 10)
(define TEXT-LOWER-Y 135)
(define MT-SC
(place-image/align
HELP-TEXT TEXT-X TEXT-UPPER-Y
"left" "top"
(place-image/align
HELP-TEXT2
TEXT-X TEXT-LOWER-Y "left" "bottom"
(empty-scene WIDTH HEIGHT))))
;
;
;
; ;
; ;
; ;;; ;;;
; ;; ;;
; ; ; ; ; ;;;; ;;; ;; ;;;
; ; ; ; ; ; ; ; ;; ;
; ; ; ; ; ; ; ; ;
; ; ; ; ;;;;;; ; ; ;
; ; ; ; ; ; ; ;
; ; ; ; ;; ; ; ;
; ;;; ;;; ;;;; ;; ;;;;;;; ;;; ;;;
;
;
;
;
;; String -> ClientState
;; Launch the Client.
(define (launch-guess-client n host)
(big-bang ClientState0
(on-draw draw-guess)
(on-key handle-keys)
(name n)
(register host)
(on-receive handle-msg)))
;; handle-keys: ClientState Key -> [Package ClientState CtoSMessage] or ClientState
;; if the key is "up" or "down", ask the server for a different guess
(define (handle-keys w key)
(cond [(key=? key "up") (make-package w "up")]
[(key=? key "down") (make-package w "down")]
[(key=? key "q") (stop-with w)]
[(key=? key "=") (stop-with w)]
[else w]))
;; handle-msg: ClientState StoCMessage -> ClientState
;; if the message is a number, you got a new guess
(define (handle-msg c msg)
(number->string msg))
;; draw-guess: ClientState -> Scene
;; renders the state as an image
(define (draw-guess c)
(overlay (text c SIZE COLOR) MT-SC))
;
;
;
;
;
; ;;;;;;; ;
; ; ; ; ;
; ; ; ; ;;; ;;;; ; ;;;;;; ;;;; ;
; ; ; ; ; ; ; ;; ; ; ;;
; ; ; ; ; ; ;
; ; ;;;;;;; ;;;;; ; ;;;;;
; ; ; ; ; ;
; ; ; ; ; ; ; ; ; ;
; ;;;;; ;;;; ;;;;;; ;;;; ;;;;;;
;
;
;
;
(module+ test
(require rackunit rackunit/text-ui)
;; testing the client's key-handling
(check-equal? (handle-keys "55" "up") (make-package "55" "up"))
(check-equal? (handle-keys "47" "down") (make-package "47" "down"))
(check-equal? (handle-keys "10" "=") (stop-with "10"))
(check-equal? (handle-keys "66" "k") "66")
;; testing the client's message handling
(check-equal? (handle-msg "100" 99) "99")
(check-equal? (handle-msg "30" -34) "-34")
;; testing the client's rendering function
(check-equal? (draw-guess ClientState0) (overlay (text ClientState0 SIZE COLOR) MT-SC))
(check-equal? (draw-guess "50") (overlay (text "50" SIZE COLOR) MT-SC))
(check-equal? (draw-guess "25") (overlay (text "25" SIZE COLOR) MT-SC))
"client: all tests run")

View File

@ -0,0 +1,30 @@
This chapter implements a distributed version of the "guess my number" game.
TO PLAY, open the file
run.rkt
in DrRacket. The instructions for playing are at the top of the file.
TO EXPERIMENT, open the files
-- run.rkt
-- server.rkt
-- client.rkt
-- shared.rkt
in four different tabs or windows in DrRacket. Switch to the 'run.rkt'
tab and select
View | Show Module browser
to see how these files are related. To switch to one of these four files,
you may click the boxes in the module browsers. Alternatively click the
tab you wish to work on. It is also possible to select tabs via key
strokes.
Each file except for 'run.rkt' comes with test submodules at the bottom of
the file.

View File

@ -0,0 +1,57 @@
#lang racket
#|
The Guess My Number game, a distributed version with a GUI
-----------------------------------------------------------
You pick a number. The program guesses the nunber,
by asking you questions. Your responses are "too
small" "too large" or "you uessed it".
In the Distributed Guess My Number game a player uses a client to connect
to the server. The Server attempts to guess what number the client is thinking
of. Each time the server guesses, the client must use the arrow keys to tell
the server if it is right, too small, or too large.
Play
----
Click Run. Pick a number X between <n> and <m>.
Evaluate
(run)
This will pop up three windows:
-- Adam: with instructions for interacting with the program
-- Universe: the console for the central server
it displays the messages that it receives and sends
-- your server's state: a window that displays the server's internal state.
Play and watch the two latter window to understand how the server and client
interact in response to your actions.
To run the game on two distinct computers:
-- copy this folder to another computer, determine its IP number "12.345.67.98"
-- open run.rkt
-- evaluate
(launch-guess-server)
-- on your own computer, open run.rkt and run
-- evaluate
(launch-guess-client "12.345.67.98")
|#
(require 2htdp/universe "client.rkt" "server.rkt")
;; play the game as "Adam"
(define (run)
(launch-many-worlds (launch-guess-client "Adam" LOCALHOST)
(launch-guess-server)))
;; what happens if two players sign up with the server simultaneously
(define (bad)
(launch-many-worlds (launch-guess-client "Adam" LOCALHOST)
(launch-guess-server)
(launch-guess-client "Beatrice" LOCALHOST)))

View File

@ -0,0 +1,182 @@
#lang racket
;; the server for distributed Guess my Number
(provide
;; starts the distributed guess my number game
;; -> GmNState
launch-guess-server)
(require 2htdp/image 2htdp/universe "shared.rkt")
;
;
;
;
;
; ;;;;;; ;
; ; ; ;
; ; ; ;;;; ;;;;;; ;;;;
; ; ; ; ; ; ; ;
; ; ; ; ; ;
; ; ; ;;;;;; ; ;;;;;;
; ; ; ; ; ; ; ;
; ; ; ; ;; ; ; ; ;;
; ;;;;;; ;;;; ;; ;;;; ;;;; ;;
;
;
;
;
;; A GmNState is one of:
;; -- #f
;; -- GuessRange
(struct interval (small big) #:transparent)
;; A GuessRange is (interval Number Number)
;; always true: (interval l u) means (<= l u)
(define u0 (interval LOWER UPPER))
;
;
;
; ;
; ;
; ;;; ;;;
; ;; ;;
; ; ; ; ; ;;;; ;;; ;; ;;;
; ; ; ; ; ; ; ; ;; ;
; ; ; ; ; ; ; ; ;
; ; ; ; ;;;;;; ; ; ;
; ; ; ; ; ; ; ;
; ; ; ; ;; ; ; ;
; ;;; ;;; ;;;; ;; ;;;;;;; ;;; ;;;
;
;
;
;
(define (launch-guess-server)
(universe #f
(state #t)
(on-new connect)
(on-msg handle-msg)))
;; GmNState IWorld -> [Bundle GmNState [Listof [Mail IWorld Nat]] [Listof IWorld]]
;; handles all new connections. It only accepts one connection.
(define (connect u client)
(if (false? u)
(make-bundle u0 (list (make-mail client (guess u0))) '())
(make-bundle u empty (list client))))
;; GmNState IWorld CtoSMessage -> [Bundle GmNState [List [Mail IWorld Nat]] Empty]
;; handles a message from the client.
(define (handle-msg u client msg)
(define w (next-interval u msg))
(make-bundle w (list (make-mail client (guess w))) '()))
;; GmNState CtoSMessage -> GmNState
;; creates the new universe for a responce
(define (next-interval u msg)
(cond [(not (string? msg)) u]
[(string=? "up" msg) (bigger u)]
[(string=? "down" msg) (smaller u)]
[else u]))
;
;
;
;
; ;; ;
; ; ;;
; ; ;; ;; ;;;; ;;;;; ;;;;;
; ; ; ; ; ; ; ; ; ;
; ; ;;;; ; ; ;;;;;; ;;;; ;;;;
; ; ; ; ; ; ; ;
; ; ; ; ;; ; ; ; ; ;
; ;;; ;; ;; ;;;;; ;;;;; ;;;;;
;
;
;
;
;; GuessRange -> Boolean
;; Does the interval represent a single number?
;; > (single? (interval 1 1))
;; #t
(define (single? w)
(= (interval-small w) (interval-big w)))
;; GuessRange -> Number
;; Calculates a guess based on the given interval
;; > (guess (interval 0 100))
;; 50
(define (guess w)
(quotient (+ (interval-small w) (interval-big w)) 2))
;; GuessRange -> GuessRange
;; Recreates a GuessRange that lowers the upper bound
;; > (smaller (interval 0 100))
;; (interval 0 50)
(define (smaller w)
(interval (interval-small w) (max (interval-small w) (sub1 (guess w)))))
;; GuessRange -> GuessRange
;; Recreates a interval that raises the lower bound
;; > (bigger (0 100)
;; (interval 51 100)
(define (bigger w)
(interval (min (interval-big w) (add1 (guess w))) (interval-big w)))
;
;
;
;
; ; ;
; ;;;;; ;;; ;;;; ;;;;; ;;;;
; ; ; ; ; ; ;
; ; ;;;;; ;; ; ;;
; ; ; ; ; ;
; ; ; ; ; ;
; ;;; ;;;; ;;;; ;;; ;;;;
;
;
(module+ test
(require rackunit rackunit/text-ui)
(define 51-100 (interval 51 100))
;; testing the server's main function
(check-equal? (connect #f iworld1)
(make-bundle (interval 0 100) `(,(make-mail iworld1 50)) '()))
(check-equal? (handle-msg (interval 0 100) iworld1 "up")
(make-bundle 51-100 `(,(make-mail iworld1 (guess 51-100))) '()))
;; testing the server's handlers
(check-true (single? (interval 50 50)))
(check-false (single? (interval 50 51)))
(check-equal? (guess (interval 0 100)) 50)
(check-equal? (guess (interval 50 100)) 75)
(check-equal? (guess (interval 0 50)) 25)
(check-equal? (smaller (interval 0 100)) (interval 0 49))
(check-equal? (smaller (interval 0 000)) (interval 0 0))
(check-equal? (smaller (interval 0 50)) (interval 0 24))
(check-equal? (smaller (interval 50 100)) (interval 50 74))
(check-equal? (smaller (bigger (bigger (interval 0 100))))
(interval 76 87))
(check-equal? (bigger (interval 0 100)) (interval 51 100))
(check-equal? (bigger (interval 0 000)) (interval 0 0))
(check-equal? (bigger (interval 0 100)) (interval 51 100))
(check-equal? (bigger (interval 51 100)) (interval 76 100))
(check-equal? (bigger (interval 0 50)) (interval 26 50))
"server: all tests run")

View File

@ -0,0 +1,63 @@
#lang racket
#|
the shared vocabulary and knowledge of server and client
Message Formats
---------------
StoCMessage is the set of numbers between LOWER and UPPER (inclusive).
The numbers represent the guess.
CtoSMessage is one of the following two strings:
-- "up"
-- "down"
with the obvious meaning.
Message Exchanges
-----------------
server client
| |
| register |
|<------------|
| |
| | <----- guess ("up", "down")
| CtoSMessage |
|<------------|
| |
| StoCMessage |
|------------>|
| |
| |
|#
(provide
;; the to-be-guessed number must be in [LOWER,UPPER]
UPPER
LOWER)
;
;
;
;
;
; ;;;;;; ;
; ; ; ;
; ; ; ;;;; ;;;;;; ;;;;
; ; ; ; ; ; ; ;
; ; ; ; ; ;
; ; ; ;;;;;; ; ;;;;;;
; ; ; ; ; ; ; ;
; ; ; ; ;; ; ; ; ;;
; ;;;;;; ;;;; ;; ;;;; ;;;; ;;
;
;
;
;
;; prefined upper and lower limits for a game.
(define UPPER 100)
(define LOWER 0)