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