0

I'm trying to get a simple tic-tac-toe program to function in the console as a way to make sure I understand loops and arrays.

It compiles, and runs as expected, with the exception that if a user inputs something that isn't a number the program races through the first if statement infinitely without a chance to add a new input. I really can't see how to fix this.

I think the issue is that chosenSquare is an integer as it needs to be compared to values, but cin can take anything in. Expected behaviour would be to check if the input is an integer between 0 and 8 (the 9 spaces on the board), and if not return a message and repeat, waiting for a new input.

Is there a simple fix for this? I'm trying to avoid specialist packages and namespaces for now while I grok the basics. I've looked at similar problems but don't follow them.

Thanks.

Code snippet:

 // Input loop
    bool valid = false;
    while (valid != true)
    {
        int chosenSquare = 0;
        cout << "Player " << currentPlayer << ", enter a number between 1 and 9:" << endl;
        cin >> chosenSquare;
        chosenSquare--; // For array indexing

        if ((chosenSquare < 0) || (chosenSquare > 8)) // <--- PROBLEM IS THIS LOOP
        {
            cout << "Invalid input. Try again." << endl;
            continue;
        }
        else if ((board[chosenSquare] == currentPlayer) || (board[chosenSquare] == lastPlayer))
        {
            cout << "Square not availible. Try again." << endl;
            continue;
        }
        else
        {
            board[chosenSquare] = currentPlayer;
            valid = true;
            break;
        }
    }
Willow
  • 25
  • 6

2 Answers2

0

There are a couple of things culminating causing this.

The first is that when an alpha character is put into the console, the error bit is set, and 0 is written to the variable you're writing to:

The behavior you want to observe changed in 2011. Until then:

If extraction fails (e.g. if a letter was entered where a digit is expected), value is left unmodified and failbit is set.

But since C++11:

If extraction fails, zero is written to value and failbit is set. [...]

(From cppr.)

That means chosenSquare is 0 after the read, so chosenSquare-- makes it -1. -1, as you know, is less than 0, so the first if-statement is true.

As to why it stays this way infinitely, you need to clear the fail-bit.

  • Thanks for that, that explains the behaviour. I'm still trying to get my head around streams. I managed to fix the code (answer is below) but I don't actually underrstand *what* fixed it. as far as I can tell `cin.fail()` is a test for a failbit in the stream, which seems to fire even though it's -1, not 0, at that point. And it seems I needed both `clear()` and `ignore()`, but it's unclear why. Edit: so failbit is independent of the content of the stream (or a variable of it?), that makes more sense. – Willow Apr 16 '20 at 23:39
  • "so failbit is independent of the content of the stream" I believe that is correct. "that makes more sense." Yeah, streams can take a while to grasp. I'm still trying to get the hang of all the ins and outs of them. –  Apr 17 '20 at 00:00
0

So I changed the first loop after a bit of reading and experiementation:

if (cin.fail()) // <--- PROBLEM IS THIS LOOP
        {
            cout << "Invalid input. Try again." << endl;
            cin.clear();
            cin.ignore();
            continue;
        }

This works, but I can't figure out what it's actually doing.

Could anyone elaborate?

Willow
  • 25
  • 6
  • You want to be able to read whatever input the user provides, whether it's a number of not, and then process it to see if it's valid. But that's not what your code does. It just reads an integer. If you want to read a line and then analyze it to see if it's a valid number, write code that does that. It's simpler and clearer. – David Schwartz Apr 16 '20 at 23:42
  • @DavidSchwartz that's what I'm trying to do? Still learning here. Far as I can tell `fail() == true` means the input wasn't an integer, so I can fire off the warning. `clear()` removes the failbit (NOT empty the stream, as I thought it would) and `ignore()` strips out whatever's still in stream so it doesn't get caught in an infinite loop. Is this the case? This is really hard to get my head around. I know it's inelegant but it's literally the second program I've tried to make. – Willow Apr 17 '20 at 00:05
  • *`ignore()` strips out whatever's still in stream* Not quite right. `ignore()` removes exactly one character. What you likely want is to remove everything up to where the user pressed enter. There is an example of how to do that in [this documentation page for `ignore`](https://en.cppreference.com/w/cpp/io/basic_istream/ignore). C++ is a rough language to learn by trial and error. Read the documentation before using a function and you'll have much easier go of it. That said, follow David's advice about reading the input as a more forgiving type, like a `std::string`, validate, and convert. – user4581301 Apr 17 '20 at 00:10
  • @Willow Yeah, but you're trying to work around having written code that doesn't do what you want to do. It's much easier to just write code that does what you actually want to do in the first place. If you want to read a line of input and parse it to see if it's valid, writing code that specifically does that is going to be much easier and clearer. Complex functions are great when they do exactly what you want, but you should roll your own from slightly simpler functions if they aren't really what you want. – David Schwartz Apr 17 '20 at 00:12