distributed server game
This commit is contained in:
parent
a354195ae4
commit
a567de9a48
148
collects/realm/chapter13/client.rkt
Normal file
148
collects/realm/chapter13/client.rkt
Normal 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")
|
30
collects/realm/chapter13/readme.txt
Normal file
30
collects/realm/chapter13/readme.txt
Normal 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.
|
||||
|
||||
|
57
collects/realm/chapter13/run.rkt
Normal file
57
collects/realm/chapter13/run.rkt
Normal 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)))
|
182
collects/realm/chapter13/server.rkt
Normal file
182
collects/realm/chapter13/server.rkt
Normal 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")
|
63
collects/realm/chapter13/shared.rkt
Normal file
63
collects/realm/chapter13/shared.rkt
Normal 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)
|
Loading…
Reference in New Issue
Block a user