diff options
Diffstat (limited to 'fourteen')
-rw-r--r-- | fourteen/client.rkt | 611 | ||||
-rw-r--r-- | fourteen/graphics/cupcake.gif | bin | 1796 -> 0 bytes | |||
-rw-r--r-- | fourteen/graphics/hungry-henry.gif | bin | 1132 -> 0 bytes | |||
-rw-r--r-- | fourteen/readme.txt | 29 | ||||
-rw-r--r-- | fourteen/run.rkt | 59 | ||||
-rw-r--r-- | fourteen/server.rkt | 1065 | ||||
-rw-r--r-- | fourteen/shared.rkt | 156 |
7 files changed, 0 insertions, 1920 deletions
diff --git a/fourteen/client.rkt b/fourteen/client.rkt deleted file mode 100644 index 52305a1..0000000 --- a/fourteen/client.rkt +++ /dev/null @@ -1,611 +0,0 @@ -#lang racket - -;; This module implements the client for the Hungry Henry game - -(provide - lets-eat ;; String String[IP Address] -> Meal - ;; launch single client and register at specified host - ) - -(require "shared.rkt" 2htdp/universe 2htdp/image) - -; -; -; -; ; ; -; ; ; -; ; ; ;;; ; ;; ; ;;; ; ; -; ; ; ; ; ;; ; ;; ; ; ; -; ;;;;; ; ; ; ; ; ; ; -; ; ; ;;;;; ; ; ; ; ; -; ; ; ; ; ; ; ; ; -; ; ; ; ; ; ; ; -; ; ; ;;;; ; ; ; ; -; ; -; ;; -; - - -;; Image Constants -(define FOOD-IMG (bitmap "graphics/cupcake.gif")) -(define PLAYER-IMG (bitmap "graphics/hungry-henry.gif")) -(define BASE (empty-scene WIDTH HEIGHT)) -(define WAYPOINT-NODE (circle 3 'solid 'black)) -;; Color Constants -(define PLAYER-COLOR "red") -(define MY-COLOR "blue") -(define WAYPOINT-COLOR "green") -;; Text Constants -(define LOADING... "Waiting For Server") -(define TEXT-SIZE 20) -(define SCORE-SIZE 20) -(define TEXT-COLOR "black") -(define END-OPEN-TEXT "your score was: ") -(define END-CLOSE-TEXT ", the winner was player ") -(define LOADING-OPEN-TEXT "\nYou are ") -(define SEPERATOR ": ") -;; PBAR constants -(define PBAR-HEIGHT 35) -(define PBAR-LOC (- HEIGHT PBAR-HEIGHT)) -(define PBAR-COLOR "red") -(define PBAR-TEXT (text "loading..." 20 "black")) -;; Message ID Constants -(define UPDATE-LENGTH 3) -(define SPLAYER-LENGTH 3) -(define SBODY-LENGTH 2) -(define END-LENGTH 2) -(define SCORE-LIST-LENGTH 2) -;; Init Constants -(define ZERO% 0) -(define LOADING (text LOADING... 20 "black")) - -;; ----------------------------------------------------------------------------- -;; State of Client - -(struct app (id img countdown) #:transparent) -(struct entree (id players food) #:transparent) - -;; Meal is one of -;; - Appetizer -;; - Entree -;; Appetizer = (app [or Id #f] Image Number∈[0,1]) -;; interpretation: -;; -- the first field is this players id, #f if it hasnt been sent yet -;; -- the second is the loading image -;; -- the third is the %%% of loading time passed, represents the loading state -;; Entree = (entree Id [Listof Feaster] [Listof Food]) -;; interpretation: -;; -- the first field is this player's id -;; -- the second field represents complete information about all players -;; -- the third field specifies the location of the cupcakes - -(define INITIAL (app #f LOADING ZERO%)) - -; -; -; -; ; -; ; -; ;;; ;;; -; ;; ;; -; ; ; ; ; ;;;; ;;; ;; ;;; -; ; ; ; ; ; ; ; ;; ; -; ; ; ; ; ; ; ; ; -; ; ;; ; ;;;;;; ; ; ; -; ; ;; ; ; ; ; ; ; -; ; ; ; ; ; ; ; -; ; ; ; ;; ; ; ; -; ;;; ;;; ;;;; ;; ;;;;;;; ;;; ;;; -; -; -; -; -; - -(define (lets-eat label server) - (big-bang INITIAL - (to-draw render-the-meal) - (on-mouse set-waypoint) - (on-receive handle-server-messages) - (register server) - (name label))) - -;; Meal Message -> Meal -;; handles incomming messages -(define (handle-server-messages meal msg) - (cond [(app? meal) (handle-appetizer-message meal msg)] - [(entree? meal) (handle-entree-message meal msg)])) - -;; Meal Number Number MouseEvent -> Meal -;; handles what happends on a click -(define (set-waypoint meal x y event) - (if (and (entree? meal) (string=? event "button-down")) - (make-package meal (list GOTO x y)) - meal)) - -;; Meal -> Image -;; deals with draw some kind of meal -(define (render-the-meal meal) - (cond [(app? meal) (render-appetizer meal)] - [(entree? meal) (render-entree meal)])) - -; -; -; -; ;;;; ; -; ; ; -; ; ; ;;; ;;;; ;;; ;;; ; ; ;;; -; ; ; ; ; ; ; ; ; ; ; ; ; -; ;;;; ; ; ; ; ; ; ; ; ; ; -; ; ; ;;;;; ; ;;;;; ; ;; ;; ;;;;; -; ; ; ; ; ; ; ; ; ; -; ; ; ; ; ; ; ; ; ; -; ; ; ;;;; ;;;; ;;;; ; ; ;;;; -; -; -; - -;; ----------------------------------------------------------------------------- -;; Appetizer - -;; Appetizer Message -> Meal -;; starts the game if the message is valid -(define (handle-appetizer-message s msg) - (cond [(id? msg) (app msg (app-img s) (app-countdown s))] - [(time? msg) (app (app-id s) (app-img s) msg)] - [(state? msg) (switch-to-entree s msg)] - ;; fault tolerant - [else s])) - -;; Appetizer State -> Meal -(define (switch-to-entree s m) - (apply entree (app-id s) (rest m))) - -;; ----------------------------------------------------------------------------- -;; Appetizer - -;; Entree Message -> Meal -;; either updates the world or ends the game -(define (handle-entree-message s msg) - (cond [(state? msg) (update-entree s msg)] - [(score? msg) (restart s msg)] - [else s])) - -;; Entree State -> Entree -;; creates a new entree based on the update mesg -(define (update-entree s state-msg) - (apply entree (entree-id s) (rest state-msg))) - -;; Entree EndMessage -> Appetizer -;; Tranistion to start state -(define (restart s end-msg) - (define score-image (render-scores end-msg)) - (app (entree-id s) (above LOADING score-image) ZERO%)) - -;; ----------------------------------------------------------------------------- -;; predicates for recognizing network messages - -;; Message -> Boolean -;; checks if message is a valid update message -(define (state? msg) - (and (list? msg) - (= UPDATE-LENGTH (length msg)) - (symbol? (first msg)) - (list? (second msg)) - (list? (third msg)) - (symbol=? SERIALIZE (first msg)) - (andmap player? (second msg)) - (andmap body? (third msg)))) - -;; Message -> Boolean -;; checks if message is a valid time message -(define (time? msg) - (and (real? msg) (<= 0 msg 1))) - -;; Message -> Boolean -;; checks if is end game message -(define (score? msg) - (and (list? msg) - (= END-LENGTH (length msg)) - (symbol? (first msg)) - (list? (second msg)) - (symbol=? SCORE (first msg)) - (score-list? (second msg)))) - -;; List -> Boolean -;; is this a list binding names to scores? -(define (score-list? l) - (for/and ([s l]) - (and (list? s) - (= SCORE-LIST-LENGTH (length s)) - (id? (first s)) - (number? (second s))))) - -; -; -; -; ; -; ; -; ;;;;;; -; ; ; -; ; ; ;; ;; ;;;; ;;; ;; ;;; ;; ;;; ;;; ;; -; ; ; ;;; ; ; ; ; ; ; ;; ; ; ;; -; ; ; ; ; ; ; ; ; ; ; ; ; -; ; ; ; ;;;;;; ; ; ; ; ; ; ; ; -; ; ; ; ; ; ; ; ; ; ; ; ; ; ; -; ; ; ; ; ; ; ; ; ; ; ; ; ; ; -; ; ; ; ; ;; ; ; ; ; ; ; ; ; ;; -; ;;;;;; ;;;;;; ;;;; ;; ; ; ;;;;;;; ;;; ;;; ;;; ; -; ; -; ;; -; ;;;; -; -; - -;; ----------------------------------------------------------------------------- -;; Appetizer Drawing - -;; Appetizer -> Image -;; tells the player that we're waiting for the server. shows id -(define (render-appetizer app) - (add-progress-bar (render-id+image app) (app-countdown app))) - -;; Image Number∈[0,1] -> Image -;; draws the progress bar -(define (add-progress-bar base count) - (place-image (render-progress count) (/ WIDTH 2) PBAR-LOC base)) - -;; Number∈[0,1] -> Image -;; draw a progress bar that is count percent complete -(define (render-progress count) - (overlay PBAR-TEXT (rectangle (* count WIDTH) PBAR-HEIGHT "solid" PBAR-COLOR))) - -;; Appetizer -> Image -;; gets the text to display on the loading screen -(define (render-id+image app) - (define id (app-id app)) - (define base-image (app-img app)) - (overlay - (cond - [(boolean? id) base-image] - [else (define s (string-append LOADING-OPEN-TEXT id)) - (above base-image (text s TEXT-SIZE TEXT-COLOR))]) - BASE)) - -;; ----------------------------------------------------------------------------- -;; Entree Drawing - -;; Entree -> Image -;; draws a Entree -(define (render-entree entree) - (define id (entree-id entree)) - (define pl (entree-players entree)) - (define fd (entree-food entree)) - (add-path id pl (add-players id pl (add-food fd BASE)))) - -;; [Listof Food] Image -> Image -;; draws all the food -(define (add-food foods base-scene) - (for/fold ([scn base-scene]) ([f foods]) - (place-image FOOD-IMG (body-x f) (body-y f) scn))) - -;; Id [Listof Feaster] Image -> Image -;; draws all players -(define (add-players id lof base-scene) - (for/fold ([scn base-scene]) ([feaster lof]) - (place-image (render-avatar id feaster) - (feaster-x feaster) (feaster-y feaster) - scn))) - -;; Id Feaster -> Image -;; gets an image for the player -(define (render-avatar id player) - (define size (body-size (player-body player))) - (define color - (if (id=? id (player-id player)) MY-COLOR PLAYER-COLOR)) - (above - (render-text (player-id player)) - (overlay (render-player-score player) - PLAYER-IMG - (circle size 'outline color)))) - -;; Feaster -> Image -;; Draw the players score -(define (render-player-score player) - (render-text (number->string (get-score (body-size (player-body player)))))) - -;; Id [Listof Feaster] Image -> Image -;; draws the path of the player whose id is passed in -(define (add-path id players base-scene) - (define player - (findf (lambda (x) (id=? id (player-id x))) players)) - (if (boolean? player) - base-scene - (add-waypoint* player base-scene))) - -;; Feaster Image -> Image -;; draws the list of way points to the scene -(define (add-waypoint* player base-scene) - (define loc (body-loc (player-body player))) - (define ways (player-waypoints player)) - (define-values (resulting-scene _) - (for/fold ([scn base-scene][from loc]) ([to ways]) - (values (add-waypoint from to scn) to))) - resulting-scene) - -;; Complex Complex Image -> Image -;; Add a waypoint to the scene at those coordinates -(define (add-waypoint from to s) - (define x-from (real-part from)) - (define y-from (imag-part from)) - (define x-to (real-part to)) - (define y-to (imag-part to)) - (define with-line (add-line s x-to y-to x-from y-from WAYPOINT-COLOR)) - (place-image WAYPOINT-NODE x-to y-to with-line)) - -;; ----------------------------------------------------------------------------- -;; render the end - -;; Score -> Image -;; draws the end of the game -(define (render-scores msg) - (define scores (sort (second msg) < #:key second)) - (for/fold ([img empty-image]) ([name-score scores]) - (define txt (get-text name-score)) - (above (render-text txt) img))) - -;; (list ID Natural) -> string -;; builds a string for that winning pair -(define (get-text name-score) - (define-values (name score) (apply values name-score)) - (string-append name SEPERATOR (number->string score))) - - -; -; -; -; -; -; ;;;;; -; ;; -; ; ; ;; ;; ;;; ;;; -; ; ; ; ; ; ; -; ; ; ; ; ; ; -; ; ; ; ; ;; -; ;;;;;; ; ; ;; -; ; ; ; ; ; ; -; ; ; ; ;; ; ; -; ;;; ;;; ;;; ;; ;;; ;;; -; -; -; -; -; - -;; String -> Image -;; draws the text -(define (render-text txt) - (text txt TEXT-SIZE TEXT-COLOR)) - -;; player -> Number -;; Gets the X coord of a entrees -(define (feaster-x feaster) - (body-x (player-body feaster))) - -;; player -> Number -;; Gets the Y coord of a entrees -(define (feaster-y feaster) - (body-y (player-body feaster))) - -;; body -> Number -;; gets the X coord of a body -(define (body-x body) - (real-part (body-loc body))) - -;; body -> Number -;; gets the Y coord of a body -(define (body-y body) - (imag-part (body-loc body))) - -; -; -; -; -; -; ;;;;;;;;; ; -; ; ; ; ; -; ; ; ; ;;;; ;;;; ; ;;;;;;; ;;;; ; -; ; ; ; ; ; ; ;; ; ; ;; -; ; ; ; ; ; ; -; ; ;;;;;;;; ;;;;; ; ;;;;; -; ; ; ; ; ; -; ; ; ; ; ; -; ; ; ; ; ; ; ; ; ; -; ;;;;; ;;;;; ;;;;;; ;;;; ;;;;;; -; -; -; -; -; - -(module+ test - - (require rackunit rackunit/text-ui) - - ;; testing main client - (check-equal? (switch-to-entree (app "foo" 'blah 1) '(STATE () ())) - (entree "foo" '()'())) - (check-equal? (handle-server-messages (app #f 'ksajfhsdkjhfr 1) .5) - (handle-appetizer-message (app #f 'ksajfhsdkjhfr 1) .5)) - ;;dispatch-mouse - (check-equal? (set-waypoint (app 1 LOADING 0) 1 1 "button-down") - (app 1 LOADING 0)) - (check-equal? (set-waypoint (app 1 LOADING 0) 1 1 "button-up") - (app 1 LOADING 0)) - (check-equal? (set-waypoint (app #f LOADING 0) 1 1 "button-down") - (app #f LOADING 0)) - (check-equal? (set-waypoint (app #f LOADING 0) 1 1 "button-up") - (app #f LOADING 0)) - (check-equal? (set-waypoint (entree "player1" (list (player "player1" (body 1 1+1i) empty)) empty) 1 1 "button-up") - (entree "player1" (list (player "player1" (body 1 1+1i) empty)) empty)) - (check-equal? (set-waypoint (entree "player1" (list (player "player1" (body 1 1+1i) empty)) empty) - 1 1 "button-down") - (make-package (entree "player1" (list (player "player1" (body 1 1+1i) empty)) empty) - (list 'goto 1 1))) - ;;render-the-meal - - ;; testing message receipt - ;; app-may-start - ;; entree-msg - ;; update-msg? - - (check-true (state? `(,SERIALIZE (,(player "player1" (body 1+4i 234) `()) ,(player "player1" (body 3 3) `())) - (,(body 1+i 2) ,(body 2 2))))) - (check-true (state? `(,SERIALIZE (,(player "player1" (body 1+4i 234) `())) - (,(body 1+i 2) ,(body 2 2))))) - (check-true (state? `(,SERIALIZE () - (,(body 1+i 2) ,(body 2 2))))) - (check-true (state? `(,SERIALIZE (,(player "player1" (body 1+4i 234) `()) ,(player "player1" (body 3 3) `())) - ()))) - - (check-false (state? `(,SERIALIZE (("player1" (1+4i 234) ()) ("player1" (3 3) ())) - ((1+i 2) (2 2))))) - (check-false (state? `(,SERIALIZE (("player1" (1+4i 234) ())) - ((1+i 2) (2 2))))) - (check-false (state? `(,SERIALIZE () - ((1+i 2) (2 2))))) - (check-false (state? `(,SERIALIZE (("player1" (1+4i 234) ()) ("player1" (3 3) ())) - ()))) - (check-true (state? `(,SERIALIZE () - ()))) - (check-false (state? `(,SERIALIZE (("player1" (1+4i 234) ()) ("player1" (3 3) ())) - ((1+i 2) (2 2))))) - (check-false (state? `(,SERIALIZE (("player1" (1+4i 234) ())) - ((1+i 2) (2 2))))) - (check-false (state? `(,SERIALIZE () - ((1+i 2) (2 2))))) - (check-false (state? `(,SERIALIZE (("player1" (1+4i 234) ()) ("player1" (3 3) ())) - ()))) - - (check-false (state? '(u ((1 1+4i 234)) - ((1+i 2) (2 2))))) - (check-false (state? '(((1 1+4i 234)) - ((1+i 2) (2 2))))) - (check-false (state? '(u ((1 1+4i)) - ((1+i 2) (2 2))))) - (check-false (state? '(u ((1 1+4i 234)) - ((1+i 2) (2 b))))) - (check-false (state? '(u ((1 1+4i 234))))) - (check-false (state? '(u ((1+i 2) (2 2))))) - (check-false (state? '(((1+i 2) (2 2))))) - (check-false (state? 4)) - (check-false (state? 'f)) - ;; score-list? - (check-true (score-list? '(("s" 0) ("l" 0) ("sdf" 0)))) - (check-true (score-list? empty)) - (check-true (score-list? '(("s" 0) ("l" 0)))) - (check-false (score-list? '(('s 0) ('l 0) ('sdf 0)))) - (check-false (score-list? '((s 0) (l 0)))) - (check-false (score-list? '((s) (l)))) - (check-false (score-list? '((s 0) (l 0)))) - ;; update-entree - (check-equal? (update-entree (entree "player10" '() '()) - `(s (,(player "player1" (body 10 10) `(3 4+9i)) - ,(player "player10" (body 103 10+4i) `(3 5+78i))) - (,(body 5 10) ,(body 30 30)))) - (entree "player10" (list (player "player1" (body 10 10) (list 3 4+9i)) - (player "player10" (body 103 10+4i) (list 3 5+78i))) - (list (body 5 10) (body 30 30)))) - - - ;; testing rendering the client - - ;; draw-app - (check-equal? (render-appetizer (app #f LOADING 0)) - (add-progress-bar (overlay LOADING - BASE) - 0)) - ;; draw-entree - - - ;; draw-players - - (check-equal? (add-players "player0" - (list (player "player1" (body 40 23+34i) empty) - (player "player0" (body 50 1+3i) empty)) - BASE) - (place-image (render-avatar "player0" (player "player0" (body 50 1+3i) empty)) - 1 3 - (place-image (render-avatar "player0" (player "player1" (body 40 23+34i) empty)) - 23 34 - BASE))) - (check-equal? (add-players "player0" - (list (player "player1" (body 40 23+34i) empty)) - BASE) - (place-image (render-avatar "player0" (player "player1" (body 40 23+34i) empty)) - 23 34 - BASE)) - - ;; draw-player - - ;; get-player-image - (check-equal? (render-avatar "player0" (player "player0" (body 30 1+3i) empty)) - (above (render-text "player0") - (overlay (text (number->string (get-score 30)) 20 'black) - PLAYER-IMG (circle 30 "outline" MY-COLOR)))) - (check-equal? (render-avatar "player0" (player "player1" (body 30 1+3i) empty)) - (above (render-text "player1") - (overlay (text (number->string (get-score 30)) 20 'black) - PLAYER-IMG (circle 30 "outline" PLAYER-COLOR)))) - - ;; draw-food - (check-equal? (add-food (list (body 34 54+3i) - (body 9 45+23i)) - BASE) - (place-image FOOD-IMG - 45 23 - (place-image - FOOD-IMG - 54 3 - BASE))) - (check-equal? (add-food (list (body 34 54+3i)) - BASE) - (place-image - FOOD-IMG - 54 3 - BASE)) - - - ;; testing auxiliary functions - ;; player-x - (check-equal? (feaster-x (player 20 (body 3 1+3i) empty)) - 1) - (check-equal? (feaster-x (player 20 (body 3 4+3i) empty)) - 4) - (check-equal? (feaster-x (player 20 (body 3 4+67i) empty)) - 4) - ;; player-y - (check-equal? (feaster-y (player 20 (body 3 1+3i) empty)) - 3) - (check-equal? (feaster-y (player 20 (body 3 4+3i) empty)) - 3) - (check-equal? (feaster-y (player 20 (body 3 4+67i) empty)) - 67) - - ;; body-x - (check-equal? (body-x (body 20 1+2i)) - 1) - (check-equal? (body-x (body 20 4+2i)) - 4) - (check-equal? (body-x (body 20 3+2i)) - 3) - ;; body-y - (check-equal? (body-y (body 20 4+1i)) - 1) - (check-equal? (body-y (body 20 1+4i)) - 4) - (check-equal? (body-y (body 20 3)) - 0) - - "client: all tests run") - diff --git a/fourteen/graphics/cupcake.gif b/fourteen/graphics/cupcake.gif Binary files differdeleted file mode 100644 index 20b1bef..0000000 --- a/fourteen/graphics/cupcake.gif +++ /dev/null diff --git a/fourteen/graphics/hungry-henry.gif b/fourteen/graphics/hungry-henry.gif Binary files differdeleted file mode 100644 index cce6948..0000000 --- a/fourteen/graphics/hungry-henry.gif +++ /dev/null diff --git a/fourteen/readme.txt b/fourteen/readme.txt deleted file mode 100644 index 042b0f4..0000000 --- a/fourteen/readme.txt +++ /dev/null @@ -1,29 +0,0 @@ -This chapter implements a distributed game, dubbed "Hungry Henry." - -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. - - diff --git a/fourteen/run.rkt b/fourteen/run.rkt deleted file mode 100644 index 4a244b7..0000000 --- a/fourteen/run.rkt +++ /dev/null @@ -1,59 +0,0 @@ -#lang racket - -#| - Hungry Henry, a multi-player, distributed game - ----------------------------------------------- - - This game is a multi-player competition for cupcakes. Each player owns an - avatar, called a "Henry", and competes for a limited number of cupcakes, - distributed over a rectangular space. A player launches her Henry via - a series of mouse clicks, so-called waypoints. Her Henry moves from waypoint - to waypoint. If it gets close enough to a cupcake, he eats the cupcake and - fattens up. As a Henry fattens up, he slows down. When all cupcakes are - consumed, the fattest Henry wins. - - Notes: - 1. The cupcakes remain in place until they are eaten. - 2. Once a waypoiny is recorded, it cannot be removed. - 3. Waypoints are visited in a first-come, first-serve order. - - Play - ---- - - Click Run. Evaluate - - (serve-dinner) - - in the Interactions Panel. This will pop up three windows: - -- Matthias, a game window - -- David, another game window - -- Universe, the game server's console - - Play. You can play the part of both participants. Alternatively, click - the David or Matthias window (to obtain focus) and click again to choose - a way point for David's or Matthias's "hungry henry". Watch the hungry - henries go for the cup cake and eat them up. You can make either one of them - win or you can force a tie. - - 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 - (bon-appetit) - - -- on your own computer, open run.rkt and run - -- evaluate - (lets-eat SomeNameAsAString "12.345.67.98") -|# - -(require (only-in "server.rkt" bon-appetit) - (only-in "client.rkt" lets-eat) - 2htdp/universe) - -;; launch server worlds for playtesting -(define (serve-dinner) - (launch-many-worlds - (bon-appetit) - (lets-eat "Matthias" LOCALHOST) - (lets-eat "David" LOCALHOST))) diff --git a/fourteen/server.rkt b/fourteen/server.rkt deleted file mode 100644 index 078533b..0000000 --- a/fourteen/server.rkt +++ /dev/null @@ -1,1065 +0,0 @@ -#lang racket - -;; This module implements the server for the Hungry Henry game - -(provide - bon-appetit ;; -> Void - ;; launch the server for Hungry Henry - ) - -(require "shared.rkt" 2htdp/universe) - -#| ----------------------------------------------------------------------------- -The server is responsible for: --- starting the game --- moving Henrys --- have Henrys eat, remove food on collision --- collecting and broadcasting information about the movement of players --- ending games -|# - -; -; -; -; ; ; ; ; -; ; ; ; ; -; ; ; ; ; ; ;; ;; ; ; ;;; ; ; ; ; ;;; ; ;; ; ;;; ; ; -; ; ; ; ; ;; ; ; ;; ;; ; ; ; ; ; ; ; ;; ; ;; ; ; ; -; ;;;;; ; ; ; ; ; ; ; ; ; ;;;;; ; ; ; ; ; ; ; -; ; ; ; ; ; ; ; ; ; ; ; ; ; ;;;;; ; ; ; ; ; -; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; -; ; ; ; ;; ; ; ; ;; ; ; ; ; ; ; ; ; ; -; ; ; ;; ; ; ; ;; ; ; ; ; ; ;;;; ; ; ; ; -; ; ; ; -; ;;; ;; ;; -; - - -;; Init Constants -(define TICK .1) -(define PLAYER-LIMIT 2) -(define START-TIME 0) -(define WAIT-TIME 250) - -(define FOOD*PLAYERS 5) - -(define WEIGHT-FACTOR 2.1) -(define BASE-SPEED (/ (expt PLAYER-SIZE 2) WEIGHT-FACTOR)) - -;; Data Definitions -(struct join (clients [time #:mutable]) #:transparent) -(struct play (players food spectators) #:transparent #:mutable) - -;; plus some update primitives: - -;; JoinUniverse Player -> JoinUniverse -(define (join-add-player j new-p) - (join (cons new-p (join-clients j)) (join-time j))) - -;; PlayUniverse IP -> PlayUniverse -(define (play-add-spectator pu new-s) - (define players (play-players pu)) - (define spectators (play-spectators pu)) - (play players (play-food pu) (cons new-s spectators))) - -;; PlayUniverse IWorld -> PlayUniverse -;; removes player that uses iworld -(define (play-remove p iw) - (define players (play-players p)) - (define spectators (play-spectators p)) - (play (rip iw players) (play-food p) (rip iw spectators))) - -;; JoinUniverse IWorld -> JoinUniverse -;; removes players and spectators that use iw from this world -(define (join-remove j iw) - (join (rip iw (join-clients j)) (join-time j))) - -;; IWorld [Listof Player] -> [Listof Player] -;; remove player that contains the given IWorld -(define (rip iw players) - (remove iw players (lambda (iw p) (iworld=? iw (ip-iw p))))) - -;; LIKE: -;; (struct ip ip? ip-id ip-iw ip-body ip-waypoints ip-player) -(define-values - (ip ip? ip-id ip-iw ip-body ip-waypoints ip-player) - (let () - (struct ip (id iw body waypoints player) #:transparent) - (define (create iw id body waypoints) - (ip id iw body waypoints (player id body waypoints))) - (values - create ip? ip-id ip-iw ip-body ip-waypoints ip-player))) - -;; ServerState is one of -;; -- JoinUniverse -;; -- PlayUniverse -;; JoinUniververse = (join [Listof IPs] Nat) -;; interpretation: -;; -- the first field lists the currently connected client-player -;; -- the second field is the number of ticks since the server started -;; PlayUniverse = (play [Listof IPs] [Listof Food] [Listof IP]) -;; interpretation: -;; -- the first field lists all participating players -;; -- the second field lists the cupcakes -;; --- the third field enumerates the spectating players -;; IP = (ip Id IWorld Body [Listof Complex] Feaster) -;; interpretation: -;; the struct represents the Universe's perspective of a connected player -;; -- the first field is the assigned unique Id -;; -- the second field is the IWorld representing the remote connection to the client -;; -- the third field is the Body of the player -;; -- the fourth field is the list of player-chosen Waypoints, -;; ordered from oldest click to most-recent -;; meaning the first one has to be visited first by the Henry -;; -- the fifth field is the serialized representation of the first four fields - -(define JOIN0 (join empty START-TIME)) - -; -; -; -; -; ;;; ;;; ; -; ;; ;; -; ;; ;; ;;;; ;;; ;; ;; -; ; ; ; ; ; ; ;; ; -; ; ; ; ;;;;; ; ; ; -; ; ; ; ; ; ; ; -; ; ; ; ;; ; ; ; -; ;;; ;;; ;;; ;; ;;;;; ;;; ;;; -; -; -; -; - -(define (bon-appetit) - (universe JOIN0 - (on-new connect) - (on-msg handle-goto-message) - (on-tick tick-tock TICK) - (on-disconnect disconnect))) - -;; ServerState IWorld -> Bundle -;; adds a new connection to a JoinUniverse and ticks. Ignores otherwise -(define (connect s iw) - (cond [(join? s) (add-player s iw)] - [(play? s) (add-spectator s iw)])) - -;; ServerState IWorld Sexpr -> Bundle -;; accepts goto messages from clients -(define (handle-goto-message s iw msg) - (cond [(and (play? s) (goto? msg)) (goto s iw msg)] - [else (empty-bundle s)])) - -;; ServerState -> Bundle -;; handle a tick event -(define (tick-tock s) - (cond [(join? s) (wait-or-play s)] - [(play? s) (move-and-eat s)])) - -;; ServerState IWorld -> Bundle -;; handles loss of a client -(define (disconnect s iw) - (cond [(join? s) (drop-client s iw)] - [(play? s) (drop-player s iw)])) - -; -; -; -; ; ; ; ; -; ; ; ; -; ; ; ;;;; ;;; ;;;;; ;;; ; ;; ;; ; -; ; ; ; ; ; ; ; ;; ; ; ;; -; ; ; ; ; ; ; ; ; ; ; ; -; ;; ;; ;;;; ; ; ; ; ; ; ; -; ;; ;; ; ; ; ; ; ; ; ; ; -; ; ; ; ;; ; ; ; ; ; ; ;; -; ; ; ;; ; ; ;;; ; ; ; ;; ; -; ; -; ;;; -; - -;; JoinUniverse -> Bundle -;; count down and might transition -(define (wait-or-play j) - (cond [(keep-waiting? j) (keep-waiting j)] - [else (start-game j)])) - -;; JoinUniverse -> Boolean -;; is it time to start? -(define (keep-waiting? j) - (or (> PLAYER-LIMIT (length (join-clients j))) - (> WAIT-TIME (join-time j)))) - -;; JoinUniverse -> [Bundle JoinUniverse] -(define (keep-waiting j) - (set-join-time! j (+ (join-time j) 1)) - (time-broadcast j)) - -;; JoinUniverse -> [Bundle JoinUniverse] -;; broadcasts the new load time fraction to the players -(define (time-broadcast j) - (define iworlds (map ip-iw (join-clients j))) - (define load% (min 1 (/ (join-time j) WAIT-TIME))) - (make-bundle j (broadcast iworlds load%) empty)) - -;; JoinUniverse -> [Bundle PlayUniverse] -;; starts the game -(define (start-game j) - (define clients (join-clients j)) - (define cupcakes (bake-cupcakes (length clients))) - (broadcast-universe (play clients cupcakes empty))) - -;; Number -> [Listof Food] -;; creates the amount of food for that number of players -(define (bake-cupcakes player#) - (for/list ([i (in-range (* player# FOOD*PLAYERS))]) - (create-a-body CUPCAKE))) - -; -; -; ;;; -; ;;;; ; ; -; ; ; ; -; ; ; ; ;;;; ; ; ;;; ; ;; ;; ; -; ; ; ; ; ; ; ; ;; ; ; ;; -; ;;; ; ; ; ; ; ; ; ; ; -; ; ; ;;;; ; ; ; ; ; ; ; -; ; ; ; ; ; ; ; ; ; ; ; -; ; ; ; ;; ; ; ; ; ; ;; -; ; ; ;; ; ; ; ; ; ;; ; -; ; ; -; ;; ;;; -; - -;; PlayUniverse -> Bundle -;; moves everything. eats. may end game -(define (move-and-eat pu) - (define nplayers (move-player* (play-players pu))) - (define nfood (feed-em-all nplayers (play-food pu))) - (progress nplayers nfood (play-spectators pu))) - -;; [Listof IP] -> [Listof IP] -;; moves all players -(define (move-player* players) - (for/list ([p players]) - (define waypoints (ip-waypoints p)) - (cond [(empty? waypoints) p] - [else (define body (ip-body p)) - (define nwpts - (move-toward-waypoint body waypoints)) - (ip (ip-iw p) (ip-id p) body nwpts)]))) - -;; Body [Listof Complex] -> [Listof Complex] -;; effect: set body's location -;; determine new waypoints for player -;; pre: (cons? waypoints) -(define (move-toward-waypoint body waypoints) - (define goal (first waypoints)) - (define bloc (body-loc body)) - (define line (- goal bloc)) - (define dist (magnitude line)) - (define speed (/ BASE-SPEED (body-size body))) - (cond - [(<= dist speed) - (set-body-loc! body goal) - (rest waypoints)] - [else ; (> distance speed 0) - (set-body-loc! body (+ bloc (* (/ line dist) speed))) - waypoints])) - -;; [Listof Player] [Listof Food] -> [Listof Food] -;; feeds all players and removes food -(define (feed-em-all players foods) - (for/fold ([foods foods]) ([p players]) - (eat-all-the-things p foods))) - -;; IP [Listof Food] -> [Listof Food] -;; effect: fatten player as he eats -;; determine left-over foods -(define (eat-all-the-things player foods) - (define b (ip-body player)) - (define (new-cupcakes) - (cond [(> (length foods) 10) '()] - [else - (for/list ([i (in-range (random 1 3))]) - (create-a-body CUPCAKE))])) - (for/fold ([foods '()]) ([f foods]) - (cond - [(body-collide? f b) - (set-body-size! b (+ PLAYER-FATTEN-DELTA (body-size b))) - (append foods (new-cupcakes))] - [else (cons f foods)]))) - -;; body body -> Boolean -;; Have two bodys collided? -(define (body-collide? s1 s2) - (<= (magnitude (- (body-loc s1) (body-loc s2))) - (+ (body-size s1) (body-size s2)))) - -;; [Listof Ip] [Listof Food] [Listof IP] -> Bundle -;; moves all objects. may end game -(define (progress pls foods spectators) - (define p (play pls foods spectators)) - (define (max-score) - (foldl (λ (pl max) - (let ([pl-score (cadr pl)]) - (if (> pl-score max) pl-score max))) - 0 (score pls))) - (define (end-game?) - (or (empty? foods) (> (max-score) 30))) - (cond [(end-game?) (end-game-broadcast p)] - [else (broadcast-universe p)])) - -;; PlayUniverse -> [Bundle JoinUniverse] -;; ends the game, and restarts it -(define (end-game-broadcast p) - (define iws (get-iws p)) - (define msg (list SCORE (score (play-players p)))) - (define mls (broadcast iws msg)) - (make-bundle (remake-join p) mls empty)) - -;; Play-Universe -> JoinUniverse -;; Readies the ServerState for a new game -(define (remake-join p) - (define players (refresh (play-players p))) - (define spectators (play-spectators p)) - (join (append players spectators) START-TIME)) - -;; [Listof Players] -> [Listof Players] -;; creates new players for new game -(define (refresh players) - (for/list ([p players]) - (create-player (ip-iw p) (ip-id p)))) - -;; [Listof IP] -> [Listof (list Id Score)] -;; makes the endgame message informing clients of all the size -(define (score ps) - (for/list ([p ps]) - (list (ip-id p) (get-score (body-size (ip-body p)))))) - -; -; -; -; -; ;;; ;;; -; ;; ;; -; ;; ;; ;;;; ;;;;; ;;;;; ;;;; ;;; ;; ;;;; ;;;;; -; ; ; ; ; ; ; ; ; ; ; ; ; ;; ; ; ; ; -; ; ; ; ;;;;;; ;;;; ;;;; ;;;;; ; ; ;;;;;; ;;;; -; ; ; ; ; ; ; ; ; ; ; ; -; ; ; ; ; ; ; ; ; ;; ; ;; ; ; ; -; ;;; ;;; ;;;;; ;;;;; ;;;;; ;;; ;; ;;; ; ;;;;; ;;;;; -; ; -; ;;;; -; -; - -;; ----------------------------------------------------------------------------- -;; Play Universe - -;; Message -> Boolean -;; checks if message is a drag -(define (goto? msg) - (and (list? msg) - (= GOTO-LENGTH (length msg)) - (symbol? (first msg)) - (number? (second msg)) - (number? (third msg)) - (symbol=? GOTO (first msg)) - (<= 0 (second msg) WIDTH) - (<= 0 (third msg) HEIGHT))) - -;; PlayUniverse IWorld GotoMessage -> PlayUniverse -;; handles a player clicking. checks for collisions, updates score, removes food -;; Effect: changes a player's waypoints -(define (goto p iw msg) - (define c (make-rectangular (second msg) (third msg))) - (set-play-players! p (add-waypoint (play-players p) c iw)) - (broadcast-universe p)) - -;; [Listof IPs] Complex IWorld -> [Listof IPs] -;; adds that complex to the waypoints of the given players -(define (add-waypoint ps c iw) - (for/list ([p ps]) - (cond [(iworld=? (ip-iw p) iw) - (ip (ip-iw p) - (ip-id p) - (ip-body p) - (append (ip-waypoints p) (list c)))] - [else p]))) - -; -; -; -; -; ;;;; ; -; ; ; ; -; ; ;;;; ;; ;; ;; ;; ;;;; ;;; ; ;;;;; ;;; ;;;; ;; ;; -; ; ; ; ;; ; ;; ; ; ; ; ;; ; ; ; ; ;; ; -; ; ; ; ; ; ; ; ;;;;;; ; ; ; ; ; ; ; -; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; -; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; -; ;;; ;;;; ;;; ;;; ;;; ;;; ;;;;; ;;;; ;;; ;;;;; ;;;; ;;; ;;; -; -; -; -; - - -;; ----------------------------------------------------------------------------- -;; Join Universe - -;; [Universe Player -> Universe] -> [Universe IWorld -> [Bundle Universe]] -;; creates a function that deals with a new connection during join or play phase -(define (make-connection adder) - (lambda (u iw) - (define player (named-player iw)) - (define mails (list (make-mail iw (ip-id player)))) - (make-bundle (adder u player) mails empty))) - -;; JoinUniverse IWorld ID -> [Bundle JoinUniverse] -;; creates an internal player for the IWorld, adds it to Universe as waiting player -(define add-player (make-connection join-add-player)) - -;; PlayUniverse IWorld -> [Bundle PlayUniverse] -;; creates an internal player for the IWorld, adds it to Universe as spectator -(define add-spectator (make-connection play-add-spectator)) - -;; [Listof IP] IWorld ->* Player -(define (named-player iw) - (create-player iw (symbol->string (gensym (iworld-name iw))))) - -; -; -; -; -; ;;; ; ; ;; ; -; ; ;; ; -; ; ;;;; ;; ;;; ;;; ;;;; ; ;;; ;;;;; ;;;; -; ;;;; ; ; ;; ; ; ; ; ; ; ; ; ; -; ; ;;;;;; ; ; ;;;;; ; ; ; ;;;;;; -; ; ; ; ; ; ; ; ; ; ; -; ;; ; ; ; ; ; ;; ; ; ; ; ; -; ; ;;; ;;;;; ;;;;; ;;;;; ;;; ;; ;;;;; ;;;;; ;;;;; ;;;;; -; -; -; -; - -;; PlayUniverse -> [Bundle PlayUniverse [Listof [Mail StateMessage]]] -;; bundle this universe, serialize it, broadcast it, and drop noone -(define (broadcast-universe p) - (define mails (broadcast-for (get-ips p) p)) - (make-bundle p mails empty)) - -;; [Listof IWorlds] Message -> [Listof Mail] -;; sends mail to all clients -(define (broadcast iws msgs) - (map (lambda (iw) (make-mail iw msgs)) iws)) - -;; PlayUniverse -> (list s [Listof SerializedPlayer] [Listof SerializedFood]) -;; prepairs a message for an update world/ServerState state -(define (serialize-universe p) - (define serialized-players (map ip-player (play-players p))) - (list SERIALIZE serialized-players (play-food p))) - -;; [Listof IPs] PlayUniverse -> [Listof Mail] -;; generates mails for all clients -(define (broadcast-for ips p) - (define (mk-mail-for pl) - (make-mail (ip-iw pl) (serialize-universe-for pl p))) - (foldl (lambda (pl mails) (cons (mk-mail-for pl) mails)) '() ips)) - -;; IP PlayUniverse -> (list s [Listof SerializedPlayer] [ListOf SerializedFood]) -;; prepares message for an update world/ServerState state for a player -(define (serialize-universe-for pl p) - (list SERIALIZE - (serialize-players-for pl (play-players p)) - (play-food p))) - -;; IP IPs -> [ListOf SerializedPlayer] -;; prepares serialized list of players for the SERIALIZE message for a -;; player. -(define (serialize-players-for pl pls) - (define (filter-out waypoints) - (if (empty? waypoints) - waypoints - (list (first waypoints)))) - (define (mk-pl plyr) - (cond [(id=? (ip-id plyr) (ip-id pl)) (ip-player plyr)] - [else (player (ip-id plyr) - (ip-body plyr) - (filter-out (ip-waypoints plyr)))])) - (foldr (lambda (plyr srlzd-pls) (cons (mk-pl plyr) srlzd-pls)) '() pls)) - -; -; -; -; -; ;;;; ; -; ; ; ; -; ; ; ;;; ;;;;; ;;; ; ;;;; ;; ;; ;; ;; ;;;; ;;; ; ;;;;; -; ; ; ; ; ; ; ;; ; ; ;; ; ;; ; ; ; ; ;; ; -; ; ; ; ;;;; ; ; ; ; ; ; ; ;;;;;; ; ; -; ; ; ; ; ; ; ; ; ; ; ; ; ; ; -; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; -; ;;;; ;;;;; ;;;;; ;;;; ;;;; ;;; ;;; ;;; ;;; ;;;;; ;;;; ;;; -; -; -; -; - -;; JoinUniverse IWorld -> Bundle -;; remove that iworld from list of clients -(define (drop-client j iw) - (empty-bundle (join-remove j iw))) - -;; PlayUniverse IWorld -> Bundle -;; removes a player from the ServerState and tells the players -(define (drop-player p iw) - (broadcast-universe (play-remove p iw))) - -; -; -; -; -; ;; -; ; -; ; ; ;; ;; ;; ;; -; ; ; ; ; ; ; -; ; ; ; ; ;; -; ;;; ; ; ;; -; ; ; ; ;; ; ; -; ;;; ;;; ;; ;; ;; ;; -; -; -; -; - -;; Number -> Body -;; creates a random body, that does not touch the edge -(define (create-a-body size) - (define x (+ size (random (- WIDTH size)))) - (define y (+ size (random (- HEIGHT size)))) - (body size (make-rectangular x y))) - -;; PlayUniverse -> [Listof IWorlds] -;; gets the iworlds of all players -(define (get-iws p) - (map ip-iw (append (play-players p) (play-spectators p)))) - -;; PlayUnivers -> [Listof IP] -(define (get-ips p) - (append (play-players p) (play-spectators p))) - -;; ServerState -> Bundle -;; makes a bundle that sends no messages and disconnects noone -(define (empty-bundle s) - (make-bundle s empty empty)) - -;; IWorld Id -> IP -;; creates a player with that idnumber -(define (create-player iw n) - (ip iw n (create-a-body PLAYER-SIZE) empty)) - -; -; -; -; -; ;;;;;;; -; ; ; ; ; -; ; ;;;; ;;;;; ;;;;; ;;;;; -; ; ; ; ; ; ; ; ; -; ; ;;;;;; ;;;; ; ;;;; -; ; ; ; ; ; -; ; ; ; ; ; ; ; ; -; ;;; ;;;;; ;;;;; ;;; ;;;;; -; -; -; -; - -(module+ test - (require rackunit rackunit/text-ui) - - (define PROP-NUM 500) - (define do-prop (make-parameter #t)) - (do-prop #f) - - ;; thunk -> void - ;; runs the thunk PROP-NUM times - (define (check-property t) - (when (do-prop) (test-begin (doo PROP-NUM t)))) - - ;; doo : number thunk -> - ;; does the thunk n times - (define (doo n l) - (l) - (unless (zero? n) - (doo (sub1 n) l))) - - ;; testing main server - - ;; new-connection - - ;; drop-client - (check-equal? (drop-client (join (list (ip iworld1 "player1" (body 10 1+10i) empty) - (ip iworld2 "player2" (body 10 1+10i) empty) - (ip iworld3 "player3" (body 10 1+10i) empty)) 100) - iworld1) - (empty-bundle (join (list (ip iworld2 "player2" (body 10 1+10i) empty) - (ip iworld3 "player3" (body 10 1+10i) empty)) - 100))) - (check-equal? (drop-client (join (list (ip iworld1 "player1" (body 10 1+10i) empty) - (ip iworld2 "player2" (body 10 1+10i) empty) - (ip iworld3 "player3" (body 10 1+10i) empty)) 100) - iworld2) - (empty-bundle (join (list (ip iworld1 "player1" (body 10 1+10i) empty) - (ip iworld3 "player3" (body 10 1+10i) empty)) 100))) - (check-equal? (drop-client (join (list (ip iworld1 "player1" (body 10 1+10i) empty) - (ip iworld2 "player2" (body 10 1+10i) empty) - (ip iworld3 "player3" (body 10 1+10i) empty)) 100) - iworld3) - (empty-bundle (join (list (ip iworld1 "player1" (body 10 1+10i) empty) - (ip iworld2 "player2" (body 10 1+10i) empty)) 100))) - - ;; remove-player - (check-equal? (drop-player - (play (list (ip iworld1 "player10" (body 10 1+10i) empty) - (ip iworld2 "player345" (body 56 3+45i) empty)) - (list (body 87 67+23i) - (body 5 3+4i)) - empty) - iworld1) - (let ([remd (play-remove - (play (list (ip iworld1 "player10" (body 10 1+10i) empty) - (ip iworld2 "player345" (body 56 3+45i) empty)) - (list (body 87 67+23i) - (body 5 3+4i)) - empty) - iworld1)]) - (broadcast-universe remd) - #; - (make-bundle remd (serial/broadcast-univ remd) empty))) - - (check-equal? (drop-player - (play (list (ip iworld2 "player345" (body 56 3+45i) empty)) - (list (body 87 67+23i) - (body 5 3+4i)) - (list (ip iworld1 "player10" (body 10 1+10i) empty))) - iworld1) - (let ([remd (play-remove - (play (list (ip iworld1 "player10" (body 10 1+10i) empty) - (ip iworld2 "player345" (body 56 3+45i) empty)) - (list (body 87 67+23i) - (body 5 3+4i)) - empty) - iworld1)]) - (broadcast-universe remd) - #; - (make-bundle remd (serial/broadcast-univ remd) empty))) - - ;; ready-to-go - (check-false (keep-waiting? (join (list (create-player iworld1 "player") - (create-player iworld2 "player")) - 250))) - (check-false (keep-waiting? (join (list (create-player iworld1 "player") - (create-player iworld1 "player") - (create-player iworld2 "player")) - 456345132135213))) - (check-true (keep-waiting? (join (list (create-player iworld2 "player")) -234))) - (check-true (keep-waiting? (join (list (create-player iworld2 "player")) 10))) - - - - ;; handle-join - ;; name - ;; update-player - - ;; remove-player-by-iworld - (check-equal? (play-remove - (play (list (ip iworld1 "player10" (body 10 1+10i) empty) - (ip iworld2 "player324" (body 56 3+45i) empty)) - (list (body 87 67+23i) - (body 5 3+4i)) - empty) - iworld1) - (play (list (ip iworld2 "player324" (body 56 3+45i) empty)) - (list (body 87 67+23i) - (body 5 3+4i)) - empty) - empty) - (check-equal? (play-remove - (play (list (ip iworld2 "player324" (body 56 3+45i) empty)) - (list (body 87 67+23i) - (body 5 3+4i)) - empty) - iworld2) - (play (list) - (list (body 87 67+23i) - (body 5 3+4i)) - empty)) - - ;; testing messaging - - ;; goto? - - (check-true (goto? '(goto 3 2))) - (check-true (goto? '(goto 3 2))) - (check-true (goto? '(goto 0 2))) - (check-true (goto? '(goto 6 2))) - (check-false (goto? `(goto ,(add1 WIDTH) 0))) - (check-false (goto? `(goto 0 ,(add1 HEIGHT)))) - (check-false (goto? '(goto -1 0))) - (check-false (goto? '(goto 0 -1))) - (check-false (goto? '(goto 1))) - (check-false (goto? '(drag 6+2i))) - (check-false (goto? '(drag 1))) - (check-false (goto? '(6+1i))) - (check-false (goto? '(1 2))) - (check-false (goto? '(goto 6+2i))) - (check-false (goto? '(drag 1 2))) - (check-false (goto? 'click)) - (check-false (goto? "click")) - (check-false (goto? #t)) - - ;;add-waypoint - - (check-equal? (add-waypoint `(,(ip iworld1 "player10" (body 10 1+10i) empty)) 8+9i iworld1) - (list (ip iworld1 "player10" (body 10 1+10i) '(8+9i)))) - (check-equal? (add-waypoint `(,(ip iworld1 "player10" (body 10 1+10i) '(23+45i))) 8+9i iworld1) - (list (ip iworld1 "player10" (body 10 1+10i) '(23+45i 8+9i)))) - - ;; goto - - (check-equal? (goto (play (list (ip iworld1 "player10" (body 10 1+10i) empty) - (ip iworld2 "player345" (body 56 3+45i) empty)) - (list (body 87 67+23i) - (body 5 3+4i)) - empty) - iworld1 '(goto 1 1)) - (let ([state (play (list (ip iworld1 "player10" (body 10 1+10i)'(1+1i)) - (ip iworld2 "player345" (body 56 3+45i) empty)) - (list (body 87 67+23i) - (body 5 3+4i)) - empty)]) - (broadcast-universe state) - #; - (make-bundle state (serial/broadcast-univ state) empty))) - - (check-equal? (goto (play (list (ip iworld1 "player10" (body 10 1+10i) '(1+4i)) - (ip iworld2 "player345" (body 56 3+45i) empty)) - (list (body 87 67+23i) - (body 5 3+4i)) - empty) - iworld1 '(goto 1 1)) - (let ([state (play (list (ip iworld1 "player10" (body 10 1+10i) '(1+4i 1+1i)) - (ip iworld2 "player345" (body 56 3+45i) empty)) - (list (body 87 67+23i) - (body 5 3+4i)) - empty)]) - (broadcast-universe state) - #; - (make-bundle state (serial/broadcast-univ state) empty))) - - ;; eat-all-the-things - (check-pred >= (length (eat-all-the-things (ip iworld1 "player10" (body 11 0) '(1+10i)) (list (body 10 0)))) - 1) - (check-equal? (eat-all-the-things (ip iworld1 "player10" (body 10 0) '(1+10i)) (list (body 10 40+5i))) - (list (body 10 40+5i))) - - ;; testing initialization - - ;; property of no motion to same point in move-body - ;; also checks for divide by zero error in move-player* - (define (property:no-same-point) - (define (random-near n) - (define ε 1/1000000) - (+ n (* (random 10) ε (sub1 (* 2 (random 2)))))) - - (define test-body (create-a-body 1)) - - (define waypoints - (for/list ([r (in-range (add1 (random 100)))]) - (define x (real-part (body-loc test-body))) - (define y (imag-part (body-loc test-body))) - (make-rectangular (random-near x) (random-near y)))) - - (define random-p (ip iworld1 "nope" test-body waypoints)) - - (define (test p) - (cond [(empty? (ip-waypoints p)) - #t] - [(= (first (ip-waypoints p)) - (body-loc (ip-body p))) - #f] - [else (test (move-player* (list p)))])) - - (check-true (test random-p))) - - ;; does spawn food create the nessecary amount of food? - (define (property:player/food-number-correct) - (define players (random 50)) - (check-equal? (length (bake-cupcakes players)) - (* FOOD*PLAYERS players))) - - ;; is random-body on the board? - (define (test-body-in-bounds) - (define size 10) - (define body (create-a-body size)) - (check-true (and (< size (real-part (body-loc body)) (- WIDTH size)) - (< size (imag-part (body-loc body)) (- HEIGHT size))) - "body out of bounds")) - - - - - ;;create-name - ;; (check-equal? (create-name empty "john") "john") - ;; (check-equal? (create-name (list (ip iworld1 "player10" (body 10 0) '(1+10i))) "player10") "player10*") - #; - (check-equal? (create-name (list (ip iworld1 "player10" (body 10 0) '(1+10i)) - (ip iworld1 "player10*" (body 10 0) '(1+10i))) - "player10") - "player10**") - #; - (check-property property:unique-name) - - ;; spawn-food - (check-property property:player/food-number-correct) - - ;; random-body - (check-property test-body-in-bounds) - - ;; testing clock tick handling - - (define tbody1 (body 100 1+3i)) - (define tbody2 (body 100 1)) - (define tbody3 (body 100 0+3i)) - (define tbody4 (body 100 101)) - - (define waypoints1 '(1+3i 1 0+3i 10+10i)) - (define waypoints2 '(100)) - - ;; move-player* - (check-equal? (move-player* - (list (ip iworld1 "player10" (body 10 1+10i) '(1+10.01i)))) - (list (ip iworld1 "player10" (body 10 1+10.01i) empty))) - (check-property property:no-same-point) - ;; move-twards-waypoint - - - (test-begin - (check-equal? (move-toward-waypoint tbody1 waypoints1) - (rest waypoints1) - "waypoint removal failed") - (check-equal? tbody1 (body 100 1+3i) "movement failed") - (set! tbody1 (body 100 1+3i))) - - (test-begin - ;; test dependent on (< BASE-SPEED 100) - (check-equal? (move-toward-waypoint tbody2 waypoints2) - waypoints2 - "waypoint removal failed") - (check-equal? tbody2 (body 100 (+ 1 (make-rectangular (/ BASE-SPEED 100) 0))) - "movement failed") - (set! tbody2 (body 100 1))) - - (test-begin - (check-equal? (move-toward-waypoint tbody4 waypoints2) - '()) - (check-equal? tbody4 (body 100 100)) - (set! tbody4 (body 100 101))) - - ;; countdown - (check-equal? (wait-or-play (join (list (ip iworld1 "player10" (body 10 1+10i) empty)) 0)) - (make-bundle - (join (list (ip iworld1 "player10" (body 10 1+10i) empty)) 1) - (broadcast (list iworld1) (/ 1 WAIT-TIME)) - empty)) - (check-equal? (wait-or-play (join empty 0)) - (empty-bundle (join empty 1))) - - ;;countdown - (check-equal? (wait-or-play (join (list (ip iworld1 "player10" (body 10 1+10i) empty) - (ip iworld1 "player345" (body 56 3+45i) empty)) - 100)) - (make-bundle (join (list (ip iworld1 "player10" (body 10 1+10i) empty) - (ip iworld1 "player345" (body 56 3+45i) empty)) - 101) - (broadcast (list iworld1 iworld1) (/ 101 WAIT-TIME)) - empty)) - (check-equal? (wait-or-play (join (list (ip iworld1 "player10" (body 10 1+10i) empty) - (ip iworld1 "player345" (body 56 3+45i) empty)) - 1)) - (make-bundle (join (list (ip iworld1 "player10" (body 10 1+10i) empty) - (ip iworld1 "player345" (body 56 3+45i) empty)) - 2) - (broadcast (list iworld1 iworld1) (/ 2 WAIT-TIME)) - empty)) - ;; progress - (check-equal? (progress - (list (ip iworld1 "player10" (body 10 1+10i) empty) - (ip iworld1 "player345" (body 56 3+45i) empty)) - (list (body 87 67+23i) - (body 5 3+4i)) - empty) - (broadcast-universe - (play (list (ip iworld1 "player10" (body 10 1+10i) empty) - (ip iworld1 "player345" (body 56 3+45i) empty)) - (list (body 87 67+23i) - (body 5 3+4i)) - empty)) - #; - (make-bundle - (play (list (ip iworld1 "player10" (body 10 1+10i) empty) - (ip iworld1 "player345" (body 56 3+45i) empty)) - (list (body 87 67+23i) - (body 5 3+4i)) - empty) - (serial/broadcast-univ (play (list (ip iworld1 "player10" (body 10 1+10i) empty) - (ip iworld1 "player345" (body 56 3+45i) empty)) - (list (body 87 67+23i) - (body 5 3+4i)) - empty)) - empty)) - - ;; body-collide? - (check-true (body-collide? (body 10 10+10i) (body 10 10+10i))) - (check-true (body-collide? (body 10 10+10i) (body 10 0+10i))) - (check-true (body-collide? (body 10 10+10i) (body 10 10))) - (check-true (body-collide? (body 10 10+10i) (body 10 20))) - (check-true (body-collide? (body 10 10+10i) (body 10 0+20i))) - - (check-false (body-collide? (body 1 10+10i) (body 1 10+13i))) - (check-false (body-collide? (body 1 10+10i) (body 1 0+10i))) - (check-false (body-collide? (body 1 10+10i) (body 1 10))) - (check-false (body-collide? (body 1 10+10i) (body 1 20))) - (check-false (body-collide? (body 1 10+10i) (body 1 0+20i))) - - ;; serial/broadcast-univ - #; - (check-equal? (serial/broadcast-univ - (play (list (ip iworld1 "player10" (body 10 1+10i) empty) - (ip iworld2 "player345" (body 56 3+45i) empty)) - (list (body 87 67+23i) - (body 5 3+4i)) - empty)) - (let ([serialized (serialize-universe (play (list (ip iworld1 "player10" (body 10 1+10i) empty) - (ip iworld2 "player345" (body 56 3+45i) empty)) - (list (body 87 67+23i) - (body 5 3+4i)) - empty))]) - (list (make-mail iworld1 serialized) - (make-mail iworld2 serialized)))) - - ;; time-broadcast - (let ([j (join '() 100)]) - (check-equal? (time-broadcast j) - (make-bundle j '() '()))) - (let ([j (join `(,(ip iworld1 "sallyjoe" (body 0 0+0i) '())) 100)]) - (check-equal? (time-broadcast j) - (make-bundle j `(,(make-mail iworld1 (/ 100 WAIT-TIME))) '()))) - - ;; testing auxiliary functions - (check-equal? (score `(,(ip iworld1 "foo" (body 1000 +inf.0) '()) - ,(ip iworld1 "bar" (body 0 +inf.0) '()))) - `(("foo" ,(get-score 1000)) - ("bar" ,(get-score 0)))) - ;; get-iws - ;; empty-bundle - (check-equal? (empty-bundle (join (list (ip iworld1 "player10" (body 10 1+10i) empty) - (ip iworld2 "player345" (body 56 3+45i) empty)) 132)) - (make-bundle (join (list (ip iworld1 "player10" (body 10 1+10i) empty) - (ip iworld2 "player345" (body 56 3+45i) empty)) 132) empty empty)) - (check-equal? (empty-bundle (join (list (ip iworld1 "player10" (body 10 1+10i) empty) - (ip iworld2 "player345" (body 56 3+45i) empty)) 345)) - (make-bundle (join (list (ip iworld1 "player10" (body 10 1+10i) empty) - (ip iworld2 "player345" (body 56 3+45i) empty)) 345) empty empty)) - (check-equal? (empty-bundle (play (list (ip iworld1 "player1" (body 87 67+23i) empty)) - (list (body 87 67+23i) - (body 89 32+345i)) - empty)) - (make-bundle - (play (list (ip iworld1 "player1" (body 87 67+23i) empty)) - (list (body 87 67+23i) - (body 89 32+345i)) - empty) - empty - empty)) - - ;; get-iws - (check-equal? (get-iws (play (list (ip iworld1 "player10" (body 10 1+10i) empty) - (ip iworld2 "player345" (body 56 3+45i) empty)) - (list (body 87 67+23i) - (body 5 3+4i)) - empty)) - (list iworld1 iworld2)) - (check-equal? (get-iws (play (list (ip iworld1 "player10" (body 10 1+10i) empty)) - empty - empty)) - (list iworld1)) - - ;; get-ips - (let ([players (list (ip iworld1 "player10" (body 10 1+10i) empty) - (ip iworld2 "player345" (body 56 3+45i) empty))] - [spectators (list (ip iworld1 "player10" (body 15 2+65i) empty))]) - (check-equal? (get-ips (play players - (list (body 87 67+23i) - (body 5 3+4i)) - spectators)) - (append players spectators)) - (check-equal? (get-ips (play (list (first players)) - (list (body 87 67+23i) - (body 5 3+4i)) - empty)) - (append (list (first players)) empty))) - - ;; broadcast - (check-equal? (broadcast (list iworld1 iworld3 iworld2) - '(testing testing 1 2 3)) - (let ([message '(testing testing 1 2 3)]) - (list (make-mail iworld1 - message) - (make-mail iworld3 - message) - (make-mail iworld2 - message)))) - (check-equal? (broadcast (list iworld1) - '(testing testing 1 2 3)) - (let ([message '(testing testing 1 2 3)]) - (list (make-mail iworld1 - message)))) - (check-equal? (broadcast (list iworld1 iworld3) - 9) - (let ([message 9]) - (list (make-mail iworld1 - message) - (make-mail iworld3 - message)))) - - ;; broadcast-state - (let ([state (play (list (ip iworld1 "player10" (body 10 1+10i) empty) - (ip iworld2 "player345" (body 56 3+45i) empty)) - (list (body 87 67+23i) - (body 5 3+4i)) - empty)]) - (check-equal? (broadcast-universe state) - (broadcast-universe state))) - - ;; serialize-players-for - (let* ([waypoints1 (list (make-rectangular 383 212) - (make-rectangular 282 192))] - [waypoints2 (list (make-rectangular 918 319) - (make-rectangular 481 119) - (make-rectangular 129 321))] - [pls (list (ip iworld1 "player10" (body 10 1+10i) waypoints1) - (ip iworld2 "player345" (body 56 3+45i) waypoints2))] - [pl (first pls)]) - (check-equal? (player-waypoints (first (serialize-players-for pl pls))) - waypoints1) - (check-equal? (player-waypoints (second (serialize-players-for pl pls))) - (list (make-rectangular 918 319)))) - - "server: all tests run") diff --git a/fourteen/shared.rkt b/fourteen/shared.rkt deleted file mode 100644 index 7f3a549..0000000 --- a/fourteen/shared.rkt +++ /dev/null @@ -1,156 +0,0 @@ -#lang racket - -;; This module describes the shared vocabulary and knowledge for the server -;; and client modules of the Hungry Henry game. - -(provide ;; type Id - id? ;; Any -> Boolean : Id - id=? ;; Id Id -> Boolean - ;; type GOTO - ;; type SOTO = Time | Ackn | State | Score - ;; type Food - ;; type Feaster - ;; type Body - (struct-out player) ;; - (struct-out body) ;; - get-score ;; Nat -> Nat - PLAYER-FATTEN-DELTA - WIDTH HEIGHT CUPCAKE PLAYER-SIZE - SCORE GOTO SERIALIZE - GOTO-LENGTH) - -#| ----------------------------------------------------------------------------- - -;; --- Tasks -------------------------------------------------------------------- - -The game server keeps track of the entire game state [to avoid cheating by -lients]. It collects waypoints, moves the avatars on behalf of the clients, -detects collisions with cupcakes, has avatars eat and grow, and discovers the -end of the game. As events occur, it informs all clients about all actions and, -at the end of the game, tallies the scores. - -Each client displays the current state of the game as broadcast by the server. -It also records and sends all mouse clicks to the server. - -;; --- Messages and Protocol --------------------------------------------------- - -The server and the client exchange messages to inform each other about -the events in the game. - -Client To Server Message: ------------------------- - - GOTO = (list GOTO PositiveNumber PositiveNumber) - represents the coordinates of player's latest waypoint, - obtained via a mouse click. - Constraint: in (list GOTO x y), (and (<= 0 x WIDTH) (<= 0 y HEIGHT)) - -Server to Client Message: -------------------------- - - SOTO is one of: - -- Number ∈ [0,1] - called a Time message - repreents the percentage of loading time left - -- ID - called an Ackn message - represents the unique id that the server assigns to the client, - based on the client's name - -- (list SERIALIZE [Listof Feaster] [Listof Food]) - called a State message - represents the complete current state of the game - -- (list SCORE [Listof (list Id Natural)]) - called a Score message - informs clients that the game is over and the sizes of each player. -|# -;; Shared Data Definitions for Messages - -(struct player (id body waypoints) #:prefab) -(struct body (size loc) #:prefab #:mutable) -;; Food = Body -;; Feaster = (player Id Body [Listof Complex]) -;; interpretation: -;; -- id is the player's id -;; -- body is the player's size and location -;; -- loc are the player's waypoints, ordered from first to last -;; Body = (body PositiveNumber Complex) -;; interpretation: any 'body' on the playing field, both players and cupcakes -;; -- the postive number specifies the body's size -;; -- the complex number represents the body's location -;; PlayerId = String -(define id? string?) -(define id=? string=?) - -;; Message ID Constants -(define SCORE 'score) -(define SERIALIZE 'state) -(define GOTO 'goto) -(define GOTO-LENGTH 3) - -#| --- Protocol ---------------------------------------------------------------- - - Client1 Client2 Server - | | | - | register(name1) | [universe protocol] - |----------------------------->| - | | | - | | ID | an identifier message - |<-----------------------------| - | | t | percentage of wait time - |<-----------------------------| - |<-----------------------------| - |<-----------------------------| - | | | - | | register(name2) - | |------------->| - | | | - | | ID | - | |<-------------| - | | t | percentage of wait time - |<-----------------------------| - | |<-------------| - |<-----------------------------| - | |<-------------| - | | | <==== end of wait time [clock, players] - | state msg | - |<-----------------------------| `(state (,feaster1 ,feaster2) ,foods) - | |<-------------| - | | | - click | GOTO | | `(goto ,x ,y) - ====> |----------------------------->| new state - | | | - | state msg | - |<-----------------------------| `(state (,feaster1 ,feaster2) ,foods) - | |<-------------| - | | | - | | | move, eat: - |<-----------------------------| `(state (,feaster1 ,feaster2) ,foods) - | |<-------------| - | | | - | click | GOTO | `(goto ,x ,y) - | ====> |------------->| - | | | - | state msg | - |<-----------------------------| `(state (,feaster1 ,feaster2) ,foods) - | |<-------------| - | | | - | score msg | all food is eaten: - |<-----------------------------| `(score ((,id ,score) ...)) - | |<-------------| - | | | - --- --- --- - -|# - -;; Shared Logical Constants -(define WIDTH 1000) -(define HEIGHT 700) -(define CUPCAKE 15) -(define PLAYER-SIZE (* 3 CUPCAKE)) -(define PLAYER-FATTEN-DELTA 5) - -;; Number -> Number ;; move to serer -;; gets aplayers score given its fatness -(define (get-score f) - (/ (- f PLAYER-SIZE) PLAYER-FATTEN-DELTA)) - |