;; The first three lines of this file were inserted by DrRacket. They record metadata ;; about the language level of this file in a form that our tools can easily process. #reader(lib "htdp-advanced-reader.ss" "lang")((modname initial-client) (read-case-sensitive #t) (teachpacks ()) (htdp-settings #(#t constructor repeating-decimal #t #t none #f ()))) ;; ***************************************************** ;; Chat client Skeleton (require 2htdp/image) (require 2htdp/universe) (define-struct client (name lines editor curse-on?)) ;; A chat Client is (make-client String [Listof Line] String Boolean) ;; Interpretation: (make-client name lines editor curse-on?) ;; where ;; name is the the (nick)name of the chatter ;; lines is a list of previous lines in the chat ;; editor is the current line being edited ;; curse-on? is the state of the cursor (true or false) - used for making ;; the cursor blink ;; A (chat) Client is our local World. ;; A Line is a list with two entries: (list text color) ;; representing a line of text (String) to be shown in the designated ;; color (Symbol or String). ;; The line of text is prefixed by the sender's nick. ;; A Package is: (make-package World Message) ;; A Message is an SExpr ;; An Atom is one of: ;; - Number ;; - Symbol ;; - String ;; An SExp is one of: ;; - Atom ;; - [List-of SExp] ;; Constants for various specifics of the client GUI (define CLIENT-HEIGHT 200) (define CLIENT-WIDTH 400) (define FONT-SIZE 12) (define FONT-COLOR 'black) (define LINE-SPACING 5) (define LINE-INDENT 5) (define DIVIDING-COLOR "blue") (define NICK-DIVIDER " : ") (define CURSOR-IMG-ON (rectangle 4 (+ 4 (* 2 LINE-SPACING)) "solid" "red")) (define CURSOR-IMG-OFF (rectangle 3 (+ 4 (* 2 LINE-SPACING)) "solid" "black")) ;; CHAT-AREA is the image onto which text is drawn. The upper area is used to ;; display sent messages. The area below the line is for the editor text. (define CHAT-AREA (local [(define SEP-Y (- CLIENT-HEIGHT FONT-SIZE (* 2 LINE-SPACING)))] (scene+line (empty-scene CLIENT-WIDTH CLIENT-HEIGHT) 0 SEP-Y CLIENT-WIDTH SEP-Y DIVIDING-COLOR))) ;;; Alfred E. Neuman (What, me worry?) (define client-1 (make-client "neuman" (list (list "neuman: Hola peeps" FONT-COLOR) (list "neuman: Lame chat party :/" FONT-COLOR)) "" false)) ;;; The Joker (define client-2 (make-client "Joker" '() "Why so seri" true)) ;; valid-lines? : Any -> Boolean ;; Check if the input is a [Listof (list String Color)] (define (valid-lines? ls) (and (list? ls) (andmap (λ (el) (and (list? el) (= 2 (length el)) (string? (first el)) (image-color? (second el)))) ls))) (check-expect (valid-lines? empty) true) (check-expect (valid-lines? '(("hello" "green"))) true) (check-expect (valid-lines? '(("hello" blue))) true) (check-expect (valid-lines? '(("hello" 8))) false) (check-expect (valid-lines? '(7)) false) ;; string-prefix? : String String -> Boolean ;; checks to see if the second string is a prefix of the first (define (string-prefix? str pre) (cond [(and (zero? (string-length str)) (zero? (string-length pre))) true] [(< (string-length str) (string-length pre)) false] [else (string=? (substring str 0 (string-length pre)) pre)])) (check-expect (string-prefix? "" "") true) (check-expect (string-prefix? "a" "") true) (check-expect (string-prefix? "" "a") false) (check-expect (string-prefix? "a" "a") true) (check-expect (string-prefix? "ab" "a") true) (check-expect (string-prefix? "ab" "b") false) ;; string-remove-prefix : String String -> String ;; returns a new string that is the same as the first string except ;; with the second string removed as a prefix (define (string-remove-prefix str pre) (if (string-prefix? str pre) (substring str (string-length pre)) str)) (check-expect (string-remove-prefix "" "") "") (check-expect (string-remove-prefix "a" "") "a") (check-expect (string-remove-prefix "" "a") "") (check-expect (string-remove-prefix "a" "a") "") (check-expect (string-remove-prefix "ab" "a") "b") (check-expect (string-remove-prefix "ab" "b") "ab") ;; W/2 : Image -> Number ;; Calculate the image width divided by 2 (define (W/2 img) (if (image? img) (/ (image-width img) 2) (error "W/2 expect an image, got: " img))) ;; H/2 : Image -> Number ;; Calculate the image height divided by 2 (define (H/2 img) (if (image? img) (/ (image-height img) 2) (error "W/2 expect an image, got: " img))) ;; chattify : String Color -> Image ;; Convert the String into a text image with the given color. (define (chattify s c) (if (and (image-color? c) (string? s)) (text s FONT-SIZE c) (error "chattify expects a string and a color, got: " s c))) (check-expect (chattify "Na na na na na na" 'black) (text "Na na na na na na" FONT-SIZE 'black)) (check-expect (chattify "BATMAN!" "yellow") (text "BATMAN!" FONT-SIZE "yellow")) (check-error (chattify 42 'blue)) ;; render-client : Client -> Scene ;; Render the client editor and stored chat lines (define (render-client c) (if (and (client? c) (string? (client-name c)) (string? (client-editor c)) (valid-lines? (client-lines c))) (local [(define editor (chattify (string-append (client-name c) NICK-DIVIDER (client-editor c)) FONT-COLOR))] (add-chattings (client-lines c) (- CLIENT-HEIGHT (image-height editor) (* LINE-SPACING 2)) (place-image (if (client-curse-on? c) CURSOR-IMG-ON CURSOR-IMG-OFF) (+ LINE-INDENT (image-width editor) 4) (- CLIENT-HEIGHT (H/2 editor)) (place-image editor (+ LINE-INDENT (W/2 editor)) (- CLIENT-HEIGHT (H/2 editor)) CHAT-AREA)))) (error "render-client expects a client, got: " c))) ;; add-chattings : [Listof (list String Color)] Number Scene -> Scene ;; Add the given strings to the chat scene... (define (add-chattings los y scn) (cond [(not (valid-lines? los)) (error "add-chattings: first argument must be a list of pairs of strings and colors, was: " los)] [(not (number? y)) (error "add-chattings: second argument must be a number, was: " y)] [(not (image? scn)) (error "add-chattings: third argument must be a scene, was: " scn)] [else ; all good (cond [(empty? los) scn] [else (local [(define txt (chattify (first (first los)) (second (first los))))] (place-image txt (+ LINE-INDENT (W/2 txt)) (- y (H/2 txt)) (add-chattings (rest los) (- y (image-height txt) LINE-SPACING) scn)))])])) ;; strip-last : String -> String ;; strips the last character (if any) from a string (define (strip-last s) (if (string? s) (substring s 0 (max (sub1 (string-length s)) 0)) (error "strip-last expects a string, got: " s))) (check-expect (strip-last "") "") (check-expect (strip-last "omg") "om") ;; tick : Client -> Client ;; Blink the cursor... (define (tick c) (if (and (client? c) (string? (client-name c)) (string? (client-editor c)) (valid-lines? (client-lines c))) (make-client (client-name c) (client-lines c) (client-editor c) (not (client-curse-on? c))) (error "tick expects a client, got: " c))) ;; handle-key : Client KeyEvent -> Client ;; handles key presses for the chat client (define (handle-key c ke) (cond [(not (and (client? c) (string? (client-name c)) (string? (client-editor c)) (valid-lines? (client-lines c)))) (error "handle-key: first argument must be a client, was: " c)] [(not (key-event? ke)) (error "handle-key: second argument must be a key-event, was: " c)] [else ; all good (cond ;; ******* MODIFY HERE ********* ;; ** Local Version ;; ** Comment out this branch to run with the Universe. [(key=? ke "\r") (make-client (client-name c) (cons (list (string-append (client-name c) NICK-DIVIDER (client-editor c)) FONT-COLOR) (client-lines c)) "" (client-curse-on? c))] ;; ******* MODIFY HERE ********* ;; ** Universe Version ;; ** Write this branch to send a Message to the server when "enter" ;; ** is pressed. ;;[(key=? ke "\r") ...] [(key=? ke "\b") (make-client (client-name c) (client-lines c) (strip-last (client-editor c)) (client-curse-on? c))] [(= (string-length ke) 1) (make-client (client-name c) (client-lines c) (string-append (client-editor c) ke) (client-curse-on? c))] [else c])])) ;; ******* MODIFY HERE ********* ;; ** How will some of these tests change when you modify handle-key to the ;; ** universe version? (check-expect (handle-key client-2 "\r") (make-client "Joker" (list (list (string-append "Joker" NICK-DIVIDER "Why so seri") FONT-COLOR)) "" true)) (check-expect (handle-key client-2 "\b") (make-client "Joker" (list) "Why so ser" true)) (check-expect (handle-key client-1 "a") (make-client "neuman" (list (list "neuman: Hola peeps" FONT-COLOR) (list "neuman: Lame chat party :/" FONT-COLOR)) "a" false)) ;; handle-msg : Client Message -> Client ;; Handle an incoming message by adding it to the lines in the client. ;; ******* MODIFY HERE ********* ;; ** When a Message is received from the server, it should be added to the Client ;(define (handle-msg c msg) ...) #| (check-expect (handle-msg client-2 "alice: What's up") (make-client "Joker" (list (list "alice: What's up" FONT-COLOR)) "Why so seri" true)) (check-expect (handle-msg client-1 "bob: Hacking") (make-client "neuman" (list (list "bob: Hacking" 'black) (list "neuman: Hola peeps" 'black) (list "neuman: Lame chat party :/" 'black)) "" false)) |# ;; run : String -> Client ;; Runs the chat client, given a nickname (define (run nick) (if (string? nick) (big-bang (make-client nick (list) "" false) (on-draw render-client) (on-key handle-key) (on-tick tick 3/4) ;; ******* MODIFY HERE ********* ;; ** Uncomment to run in Universe ;; ** Make sure you are registering with the right server! ;; (register "dubnium.ccs.neu.edu") ;; (on-receive handle-msg) (name nick)) (error "run expects a string, got: " nick))) ;; ******* MODIFY HERE ********* ;; ** Change your nickname to something interesting. (run "neuman")