Due: Wednesday, September 28th, 11:59PM.

Available on http://autograde.org. Use your credentials to login.

In this project, you will parse and render ASCII art diagrams. The point of this project is to (a) get you used to extending code others (I) have written, (b) practice with recursion, and (c) to understand the basic concept of (de)compilation to an intermediate representation. In this project, we will consume ASCII art diagrams

.-----------------------------------------------------------------------------.
 |Es| |F1 |F2 |F3 |F4 |F5 | |F6 |F7 |F8 |F9 |F10|                  C= AMIGA   |
 |__| |___|___|___|___|___| |___|___|___|___|___|                             |
  _____________________________________________     ________    ___________   |
 |~  |! |" |§ |$ |% |& |/ |( |) |= |? |` || |<-|   |Del|Help|  |{ |} |/ |* |  |
 |`__|1_|2_|3_|4_|5_|6_|7_|8_|9_|0_|ß_|´_|\_|__|   |___|____|  |[ |]_|__|__|  |
 |<-  |Q |W |E |R |T |Z |U |I |O |P |Ü |* |   ||               |7 |8 |9 |- |  |
 |->__|__|__|__|__|__|__|__|__|__|__|__|+_|_  ||               |__|__|__|__|  |
 |Ctr|oC|A |S |D |F |G |H |J |K |L |Ö |Ä |^ |<'|               |4 |5 |6 |+ |  |
 |___|_L|__|__|__|__|__|__|__|__|__|__|__|#_|__|       __      |__|__|__|__|  |
 |^    |> |Y |X |C |V |B |N |M |; |: |_ |^     |      |A |     |1 |2 |3 |E |  |
 |_____|<_|__|__|__|__|__|__|__|,_|._|-_|______|    __||_|__   |__|__|__|n |  |
    |Alt|A  |                       |A  |Alt|      |<-|| |->|  |0    |. |t |  |
    |___|___|_______________________|___|___|      |__|V_|__|  |_____|__|e_|  |
                                                                              |
 -----------------------------------------------------------------------------'

We will transform this into an “intermediate representation:” a list of triples, each specifying an X/Y coordinate along with a character to be printed. For example, the following line:

 = . =

Is encoded in our representation as:

'((0 1 #\=) (0 3 #\.) (0 5 #\=))

You will implement a function, draw-ascii-diagram, which subsequently renders these images. We (the instructors) have implemented a function, parse-ascii-diagram, which reads ASCII art pictures into the diagram format. Together, we can think of these as forming a kind of (de)compiler: parse-ascii-diagram reads an ASCII diagram into our internal format and your draw-ascii-diagram then outputs an equivalent photo. In terms of the following diagram, output0 and output1 must be equivalent.

    ASCII Text   ---  displayln --> output0
        |                             ||
parse-ascii-diagram                   ||
        |                            equal?
 draw-ascii-diagram                   ||
        |                             ||
	    +---------- displayln ---> output1

You are encouraged to read the beginning of ascii.rkt for a more precise specification of the input format.

Academic Integrity

The coding on this project is to be completed by you alone, without help from any other students. You are encouraged to discuss the project specification at a high level, but should not discuss specifics or show students your solution code.

Tasks

You will implement three functions; look for each usage of TODO. For many functions, starter code is provided. You are allowed to change the implementation (i.e., throw away) of any starter code if you find it helps you, but the instructors discourage doing so.

  • (draw-ascii-line l) – Given an ASCII line (specified as a list of triples), draw the line. Assume that all characters are on the same line (i.e., ignore the line number). Returns a string, which will be printed by the testing infrastructure. This is very similar to the problem on exercise e0, but you must account for the line number (which you should ignore).

  • (newlines n) – which renders n newlines in a row; returns a string.

  • draw-ascii-diagram – which renders an entire diagram. Your solution should make use of the functions you have previously defined to accomplish its task. Ensure you handle corner cases like empty lines (no testcases will be provided which include trailing newlines)

I would start with newlines, it is the easiest. Then move on to draw-ascii-line. Finally, read the code carefully and implement draw-ascii-diagram. If confused, consult the in-class examples and starter code, and ask clarifying questions on Zulip and in class.

Testing

This project has 6 public tests and 5 secret tests. You are encouraged to test your code piecemeal in the REPL using the public tests. You are also encouraged to create your own tests, or even (hint) use the tests provided in the pictures folder.

Once your code is working correctly, the demo function will work, unlocking the ability to call ascii.rkt directly via the command line to demo your solution. To do this type:

racket ascii.rkt pictures/1.txt

We have included a testing corpus of pictures in pictures/*.txt. You may add more pictures, but do not remove or change the ones in those starter files.

To run the testing infrastructure on your code, use tester.py. It is invoked as follows:

[kmicinski] ascii-art % python3 tester.py
---------------------

Running test: public-draw-0
     PASSED
---------------------
...
---------------------

Running test: secret-draw-line-2
     PASSED

===========================
Summary: 11 / 11 tests passed
===========================

Submitting your code for testing

NOTE Before you can submit your project for grading you must git add, commit, and push. At a terminal (in your project directory) type:

# Add all files in the directory
git add .
# Make a commit
git commit -m "my commit message here"
# Push to server
git push

Once you have done a git commit and push, go to the Autograder and select for your project commit to be graded.

Starter code

For those interested who did not pay for the class (and thus access to the autograder) but want to work the project.

#lang racket
(provide (all-defined-out))

;;
;; CIS352 (Fall '22) Project 1 -- ASCII Art
;; 

;; To see the demo, invoke using:
;;     racket ascii.rkt <ascii-art>.txt

;; READ, DON'T EDIT

;; A point is specified as a list of length three containing an (0)
;; X-column-index, (1) Y-column-index, and (2) character to draw at
;; the specified X/Y coordinate position.
;;
;; For example, '(5 4 #\+) specifies the point at (5,4) in the diagram
;; is #\+.
(define (point-spec? pt)
  (match pt
    ;; points are lists of size three specifying a line, column, and
    ;; character to be printed
    [`(,(? nonnegative-integer? line) ,(? nonnegative-integer? column) ,(? char? char)) #t]
    [_ #f]))

;; a digram is a list of valid point-spec?s
(define (diagram-spec? l)
  (and (list? l) (andmap (lambda (x) (point-spec? x)) l)))

;; Takes a list of strings, returns an (ordered) diagram-spec?
;;
;; The basic idea is to walk over each line using a recursive function
;; which tracks a counter for the line number. Within that function,
;; we split up the string into its constituent characters using
;; string->list and use a *second* recursive walk over each character,
;; tracking the column number.
(define (parse-ascii-diagram lines)
  ;; parse a single line, characters is a list of char?s line-no
  ;; is current line number.
  (define (parse-line characters line-no)
    ;; for each character, chars is a list of char?s, col-no is
    ;; the number of the current column.
    (define (for-each-char chars col-no)
      (cond [(empty? chars) '()] ;; done, return
            [(equal? (first chars) #\space)
             ;; if the char is a space, skip it
             (for-each-char (rest chars) (add1 col-no))]
            [else
             (cons (list line-no col-no (first chars))
                   (for-each-char (rest chars) (add1 col-no)))]))
    (for-each-char characters 0))
  ;; for each line, lines-list is the rest of the lines, line-no is
  ;; current line number.
  (define (for-each-line lines-list line-no)
    (if (empty? lines-list)
        ;; we're done, return
        '()
        (let ([characters (string->list (first lines-list))]
              [rest-lines (rest lines-list)])
          (append (parse-line characters line-no)
                  (for-each-line rest-lines (add1 line-no))))))
  (for-each-line lines 0))

;; YOUR CODE HERE -- EDIT BEYOND THIS

;; helper: generate n spaces
(define (spaces n) (make-string n #\space))

;; Note: this is slightly different than the version in exercise e0,
;; but only superficially, a line number (which you should ignore) is
;; added.
;; 
;; Assume `l` is a (length-3) list of points (defined above) whose
;; first element is a line number, second element is the column
;; position, and third element is the character to be rendered at that
;; line/column pair. You will return a string representing the line.
;; 
;; Assume that all characters are on the same line--another function
;; will wrap this function.
;; 
;; Example:
;;    (draw-ascii-line '((3 3 #\=) (3 4 #\=) (3 5 #\=) (3 7 . #\.)
;;                       (3 9 #\=) (3 10 #\=) (3 11 #\=)))
;; > "   === . ==="
(define (draw-ascii-line l)
  ;; for each point
  (define (h l cur-pos)
    (if (empty? l)
      ""
      ;; else
      (let ([next-index (second (first l))]
            [next-char  (third (first l))]
            [rest-list  (rest l)])
        ;; TODO TODO TODO TODO
        'todo)))
  ;; assume input is sorted ascending on column number
  (h l 0))

;; Given a list of points, return the number of the next line. Assume
;; the list l is nonempty (calling this function on an empty list is
;; an error)
(define (line-number l)
  (first (first l)))

;; This function is implemented for you to use
;; 
;; Given a list of points, grab the next "line" from the input, return
;; (a) the next line and (b) the rest of the input.
(define (grab-next-line points)
  ;; conceptually: recur over l, consing on the next element as long
  ;; as it is on the next line
  (define (h lst)
    (match lst
      ['() (cons '() '())]
      [`(,hd . ,rest) (if (equal? (first hd) (line-number points)) 
                          (let ([rest-ans (h rest)])
                            (cons (cons hd (car rest-ans)) (cdr rest-ans)))
                          (cons '() rest))]))
  (h points))

;; generate a string of n newlines in a row
(define (newlines n)
  ;; TODO TODO TODO TODO
  "todo")

;; Draw a whole ASCII diagram, given as a list of point
;; specifications.
(define (draw-ascii-diagram diagram)
  (define (do-lines lst line-no)
    (if (empty? lst)
        ""
        (let* ([parsed-line (grab-next-line lst)]
           [next-line (car parsed-line)]
           [rest-list (cdr parsed-line)]
           [next-line-no (line-number next-line)])
          ;; TODO TODO TODO TODO
          "todo")))
  (do-lines diagram 0))

;; DO NOT EDIT BELOW HERE 

(define (demo file)
  (define lines (file->lines file))
  (define input (string-join lines "\n"))
  (define diagram (parse-ascii-diagram lines))
  (define answer (draw-ascii-diagram diagram))
  (displayln "The input is:")
  (displayln input)
  (displayln "The decompiled diagram is:")
  (pretty-print diagram)
  (displayln "Rendering this diagram produces the following:")
  (displayln (draw-ascii-diagram diagram))) 

(define file
  (command-line
   #:program "ascii.rkt"
   #:args ([filename ""])
   filename))

;; if called with a single argument, this racket program will execute
;; the demo.
(if (not (equal? file "")) (demo file) (void))