Quantcast
Channel: User Lundin - Code Review Stack Exchange
Viewing all articles
Browse latest Browse all 42

Answer by Lundin for Text-based Tic-Tac-Toe in C

$
0
0

The overall pattern here is that you write code in needlessly complex ways. Good C programming practice is to write code as simple as possible, obeying the KISS principle.

  • Using octal notation with no explanation why is very bad practice. It's an exotic format and C programmers aren't used to it. One might argue that this algorithm here is one valid use for it since it gives groups of 3, but if so, you need to comment on why you are using it and why it works.

    A more readable notation than for example 0007, 0070, 0700 would be 0x3u << 0, 0x3u << 3, 0x3u << 6, because you can expect C programmers to know hex. (Upcoming C2x will also potentially allow 0b binary constants, but they are non-standard for now.)

  • Shifting by a signed integer constant such as #define SQUARE(i) (1 << i) is always wrong, because in the event that i ends up for example 31 on a 32 bit int system, this code invokes undefined behavior. Always use unsigned numbers when dealing with bitwise arithmetic, in this case 1u.

  • It is good practice to always wrap function-like macro parameters in parenthesis: #define SQUARE(i) (1u << (i)), in case the caller happens to pass an expression.

  • SQUARE() suggests that this is a macro used for multiplication, maybe rename it to SQUARE_MASK or similar. Then the code turns self-documenting and comments like /* mask for square i */ become superfluous.

  • I wouldn't use a typedef to create a "bitfield" type. The type in this case isunsigned int (or uint32_t) and one needs to know that in order to make sense of the functions taking "bitfield" parameters. Rather, it would seem that "bitfield" should be in the identifiers: uint32_t bitfield_me, uint32_t bitfield_opp. Or perhaps just make it clear that the code is dealing with bitfields through comments.

  • typedef enum { X, O, NONE = -1 } side; and the logic behind this is needlessly complex and obscure. This could have been implemented as for example bool x_turn; and turns shifted as x_turn = !x_turn;. Keep it simple. You can still keep an enum to keep track of which character to print, but don't mix up the logic for what to print with the logic for who's turn it is.

  • Similarly, avoid needlessly complex boolean expressions and negations. if (!(0 <= move && move < 9)) can be simplified through De Morgan's Laws. Human users probably expect to type 1 to 9, so we should use the much more readable if (move < 1 || move > 9) /* error */ then translate this human input into programmer index-0 based logic by subtracting by 1.

  • There is no obvious reason to use recursion here, it makes the code needlessly slow for no good reason and you could implement that function as a loop instead. I suppose this is some algorithm "alpha-beta pruning", but algorithm theory doesn't translate well to C code and mathematical recursion doesn't translate well to C recursion. It would seem that you could simply keep track of all variables and replace the recursive call with a loop/loops.

  • int get_move() is obsolete style function declarations/definitions, you should always write int get_move (void).

  • atoi is one of those standard lib functions that should never be used for any purpose, because it has no well-defined error handling. Always use strtol (family of functions) instead.


Viewing all articles
Browse latest Browse all 42

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>