;; 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-intermediate-lambda-reader.ss" "lang")((modname initial-client) (read-case-sensitive #t) (teachpacks ()) (htdp-settings #(#t constructor repeating-decimal #f #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))) ;; 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)])) ;; 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)) ;; W/2 : Image -> Number ;; Calculate the image width divided by 2 (define (W/2 img) (/ (image-width img) 2)) ;; H/2 : Image -> Number ;; Calculate the image height divided by 2 (define (H/2 img) (/ (image-height img) 2)) ;; chattify : String Color -> Image ;; Convert the String into a text image with the given color. (define (chattify s c) (text s FONT-SIZE c)) ;; render-client : Client -> Scene ;; Render the client editor and stored chat lines (define (render-client 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))))) ;; add-chattings : [Listof (list String Color)] Number Scene -> Scene ;; Add the given strings to the chat scene... (define (add-chattings los y scn) (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) (substring s 0 (max (sub1 (string-length s)) 0))) ;; tick : Client -> Client ;; Blink the cursor... (define (tick c) (make-client (client-name c) (client-lines c) (client-editor c) (not (client-curse-on? c)))) ;; handle-key : Client KeyEvent -> Client ;; handles key presses for the chat client (define (handle-key c ke) (cond [(key=? ke "\r") (make-package (make-client (client-name c) (client-lines c) "" (client-curse-on? c)) (parse-msg c))] [(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])) ;; parse-msg : Client -> Message ;; Parses the message to turn it into a Message (define (parse-msg c) (local [(define msg (client-editor c))] (cond [(string-prefix? msg "/color ") (list "COLOR" (string->number (string-remove-prefix msg "/color ")))] [else (list "MSG" msg)]))) ;; handle-msg : Client Message -> Client ;; Handle an incoming message by adding it to the lines in the client. (define (handle-msg c msg) (make-client (client-name c) (cons (list (string-append (second msg) NICK-DIVIDER (third msg)) (fourth msg)) (client-lines c)) (client-editor c) (client-curse-on? c))) ;; run : String -> Client ;; Runs the chat client, given a nickname (define (run nick) (big-bang (make-client nick (list) "" false) (on-draw render-client) (on-key handle-key) (on-tick tick 3/4) (register "127.0.0.1") (on-receive handle-msg) (name nick))) ;; ******* MODIFY HERE ********* ;; ** Change your nickname to something interesting. (run "neuman")