1

So I just need some guidance as to how I could go about fixing this program, So I need to read a file titled "infile.txt" and inside the file are the instructions that describe the shape that should be drawn (A single uppercase char, namely R,T,D,S,E) then it gives the char that the shape should be filled with then the number of columns and rows in an int. The txt file looks like this:

T & 4
S @ 6
T x 5
R * 5 7
D $ 7
D + 5
R = 4 3
E

Now, I'm not even quite sure if I can even accomplish this using a switch statement since the infile is read as a string. But then I'm confused as to how to even change from a switch statement. Anyways, after the infile is read I have to output the shape that is drawn to an outfile. Hopefully this makes sense, I'm super entry level and barely know what I'm doing. So here's my code:

#include <iostream>
#include <fstream>
#include <cstdlib>
using namespace std;


void draw_rect (char out_char, int rows, int columns); // Draws a rectangle shape

void draw_square (char out_char, int rows); //Draws a square shape

void draw_triangle (char out_char, int rows);// Draws a triangle shape

void draw_diamond (char out_char, int rows); // Draws a diamond shape

//void dimension_instructions(char value);

int main()
{
    ofstream outfile;
    ifstream infile;
    int row, col;
    bool exit = false;
    char value;
    char code;
    infile.open("infile.txt");
    outfile.open("outfile.txt");
    if(!infile.good())
    {
        cout << "failed to open\n";
    }else
    {
        string buffer;
        while(!infile.eof())
        {
            getline(infile, buffer);
            cout << buffer << endl;
        }

        while(!exit)
        {
            cout << "Enter your shape R for rectangle, T for triangle, D for diamond, S for square, and E to exit" << endl;
            cin >> code;
            switch(code)
            {
            case 'R':
               dimension_instructions(code);
               cin >> value >> row >> col;
               draw_rect(value, row, col);
               break;
            case 'T':
                dimension_instructions(code);
                cin >> value >> row;
                draw_triangle(value, row);
                break;
            case 'D':
                dimension_instructions(code);
                cin >> value >> row;
                draw_diamond(value, row);
                break;
            case 'S':
                dimension_instructions(code);
                cin >> value >> row;
                draw_square(value, row);
                break;
            case 'E':
                cout << "Exiting";
                exit = true;
                break;
            default:
                cout << "Invalid input, try again" << endl;
            }
        }
     infile.close();
    }
    outfile.close();

    return  0;

}

/*void dimension_instructions(char value)
{
    if (value == 'R')
    {
        cout << "Enter your character rows and columns values." << endl;
    }else
    {
        cout << "Enter your character and row values" << endl;
    }
}*/

void draw_diamond (char out_char, int rows)
{
    int space = 1;
    space = rows - 1;
    for (int i = 1; i <= rows; i++)
    {
        for (int k = 1; k <= space; k++)
        {
            cout << " ";
        }
        space--;
        for( int k = 1; k <= 2*i-1; k++)
        {
            cout << out_char;
        }
        cout << endl;
    }
    space = 1;
    for (int i = 1; i <= rows; i++)
    {
       for(int k = 1; k <= space; k++)
       {
           cout << " ";
       }
       space++;
       for(int k = 1; k <= 2*(rows-i)-1; k++)
       {
           cout << out_char;
       }
       cout << endl;
    }
}

void draw_triangle (char out_char, int rows)
{
     for (int i = 0; i < rows; i++)
    {
        for (int j = 0; j <= i; j++)
        {
            cout << out_char;
        }
        cout << endl;
}
}

void draw_square (char out_char, int rows)
{
    for (int i = 0; i < rows; i++)
    {
        for (int i = 0; i < rows; i++)
        {
            cout << out_char;
        }
        cout << endl;
    }
}

void draw_rect (char out_char, int rows, int columns)
{
    for (int i = 0; i < rows; i++)
    {
        for (int i = 0; i < columns; i++)
        {
            cout << out_char;
        }
        cout << endl;
    }
}
northernSage
  • 1,264
  • 1
  • 6
  • 18
Aux1e
  • 21
  • 3
  • Why are you reading `while(!infile.eof())` to the end and then using `cin >>` in your `switch()`? Also see: [Why !.eof() inside a loop condition is always wrong.](https://stackoverflow.com/q/5605125/9254539) You will want to review [Why is iostream::eof inside a loop condition considered wrong?](https://stackoverflow.com/questions/5605125/why-is-iostreameof-inside-a-loop-condition-considered-wrong) – David C. Rankin Mar 14 '19 at 02:25
  • I was using cin originally to test to make sure all my functions work manually, I guess I'm trying to "convert" from user input to file input and I haven't converted all the code. – Aux1e Mar 14 '19 at 02:28

1 Answers1

1

Ok, based on your comment I see where you are stuck and why. (also you need to fix your loop variables in draw_square and draw_rect if you haven't already done it).

Your primary problem is not understanding how to handle a varying number of inputs per-line. When you are faced with this problem, you have correctly chosen getline to read each line into buffer, but what then? That is where stringstream makes all the difference.

Why? Two reasons, (1) it allows you to parse the contents of buffer word-by-word with basic iostream >> and (2) it, when needed, allows you to loop until end-of-stream reading as many (or as few) tokens as are present, stopping when you reach the end of the line (which isn't possible using >> on the file-stream itself as >> consumes whitespace and will happily skip right over each '\n')

With that out of your way, your code really needed just a bit of refactoring (a fancy word for fixing the jumbled logic).

To start Don't Hardcode Filenames or use magic-numbers in your code. Use the arguments to main() to pass filenames to your program and declare constants where needed. Also avoid using char which will not consume leading whitespace. cin >> a_char; is just as happy reading a ' ' (space) as it is reading something else.

Also scope your variables appropriately. You don't need all your variables declared so they are visible throughout main(). Declare/define them in their proper scopes.

For example:

...
#include <sstream>
...
int main (int argc, char **argv) {  /* don't hardcode filenames */

    ifstream infile;    /* infile and buffer are the only variables */
    string buffer;      /* that need to be scoped at main() */

When passing a filename as an argument, just validate your user provided a filename or provide him with usage information before bailing.

    if (argc < 2) {     /* validate at least 1 argument is provided */
        cerr << "error: insufficient input.\n"
                "usage: " << argv[0] << " filename.\n";
        return 1;
    }

You have your argument, now validate you open your file for reading:

    infile.open (argv[1]);  /* open filename provided as 1st argument */

    if(!infile.good()) {    /* validate file is open for reading */
        cerr << "failed to open infile\n";
        return 1;
    }

Now the important changes to how you control your read loop. getline provides all you need. Simply loop while getline provides good input to buffer, e.g.

    while (getline(infile, buffer)) {   /* loop reading each line */
        int row, col;               /* remaining variables scoped inside */
        string value, code;         /* your read loop, use strings */

        stringstream ss(buffer);    /* create stringstream from buffer */

Now you are reading each line and you have created a stringstream from buffer to parse your characters from -- except note how value, code are declared as string and not char -- that provides a simple way to skip leading whitespace only reading non-whitespace characters. Then you can simply access the character you need with, e.g. value[0].

Validate you have a good read of code

        if (!(ss >> code)) {        /* validate code read into string */
            cerr << "error: ss >> code.\n";
            break;
        }

Then it is just a matter of repeating the same validation of the read of needed data a calling the correct function in each switch() case:, e.g.

        switch (code[0])    /* switch on 1st char of code */
        {
            case 'R':
                if ((ss >> value >> row >> col))    /* validate read */
                    draw_rect (value[0], row, col); /* draw rect */
                else    /* or handle error */
                    cerr << "error: 'R' invalid format '" << buffer << "'\n'";
                break;
            case 'T':
                if ((ss >> value >> row))   /* ditto for rest of shapes */
                    draw_triangle(value[0], row);
                else
                    cerr << "error: 'T' invalid format '" << buffer << "'\n'";
                break;
            case 'D':
                if ((ss >> value >> row))
                    draw_diamond(value[0], row);
                else
                    cerr << "error: 'D' invalid format '" << buffer << "'\n'";
                break;
            case 'S':
                if ((ss >> value >> row))
                    draw_square(value[0], row);
                else
                    cerr << "error: 'S' invalid format '" << buffer << "'\n'";
                break;
            case 'E':
                cout << "Exiting\n";
                goto exitE;     /* goto to break nested loops / scopes */
                break;
            default:
                cout << "Invalid input, try again" << endl;
        }
    }
    exitE:;     /* the lowly goto provides a simple exit */

That's it aside from closing infile (which will happen automatically, but doesn't hurt to manually show your consideration of the close).

Note however the use of goto rather than the flag you had for exit. While goto does not get much press, it has one invaluable purpose that remains -- the ability to break out of nested loops and scopes cleanly. Do not use it to jump out of functions (the longjmp is the technical limitation), but it can greatly simplify your logic for breaking nested loops and jumping a few lines down. (it is also useful in the same setting to jump past code normally executed at the end of your loops on an error condition)

So understand its uses. You are free to use a flag, but you may find goto cleaner in a number of settings.

With that, you can put it altogether (ignoring outfile for now) with something similar to:

#include <iostream>
#include <fstream>
#include <sstream>
#include <cstdlib>

using namespace std;

void draw_rect (char out_char, int rows, int columns); // Draws a rectangle shape
void draw_square (char out_char, int rows); //Draws a square shape
void draw_triangle (char out_char, int rows);// Draws a triangle shape
void draw_diamond (char out_char, int rows); // Draws a diamond shape

int main (int argc, char **argv) {  /* don't hardcode filenames */

    ifstream infile;    /* infile and buffer are the only variables */
    string buffer;      /* that need to be scoped at main() */

    if (argc < 2) {     /* validate at least 1 argument is provided */
        cerr << "error: insufficient input.\n"
                "usage: " << argv[0] << " filename.\n";
        return 1;
    }
    infile.open (argv[1]);  /* open filename provided as 1st argument */

    if(!infile.good()) {    /* validate file is open for reading */
        cerr << "failed to open infile\n";
        return 1;
    }

    while (getline(infile, buffer)) {   /* loop reading each line */
        int row, col;               /* remaining variables scoped inside */
        string value, code;         /* your read loop, use strings */

        stringstream ss(buffer);    /* create stringstream from buffer */

        if (!(ss >> code)) {        /* validate code read into string */
            cerr << "error: ss >> code.\n";
            break;
        }

        switch (code[0])    /* switch on 1st char of code */
        {
            case 'R':
                if ((ss >> value >> row >> col))    /* validate read */
                    draw_rect (value[0], row, col); /* draw rect */
                else    /* or handle error */
                    cerr << "error: 'R' invalid format '" << buffer << "'\n'";
                break;
            case 'T':
                if ((ss >> value >> row))   /* ditto for rest of shapes */
                    draw_triangle(value[0], row);
                else
                    cerr << "error: 'T' invalid format '" << buffer << "'\n'";
                break;
            case 'D':
                if ((ss >> value >> row))
                    draw_diamond(value[0], row);
                else
                    cerr << "error: 'D' invalid format '" << buffer << "'\n'";
                break;
            case 'S':
                if ((ss >> value >> row))
                    draw_square(value[0], row);
                else
                    cerr << "error: 'S' invalid format '" << buffer << "'\n'";
                break;
            case 'E':
                cout << "Exiting\n";
                goto exitE;     /* goto to break nested loops / scopes */
                break;
            default:
                cout << "Invalid input, try again" << endl;
        }
    }
    exitE:;     /* the lowly goto provides a simple exit */

    infile.close();

    return  0;

}

void draw_diamond (char out_char, int rows)
{
    int space = 1;
    space = rows - 1;
    for (int i = 1; i <= rows; i++)
    {
        for (int k = 1; k <= space; k++)
        {
            cout << " ";
        }
        space--;
        for( int k = 1; k <= 2*i-1; k++)
        {
            cout << out_char;
        }
        cout << endl;
    }
    space = 1;
    for (int i = 1; i <= rows; i++)
    {
    for(int k = 1; k <= space; k++)
    {
        cout << " ";
    }
    space++;
    for(int k = 1; k <= 2*(rows-i)-1; k++)
    {
        cout << out_char;
    }
    cout << endl;
    }
}

void draw_triangle (char out_char, int rows)
{
    for (int i = 0; i < rows; i++)
    {
        for (int j = 0; j <= i; j++)
        {
            cout << out_char;
        }
        cout << endl;
    }
}

void draw_square (char out_char, int rows)
{
    for (int i = 0; i < rows; i++)
    {
        for (int j = 0; j < rows; j++)
        {
            cout << out_char;
        }
        cout << endl;
    }
}

void draw_rect (char out_char, int rows, int columns)
{
    for (int i = 0; i < rows; i++)
    {
        for (int j = 0; j < columns; j++)
        {
            cout << out_char;
        }
        cout << endl;
    }
}

(note: the loop variable i, j fixes to draw_square and draw_rect which appeared to be copy/paste errors -- beyond that, no changes were made to your shape functions)

Example Use/Output

$ ./bin/drawshapes dat/drawshapes.txt
&
&&
&&&
&&&&
@@@@@@
@@@@@@
@@@@@@
@@@@@@
@@@@@@
@@@@@@
x
xx
xxx
xxxx
xxxxx
*******
*******
*******
*******
*******
      $
     $$$
    $$$$$
   $$$$$$$
  $$$$$$$$$
 $$$$$$$$$$$
$$$$$$$$$$$$$
 $$$$$$$$$$$
  $$$$$$$$$
   $$$$$$$
    $$$$$
     $$$
      $

    +
   +++
  +++++
 +++++++
+++++++++
 +++++++
  +++++
   +++
    +

===
===
===
===
Exiting

Look things over and let me know if you have further questions.

David C. Rankin
  • 69,681
  • 6
  • 44
  • 72
  • Ok, thanks for the extensive write up. Is there anyway I could accomplish this in a simpler way? I don't need to do any verification and to be frank I'm not skilled enough to understand half of the things you're describing in your post. I've never used arg or arrays or anything like that so I'm not familiar with them at all. To give perspective this is an assignment for an intro c++ college class. Regardless thank you for the help, I've definitely learned some things from your post! Hardcoded filenames are also required for the assignment unfortunately, that's why they are there. – Aux1e Mar 14 '19 at 04:57
  • Well.... actually you do. Validation protects against *Undefined Behavior*. True, you don't have to validate, but building solid skills now will pays dividends down the road. The use of `argc` (argument count) and `argv` (argument vector) is simply the right way to get the filename into your program (`argc` is just the count of the arguments past on the command line - and `argv` is just an array of pointers to them - `argv[0]` is always the program name, so `argv[1]` is the 1st user argument). I'd rather try and answer your questions than tell you how to just hack it together. – David C. Rankin Mar 14 '19 at 05:07
  • The `stringstream` is just a way to take what you read with `getline` and be able to `>>` the individual words out of `buffer`. Where you had `cin >> value >> row >> col` I simply have `if ((ss >> value >> row >> col))` to make sure `value, row, col` were all read before proceeding further. There really is nothing beyond into-level stuff in the code. (and I know it feels like you are "Drinking from a Fire-Hose" trying to digest it all, but take it step by step -- it will start to fall into place -- and you will be the better programmer for it) – David C. Rankin Mar 14 '19 at 05:11
  • And you can declare the variable any where you like, but by thinking about what set of `{ .... }` the variables are needed in (the scope), you can avoid having to re-set the variables on each loop and you avoid the potential for having, e.g. two `i` variables declared in different locations. But do this, if you want to strip things down, create a copy of the file, remove the `argc/argv` and the checks and put `"infile.txt"` back in and then compare both files side-by-side, I think you will start to see there is no magic, just a more proper way of doing the same thing. Good luck. – David C. Rankin Mar 14 '19 at 05:18
  • Lastly, keep [cppreference.com](https://en.cppreference.com/w/) handy. Any question (e.g. "What does this do?") can be found there. Other than the C++ standard itself -- that is probably the best reference going. Spend an hour picking around in it. A good way is probably to just start with the "Header Files" link and see what functions are hidden where. The look at the general topics. It's really quite good, a bit cryptic at first, but that fades soon too. – David C. Rankin Mar 14 '19 at 05:21
  • Just to clarify, when I copy and paste your code every time I run it I get back `error:insufficient input` Am I doing something wrong? – Aux1e Mar 14 '19 at 05:47
  • @Aux1e Yes, you need to run it as `programname filename_to_read`. I'm sorry I thought that was clear. (where `filename_to_Read` is your data file with the instructions for triangle, square, etc.. in it) – David C. Rankin Mar 14 '19 at 06:21