views:

113

answers:

4

Hello everyone,

I am writing a text-based Scrabble implementation for a college project.

The specification states that the user's position input must be read from single line, like this:

Coordinates of the word's first letter and orientation (<A – P> <1 – 15> <H ou V>): G 5 H 

G 5 H is the user's input for that particular example. The order, as shown, must be char int char.

What is the best way to read the user's input?

cin >> row >> column >> orientation will cause crashes if the user screws up.

A getline and a subsequent string parser are a valid solution, but represent a bit of work.

Is there another, better, way to do this, that I am missing?

Thanks for your time!

+2  A: 

Sorry, but getline and parsing the string are your best bet. However, you can make your system a little bit more reusable by creating a class to represent the input options and then overloading operator>> so that it uses getline and parses the string. That way, you don't have to repeat any of your parsing code.

Michael Aaron Safyan
I see :( I'll wait for some more input and then I'll have a go at it.
Francisco P.
+2  A: 

getline and parsing doesn't necessarily have to add much work. Since you already know how to read (correct) data from a stream, just read a line with getline, then create an istringstream from the line and read from there.

The one thing I'd add would be that it might very well make sense to create a class to hold the data for a particular move, and overload operator>> for that class to read data for a move. A rough sketch would be something like this:

class move { 
    char row;
    int column;
    char orientation;
public:
    friend std::istream &operator>>(std::istream &is, move &m);
};

std::istream &operator>>(std::istream &is, move &m) { 
    std::string temp;
    std::getline(is, temp);
    std::istringstream buffer(temp);

    // Now we can parse data from buffer just like we could from another stream.
    return is;
}

At least for the moment, I haven't included any error handling code. Depending on how picky you want to be, this can get a little tricky (e.g., if input from the stringstream fails, setting the fail bit in the original input stream).

Jerry Coffin
+1 This would allow you to save moves and 'undo' them.
Thomas Matthews
A: 

Another suggestion is to input from the console, using one item at a time and apply an error checking:

char row;
bool is_valid = false;
while (!is_valid)
{
   while (!(cin >> row))
   {
      cout << "Error reading row, please enter data again.\n";
   }

   row = toupper(row);
   static const std::string  valid_rows("ABCDEFGHIJKLMNO");
   is_valid = valid_rows.find(row) != std::string::npos;
   if (!is_valid)
   {
      cout << 'Row ' << row << ' is not a valid row letter, please re-enter.\n";
   }
}

By reading one variable at a time, rather than all three at once, you can give the user earlier warning about error detection.

Thomas Matthews
+1  A: 

I got something like this:

#include <iostream>
#include <limits>
#include <string>

using namespace std;

template<class T> T getValue(const string& name)
{
        T ret;
        while(!(cin >> ret))
        { 
                // normally here you'd go into an infinite loop, but since you're going to ignore the rest of the line, you can ensure that you won't go into an infinite loop and you can re-ask the user to input the correct data
                cout << "Invalid input for " << name << " please try again" << endl;
                cin.clear();
                cin.ignore(numeric_limits<streamsize>::max(), '\n');
        }
        return ret;
}

int main(void)
{
        bool valid = false;
        char row, orientation;
        int column;

        do {
                cout << "Enter row, column, and orientation (<A-P> <1-15> <H to V>): " << endl;
                row = getValue<char>("row");
                column = getValue<int>("column");
                orientation = getValue<char>("orientation");

                if(row<'A' || row>'P')
                        cout << "Invalid row please enter A-P" << endl;
                else if(column<1 || column>15)
                        cout << "Invalid column please enter 1-15" << endl;
                else if(orientation<'H' || orientation>'V')
                        cout << "Invalid orientation please enter H-V" << endl;
                else
                        valid = true;
        } while(!valid);

        cout << "Row: " << row << endl
             << "Column: " << column << endl
             << "Orientation: " << orientation << endl;

        return 0;
}

Of course, if you enter something invalid like:

A B C

It would produce some potentially confusing problems. The first A would be successfully copied in row char variable. However since B is not numerical, it would ignore the remaining buffer, so you lose B and C. You get an error message that you entered an invalid input for column, but once you successfully enter a valid number, you would still have to enter a orientation again. So the user isn't clear on that point based on this application. You can make such modifications easily such that if you enter an invalid input, it would reask you to enter the whole thing.

Daniel