I feel like I have duplicated code in some places (with only minor changes being the difference), refining them down a bit would be nice.

Better parsing of input

But any and all suggestions are acceptable. If you are interesting in looking at some occasionally updated versions of this code, take a look the the Github repository that houses the code (feel free to send fork and send pull requests).

Any user input that you pass to isdigit (or any of the other isXXX functions/macros from ctype.h) should be cast to unsigned char first. Passing a negative number (other than EOF) to isXXX gives undefined behavior. In a typical case, any character outside the basic US-ASCII set (e.g., any letter that's not used in English, plus anything with a diacritic mark) will have a negative value when stored in a char.

Though some disagree, I think most programmers would prefer each variable in a separate definition. If (for some reason) you prefer not to do that, I'd at least format each variable onto a separate line.

for(; getchar() != '\n'; getchar()); // pick up superfluous input so we don't run into problems when we scan for input again

This looks buggy. In the condition you're calling getchar(), and checking the return value--but then in the increment part of the loop, you're calling getchar() again without checking the return value.

One quick observation I have is about the way you are storing the playable spaces. While a 9 by 9 grid is a simple way to do it, it isn't the most clear way to store and ends up requiring some complicated addressing logic to get each sub-board.

Your options to deal with it are a bit limited by C, but there is support for nesting types. You can use this to define a MainBoard type that takes a 3 by 3 matrix of SubBoard types and a SubBoard type that takes a 3 by 3 grid of playable squares (characters).

I'm not 100% sure on the syntax since my C is a bit rusty, but this should allow you to use much simpler addressing. If the player uses space 1,3 on the sub-board they are playing, you can then get board 1,3 for the next play.

The ellipsis ... in a GCC extension to the C programming language know as Case Ranges. Using it hinders portability since not all compilers support it (as for most of the extensions). If you want to get rid of it, you should used the function getBound proposed by Jerry Coffin: