[racket] designing animated text-box with universe

From: Matthias Felleisen (matthias at ccs.neu.edu)
Date: Mon Aug 12 09:42:02 EDT 2013

On Aug 11, 2013, at 6:38 PM, grant centauri wrote:

> I've rewritten a number of times, and was just hoping for some design advice before I start banging my head against the wall


Use the design recipe completely until it becomes second nature: 

1. Separate global constants from values that change over the course of the game. [World Design]

2. You have vague data definitions. You lack data examples. [Function Design]

3. You lack contracts, but I accept that they are mostly obvious. 

4. Your functions don't seem to match the data definition that they consume. 

5. You lack function examples. Formulate them as tests. 
Even after you figure out everything else, don't drop this step. Ever. 

;; --------------------------------------------

Structures within structures are okay. As you grow up, you need to learn to use
match-define because it gives you access to several nested fields at once. 

I have made your program Rackety and added some suggestions on what to look out for. 
I never figured out what the program does or is supposed to do but I am sure my rewrite
works exactly as yours before, including all the mistakes. So they remain yours to find. 

Good luck -- Matthias



#lang racket

;; ---------------------------------------------------------------------------------------------------
;; interface 

(provide
 (contract-out
  ;; start launches the game with ... as initial state and ... 
  (start (-> text-box?))))

;; ---------------------------------------------------------------------------------------------------
;; implementation 
(require 2htdp/universe 2htdp/image)

(module+ test
  (require rackunit))

;; ---------------------------------------------------------------------------------------------------
;; This program ... 

;; ---------------------------------------------------------------------------------------------------
;; data definitions 

;; current word is a list of chars, string-to-print is a list of strings
(struct buffer (current-word string-to-print))

;; defining a font structure, this is to use with the text/font function
(struct font (size color face family style weight underline?))

;; input is a buffer, paper is an image, font is a font, cursor-x and -y are positive integers
;; **: I consider it bad style to use an image as the state of the world or even a part of the state.
;; **: I recommend that you store the values that change and construct the image inside of render. 
(struct text-box (input paper font cursor-x cursor-y))

;; World = (text-box ...)
;; Font = ...
;; Buffer = ... 

;; ---------------------------------------------------------------------------------------------------
;; sample data and constants 

(define terminus-16 (font 16 'white "Terminus" 'system 'normal 'bold #f))

(define my-text
  (string-append 
   "Now THIS is the string I'd like to print!  Oh my!, it appears to be working."
   "However, there are some errors in my code (such as calling first on an empty "
   "list now and then) and it seems to slow down quite a bit."))

(define my-textbox
  (text-box (buffer empty (string-split my-text)) (rectangle 400 300 'solid 'blue) terminus-16 10 10))

;; ===================================================================================================
;; the main function 

(define (start)
  (big-bang my-textbox
            (on-tick next-char)
            (to-draw render)))

;; ---------------------------------------------------------------------------------------------------
;; World -> World 
;; updates the "paper" so that the next character is placed on it, and updates the current-word
(define (next-char tb)
  (match-define (text-box (buffer word s) paper font x y) tb)
  (define char-width (image-width (game-print " " font)))
  (cond
    [(empty? word) (next-char (next-word tb))]
    [else (define new-image (place-image (game-print (string (first word)) font) x y paper))
          (text-box (buffer (rest word) s) new-image font (+ char-width x) y)]))

;; World -> World 
;; places the next word from the text-box-input's string-to-print into current-word
(define (next-word tb)
  ;; ** the following match assumes that you have a non-empty list in the 2nd buffer position 
  ;; ** does your function call assure this? 
  (match-define (text-box (buffer _ (cons word rst)) paper font x y) tb)
  (define word-width (image-width (game-print word font)))
  (define paper-width (image-width paper))
  ;; --- are the following three lines really going to vary over the course of the game, constant? 
  (define a-char (game-print " " font))
  (define char-width (image-width a-char))
  (define char-hight (image-height a-char))
  (define line-height (+ 5 char-hight))
  (cond ;; [(empty? s) tb] ;; **: This clause looks highly suspicious 
        ;; this clause determines if we need to start a new line.
        [(> (+ x word-width) paper-width)
         (text-box (buffer (string->list word) rst) paper font 10 (+ line-height y))]
        [else 
         (text-box (buffer (string->list word) rst) paper font (+ char-width x) y)]))

;; ---------------------------------------------------------------------------------------------------
;; World -> Image 
;; render the game as an image 

(module+ test
  (check-equal? (render my-textbox)
                (overlay (text-box-paper my-textbox) (empty-scene 500 450 'black))
                "testing render on the initial world"))

(define (render tb)
  (overlay (text-box-paper tb) (empty-scene 500 450 'black)))

;; String Font -> Image
;; draws string s with the font f
(define (game-print s f) 
  (match-define (font size color face family style weight underline?) f)
  (text/font s size color face family style weight underline?))

;; ---------------------------------------------------------------------------------------------------
;; run program run 
;; ** I recommend launching this from the repl instead. 
;; (start)


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.racket-lang.org/users/archive/attachments/20130812/08c7b915/attachment.html>

Posted on the users mailing list.