diff --git a/examples/bouncing-ball-client.rkt b/examples/bouncing-ball-client.rkt new file mode 100644 index 0000000..5b20827 --- /dev/null +++ b/examples/bouncing-ball-client.rkt @@ -0,0 +1,85 @@ +#lang racketscript/base +(require racketscript/htdp/peer-universe + racketscript/htdp/image) + +;; Implementation of bouncing ball example from +;; 2htdp/universe docs +;; ( https://docs.racket-lang.org/teachpack/2htdpuniverse.html ) +;; (ctrl + F search for "Designing the Ball World") + +(define SPEED 5) +(define RADIUS 10) +(define WORLD0 'RESTING) + +(define WIDTH 600) +(define HEIGHT 400) +(define MT (empty-scene WIDTH HEIGHT)) +(define BALL (circle RADIUS 'solid 'blue)) + +(define (move ws) + (define is-active (number? ws)) + (if is-active + (if (<= ws 0) + (make-package 'RESTING #js"done") + (- ws SPEED)) + ws)) + +(define (draw ws) + (cond + [(number? ws) (underlay/xy MT 50 ws BALL)] + [else (underlay/xy MT 50 50 (text "Resting" 24 'blue))])) + +;; The only message that the server send is the your-turn one, +;; so this will always return HEIGHT as the next world state +(define (receive ws msg) + (if (number? ws) + ws + HEIGHT)) + +;; Stops world when ws == "stop" +(define (stop? ws) (equal? ws "stop")) + +(define (handle-key ws key) + (if (equal? key " ") + "stop" ws)) + +(define (start-world client-name server-id) + (big-bang WORLD0 + [on-tick move] + [to-draw draw] + [on-receive receive] + [register server-id] + [name client-name] + [on-key handle-key] + [stop-when stop?])) + +;; +;; User login UI +;; + +(define join-form (#js*.document.createElement #js"form")) +(define server-id-label (#js*.document.createElement #js"label")) +(define br-1 (#js*.document.createElement #js"br")) +(define server-id-input (#js*.document.createElement #js"input")) +(define br-2 (#js*.document.createElement #js"br")) +(define form-submit (#js*.document.createElement #js"input")) + +($/:= #js.server-id-label.innerHTML #js"Server's Peer ID") +($/:= #js.server-id-input.placeholder #js"42adwadwa#$021") +($/:= #js.form-submit.type #js"submit") +($/:= #js.form-submit.value #js"Join!") + +(for-each (λ (el) + (#js.join-form.appendChild el) + 0) + (list server-id-label br-1 server-id-input + br-2 + form-submit)) + +($/:= #js.join-form.onsubmit + (λ () + (start-world "user" + (js-string->string #js.server-id-input.value)) + (#js.join-form.remove))) + +(#js*.document.body.appendChild join-form) \ No newline at end of file diff --git a/examples/bouncing-ball-server.rkt b/examples/bouncing-ball-server.rkt new file mode 100644 index 0000000..4d84d0c --- /dev/null +++ b/examples/bouncing-ball-server.rkt @@ -0,0 +1,130 @@ +#lang racketscript/base + +(require racketscript/htdp/peer-universe + racketscript/htdp/image + racket/list) + + +;; +;; Funny words courtesy of ChatGPT +;; + +(define funny-adjectives (list "bumbling" + "quizzical" + "wacky" + "zany" + "fluffy" + "bizarre" + "hilarious" + "whimsical" + "absurd" + "goofy" + "ridiculous" + "loopy" + "nutty" + "eccentric" + "silly" + "quirky" + "jovial" + "giggly" + "mirthful" + "haphazard" + "chucklesome" + "fanciful" + "droll" + "boisterous" + "offbeat" + "hysterical" + "peculiar" + "lighthearted" + "playful" + "amusing")) + +(define funny-nouns (list "goober" + "banana" + "sock-puppet" + "llama" + "rubber-chicken" + "pajamas" + "gobbledygook" + "poodle" + "bubble-wrap" + "tater-tot" + "cheeseburger" + "wiggle" + "snorkel" + "ticklemonster" + "jello" + "balloon-animal" + "slinky" + "spaghetti" + "bumblebee" + "dingleberry" + "flapdoodle" + "doohickey" + "noodle" + "gobbledygook" + "whatchamacallit" + "snickerdoodle" + "popsicle" + "gigglesnort" + "wobble" + "hootenanny" + "noodle")) + +(define (generate-id) + (define adjective (list-ref funny-adjectives + (random (length funny-adjectives)))) + (define noun (list-ref funny-nouns + (random (length funny-nouns)))) + (format "~a-~a" adjective noun)) + + +;; +;; Helper functions +;; + +(define (make-curr-mail ws) + (define curr-iw (first ws)) + (list (make-mail curr-iw #js"it-is-your-turn"))) + + +;; +;; Event handlers +;; + +(define (handle-new ws iw) + (define ws* (append ws (list iw))) + (define mails (make-curr-mail ws*)) + (define to-remove '()) + (make-bundle ws* mails to-remove)) + +(define (handle-msg ws iw msg) + (define ws* (append (rest ws) (list (first ws)))) + (define mails (make-curr-mail ws*)) + (define to-remove '()) + (make-bundle ws* mails to-remove)) + +(define (handle-disconnect ws iw) + (define ws* (remove iw ws)) + (define mails (make-curr-mail ws*)) + (define to-remove '()) + (make-bundle ws* mails to-remove)) + +(define (handle-tick ws) + (make-bundle ws '() '())) + + +;; +;; Starting server on window load +;; + +(define (start-universe) + (universe '() + [server-id (generate-id)] + [on-new handle-new] + [on-msg handle-msg] + [on-tick handle-tick] + [on-disconnect handle-disconnect])) + +($/:= #js*.window.onload start-universe) \ No newline at end of file diff --git a/examples/chatroom-client.rkt b/examples/chatroom-client.rkt new file mode 100644 index 0000000..76e4e3f --- /dev/null +++ b/examples/chatroom-client.rkt @@ -0,0 +1,247 @@ +#lang racketscript/base + +(require racket/list + racketscript/htdp/peer-universe + racketscript/htdp/image) + +;; Example based on https://course.khoury.northeastern.edu/cs5010sp15/set08.html + +;; MsgFromServer +;; (list 'userlist ListOf) ;; don't include in event-messages +;; (list 'join UserName) +;; (list 'leave UserName) +;; (list 'error Message) +;; (list 'private UserName Message) ;; a private msg from a user +;; (list 'broadcast UserName Message) ;; a public msg from a user + +;; WorldState +;; (list client-name connected-users> event-messages> curr-input) + + +;; +;; Helper functions +;; + +(define (slice-list l start stop) + (take (drop l start) (- stop start))) + +(define (list-contains list v) + (number? (index-of list v))) + +(define (not-in-list bigger-list smaller-list) + (list-ref (filter (λ (el) (not (list-contains smaller-list el))) bigger-list) 0)) + + +;; +;; Constants +;; + +(define FONT-SIZE 12) +(define MARGIN 3) +(define MT (empty-scene 400 400)) +(define CHARS-PER-LINE 44) ;; max chars that can fit in the input box +(define MAX-MESSAGES 25) + + +;; +;; WorldState parsers +;; + +(define (get-client-name ws) + (list-ref ws 0)) + +(define (get-connected-users ws) + (list-ref ws 1)) + +(define (get-event-messages ws) + (list-ref ws 2)) + +(define (get-curr-input ws) + (list-ref ws 3)) + + +;; +;; Message Parsing +;; + +(define (parse-user-list u-list) + (#js.u-list.reduce (lambda (result curr) + (append result (list (js-string->string curr)))) + '())) + + +;; +;; Drawing Functions +;; + +(define (participant-names-column names) + (define container (rectangle 100 400 'outline 'black)) + (define i 0) + (foldl (lambda (name res) + (define name-text (text name FONT-SIZE 'black)) + (define col (underlay/xy res + MARGIN (+ MARGIN (* i 20)) + name-text)) + (set! i (+ i 1)) + col) + container + names)) + +(define (message-textbox curr-text cursor-pos) + (define container (rectangle 300 20 'outline 'black)) + (define text-len (string-length curr-text)) + (define input-text (text (if (> text-len CHARS-PER-LINE) + (substring curr-text (- text-len CHARS-PER-LINE) text-len) + curr-text) + FONT-SIZE + 'black)) + + (underlay/xy container MARGIN MARGIN input-text)) + +(define (message-log-display event-list username) + (define background (rectangle 300 380 'outline 'black)) + (define count 0) + (foldl (lambda (evt res) + (define evt-type (list-ref evt 0)) + (define (add-text img) + (define new-res (underlay/xy res MARGIN (+ (* count FONT-SIZE) (* (+ count 1) MARGIN)) img)) + (set! count (+ count 1)) + new-res) + (define (message-text user msg color) + (text (format "<~a> ~a" user msg) FONT-SIZE color)) + (case evt-type + [(broadcast) (add-text (message-text (list-ref evt 1) + (list-ref evt 2) + 'black))] + [(private) (add-text (message-text (format "~a->~a" (list-ref evt 1) username) + (list-ref evt 2) + 'blue))] + [(join) (add-text (text (format "~a joined." (list-ref evt 1)) + FONT-SIZE + 'gray))] + [(leave) (add-text (text (format "~a left the chat." (list-ref evt 1)) + FONT-SIZE + 'gray))] + [(error) (add-text (text (list-ref evt 1) FONT-SIZE 'red))] + [else res])) + background + (if (> (length event-list) MAX-MESSAGES) + (slice-list event-list + (- (length event-list) MAX-MESSAGES) + (length event-list)) + event-list))) + +(define (draw ws) + (define textbox (message-textbox (get-curr-input ws))) + (define users (participant-names-column (get-connected-users ws))) + (define log (message-log-display (get-event-messages ws) (get-client-name ws))) + + (underlay/xy (underlay/xy (underlay/xy MT 0 0 users) + 100 380 + textbox) + 100 0 + log)) + + +;; +;; Event Handlers +;; + +(define (handle-key ws k) + (define curr-text (get-curr-input ws)) + (define new-text (cond + [(> (string-length k) 1) curr-text] + [(key=? k "\b") (if (<= (string-length curr-text) 0) + curr-text + (substring curr-text + 0 + (- (string-length curr-text) 1)))] + [else (string-append curr-text k)])) + (if (key=? k "\r") + (make-package (list (get-client-name ws) + (get-connected-users ws) + (get-event-messages ws) + "") + curr-text) + (list (get-client-name ws) + (get-connected-users ws) + (get-event-messages ws) + new-text))) + +(define (handle-receive ws msg) + (define username (get-client-name ws)) + (define users (get-connected-users ws)) + (define messages (get-event-messages ws)) + (define input (get-curr-input ws)) + + (define msg-type (list-ref msg 0)) + (case msg-type + [(userlist) (begin + (define users* (list-ref msg 1)) + + (set! messages + (append messages + (if (> (length users*) (length users)) + (list (list 'join + (list-ref users* + (- (length users*) 1)))) + (list (list 'leave + (not-in-list users users*)))))) + (set! users users*))] + [(broadcast) (set! messages (append messages (list msg)))]) + + (list username users messages input)) + + +;; +;; Start func +;; + +(define (start-world username server-id) + (big-bang #:dom-root #js*.document.body + (list username '() '() "") + [to-draw draw] + [on-key handle-key] + [on-receive handle-receive] + [name username] + [register server-id])) + + +;; +;; User login UI +;; + +(define join-form (#js*.document.createElement #js"form")) +(define name-label (#js*.document.createElement #js"label")) +(define br-1 (#js*.document.createElement #js"br")) +(define name-input (#js*.document.createElement #js"input")) +(define br-2 (#js*.document.createElement #js"br")) +(define server-id-label (#js*.document.createElement #js"label")) +(define br-3 (#js*.document.createElement #js"br")) +(define server-id-input (#js*.document.createElement #js"input")) +(define br-4 (#js*.document.createElement #js"br")) +(define form-submit (#js*.document.createElement #js"input")) + +($/:= #js.name-label.innerHTML #js"Username") +($/:= #js.server-id-label.innerHTML #js"Server's Peer ID") +($/:= #js.name-input.placeholder #js"my_name1234") +($/:= #js.server-id-input.placeholder #js"42adwadwa#$021") +($/:= #js.form-submit.type #js"submit") +($/:= #js.form-submit.value #js"Join!") + +(for-each (λ (el) + (#js.join-form.appendChild el) + 0) + (list name-label br-1 name-input + br-2 + server-id-label br-3 server-id-input + br-4 + form-submit)) + +($/:= #js.join-form.onsubmit + (λ () + (start-world (js-string->string #js.name-input.value) + (js-string->string #js.server-id-input.value)) + (#js.join-form.remove))) + +(#js*.document.body.appendChild join-form) \ No newline at end of file diff --git a/examples/chatroom-server.rkt b/examples/chatroom-server.rkt new file mode 100644 index 0000000..d7c2b4b --- /dev/null +++ b/examples/chatroom-server.rkt @@ -0,0 +1,138 @@ +#lang racketscript/base + +(require racketscript/htdp/peer-universe + racketscript/htdp/image) + +;; Example based on https://course.khoury.northeastern.edu/cs5010sp15/set08.html + +;; WorldState: +;; (list ListOf ListOf) + + +;; +;; Funny words courtesy of ChatGPT +;; + +(define funny-adjectives (list "bumbling" + "quizzical" + "wacky" + "zany" + "fluffy" + "bizarre" + "hilarious" + "whimsical" + "absurd" + "goofy" + "ridiculous" + "loopy" + "nutty" + "eccentric" + "silly" + "quirky" + "jovial" + "giggly" + "mirthful" + "haphazard" + "chucklesome" + "fanciful" + "droll" + "boisterous" + "offbeat" + "hysterical" + "peculiar" + "lighthearted" + "playful" + "amusing")) + +(define funny-nouns (list "goober" + "banana" + "sock-puppet" + "llama" + "rubber-chicken" + "pajamas" + "gobbledygook" + "poodle" + "bubble-wrap" + "tater-tot" + "cheeseburger" + "wiggle" + "snorkel" + "ticklemonster" + "jello" + "balloon-animal" + "slinky" + "spaghetti" + "bumblebee" + "dingleberry" + "flapdoodle" + "doohickey" + "noodle" + "gobbledygook" + "whatchamacallit" + "snickerdoodle" + "popsicle" + "gigglesnort" + "wobble" + "hootenanny" + "noodle")) + +(define (generate-id) + (define adjective (list-ref funny-adjectives + (random (length funny-adjectives)))) + (define noun (list-ref funny-nouns + (random (length funny-nouns)))) + (format "~a-~a" adjective noun)) + +;; +;; Helper functions +;; + +(define (make-client-list ws) + (foldl (lambda (iw result) (append result (list (iworld-name iw)))) '() ws)) + +(define (make-broadcast sender msg) + (format "[]")) + +(define (mail-to-all ws content) + (foldl (lambda (iw result) + (append result (list (make-mail iw content)))) + '() + ws)) + +(define (client-list-mails ws) + (define client-list (make-client-list ws)) + (mail-to-all ws (list 'userlist client-list))) + + +;; +;; Event handlers +;; + +(define (handle-new ws iw) + (define ws* (append ws (list iw))) + (define mails (client-list-mails ws*)) + (define to-remove '()) + (make-bundle ws* mails to-remove)) + +(define (handle-msg ws iw msg) + (define msg-mail (list 'broadcast (iworld-name iw) msg)) + (define ws* ws) + (define mails (mail-to-all ws* msg-mail)) + (define to-remove '()) + (make-bundle ws* mails to-remove)) + +(define (handle-disconnect ws iw) + (define ws* (remove iw ws)) + (define mails (client-list-mails ws*)) + (define to-remove '()) + (make-bundle ws* mails to-remove)) + +(define (start-universe root) + (universe #:dom-root root + '() + [server-id (generate-id)] + [on-new handle-new] + [on-msg handle-msg] + [on-disconnect handle-disconnect])) + +(start-universe) \ No newline at end of file diff --git a/static/index.html b/static/index.html index f2d05c7..9591425 100644 --- a/static/index.html +++ b/static/index.html @@ -63,7 +63,7 @@