/* shape.cpp - CSE2050 homework 7. Matt Mahoney, mmahoney@cs.fit.edu ANALYSIS This program reads a series of graphics commands from standard input and writes a drawing using ASCII art to standard output. Commands have the following forms: POINT x y - draws a "+" at coordinates (x, y), where x and y are integer coordinates, x=0 is the left edge of the screen, increasing to the right, and y=0 is the bottom of the image, increasing upwards. For example, shape point 4 2 Point 0 0 POINT 2 1 ^D (UNIX end of file character, ^Z in Windows) + (output point at 4, 2) + (output point at 2, 1) + (output point at 0, 0) HLINE x y w - Draws a line from (x, y) to (x+w, y) consisting of w+1 "-" characters. For example: shape point 0 0 hline 1 1 3 ^D ---- (line from 1,1 to 4,1) + VLINE x y h - Draws a line from (x, y) to (x, y+h) consisting of h+1 "|" characters. For example: shape point 0 0 vline 1 1 3 ^D | (line ends at 1,4) | | | (line starts at 1,1) + LINE x y w1 h1 w2 h2 ... - draws a series of line segments starting at (x, y), then going right by w1, up by h1, right by w2, up by h2, etc. The w and h values may be negative to go left or down, respectively. Line segments are drawn using "-" across or "|" vertically, and are connected by "+" where consecutive nonzero length line segments intersect. For example: shape point 0 0 line 1 1 0 2 3 -2 ^D +--+ | | | | (start at 1,1, end at 4,1) + BOX x y w h - draws a box with lower left corner at (x, y) and upper right corner at (x+w, y+h). The box is drawn with "+" at the corners, "-" along the top and bottom edges, "|" along the left and right edges, and is filled with spaces. For example: shape point 0 0 box 1 2 3 4 ^D +--+ (upper right corner is 4,6) | | | | | | +--+ (lower left corner is 1,2) + LABEL x y text - Prints the text starting with the first printable (non space) character at (x, y), to the end of the input line. For example: shape point 0 0 label 2 1 Hello World! ^D Hello World! + If shapes overlap, then the first command takes precedence. For example: shape hline 0 1 2 vline 1 0 2 ^D | --- (hline is on top) | shape box 4 0 2 2 label 0 1 abcdefghij ^D +-+ abcd| |hij (box is on top, including interior spaces) +-+ Numeric coordinates are unbounded, but will be clipped on the lower left at (0,0). shape point 0 0 box -1 -2 3 4 ^D --+ | + | The keywords POINT, HLINE, VLINE, LINE, BOX, and LABEL are not case sensitive, and must be the first word on the input line except for leading white space. If the first word is not one of these keywords, the line is ignored without warning. shape foo bar ^D (no output) Parameters (except label text) are integers consisting of the digits 0-9 with a possible leading - sign. If there are any other characters, then it and the following characters are ignored. If there are no valid digits, then the value is taken as 0. Any extra parameters are ignored. Any missing parameters are taken as 0. For example: shape Point 3.2 foo 4 pOiNt 5-7 POINT ^D + + + (assumed 3,0; 5,0; and 0,0) The output is generated by printing as few characters as possible on each line, but printing all lines from the maximum y coordinate down to y = 0, plus an extra blank line at the beginning. shape point 2 2 point 1 1 ^D (newline) + (y=2: space space "+" newline) + (y=1: space "+" newline) (y=0: newline) DESIGN This program reads the input file and builds a list of shapes, one for each command. Then the shapes are drawn to a buffer (a 2 dimensional array of char) in the reverse order from which they were read. Then the buffer is printed to the screen. The list of shapes is implemented as a vector, where Shape is the abstract base class of the graphical elements as follows: Shape / \ Label Line / | \ Hline Vline Box | Point To compile: g++ shape.cpp */ #include #include #include #include #include using namespace std; // nextword(s) removes and returns the first word of string s as lower case, // removing any surrounding white space. For example, if s=" Hi There " // then nextword(s) sets s="There " and returns "hi". string nextword(string& s) { while (s.size()>0 && isspace(s[0])) // trim leading spaces s=s.substr(1); int i; for (i=0; i > data; // 2-D array: data[y][x] stores line y, col x public: void put(int x, int y, char c); // write c to data void print(ostream& out) const; // write data to out }; // store c at data[y][x], growing as needed. Discard if x<0 or y<0. void Buffer::put(int x, int y, char c) { if (x<0 || y<0) return; if (y>=int(data.size())) data.resize(y+1); if (x>=int(data[y].size())) data[y].resize(x+1); data[y][x]=c; } // Print data to out, last row first void Buffer::print(ostream& out) const { for (int y=data.size()-1; y>=0; --y) { for (int x=0; x param; // Numeric parameters: x y h1 v1 h2 v2 ... public: Line(string s); // s = "x y h1 v1 h2 v2 ..." void draw(Buffer&) const; }; Line::Line(string s) { while (s!="") param.push_back(nextint(s)); } void Line::draw(Buffer& b) const { if (param.size()<2) return; int x=param[0]; // Current drawing location int y=param[1]; int j=0; // position in current line segment for (int i=2; i2 && param[i-1]!=0) { // draw corner '+' b.put(x, y, '+'); j=1; } // draw the rest of the segment if (param[i]>0) { // up or right for (; j<=param[i]; ++j) { if (i%2) // up b.put(x, y+j, '|'); else // right b.put(x+j, y, '-'); } } else if (param[i]<0) { // down or left for (; j<=-param[i]; ++j) { if (i%2) // down b.put(x, y-j, '|'); else b.put(x-j, y, '-'); } } if (i%2) // go to next segment y+=param[i]; else x+=param[i]; } } // An Hline is a Line with only one horizontal segment class Hline: public Line { public: Hline(const string& s): Line(s) { // s = "x y h", discard any extra param.resize(3); // just in case of input error } }; // A Vline is a Line with only one vertical segment class Vline: public Line { public: Vline(const string& s): Line(s) { // s = "x y v", replace with "x y 0 v" param.resize(4); param[3]=param[2]; // insert 0 before v param[2]=0; } }; // a Box x y w h is a box at x, y with width w, height h class Box: public Line { public: Box(const string& s): Line(s) { // s = "x y w h", store them param.resize(4); // just in case } void draw(Buffer&) const; // but draw a Box, not a Line }; void Box::draw(Buffer& b) const { for (int x=param[2]; x>=0; --x) { for (int y=param[3]; y>=0; --y) { char c; // what to draw if ((x==0 || x==param[2]) && (y==0 || y==param[3])) // corner c='+'; else if (x==0 || x==param[2]) // side c='|'; else if (y==0 || y==param[3]) // top or bottom c='-'; else // interior c=' '; b.put(param[0]+x, param[1]+y, c); } } } // A Point is a Box with size 0 class Point: public Box { public: Point(const string& s): Box(s) { // s = "x y" param[2]=param[3]=0; // width and heigth } }; // A Label is a Shape containing text at x, y class Label: public Shape { private: string text; int x, y; public: Label(string s) { // s = "x y text" x=nextint(s); y=nextint(s); text=s; } void draw(Buffer& b) const { for (int i=text.size()-1; i>=0; --i) b.put(x+i, y, text[i]); } }; int main() { // Read the input and store the shapes vector shapes; // shapes[i] points to Shape for i'th command string s; // input line while (getline(cin, s)) { string keyword=nextword(s); if (keyword=="point") shapes.push_back(new Point(s)); else if (keyword=="line") shapes.push_back(new Line(s)); else if (keyword=="hline") shapes.push_back(new Hline(s)); else if (keyword=="vline") shapes.push_back(new Vline(s)); else if (keyword=="box") shapes.push_back(new Box(s)); else if (keyword=="label") shapes.push_back(new Label(s)); } // Draw and delete the shapes in reverse order Buffer b; for (int i=shapes.size()-1; i>=0; --i) { shapes[i]->draw(b); delete shapes[i]; } cout << '\n' << b; return 0; }