Saturday, March 9, 2013

Chess in C (Part 4) - I'm asking for input

Input validation is extremely important. Whether it's a C application or a public-facing website, if you're taking input from a user, you are giving that user an invitation to input garbage. If the user is required to input their name, they'll input their phone number. If required to choose between 1 to 3, they'll enter negative 42. If required to select Yes or No, they'll find a way of typing in their cat's name. And that's if you're lucky.

If you're unlucky, it'll be an attacker trying to overflow your input buffer to break your application so they can steal your password database. But if you're lucky, it'll just be your tutor/lecturer/coworker trying to teach you a lesson. One of my C assignments at university involved writing code to parse command line arguments. The lecturer had warned us that we should treat all user input as hostile, and that our code would be tested for invalid user input. I believed I had thought of every category of invalid data and asked the the tutor to confirm my code was perfect. He looked at my code then asked, "What happens if you specify an argument and leave it blank?" My response was along the lines of...


This post is part three of the Chess in C series and covers input validation. This series is for beginners. If you know how to iterate through two dimensional arrays and write basic algorithms, this isn't the blog post you're looking for! Unless you want to feel smug and superior.

Chess in C (Part 1)
Chess in C (Part 2) - Insert Pawn Pun Here
Chess in C (Part 3) - Rook, Rooks, Rookies, Wookies, same thing
Chess in C (Part 4) - I'm asking for input
Chess in C (Part 5) - Potential moves of a bishop: up-left, cardinal, pope

So last time I left you, I challenged you to take input from the user then draw the position of the rook. I'll explain the answer, then I'll post some working code. To begin, here's a reminder of the possible moves of a rook. In the following diagram, the rook is in position (3,4).

A representation of an array.
NOT a Cartesian plane. Do NOT draw this in a maths exams.
You might think, "What the heck? Why isn't (0,0) in the bottom left-hand corner like my maths textbooks?" Remember, this diagram is not a Cartesian plane. It's a representation of a two-dimension array in C that happens to look like a chess board. And the top array value is (0,0), because in C we start counting at zero.

Okay, so you want to take input from the user? Ask for it! Here's one way of doing it.

There are lots of ways to get input from the user. Asking a programmer the best way of getting string input from a user is like asking which car is the best. You'll get a long winded answer and if you're unlucky, everyone in earshot will hear the question and give you their opinion as well. The method I've written does a simple check to see whether it's a number, check whether the user didn't just press enter (which would appear as a '\n' in position zero of a character array) and some simple bounds checking. It's a quick and dirty way of getting a row and column number, and you'll see why it's dirty later. Don't reuse this code if you're writing the user interface to a insulin pump or a medical device.

Now, let's glue it all together. Copy and paste the following code into your IDE and compile it.

Let's run it with some inputs and see what we get...

Seems legit to me.

It's always good to do "bounds testing". Test the maximum column value allowed. 
Test the maximum row.

Test the maximum row and column.

Test one over and one under the bounds.
Hey, just wait...we're getting some strange output here!

Test one invalid input and one valid input.
Strange input, again!
Test some letters.
...and remove the pickle.
I pressed enter without typing anything.
You'd be surprised how many students programs break
when you just press enter at prompts.

It seems like the code works, but invalid input can result in strange results! How do we avoid a situation where invalid output is displayed? A few points:

  • If the user answers the first question (for the row number) incorrectly, you won't be able to draw the board correctly so there's no point in asking the second question.
  • If the user answers the first question correctly but the second question incorrectly, there isn't drawing the board.
How best to implement this decision tree? Well, the easiest way is to nest the code for the second question in the code path executed when the user answers the first question correctly. This method would be both straightforward and easy but horrible to maintain. The decision tree is simple today, but what if you asked a third question? Or a fourth? You could have if statements nested in if statements nested in if statements! I know Chess in C is just an introductory level blog series, but I don't want you to get into the habit of writing unmaintainable code!

The questions we need to ask and the target state.
But how do we implement this?
A more maintainable way to handle this situation would be to use an additional variable for flow control. You could create an extra variable called badAnswer which is initially set to 0. If either of the questions were answered incorrectly, badAnswer would be set to 1. You could then wrap an if/then statement around any code you only wanted to execute if the answers given weren't bad. This would stop the second question from being asked if the answer to the first question was bad, and it would stop the board from being drawn if the answer to the second question was bad. Here's the code. Copy and paste and experiment.

Now let's retry the tests that broke the code.

Seems ok this time.

Also seems ok this time.

Also also seems ok this time.

No cheese burger for you!
I pressed enter when prompted. It works!

Great! We've rewritten the code so that if incorrect inputs are given, the program will close gracefully without showing a board that looks like gibberish.

No comments:

Post a Comment