Tic-Tac-Toe for the terminalTic-Tac-Toe optimization 2.0 with AISQL Tic-Tac-Toe attemptSimple class-oriented Tic Tac Toe game in C++Tic Tac Toe in JavaText-based Tic Tac Toe with DRY and PEP-8Checking for a win in Tic Tac ToeTic Tac Toe - Verifying a player wonCheck for a win on the boardModularized Tic Tac Toe in CSimple text-based tic-tac-toe

California: "For quality assurance, this phone call is being recorded"

Secure offsite backup, even in the case of hacker root access

Working in the USA for living expenses only; allowed on VWP?

Why were the Night's Watch required to be celibate?

Why is the relationship between frequency and pitch exponential?

Can we use the verb "says" for advertisement?

Why don't B747s start takeoffs with full throttle?

Can you please explain this joke: "I'm going bananas is what I tell my bananas before I leave the house"?

Chopin: marche funèbre bar 15 impossible place

Can a magnetic field of an object be stronger than its gravity?

My ESTA has been refused

Avoiding cliches when writing gods

The ring of global sections of a regular scheme

Comma Code - Ch. 4 Automate the Boring Stuff

Is it OK to bring delicacies from hometown as tokens of gratitude for an out-of-town interview?

What is the right way to float a home lab?

What's the correct term for a waitress in the Middle Ages?

What happens to foam insulation board after you pour concrete slab?

When writing an error prompt, should we end the sentence with a exclamation mark or a dot?

What does War Machine's "Canopy! Canopy!" line mean in "Avengers: Endgame"?

Old black and white movie: glowing black rocks slowly turn you into stone upon touch

How can I determine the spell save DC of a monster/NPC?

How bad would a partial hash leak be, realistically?

Credit card offering 0.5 miles for every cent rounded up. Too good to be true?



Tic-Tac-Toe for the terminal


Tic-Tac-Toe optimization 2.0 with AISQL Tic-Tac-Toe attemptSimple class-oriented Tic Tac Toe game in C++Tic Tac Toe in JavaText-based Tic Tac Toe with DRY and PEP-8Checking for a win in Tic Tac ToeTic Tac Toe - Verifying a player wonCheck for a win on the boardModularized Tic Tac Toe in CSimple text-based tic-tac-toe






.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty margin-bottom:0;








17












$begingroup$


I recently started learning C, and this is my first fairly large (to me) program. It's a basic Tic Tac Toe game for the console. There's no AI, it's just a 2-player game. Is there anything I can improve?



Some problems I've been told elsewhere:



  • There's no case for tying/drawing.


  • I should split the place function into two.


I also think it would be better to use global variables for board and player, since they're used so often.



// Tic Tac Toe in C. V1.0 by James 26/05/2019

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

#define SIZE 3

// Draws the board along with rows/cols, numbered.
void draw_board(char board[])

system("clear");
printf("# 1 2 3n");

// Rows
for (int i = 0, n = 0; i < SIZE; i++)

// Columns
printf("%d ", i + 1);
for (int j = 0; j < SIZE; j++)

printf("%c ", board[n]);
n++;

printf("n");



// Initializes board to '-' characters.
void init_board(char board[])

for (int i = 0; i < SIZE * SIZE; i++)

board[i] = '-';



// Returns true if the piece was successfully placed,
// false if the position was invalid or already taken.
bool place(char board[], char player)

char posinput[64];

printf("%c, pick your position (xy, rc): ", player);
scanf("%s", posinput); // <-- I realise that this is potentially bad, but realistically,
// the user would have to be trying to break something, and
// this is just a little program I made for practice.

int row = (posinput[0] - '0') - 1;
int col = (posinput[1] - '0') - 1;

int pos = col + row * SIZE;

if (pos >= 0 && pos < SIZE * SIZE)

if (board[pos] == 'x'

return false;


// Returns true if there are three of the same chars in a row.
// b = board, p = player. Shortened for readability.
bool check(char b[], char p)

// Check rows
if (b[0] == p && b[1] == p && b[2] == p)
return true;
if (b[3] == p && b[4] == p && b[5] == p)
return true;
if (b[6] == p && b[7] == p && b[8] == p)
return true;


// Check columns
if (b[0] == p && b[3] == p && b[6] == p)
return true;
if (b[1] == p && b[4] == p && b[7] == p)
return true;
if (b[2] == p && b[5] == p && b[8] == p)
return true;


// Check diagonals
if (b[0] == p && b[4] == p && b[8] == p)
return true;
if (b[2] == p && b[4] == p && b[6] == p)
return true;

// If no one won, return false
return false;


int main(void)

char board[SIZE * SIZE];
char player = 'x';
init_board(board);

while (true)

draw_board(board);

if (place(board, player))

if (check(board, player))
break;

if (player == 'x')
player = 'o';
else
player = 'x';



draw_board(board);
printf("-----------------------------n");
printf("Player %c wins!!!n", player);
printf("-----------------------------n");










share|improve this question









New contributor



J. Czekaj is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.






$endgroup$







  • 6




    $begingroup$
    Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers. Feel free to post a follow-up question linking back to this one instead, but it's advised to wait at least a day before posting a follow-up.
    $endgroup$
    – Mast
    May 26 at 13:11










  • $begingroup$
    Mod note: If you want to critique an aspect of the code as it is presented here, please write an answer. If you want to argue about the peculiarities of C and your preferences in dealing with forward declarations, please use Code Review Chat. Comments are not the place for either of these things. Thanks :)
    $endgroup$
    – Vogel612
    2 days ago

















17












$begingroup$


I recently started learning C, and this is my first fairly large (to me) program. It's a basic Tic Tac Toe game for the console. There's no AI, it's just a 2-player game. Is there anything I can improve?



Some problems I've been told elsewhere:



  • There's no case for tying/drawing.


  • I should split the place function into two.


I also think it would be better to use global variables for board and player, since they're used so often.



// Tic Tac Toe in C. V1.0 by James 26/05/2019

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

#define SIZE 3

// Draws the board along with rows/cols, numbered.
void draw_board(char board[])

system("clear");
printf("# 1 2 3n");

// Rows
for (int i = 0, n = 0; i < SIZE; i++)

// Columns
printf("%d ", i + 1);
for (int j = 0; j < SIZE; j++)

printf("%c ", board[n]);
n++;

printf("n");



// Initializes board to '-' characters.
void init_board(char board[])

for (int i = 0; i < SIZE * SIZE; i++)

board[i] = '-';



// Returns true if the piece was successfully placed,
// false if the position was invalid or already taken.
bool place(char board[], char player)

char posinput[64];

printf("%c, pick your position (xy, rc): ", player);
scanf("%s", posinput); // <-- I realise that this is potentially bad, but realistically,
// the user would have to be trying to break something, and
// this is just a little program I made for practice.

int row = (posinput[0] - '0') - 1;
int col = (posinput[1] - '0') - 1;

int pos = col + row * SIZE;

if (pos >= 0 && pos < SIZE * SIZE)

if (board[pos] == 'x'

return false;


// Returns true if there are three of the same chars in a row.
// b = board, p = player. Shortened for readability.
bool check(char b[], char p)

// Check rows
if (b[0] == p && b[1] == p && b[2] == p)
return true;
if (b[3] == p && b[4] == p && b[5] == p)
return true;
if (b[6] == p && b[7] == p && b[8] == p)
return true;


// Check columns
if (b[0] == p && b[3] == p && b[6] == p)
return true;
if (b[1] == p && b[4] == p && b[7] == p)
return true;
if (b[2] == p && b[5] == p && b[8] == p)
return true;


// Check diagonals
if (b[0] == p && b[4] == p && b[8] == p)
return true;
if (b[2] == p && b[4] == p && b[6] == p)
return true;

// If no one won, return false
return false;


int main(void)

char board[SIZE * SIZE];
char player = 'x';
init_board(board);

while (true)

draw_board(board);

if (place(board, player))

if (check(board, player))
break;

if (player == 'x')
player = 'o';
else
player = 'x';



draw_board(board);
printf("-----------------------------n");
printf("Player %c wins!!!n", player);
printf("-----------------------------n");










share|improve this question









New contributor



J. Czekaj is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.






$endgroup$







  • 6




    $begingroup$
    Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers. Feel free to post a follow-up question linking back to this one instead, but it's advised to wait at least a day before posting a follow-up.
    $endgroup$
    – Mast
    May 26 at 13:11










  • $begingroup$
    Mod note: If you want to critique an aspect of the code as it is presented here, please write an answer. If you want to argue about the peculiarities of C and your preferences in dealing with forward declarations, please use Code Review Chat. Comments are not the place for either of these things. Thanks :)
    $endgroup$
    – Vogel612
    2 days ago













17












17








17


4



$begingroup$


I recently started learning C, and this is my first fairly large (to me) program. It's a basic Tic Tac Toe game for the console. There's no AI, it's just a 2-player game. Is there anything I can improve?



Some problems I've been told elsewhere:



  • There's no case for tying/drawing.


  • I should split the place function into two.


I also think it would be better to use global variables for board and player, since they're used so often.



// Tic Tac Toe in C. V1.0 by James 26/05/2019

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

#define SIZE 3

// Draws the board along with rows/cols, numbered.
void draw_board(char board[])

system("clear");
printf("# 1 2 3n");

// Rows
for (int i = 0, n = 0; i < SIZE; i++)

// Columns
printf("%d ", i + 1);
for (int j = 0; j < SIZE; j++)

printf("%c ", board[n]);
n++;

printf("n");



// Initializes board to '-' characters.
void init_board(char board[])

for (int i = 0; i < SIZE * SIZE; i++)

board[i] = '-';



// Returns true if the piece was successfully placed,
// false if the position was invalid or already taken.
bool place(char board[], char player)

char posinput[64];

printf("%c, pick your position (xy, rc): ", player);
scanf("%s", posinput); // <-- I realise that this is potentially bad, but realistically,
// the user would have to be trying to break something, and
// this is just a little program I made for practice.

int row = (posinput[0] - '0') - 1;
int col = (posinput[1] - '0') - 1;

int pos = col + row * SIZE;

if (pos >= 0 && pos < SIZE * SIZE)

if (board[pos] == 'x'

return false;


// Returns true if there are three of the same chars in a row.
// b = board, p = player. Shortened for readability.
bool check(char b[], char p)

// Check rows
if (b[0] == p && b[1] == p && b[2] == p)
return true;
if (b[3] == p && b[4] == p && b[5] == p)
return true;
if (b[6] == p && b[7] == p && b[8] == p)
return true;


// Check columns
if (b[0] == p && b[3] == p && b[6] == p)
return true;
if (b[1] == p && b[4] == p && b[7] == p)
return true;
if (b[2] == p && b[5] == p && b[8] == p)
return true;


// Check diagonals
if (b[0] == p && b[4] == p && b[8] == p)
return true;
if (b[2] == p && b[4] == p && b[6] == p)
return true;

// If no one won, return false
return false;


int main(void)

char board[SIZE * SIZE];
char player = 'x';
init_board(board);

while (true)

draw_board(board);

if (place(board, player))

if (check(board, player))
break;

if (player == 'x')
player = 'o';
else
player = 'x';



draw_board(board);
printf("-----------------------------n");
printf("Player %c wins!!!n", player);
printf("-----------------------------n");










share|improve this question









New contributor



J. Czekaj is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.






$endgroup$




I recently started learning C, and this is my first fairly large (to me) program. It's a basic Tic Tac Toe game for the console. There's no AI, it's just a 2-player game. Is there anything I can improve?



Some problems I've been told elsewhere:



  • There's no case for tying/drawing.


  • I should split the place function into two.


I also think it would be better to use global variables for board and player, since they're used so often.



// Tic Tac Toe in C. V1.0 by James 26/05/2019

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

#define SIZE 3

// Draws the board along with rows/cols, numbered.
void draw_board(char board[])

system("clear");
printf("# 1 2 3n");

// Rows
for (int i = 0, n = 0; i < SIZE; i++)

// Columns
printf("%d ", i + 1);
for (int j = 0; j < SIZE; j++)

printf("%c ", board[n]);
n++;

printf("n");



// Initializes board to '-' characters.
void init_board(char board[])

for (int i = 0; i < SIZE * SIZE; i++)

board[i] = '-';



// Returns true if the piece was successfully placed,
// false if the position was invalid or already taken.
bool place(char board[], char player)

char posinput[64];

printf("%c, pick your position (xy, rc): ", player);
scanf("%s", posinput); // <-- I realise that this is potentially bad, but realistically,
// the user would have to be trying to break something, and
// this is just a little program I made for practice.

int row = (posinput[0] - '0') - 1;
int col = (posinput[1] - '0') - 1;

int pos = col + row * SIZE;

if (pos >= 0 && pos < SIZE * SIZE)

if (board[pos] == 'x'

return false;


// Returns true if there are three of the same chars in a row.
// b = board, p = player. Shortened for readability.
bool check(char b[], char p)

// Check rows
if (b[0] == p && b[1] == p && b[2] == p)
return true;
if (b[3] == p && b[4] == p && b[5] == p)
return true;
if (b[6] == p && b[7] == p && b[8] == p)
return true;


// Check columns
if (b[0] == p && b[3] == p && b[6] == p)
return true;
if (b[1] == p && b[4] == p && b[7] == p)
return true;
if (b[2] == p && b[5] == p && b[8] == p)
return true;


// Check diagonals
if (b[0] == p && b[4] == p && b[8] == p)
return true;
if (b[2] == p && b[4] == p && b[6] == p)
return true;

// If no one won, return false
return false;


int main(void)

char board[SIZE * SIZE];
char player = 'x';
init_board(board);

while (true)

draw_board(board);

if (place(board, player))

if (check(board, player))
break;

if (player == 'x')
player = 'o';
else
player = 'x';



draw_board(board);
printf("-----------------------------n");
printf("Player %c wins!!!n", player);
printf("-----------------------------n");







beginner c console tic-tac-toe linux






share|improve this question









New contributor



J. Czekaj is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.










share|improve this question









New contributor



J. Czekaj is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.








share|improve this question




share|improve this question








edited May 27 at 4:27









Jamal

30.8k12122229




30.8k12122229






New contributor



J. Czekaj is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.








asked May 26 at 6:43









J. CzekajJ. Czekaj

8915




8915




New contributor



J. Czekaj is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.




New contributor




J. Czekaj is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.









  • 6




    $begingroup$
    Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers. Feel free to post a follow-up question linking back to this one instead, but it's advised to wait at least a day before posting a follow-up.
    $endgroup$
    – Mast
    May 26 at 13:11










  • $begingroup$
    Mod note: If you want to critique an aspect of the code as it is presented here, please write an answer. If you want to argue about the peculiarities of C and your preferences in dealing with forward declarations, please use Code Review Chat. Comments are not the place for either of these things. Thanks :)
    $endgroup$
    – Vogel612
    2 days ago












  • 6




    $begingroup$
    Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers. Feel free to post a follow-up question linking back to this one instead, but it's advised to wait at least a day before posting a follow-up.
    $endgroup$
    – Mast
    May 26 at 13:11










  • $begingroup$
    Mod note: If you want to critique an aspect of the code as it is presented here, please write an answer. If you want to argue about the peculiarities of C and your preferences in dealing with forward declarations, please use Code Review Chat. Comments are not the place for either of these things. Thanks :)
    $endgroup$
    – Vogel612
    2 days ago







6




6




$begingroup$
Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers. Feel free to post a follow-up question linking back to this one instead, but it's advised to wait at least a day before posting a follow-up.
$endgroup$
– Mast
May 26 at 13:11




$begingroup$
Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers. Feel free to post a follow-up question linking back to this one instead, but it's advised to wait at least a day before posting a follow-up.
$endgroup$
– Mast
May 26 at 13:11












$begingroup$
Mod note: If you want to critique an aspect of the code as it is presented here, please write an answer. If you want to argue about the peculiarities of C and your preferences in dealing with forward declarations, please use Code Review Chat. Comments are not the place for either of these things. Thanks :)
$endgroup$
– Vogel612
2 days ago




$begingroup$
Mod note: If you want to critique an aspect of the code as it is presented here, please write an answer. If you want to argue about the peculiarities of C and your preferences in dealing with forward declarations, please use Code Review Chat. Comments are not the place for either of these things. Thanks :)
$endgroup$
– Vogel612
2 days ago










7 Answers
7






active

oldest

votes


















18












$begingroup$

First of all: nice work! It's easy to read and understand.



Program organization



It's very good that you split the task to small functions.
Reading the body of main reveals nicely the overall flow.



Ideas for further improvement:



  • place does two things: it reads input from user and updates the state of the board. It would be good to separate these logically distinct steps to different functions. A new function, say read_next_move could return boolean just like place, and take parameters board and pointers to x and y, whose values will be used when updating the board state.


  • The name check is too generic. How about is_game_over or has_player_won.


  • Printing the winner would be good to move to a dedicated function, as you did with other steps.


Use more constants



The special symbols -, x and o would be good to define as constants.
So that instead of if (board[pos] == 'x' || board[pos] == 'o'),
you could write more descriptively if (board[pos] == PLAYER_X || board[pos] == PLAYER_O), and if ever needed change the values easily.
By the way, in this particular example I would replace the condition with if (board[pos] != AVAILABLE).






share|improve this answer









$endgroup$












  • $begingroup$
    Thanks, I actually split up place into 2 functions, but I wasn't allowed to add a new version of the code to this post. I made a function called get_pos that gets input for the coord, then calculates the position in the array, and returns it, place uses that function, and tries to place, checking if it can. I also renamed check to check_win, and also have a function, check_draw. I'll also use constants for the player pieces, thanks!
    $endgroup$
    – J. Czekaj
    May 27 at 12:41











  • $begingroup$
    @J.Czekaj: Yup, instead of returning both x and y separately, it makes more sense for a get_move function to handle encoding them into an array index like the rest of the program uses. (Or better, have your players enter moves using the number pad in a way that matches the TTT grid layout, so it's just a single 1-9 digit: subtract '1' from it to get a 0..8 index). You might handle the illegal-move / try-again prompting inside that one function, so it only returns an error on I/O error or something.
    $endgroup$
    – Peter Cordes
    May 28 at 5:25


















10












$begingroup$

There's two things I'd change to simplify your code:



  • Use a two dimensional array. Sure you can do the conversion from a 2d position to 1d easily, but the compiler can do and board[1][1] is rather more obviously the middle of the board than board[4].

  • Instead of hardcoding your logic for what positions you have to check, think about a programmatic approach using a loop. This not only avoids duplicating lots of code, but also makes it trivial to extend your tic-tac-toe game to a larger board.





share|improve this answer









$endgroup$












  • $begingroup$
    Comments are not for extended discussion; this conversation has been moved to chat.
    $endgroup$
    – Vogel612
    2 days ago


















9












$begingroup$

I think youre code is quite nice. Easy to read and the tasks are nicely splitted into functions. So theres not much to add.



Theres just one thing bothering me.



Don't omit curly braces. This:



// Check rows
if (b[0] == p && b[1] == p && b[2] == p)
return true;
if (b[3] == p && b[4] == p && b[5] == p)
return true;
if (b[6] == p && b[7] == p && b[8] == p)
return true;


Should be this:



// Check rows
if (b[0] == p && b[1] == p && b[2] == p)
return true;

if (b[3] == p && b[4] == p && b[5] == p)
return true;

if (b[6] == p && b[7] == p && b[8] == p)
return true;



You wonder why? It can lead to bugs. One example is apple's SSL bug



For more discussion see: https://stackoverflow.com/questions/359732/why-is-it-considered-a-bad-practice-to-omit-curly-braces






share|improve this answer









$endgroup$








  • 2




    $begingroup$
    Thanks, for the case I'm using it in, I think it's okay. If I added curly braces, it would just add extra clutter, since there's so many if's. If it was one single if, then it would make sense, but when they're all lined up like that, it's clear to see if you made a mistake.
    $endgroup$
    – J. Czekaj
    May 26 at 11:34






  • 1




    $begingroup$
    @J.Czekaj: another alternative is to make one big if with a multi-line condition. It's non-terrible if you format carefully, with || at the end of each line. But yes I'd agree with bending that style guideline for this function where there are many similar conditions to check, and that's all you're doing.
    $endgroup$
    – Peter Cordes
    May 26 at 17:26







  • 1




    $begingroup$
    @J.Czekaj: But note that "many similar" is a red flag that maybe you should write a loop or helper function instead. Or take advantage of an early-out, e.g. only check the diagonals and central + squares inside if(b[4] == p) ... /* middle square */ . It's a tradeoff between more complex logic for a human programmer to verify (compiler hopefully spots it either way) vs. simpler but larger code with those fully unrolled checks of all 3 rows / cols.
    $endgroup$
    – Peter Cordes
    May 26 at 17:30







  • 1




    $begingroup$
    If you let your IDE format the code automatically and compile the code with -Wmisleading-indentation, it's perfectly ok to omit the curly braces.
    $endgroup$
    – Roland Illig
    May 26 at 18:34






  • 2




    $begingroup$
    Let’s please collectively stop using the “goto fail” bug as an argument to justify our preferences, and let’s pretend that the bug was caused by lack of braces rather than by a merge conflict that could also have happened with braces (albeit less likely). In fact, the article you link in your answer (and which you probably didn’t read) also says that the lack of braces isn’t the problem here.
    $endgroup$
    – Konrad Rudolph
    May 27 at 10:05



















7












$begingroup$

The following remarks may be nitpicks. Since your code is already quite good, it's all I have to say. :)



#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>


Since all the above headers are from the standard C library, they should be in alphabetical order.



When you include other headers like <sys/type.h>, the order is sometimes important. But not in your simple program.



#define SIZE 3


Having this constant means that anyone else may later change the 3 into a 5 and can expect that the program still works reasonably well. If your code doesn't provide this guarantee, you should add a small comment explaining that this constant should not be modified.



// Draws the board along with rows/cols, numbered.
void draw_board(char board[])


Function signatures in C should not contain arrays since these behave surprisingly in several cases (like multi-dimensional arrays). Also, it sounds strange to say "to draw the board, given a character array". The function signature would sound a lot better like this:



void draw_board(tic_tac_toe_board *board)


This changes the wording to "draw the board, given a board", which is a bit redundant, but it focuses on the problem domain instead of the technical level, which makes the code easier to understand, especially if you want to explain programming to laymen.



To make the above function signature valid, you need to declare the tic_tac_toe_board as a type:



typedef struct 
char cell[SIZE * SIZE];
tic_tac_toe_board;


With this type definition, your code may look like this:



void init_board(tic_tac_toe_board *board)

for (int i = 0; i < SIZE * SIZE; i++)
board->cell[i] = '-';



It's a bit more effort to write board->cell[i] instead of the simple board[i] from before, but it's worth it since you can now talk about a tic-tac-toe board, without having to mention that it is implemented as a character array.



Having this abstraction also means that you can easily extend the board by recording the history of moves, just in case you want to implement an undo feature later:



typedef struct 
char cell[SIZE * SIZE];
int moves_count;
int moves[SIZE * SIZE];
tic_tac_toe_board;


Next topic, the communication with the user:



 printf("%c, pick your position (xy, rc): ", player);


That message is a mistake: xy is not the same as rc since r corresponds to y, not x. It should be either (xy, cr) or (yx, rc).



 scanf("%s", posinput);


Instead of reading a string here, there's a completely different idea. Most keyboards have a numeric block, which by coincidence consists of 3×3 keys. When you map the keyboard layout as follows, the human players just need a single key instead of two to enter a coordinate, plus the position on the keyboard matches exactly the position on the board:



7 8 9
4 5 6
1 2 3


It only works for 3×3 though. But if you accept that limitation:



int pos;
if (scanf("%d", &pos) == 1)
int row = (SIZE - 1) - (pos - 1) / SIZE;
int col = (pos - 1) % SIZE;
int board_pos = col + row * SIZE;



Going further in your code:



if (board[pos] == 'x' || board[pos] == 'o')
return false;


I would rather say board[pos] != '-', for 3 reasons: it is shorter, it is faster, and when you add a third player someday the code is still correct.



// Returns true if there are three of the same chars in a row.
// b = board, p = player. Shortened for readability.


A very nice comment. Short, to the point, and informative.



The rest of the code looks good already. There's just one missing thing. Anecdotal evidence suggests that in the game of tic-tac-toe, players familiar with each other will tie 100% of the time due to the limited number of outcomes. I suggest you implement the tie rule. It's very simple: if SIZE * SIZE moves have been played and there is still no winner, it's a tie.






share|improve this answer









$endgroup$








  • 1




    $begingroup$
    The history should not be part of the ttt_board struct. When you add an AI that recursively tries moves to find forced wins and avoid forced losses, it's going to be passing around copies of boards with a move made, and the history is in the recursion stack frames (local vars). Making the board struct 4x larger (on most C implementations) with an int array sounds terrible.
    $endgroup$
    – Peter Cordes
    May 28 at 5:13










  • $begingroup$
    @Peter that's a good argument, I agree fully with it. It was just the first thing that came to my mind when I wrote the answer and wanted to show how easily a struct can be extended. Maybe it would have been a better idea to add a field char turn that tells whose turn it is.
    $endgroup$
    – Roland Illig
    May 28 at 6:36










  • $begingroup$
    Maybe, but that's redundant for some cases where you want to use a ttt_board. You can nest structs, though, for a complete game-position struct that includes the turn.
    $endgroup$
    – Peter Cordes
    May 28 at 7:15


















6












$begingroup$

The code looks clean and readable.



You could rename check to checkForWin to tell what it checks.



I would also suggest to improve that procedure by declaring the lines as a constant array instad of writing a similar test 8 times:



const int lines[8][3] = 
0, 1, 2 , // rows
3, 4, 5 ,
6, 7, 8 ,
0, 3, 6 , // columns
1, 4, 7 ,
2, 5, 8 ,
0, 4, 8 , // diagonals
2, 4, 6
;

bool checkForWin(char b[], char p)

for (int i = 0 ; i < 8 ; i++ )
if (b[lines[i][0]] == p && b[lines[i][1]] == p && b[lines[i][2]] == p)
return true;


// No winnig line found
return false;






share|improve this answer









$endgroup$








  • 2




    $begingroup$
    Neither of those two options are very scalable (it's nice to declare a SIZE constant, less nice if you can't change it anyhow with adapting the logic). The logic is very easy to implement for arrays of every size - check all rows, all columns and the two diagonals. No need to hardcode it.
    $endgroup$
    – Voo
    May 26 at 17:47










  • $begingroup$
    Yes, the SIZE problem needs to be addressed. If you want to honor the SIZE constant, you can generate the array programmatically (and introduce variables for the array sizes). It is clean to define the topology and then use it to drive the program. For instance, if the next step is to have the computer play, and it has to find opportunities for double-threats, it will be much simpler with such a lines array. If you change for a 3x3x3 board, the code doesn't change. There are just more lines.
    $endgroup$
    – Florian F
    May 26 at 18:20










  • $begingroup$
    But as soon as you generate the lines array programmatically, you already have the code to do the check directly (since that's basically what you're doing when creating the array). Just seems rather roundabout.
    $endgroup$
    – Voo
    May 26 at 20:18







  • 1




    $begingroup$
    It is very enlightening to see that you can abstract the board as a generic set of cells joined by a set of lines, to see that you can define the shape of the board in one place and program the gameplay regardless of that shape. It is called separation of concerns.
    $endgroup$
    – Florian F
    May 26 at 21:02






  • 4




    $begingroup$
    Hard coding those “lines” into rows columns and diagonals feels odd. Not very human readable, and at the same time not dynamic to board size. So kind of a lose lose.
    $endgroup$
    – Cooper Buckingham
    May 27 at 2:27


















5












$begingroup$

Starting from the top, defining the SIZE macro is good - this potentially allows you to change the size of the board by modifying one number. It is especially good because it allows you to later come back and change it to the name of a global variable instead of a constant number, which then allows you to define the size of the board runtime.



You then throw away some of that utility in draw_board(), by printing the column numbers as a constant ("# 1 2 3n"), rather than by using the nice macro you defined just a few lines prior. You should print the column numbers with a loop, just like you print the board itself. Consider also writing ones for the symbols (i.e. EMPTY, PLAYER1 and PLAYER2) so you can change them if desired.



init_board() is great, but you could use memset(board, '-', SIZE * SIZE) instead of the loop.



In place(), your scanf() format should not be "%s". You're expecting two numbers (or digits), so scan for those: "%d%d" or "%c%c" will do the job and allow for improvements later down the line. In addition, you can check for whether there already is a symbol at the coordinate in a single comparison with board[pos] != '-'.



Also, you should return the index on success and a faulty value like -1 on failure, so that your check() can take that as input and only check the rows, columns and diagonals that the token is a part of. Something like this:



// In the main loop:
int index = place();
if (index >= 0)

if (check(board, index, player))
break; // Placement succeeded and resulted in a win, break loop.
/* … */



Next, in check(), you check a 3x3 board, completely ignoring that nice SIZE macro you defined. You should instead use a loop to check because then you can freely change the SIZE of the board later down the line. Combined with the index for input, you'd have something like...



bool check(char board[], int index, char player)
colCount == SIZE


Finally, you may want to define a function-like macro or a function for indexing the board to reduce the chance of having a typo cause a bug and to make it somewhat more legible. It can also reduce the amount of work you need to put in if you choose to change the indexing later on.



#define AT(X, Y) [(Y) * SIZE + (X)]
int at(int x, int y) return y * SIZE + x;
// which allow you to write something like...
if ( board AT(x, y) == '-' )
board[at(x, y)] = 'x';





share|improve this answer










New contributor



Rogem is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.





$endgroup$








  • 3




    $begingroup$
    Using a macro for something that can easily be done by a function is rather awful.
    $endgroup$
    – Voo
    May 26 at 18:38










  • $begingroup$
    @Voo Uh... What do you think macros are useful for?
    $endgroup$
    – Rogem
    May 26 at 18:52






  • 4




    $begingroup$
    Macros are for things that cannot be done by inline functions. Which, yes, means they should be used as rarely as possible.
    $endgroup$
    – Cody Gray
    May 26 at 19:58






  • 1




    $begingroup$
    macros dont follow the rules of the language. they are maintenance hazards and introduve hard to spot bugs. besides using them for constants in c you should rarely use them...
    $endgroup$
    – Sandro4912
    May 27 at 3:53






  • 1




    $begingroup$
    @PeterCordes It will also match a leading - if there is one, but reading input is a beast of its own...
    $endgroup$
    – Rogem
    May 28 at 15:04


















2












$begingroup$

Overall, this is well-written code. One thing I would change besides what has already been suggested by others is your call to system("clear") which creates an OS-specific dependency.



You can instead use #ifdef to check the platform and then call the appropriate command to clear the screen. Additionally, this can be wrapped in a function to allow for code that is easy to reuse.



Example:



void clear() 
#ifdef _WIN32
system("cls");
#else
system("clear");
#endif






share|improve this answer









$endgroup$








  • 1




    $begingroup$
    Thanks, I was thinking about adding something like this, but I just ended up forgetting about it. I didn't think it was that simple!
    $endgroup$
    – J. Czekaj
    2 days ago











Your Answer






StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "196"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);






J. Czekaj is a new contributor. Be nice, and check out our Code of Conduct.









draft saved

draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f221035%2ftic-tac-toe-for-the-terminal%23new-answer', 'question_page');

);

Post as a guest















Required, but never shown

























7 Answers
7






active

oldest

votes








7 Answers
7






active

oldest

votes









active

oldest

votes






active

oldest

votes









18












$begingroup$

First of all: nice work! It's easy to read and understand.



Program organization



It's very good that you split the task to small functions.
Reading the body of main reveals nicely the overall flow.



Ideas for further improvement:



  • place does two things: it reads input from user and updates the state of the board. It would be good to separate these logically distinct steps to different functions. A new function, say read_next_move could return boolean just like place, and take parameters board and pointers to x and y, whose values will be used when updating the board state.


  • The name check is too generic. How about is_game_over or has_player_won.


  • Printing the winner would be good to move to a dedicated function, as you did with other steps.


Use more constants



The special symbols -, x and o would be good to define as constants.
So that instead of if (board[pos] == 'x' || board[pos] == 'o'),
you could write more descriptively if (board[pos] == PLAYER_X || board[pos] == PLAYER_O), and if ever needed change the values easily.
By the way, in this particular example I would replace the condition with if (board[pos] != AVAILABLE).






share|improve this answer









$endgroup$












  • $begingroup$
    Thanks, I actually split up place into 2 functions, but I wasn't allowed to add a new version of the code to this post. I made a function called get_pos that gets input for the coord, then calculates the position in the array, and returns it, place uses that function, and tries to place, checking if it can. I also renamed check to check_win, and also have a function, check_draw. I'll also use constants for the player pieces, thanks!
    $endgroup$
    – J. Czekaj
    May 27 at 12:41











  • $begingroup$
    @J.Czekaj: Yup, instead of returning both x and y separately, it makes more sense for a get_move function to handle encoding them into an array index like the rest of the program uses. (Or better, have your players enter moves using the number pad in a way that matches the TTT grid layout, so it's just a single 1-9 digit: subtract '1' from it to get a 0..8 index). You might handle the illegal-move / try-again prompting inside that one function, so it only returns an error on I/O error or something.
    $endgroup$
    – Peter Cordes
    May 28 at 5:25















18












$begingroup$

First of all: nice work! It's easy to read and understand.



Program organization



It's very good that you split the task to small functions.
Reading the body of main reveals nicely the overall flow.



Ideas for further improvement:



  • place does two things: it reads input from user and updates the state of the board. It would be good to separate these logically distinct steps to different functions. A new function, say read_next_move could return boolean just like place, and take parameters board and pointers to x and y, whose values will be used when updating the board state.


  • The name check is too generic. How about is_game_over or has_player_won.


  • Printing the winner would be good to move to a dedicated function, as you did with other steps.


Use more constants



The special symbols -, x and o would be good to define as constants.
So that instead of if (board[pos] == 'x' || board[pos] == 'o'),
you could write more descriptively if (board[pos] == PLAYER_X || board[pos] == PLAYER_O), and if ever needed change the values easily.
By the way, in this particular example I would replace the condition with if (board[pos] != AVAILABLE).






share|improve this answer









$endgroup$












  • $begingroup$
    Thanks, I actually split up place into 2 functions, but I wasn't allowed to add a new version of the code to this post. I made a function called get_pos that gets input for the coord, then calculates the position in the array, and returns it, place uses that function, and tries to place, checking if it can. I also renamed check to check_win, and also have a function, check_draw. I'll also use constants for the player pieces, thanks!
    $endgroup$
    – J. Czekaj
    May 27 at 12:41











  • $begingroup$
    @J.Czekaj: Yup, instead of returning both x and y separately, it makes more sense for a get_move function to handle encoding them into an array index like the rest of the program uses. (Or better, have your players enter moves using the number pad in a way that matches the TTT grid layout, so it's just a single 1-9 digit: subtract '1' from it to get a 0..8 index). You might handle the illegal-move / try-again prompting inside that one function, so it only returns an error on I/O error or something.
    $endgroup$
    – Peter Cordes
    May 28 at 5:25













18












18








18





$begingroup$

First of all: nice work! It's easy to read and understand.



Program organization



It's very good that you split the task to small functions.
Reading the body of main reveals nicely the overall flow.



Ideas for further improvement:



  • place does two things: it reads input from user and updates the state of the board. It would be good to separate these logically distinct steps to different functions. A new function, say read_next_move could return boolean just like place, and take parameters board and pointers to x and y, whose values will be used when updating the board state.


  • The name check is too generic. How about is_game_over or has_player_won.


  • Printing the winner would be good to move to a dedicated function, as you did with other steps.


Use more constants



The special symbols -, x and o would be good to define as constants.
So that instead of if (board[pos] == 'x' || board[pos] == 'o'),
you could write more descriptively if (board[pos] == PLAYER_X || board[pos] == PLAYER_O), and if ever needed change the values easily.
By the way, in this particular example I would replace the condition with if (board[pos] != AVAILABLE).






share|improve this answer









$endgroup$



First of all: nice work! It's easy to read and understand.



Program organization



It's very good that you split the task to small functions.
Reading the body of main reveals nicely the overall flow.



Ideas for further improvement:



  • place does two things: it reads input from user and updates the state of the board. It would be good to separate these logically distinct steps to different functions. A new function, say read_next_move could return boolean just like place, and take parameters board and pointers to x and y, whose values will be used when updating the board state.


  • The name check is too generic. How about is_game_over or has_player_won.


  • Printing the winner would be good to move to a dedicated function, as you did with other steps.


Use more constants



The special symbols -, x and o would be good to define as constants.
So that instead of if (board[pos] == 'x' || board[pos] == 'o'),
you could write more descriptively if (board[pos] == PLAYER_X || board[pos] == PLAYER_O), and if ever needed change the values easily.
By the way, in this particular example I would replace the condition with if (board[pos] != AVAILABLE).







share|improve this answer












share|improve this answer



share|improve this answer










answered May 26 at 8:41









janosjanos

100k13127353




100k13127353











  • $begingroup$
    Thanks, I actually split up place into 2 functions, but I wasn't allowed to add a new version of the code to this post. I made a function called get_pos that gets input for the coord, then calculates the position in the array, and returns it, place uses that function, and tries to place, checking if it can. I also renamed check to check_win, and also have a function, check_draw. I'll also use constants for the player pieces, thanks!
    $endgroup$
    – J. Czekaj
    May 27 at 12:41











  • $begingroup$
    @J.Czekaj: Yup, instead of returning both x and y separately, it makes more sense for a get_move function to handle encoding them into an array index like the rest of the program uses. (Or better, have your players enter moves using the number pad in a way that matches the TTT grid layout, so it's just a single 1-9 digit: subtract '1' from it to get a 0..8 index). You might handle the illegal-move / try-again prompting inside that one function, so it only returns an error on I/O error or something.
    $endgroup$
    – Peter Cordes
    May 28 at 5:25
















  • $begingroup$
    Thanks, I actually split up place into 2 functions, but I wasn't allowed to add a new version of the code to this post. I made a function called get_pos that gets input for the coord, then calculates the position in the array, and returns it, place uses that function, and tries to place, checking if it can. I also renamed check to check_win, and also have a function, check_draw. I'll also use constants for the player pieces, thanks!
    $endgroup$
    – J. Czekaj
    May 27 at 12:41











  • $begingroup$
    @J.Czekaj: Yup, instead of returning both x and y separately, it makes more sense for a get_move function to handle encoding them into an array index like the rest of the program uses. (Or better, have your players enter moves using the number pad in a way that matches the TTT grid layout, so it's just a single 1-9 digit: subtract '1' from it to get a 0..8 index). You might handle the illegal-move / try-again prompting inside that one function, so it only returns an error on I/O error or something.
    $endgroup$
    – Peter Cordes
    May 28 at 5:25















$begingroup$
Thanks, I actually split up place into 2 functions, but I wasn't allowed to add a new version of the code to this post. I made a function called get_pos that gets input for the coord, then calculates the position in the array, and returns it, place uses that function, and tries to place, checking if it can. I also renamed check to check_win, and also have a function, check_draw. I'll also use constants for the player pieces, thanks!
$endgroup$
– J. Czekaj
May 27 at 12:41





$begingroup$
Thanks, I actually split up place into 2 functions, but I wasn't allowed to add a new version of the code to this post. I made a function called get_pos that gets input for the coord, then calculates the position in the array, and returns it, place uses that function, and tries to place, checking if it can. I also renamed check to check_win, and also have a function, check_draw. I'll also use constants for the player pieces, thanks!
$endgroup$
– J. Czekaj
May 27 at 12:41













$begingroup$
@J.Czekaj: Yup, instead of returning both x and y separately, it makes more sense for a get_move function to handle encoding them into an array index like the rest of the program uses. (Or better, have your players enter moves using the number pad in a way that matches the TTT grid layout, so it's just a single 1-9 digit: subtract '1' from it to get a 0..8 index). You might handle the illegal-move / try-again prompting inside that one function, so it only returns an error on I/O error or something.
$endgroup$
– Peter Cordes
May 28 at 5:25




$begingroup$
@J.Czekaj: Yup, instead of returning both x and y separately, it makes more sense for a get_move function to handle encoding them into an array index like the rest of the program uses. (Or better, have your players enter moves using the number pad in a way that matches the TTT grid layout, so it's just a single 1-9 digit: subtract '1' from it to get a 0..8 index). You might handle the illegal-move / try-again prompting inside that one function, so it only returns an error on I/O error or something.
$endgroup$
– Peter Cordes
May 28 at 5:25













10












$begingroup$

There's two things I'd change to simplify your code:



  • Use a two dimensional array. Sure you can do the conversion from a 2d position to 1d easily, but the compiler can do and board[1][1] is rather more obviously the middle of the board than board[4].

  • Instead of hardcoding your logic for what positions you have to check, think about a programmatic approach using a loop. This not only avoids duplicating lots of code, but also makes it trivial to extend your tic-tac-toe game to a larger board.





share|improve this answer









$endgroup$












  • $begingroup$
    Comments are not for extended discussion; this conversation has been moved to chat.
    $endgroup$
    – Vogel612
    2 days ago















10












$begingroup$

There's two things I'd change to simplify your code:



  • Use a two dimensional array. Sure you can do the conversion from a 2d position to 1d easily, but the compiler can do and board[1][1] is rather more obviously the middle of the board than board[4].

  • Instead of hardcoding your logic for what positions you have to check, think about a programmatic approach using a loop. This not only avoids duplicating lots of code, but also makes it trivial to extend your tic-tac-toe game to a larger board.





share|improve this answer









$endgroup$












  • $begingroup$
    Comments are not for extended discussion; this conversation has been moved to chat.
    $endgroup$
    – Vogel612
    2 days ago













10












10








10





$begingroup$

There's two things I'd change to simplify your code:



  • Use a two dimensional array. Sure you can do the conversion from a 2d position to 1d easily, but the compiler can do and board[1][1] is rather more obviously the middle of the board than board[4].

  • Instead of hardcoding your logic for what positions you have to check, think about a programmatic approach using a loop. This not only avoids duplicating lots of code, but also makes it trivial to extend your tic-tac-toe game to a larger board.





share|improve this answer









$endgroup$



There's two things I'd change to simplify your code:



  • Use a two dimensional array. Sure you can do the conversion from a 2d position to 1d easily, but the compiler can do and board[1][1] is rather more obviously the middle of the board than board[4].

  • Instead of hardcoding your logic for what positions you have to check, think about a programmatic approach using a loop. This not only avoids duplicating lots of code, but also makes it trivial to extend your tic-tac-toe game to a larger board.






share|improve this answer












share|improve this answer



share|improve this answer










answered May 26 at 17:56









VooVoo

3891210




3891210











  • $begingroup$
    Comments are not for extended discussion; this conversation has been moved to chat.
    $endgroup$
    – Vogel612
    2 days ago
















  • $begingroup$
    Comments are not for extended discussion; this conversation has been moved to chat.
    $endgroup$
    – Vogel612
    2 days ago















$begingroup$
Comments are not for extended discussion; this conversation has been moved to chat.
$endgroup$
– Vogel612
2 days ago




$begingroup$
Comments are not for extended discussion; this conversation has been moved to chat.
$endgroup$
– Vogel612
2 days ago











9












$begingroup$

I think youre code is quite nice. Easy to read and the tasks are nicely splitted into functions. So theres not much to add.



Theres just one thing bothering me.



Don't omit curly braces. This:



// Check rows
if (b[0] == p && b[1] == p && b[2] == p)
return true;
if (b[3] == p && b[4] == p && b[5] == p)
return true;
if (b[6] == p && b[7] == p && b[8] == p)
return true;


Should be this:



// Check rows
if (b[0] == p && b[1] == p && b[2] == p)
return true;

if (b[3] == p && b[4] == p && b[5] == p)
return true;

if (b[6] == p && b[7] == p && b[8] == p)
return true;



You wonder why? It can lead to bugs. One example is apple's SSL bug



For more discussion see: https://stackoverflow.com/questions/359732/why-is-it-considered-a-bad-practice-to-omit-curly-braces






share|improve this answer









$endgroup$








  • 2




    $begingroup$
    Thanks, for the case I'm using it in, I think it's okay. If I added curly braces, it would just add extra clutter, since there's so many if's. If it was one single if, then it would make sense, but when they're all lined up like that, it's clear to see if you made a mistake.
    $endgroup$
    – J. Czekaj
    May 26 at 11:34






  • 1




    $begingroup$
    @J.Czekaj: another alternative is to make one big if with a multi-line condition. It's non-terrible if you format carefully, with || at the end of each line. But yes I'd agree with bending that style guideline for this function where there are many similar conditions to check, and that's all you're doing.
    $endgroup$
    – Peter Cordes
    May 26 at 17:26







  • 1




    $begingroup$
    @J.Czekaj: But note that "many similar" is a red flag that maybe you should write a loop or helper function instead. Or take advantage of an early-out, e.g. only check the diagonals and central + squares inside if(b[4] == p) ... /* middle square */ . It's a tradeoff between more complex logic for a human programmer to verify (compiler hopefully spots it either way) vs. simpler but larger code with those fully unrolled checks of all 3 rows / cols.
    $endgroup$
    – Peter Cordes
    May 26 at 17:30







  • 1




    $begingroup$
    If you let your IDE format the code automatically and compile the code with -Wmisleading-indentation, it's perfectly ok to omit the curly braces.
    $endgroup$
    – Roland Illig
    May 26 at 18:34






  • 2




    $begingroup$
    Let’s please collectively stop using the “goto fail” bug as an argument to justify our preferences, and let’s pretend that the bug was caused by lack of braces rather than by a merge conflict that could also have happened with braces (albeit less likely). In fact, the article you link in your answer (and which you probably didn’t read) also says that the lack of braces isn’t the problem here.
    $endgroup$
    – Konrad Rudolph
    May 27 at 10:05
















9












$begingroup$

I think youre code is quite nice. Easy to read and the tasks are nicely splitted into functions. So theres not much to add.



Theres just one thing bothering me.



Don't omit curly braces. This:



// Check rows
if (b[0] == p && b[1] == p && b[2] == p)
return true;
if (b[3] == p && b[4] == p && b[5] == p)
return true;
if (b[6] == p && b[7] == p && b[8] == p)
return true;


Should be this:



// Check rows
if (b[0] == p && b[1] == p && b[2] == p)
return true;

if (b[3] == p && b[4] == p && b[5] == p)
return true;

if (b[6] == p && b[7] == p && b[8] == p)
return true;



You wonder why? It can lead to bugs. One example is apple's SSL bug



For more discussion see: https://stackoverflow.com/questions/359732/why-is-it-considered-a-bad-practice-to-omit-curly-braces






share|improve this answer









$endgroup$








  • 2




    $begingroup$
    Thanks, for the case I'm using it in, I think it's okay. If I added curly braces, it would just add extra clutter, since there's so many if's. If it was one single if, then it would make sense, but when they're all lined up like that, it's clear to see if you made a mistake.
    $endgroup$
    – J. Czekaj
    May 26 at 11:34






  • 1




    $begingroup$
    @J.Czekaj: another alternative is to make one big if with a multi-line condition. It's non-terrible if you format carefully, with || at the end of each line. But yes I'd agree with bending that style guideline for this function where there are many similar conditions to check, and that's all you're doing.
    $endgroup$
    – Peter Cordes
    May 26 at 17:26







  • 1




    $begingroup$
    @J.Czekaj: But note that "many similar" is a red flag that maybe you should write a loop or helper function instead. Or take advantage of an early-out, e.g. only check the diagonals and central + squares inside if(b[4] == p) ... /* middle square */ . It's a tradeoff between more complex logic for a human programmer to verify (compiler hopefully spots it either way) vs. simpler but larger code with those fully unrolled checks of all 3 rows / cols.
    $endgroup$
    – Peter Cordes
    May 26 at 17:30







  • 1




    $begingroup$
    If you let your IDE format the code automatically and compile the code with -Wmisleading-indentation, it's perfectly ok to omit the curly braces.
    $endgroup$
    – Roland Illig
    May 26 at 18:34






  • 2




    $begingroup$
    Let’s please collectively stop using the “goto fail” bug as an argument to justify our preferences, and let’s pretend that the bug was caused by lack of braces rather than by a merge conflict that could also have happened with braces (albeit less likely). In fact, the article you link in your answer (and which you probably didn’t read) also says that the lack of braces isn’t the problem here.
    $endgroup$
    – Konrad Rudolph
    May 27 at 10:05














9












9








9





$begingroup$

I think youre code is quite nice. Easy to read and the tasks are nicely splitted into functions. So theres not much to add.



Theres just one thing bothering me.



Don't omit curly braces. This:



// Check rows
if (b[0] == p && b[1] == p && b[2] == p)
return true;
if (b[3] == p && b[4] == p && b[5] == p)
return true;
if (b[6] == p && b[7] == p && b[8] == p)
return true;


Should be this:



// Check rows
if (b[0] == p && b[1] == p && b[2] == p)
return true;

if (b[3] == p && b[4] == p && b[5] == p)
return true;

if (b[6] == p && b[7] == p && b[8] == p)
return true;



You wonder why? It can lead to bugs. One example is apple's SSL bug



For more discussion see: https://stackoverflow.com/questions/359732/why-is-it-considered-a-bad-practice-to-omit-curly-braces






share|improve this answer









$endgroup$



I think youre code is quite nice. Easy to read and the tasks are nicely splitted into functions. So theres not much to add.



Theres just one thing bothering me.



Don't omit curly braces. This:



// Check rows
if (b[0] == p && b[1] == p && b[2] == p)
return true;
if (b[3] == p && b[4] == p && b[5] == p)
return true;
if (b[6] == p && b[7] == p && b[8] == p)
return true;


Should be this:



// Check rows
if (b[0] == p && b[1] == p && b[2] == p)
return true;

if (b[3] == p && b[4] == p && b[5] == p)
return true;

if (b[6] == p && b[7] == p && b[8] == p)
return true;



You wonder why? It can lead to bugs. One example is apple's SSL bug



For more discussion see: https://stackoverflow.com/questions/359732/why-is-it-considered-a-bad-practice-to-omit-curly-braces







share|improve this answer












share|improve this answer



share|improve this answer










answered May 26 at 9:12









Sandro4912Sandro4912

1,5741427




1,5741427







  • 2




    $begingroup$
    Thanks, for the case I'm using it in, I think it's okay. If I added curly braces, it would just add extra clutter, since there's so many if's. If it was one single if, then it would make sense, but when they're all lined up like that, it's clear to see if you made a mistake.
    $endgroup$
    – J. Czekaj
    May 26 at 11:34






  • 1




    $begingroup$
    @J.Czekaj: another alternative is to make one big if with a multi-line condition. It's non-terrible if you format carefully, with || at the end of each line. But yes I'd agree with bending that style guideline for this function where there are many similar conditions to check, and that's all you're doing.
    $endgroup$
    – Peter Cordes
    May 26 at 17:26







  • 1




    $begingroup$
    @J.Czekaj: But note that "many similar" is a red flag that maybe you should write a loop or helper function instead. Or take advantage of an early-out, e.g. only check the diagonals and central + squares inside if(b[4] == p) ... /* middle square */ . It's a tradeoff between more complex logic for a human programmer to verify (compiler hopefully spots it either way) vs. simpler but larger code with those fully unrolled checks of all 3 rows / cols.
    $endgroup$
    – Peter Cordes
    May 26 at 17:30







  • 1




    $begingroup$
    If you let your IDE format the code automatically and compile the code with -Wmisleading-indentation, it's perfectly ok to omit the curly braces.
    $endgroup$
    – Roland Illig
    May 26 at 18:34






  • 2




    $begingroup$
    Let’s please collectively stop using the “goto fail” bug as an argument to justify our preferences, and let’s pretend that the bug was caused by lack of braces rather than by a merge conflict that could also have happened with braces (albeit less likely). In fact, the article you link in your answer (and which you probably didn’t read) also says that the lack of braces isn’t the problem here.
    $endgroup$
    – Konrad Rudolph
    May 27 at 10:05













  • 2




    $begingroup$
    Thanks, for the case I'm using it in, I think it's okay. If I added curly braces, it would just add extra clutter, since there's so many if's. If it was one single if, then it would make sense, but when they're all lined up like that, it's clear to see if you made a mistake.
    $endgroup$
    – J. Czekaj
    May 26 at 11:34






  • 1




    $begingroup$
    @J.Czekaj: another alternative is to make one big if with a multi-line condition. It's non-terrible if you format carefully, with || at the end of each line. But yes I'd agree with bending that style guideline for this function where there are many similar conditions to check, and that's all you're doing.
    $endgroup$
    – Peter Cordes
    May 26 at 17:26







  • 1




    $begingroup$
    @J.Czekaj: But note that "many similar" is a red flag that maybe you should write a loop or helper function instead. Or take advantage of an early-out, e.g. only check the diagonals and central + squares inside if(b[4] == p) ... /* middle square */ . It's a tradeoff between more complex logic for a human programmer to verify (compiler hopefully spots it either way) vs. simpler but larger code with those fully unrolled checks of all 3 rows / cols.
    $endgroup$
    – Peter Cordes
    May 26 at 17:30







  • 1




    $begingroup$
    If you let your IDE format the code automatically and compile the code with -Wmisleading-indentation, it's perfectly ok to omit the curly braces.
    $endgroup$
    – Roland Illig
    May 26 at 18:34






  • 2




    $begingroup$
    Let’s please collectively stop using the “goto fail” bug as an argument to justify our preferences, and let’s pretend that the bug was caused by lack of braces rather than by a merge conflict that could also have happened with braces (albeit less likely). In fact, the article you link in your answer (and which you probably didn’t read) also says that the lack of braces isn’t the problem here.
    $endgroup$
    – Konrad Rudolph
    May 27 at 10:05








2




2




$begingroup$
Thanks, for the case I'm using it in, I think it's okay. If I added curly braces, it would just add extra clutter, since there's so many if's. If it was one single if, then it would make sense, but when they're all lined up like that, it's clear to see if you made a mistake.
$endgroup$
– J. Czekaj
May 26 at 11:34




$begingroup$
Thanks, for the case I'm using it in, I think it's okay. If I added curly braces, it would just add extra clutter, since there's so many if's. If it was one single if, then it would make sense, but when they're all lined up like that, it's clear to see if you made a mistake.
$endgroup$
– J. Czekaj
May 26 at 11:34




1




1




$begingroup$
@J.Czekaj: another alternative is to make one big if with a multi-line condition. It's non-terrible if you format carefully, with || at the end of each line. But yes I'd agree with bending that style guideline for this function where there are many similar conditions to check, and that's all you're doing.
$endgroup$
– Peter Cordes
May 26 at 17:26





$begingroup$
@J.Czekaj: another alternative is to make one big if with a multi-line condition. It's non-terrible if you format carefully, with || at the end of each line. But yes I'd agree with bending that style guideline for this function where there are many similar conditions to check, and that's all you're doing.
$endgroup$
– Peter Cordes
May 26 at 17:26





1




1




$begingroup$
@J.Czekaj: But note that "many similar" is a red flag that maybe you should write a loop or helper function instead. Or take advantage of an early-out, e.g. only check the diagonals and central + squares inside if(b[4] == p) ... /* middle square */ . It's a tradeoff between more complex logic for a human programmer to verify (compiler hopefully spots it either way) vs. simpler but larger code with those fully unrolled checks of all 3 rows / cols.
$endgroup$
– Peter Cordes
May 26 at 17:30





$begingroup$
@J.Czekaj: But note that "many similar" is a red flag that maybe you should write a loop or helper function instead. Or take advantage of an early-out, e.g. only check the diagonals and central + squares inside if(b[4] == p) ... /* middle square */ . It's a tradeoff between more complex logic for a human programmer to verify (compiler hopefully spots it either way) vs. simpler but larger code with those fully unrolled checks of all 3 rows / cols.
$endgroup$
– Peter Cordes
May 26 at 17:30





1




1




$begingroup$
If you let your IDE format the code automatically and compile the code with -Wmisleading-indentation, it's perfectly ok to omit the curly braces.
$endgroup$
– Roland Illig
May 26 at 18:34




$begingroup$
If you let your IDE format the code automatically and compile the code with -Wmisleading-indentation, it's perfectly ok to omit the curly braces.
$endgroup$
– Roland Illig
May 26 at 18:34




2




2




$begingroup$
Let’s please collectively stop using the “goto fail” bug as an argument to justify our preferences, and let’s pretend that the bug was caused by lack of braces rather than by a merge conflict that could also have happened with braces (albeit less likely). In fact, the article you link in your answer (and which you probably didn’t read) also says that the lack of braces isn’t the problem here.
$endgroup$
– Konrad Rudolph
May 27 at 10:05





$begingroup$
Let’s please collectively stop using the “goto fail” bug as an argument to justify our preferences, and let’s pretend that the bug was caused by lack of braces rather than by a merge conflict that could also have happened with braces (albeit less likely). In fact, the article you link in your answer (and which you probably didn’t read) also says that the lack of braces isn’t the problem here.
$endgroup$
– Konrad Rudolph
May 27 at 10:05












7












$begingroup$

The following remarks may be nitpicks. Since your code is already quite good, it's all I have to say. :)



#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>


Since all the above headers are from the standard C library, they should be in alphabetical order.



When you include other headers like <sys/type.h>, the order is sometimes important. But not in your simple program.



#define SIZE 3


Having this constant means that anyone else may later change the 3 into a 5 and can expect that the program still works reasonably well. If your code doesn't provide this guarantee, you should add a small comment explaining that this constant should not be modified.



// Draws the board along with rows/cols, numbered.
void draw_board(char board[])


Function signatures in C should not contain arrays since these behave surprisingly in several cases (like multi-dimensional arrays). Also, it sounds strange to say "to draw the board, given a character array". The function signature would sound a lot better like this:



void draw_board(tic_tac_toe_board *board)


This changes the wording to "draw the board, given a board", which is a bit redundant, but it focuses on the problem domain instead of the technical level, which makes the code easier to understand, especially if you want to explain programming to laymen.



To make the above function signature valid, you need to declare the tic_tac_toe_board as a type:



typedef struct 
char cell[SIZE * SIZE];
tic_tac_toe_board;


With this type definition, your code may look like this:



void init_board(tic_tac_toe_board *board)

for (int i = 0; i < SIZE * SIZE; i++)
board->cell[i] = '-';



It's a bit more effort to write board->cell[i] instead of the simple board[i] from before, but it's worth it since you can now talk about a tic-tac-toe board, without having to mention that it is implemented as a character array.



Having this abstraction also means that you can easily extend the board by recording the history of moves, just in case you want to implement an undo feature later:



typedef struct 
char cell[SIZE * SIZE];
int moves_count;
int moves[SIZE * SIZE];
tic_tac_toe_board;


Next topic, the communication with the user:



 printf("%c, pick your position (xy, rc): ", player);


That message is a mistake: xy is not the same as rc since r corresponds to y, not x. It should be either (xy, cr) or (yx, rc).



 scanf("%s", posinput);


Instead of reading a string here, there's a completely different idea. Most keyboards have a numeric block, which by coincidence consists of 3×3 keys. When you map the keyboard layout as follows, the human players just need a single key instead of two to enter a coordinate, plus the position on the keyboard matches exactly the position on the board:



7 8 9
4 5 6
1 2 3


It only works for 3×3 though. But if you accept that limitation:



int pos;
if (scanf("%d", &pos) == 1)
int row = (SIZE - 1) - (pos - 1) / SIZE;
int col = (pos - 1) % SIZE;
int board_pos = col + row * SIZE;



Going further in your code:



if (board[pos] == 'x' || board[pos] == 'o')
return false;


I would rather say board[pos] != '-', for 3 reasons: it is shorter, it is faster, and when you add a third player someday the code is still correct.



// Returns true if there are three of the same chars in a row.
// b = board, p = player. Shortened for readability.


A very nice comment. Short, to the point, and informative.



The rest of the code looks good already. There's just one missing thing. Anecdotal evidence suggests that in the game of tic-tac-toe, players familiar with each other will tie 100% of the time due to the limited number of outcomes. I suggest you implement the tie rule. It's very simple: if SIZE * SIZE moves have been played and there is still no winner, it's a tie.






share|improve this answer









$endgroup$








  • 1




    $begingroup$
    The history should not be part of the ttt_board struct. When you add an AI that recursively tries moves to find forced wins and avoid forced losses, it's going to be passing around copies of boards with a move made, and the history is in the recursion stack frames (local vars). Making the board struct 4x larger (on most C implementations) with an int array sounds terrible.
    $endgroup$
    – Peter Cordes
    May 28 at 5:13










  • $begingroup$
    @Peter that's a good argument, I agree fully with it. It was just the first thing that came to my mind when I wrote the answer and wanted to show how easily a struct can be extended. Maybe it would have been a better idea to add a field char turn that tells whose turn it is.
    $endgroup$
    – Roland Illig
    May 28 at 6:36










  • $begingroup$
    Maybe, but that's redundant for some cases where you want to use a ttt_board. You can nest structs, though, for a complete game-position struct that includes the turn.
    $endgroup$
    – Peter Cordes
    May 28 at 7:15















7












$begingroup$

The following remarks may be nitpicks. Since your code is already quite good, it's all I have to say. :)



#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>


Since all the above headers are from the standard C library, they should be in alphabetical order.



When you include other headers like <sys/type.h>, the order is sometimes important. But not in your simple program.



#define SIZE 3


Having this constant means that anyone else may later change the 3 into a 5 and can expect that the program still works reasonably well. If your code doesn't provide this guarantee, you should add a small comment explaining that this constant should not be modified.



// Draws the board along with rows/cols, numbered.
void draw_board(char board[])


Function signatures in C should not contain arrays since these behave surprisingly in several cases (like multi-dimensional arrays). Also, it sounds strange to say "to draw the board, given a character array". The function signature would sound a lot better like this:



void draw_board(tic_tac_toe_board *board)


This changes the wording to "draw the board, given a board", which is a bit redundant, but it focuses on the problem domain instead of the technical level, which makes the code easier to understand, especially if you want to explain programming to laymen.



To make the above function signature valid, you need to declare the tic_tac_toe_board as a type:



typedef struct 
char cell[SIZE * SIZE];
tic_tac_toe_board;


With this type definition, your code may look like this:



void init_board(tic_tac_toe_board *board)

for (int i = 0; i < SIZE * SIZE; i++)
board->cell[i] = '-';



It's a bit more effort to write board->cell[i] instead of the simple board[i] from before, but it's worth it since you can now talk about a tic-tac-toe board, without having to mention that it is implemented as a character array.



Having this abstraction also means that you can easily extend the board by recording the history of moves, just in case you want to implement an undo feature later:



typedef struct 
char cell[SIZE * SIZE];
int moves_count;
int moves[SIZE * SIZE];
tic_tac_toe_board;


Next topic, the communication with the user:



 printf("%c, pick your position (xy, rc): ", player);


That message is a mistake: xy is not the same as rc since r corresponds to y, not x. It should be either (xy, cr) or (yx, rc).



 scanf("%s", posinput);


Instead of reading a string here, there's a completely different idea. Most keyboards have a numeric block, which by coincidence consists of 3×3 keys. When you map the keyboard layout as follows, the human players just need a single key instead of two to enter a coordinate, plus the position on the keyboard matches exactly the position on the board:



7 8 9
4 5 6
1 2 3


It only works for 3×3 though. But if you accept that limitation:



int pos;
if (scanf("%d", &pos) == 1)
int row = (SIZE - 1) - (pos - 1) / SIZE;
int col = (pos - 1) % SIZE;
int board_pos = col + row * SIZE;



Going further in your code:



if (board[pos] == 'x' || board[pos] == 'o')
return false;


I would rather say board[pos] != '-', for 3 reasons: it is shorter, it is faster, and when you add a third player someday the code is still correct.



// Returns true if there are three of the same chars in a row.
// b = board, p = player. Shortened for readability.


A very nice comment. Short, to the point, and informative.



The rest of the code looks good already. There's just one missing thing. Anecdotal evidence suggests that in the game of tic-tac-toe, players familiar with each other will tie 100% of the time due to the limited number of outcomes. I suggest you implement the tie rule. It's very simple: if SIZE * SIZE moves have been played and there is still no winner, it's a tie.






share|improve this answer









$endgroup$








  • 1




    $begingroup$
    The history should not be part of the ttt_board struct. When you add an AI that recursively tries moves to find forced wins and avoid forced losses, it's going to be passing around copies of boards with a move made, and the history is in the recursion stack frames (local vars). Making the board struct 4x larger (on most C implementations) with an int array sounds terrible.
    $endgroup$
    – Peter Cordes
    May 28 at 5:13










  • $begingroup$
    @Peter that's a good argument, I agree fully with it. It was just the first thing that came to my mind when I wrote the answer and wanted to show how easily a struct can be extended. Maybe it would have been a better idea to add a field char turn that tells whose turn it is.
    $endgroup$
    – Roland Illig
    May 28 at 6:36










  • $begingroup$
    Maybe, but that's redundant for some cases where you want to use a ttt_board. You can nest structs, though, for a complete game-position struct that includes the turn.
    $endgroup$
    – Peter Cordes
    May 28 at 7:15













7












7








7





$begingroup$

The following remarks may be nitpicks. Since your code is already quite good, it's all I have to say. :)



#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>


Since all the above headers are from the standard C library, they should be in alphabetical order.



When you include other headers like <sys/type.h>, the order is sometimes important. But not in your simple program.



#define SIZE 3


Having this constant means that anyone else may later change the 3 into a 5 and can expect that the program still works reasonably well. If your code doesn't provide this guarantee, you should add a small comment explaining that this constant should not be modified.



// Draws the board along with rows/cols, numbered.
void draw_board(char board[])


Function signatures in C should not contain arrays since these behave surprisingly in several cases (like multi-dimensional arrays). Also, it sounds strange to say "to draw the board, given a character array". The function signature would sound a lot better like this:



void draw_board(tic_tac_toe_board *board)


This changes the wording to "draw the board, given a board", which is a bit redundant, but it focuses on the problem domain instead of the technical level, which makes the code easier to understand, especially if you want to explain programming to laymen.



To make the above function signature valid, you need to declare the tic_tac_toe_board as a type:



typedef struct 
char cell[SIZE * SIZE];
tic_tac_toe_board;


With this type definition, your code may look like this:



void init_board(tic_tac_toe_board *board)

for (int i = 0; i < SIZE * SIZE; i++)
board->cell[i] = '-';



It's a bit more effort to write board->cell[i] instead of the simple board[i] from before, but it's worth it since you can now talk about a tic-tac-toe board, without having to mention that it is implemented as a character array.



Having this abstraction also means that you can easily extend the board by recording the history of moves, just in case you want to implement an undo feature later:



typedef struct 
char cell[SIZE * SIZE];
int moves_count;
int moves[SIZE * SIZE];
tic_tac_toe_board;


Next topic, the communication with the user:



 printf("%c, pick your position (xy, rc): ", player);


That message is a mistake: xy is not the same as rc since r corresponds to y, not x. It should be either (xy, cr) or (yx, rc).



 scanf("%s", posinput);


Instead of reading a string here, there's a completely different idea. Most keyboards have a numeric block, which by coincidence consists of 3×3 keys. When you map the keyboard layout as follows, the human players just need a single key instead of two to enter a coordinate, plus the position on the keyboard matches exactly the position on the board:



7 8 9
4 5 6
1 2 3


It only works for 3×3 though. But if you accept that limitation:



int pos;
if (scanf("%d", &pos) == 1)
int row = (SIZE - 1) - (pos - 1) / SIZE;
int col = (pos - 1) % SIZE;
int board_pos = col + row * SIZE;



Going further in your code:



if (board[pos] == 'x' || board[pos] == 'o')
return false;


I would rather say board[pos] != '-', for 3 reasons: it is shorter, it is faster, and when you add a third player someday the code is still correct.



// Returns true if there are three of the same chars in a row.
// b = board, p = player. Shortened for readability.


A very nice comment. Short, to the point, and informative.



The rest of the code looks good already. There's just one missing thing. Anecdotal evidence suggests that in the game of tic-tac-toe, players familiar with each other will tie 100% of the time due to the limited number of outcomes. I suggest you implement the tie rule. It's very simple: if SIZE * SIZE moves have been played and there is still no winner, it's a tie.






share|improve this answer









$endgroup$



The following remarks may be nitpicks. Since your code is already quite good, it's all I have to say. :)



#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>


Since all the above headers are from the standard C library, they should be in alphabetical order.



When you include other headers like <sys/type.h>, the order is sometimes important. But not in your simple program.



#define SIZE 3


Having this constant means that anyone else may later change the 3 into a 5 and can expect that the program still works reasonably well. If your code doesn't provide this guarantee, you should add a small comment explaining that this constant should not be modified.



// Draws the board along with rows/cols, numbered.
void draw_board(char board[])


Function signatures in C should not contain arrays since these behave surprisingly in several cases (like multi-dimensional arrays). Also, it sounds strange to say "to draw the board, given a character array". The function signature would sound a lot better like this:



void draw_board(tic_tac_toe_board *board)


This changes the wording to "draw the board, given a board", which is a bit redundant, but it focuses on the problem domain instead of the technical level, which makes the code easier to understand, especially if you want to explain programming to laymen.



To make the above function signature valid, you need to declare the tic_tac_toe_board as a type:



typedef struct 
char cell[SIZE * SIZE];
tic_tac_toe_board;


With this type definition, your code may look like this:



void init_board(tic_tac_toe_board *board)

for (int i = 0; i < SIZE * SIZE; i++)
board->cell[i] = '-';



It's a bit more effort to write board->cell[i] instead of the simple board[i] from before, but it's worth it since you can now talk about a tic-tac-toe board, without having to mention that it is implemented as a character array.



Having this abstraction also means that you can easily extend the board by recording the history of moves, just in case you want to implement an undo feature later:



typedef struct 
char cell[SIZE * SIZE];
int moves_count;
int moves[SIZE * SIZE];
tic_tac_toe_board;


Next topic, the communication with the user:



 printf("%c, pick your position (xy, rc): ", player);


That message is a mistake: xy is not the same as rc since r corresponds to y, not x. It should be either (xy, cr) or (yx, rc).



 scanf("%s", posinput);


Instead of reading a string here, there's a completely different idea. Most keyboards have a numeric block, which by coincidence consists of 3×3 keys. When you map the keyboard layout as follows, the human players just need a single key instead of two to enter a coordinate, plus the position on the keyboard matches exactly the position on the board:



7 8 9
4 5 6
1 2 3


It only works for 3×3 though. But if you accept that limitation:



int pos;
if (scanf("%d", &pos) == 1)
int row = (SIZE - 1) - (pos - 1) / SIZE;
int col = (pos - 1) % SIZE;
int board_pos = col + row * SIZE;



Going further in your code:



if (board[pos] == 'x' || board[pos] == 'o')
return false;


I would rather say board[pos] != '-', for 3 reasons: it is shorter, it is faster, and when you add a third player someday the code is still correct.



// Returns true if there are three of the same chars in a row.
// b = board, p = player. Shortened for readability.


A very nice comment. Short, to the point, and informative.



The rest of the code looks good already. There's just one missing thing. Anecdotal evidence suggests that in the game of tic-tac-toe, players familiar with each other will tie 100% of the time due to the limited number of outcomes. I suggest you implement the tie rule. It's very simple: if SIZE * SIZE moves have been played and there is still no winner, it's a tie.







share|improve this answer












share|improve this answer



share|improve this answer










answered May 26 at 19:19









Roland IlligRoland Illig

12.9k12051




12.9k12051







  • 1




    $begingroup$
    The history should not be part of the ttt_board struct. When you add an AI that recursively tries moves to find forced wins and avoid forced losses, it's going to be passing around copies of boards with a move made, and the history is in the recursion stack frames (local vars). Making the board struct 4x larger (on most C implementations) with an int array sounds terrible.
    $endgroup$
    – Peter Cordes
    May 28 at 5:13










  • $begingroup$
    @Peter that's a good argument, I agree fully with it. It was just the first thing that came to my mind when I wrote the answer and wanted to show how easily a struct can be extended. Maybe it would have been a better idea to add a field char turn that tells whose turn it is.
    $endgroup$
    – Roland Illig
    May 28 at 6:36










  • $begingroup$
    Maybe, but that's redundant for some cases where you want to use a ttt_board. You can nest structs, though, for a complete game-position struct that includes the turn.
    $endgroup$
    – Peter Cordes
    May 28 at 7:15












  • 1




    $begingroup$
    The history should not be part of the ttt_board struct. When you add an AI that recursively tries moves to find forced wins and avoid forced losses, it's going to be passing around copies of boards with a move made, and the history is in the recursion stack frames (local vars). Making the board struct 4x larger (on most C implementations) with an int array sounds terrible.
    $endgroup$
    – Peter Cordes
    May 28 at 5:13










  • $begingroup$
    @Peter that's a good argument, I agree fully with it. It was just the first thing that came to my mind when I wrote the answer and wanted to show how easily a struct can be extended. Maybe it would have been a better idea to add a field char turn that tells whose turn it is.
    $endgroup$
    – Roland Illig
    May 28 at 6:36










  • $begingroup$
    Maybe, but that's redundant for some cases where you want to use a ttt_board. You can nest structs, though, for a complete game-position struct that includes the turn.
    $endgroup$
    – Peter Cordes
    May 28 at 7:15







1




1




$begingroup$
The history should not be part of the ttt_board struct. When you add an AI that recursively tries moves to find forced wins and avoid forced losses, it's going to be passing around copies of boards with a move made, and the history is in the recursion stack frames (local vars). Making the board struct 4x larger (on most C implementations) with an int array sounds terrible.
$endgroup$
– Peter Cordes
May 28 at 5:13




$begingroup$
The history should not be part of the ttt_board struct. When you add an AI that recursively tries moves to find forced wins and avoid forced losses, it's going to be passing around copies of boards with a move made, and the history is in the recursion stack frames (local vars). Making the board struct 4x larger (on most C implementations) with an int array sounds terrible.
$endgroup$
– Peter Cordes
May 28 at 5:13












$begingroup$
@Peter that's a good argument, I agree fully with it. It was just the first thing that came to my mind when I wrote the answer and wanted to show how easily a struct can be extended. Maybe it would have been a better idea to add a field char turn that tells whose turn it is.
$endgroup$
– Roland Illig
May 28 at 6:36




$begingroup$
@Peter that's a good argument, I agree fully with it. It was just the first thing that came to my mind when I wrote the answer and wanted to show how easily a struct can be extended. Maybe it would have been a better idea to add a field char turn that tells whose turn it is.
$endgroup$
– Roland Illig
May 28 at 6:36












$begingroup$
Maybe, but that's redundant for some cases where you want to use a ttt_board. You can nest structs, though, for a complete game-position struct that includes the turn.
$endgroup$
– Peter Cordes
May 28 at 7:15




$begingroup$
Maybe, but that's redundant for some cases where you want to use a ttt_board. You can nest structs, though, for a complete game-position struct that includes the turn.
$endgroup$
– Peter Cordes
May 28 at 7:15











6












$begingroup$

The code looks clean and readable.



You could rename check to checkForWin to tell what it checks.



I would also suggest to improve that procedure by declaring the lines as a constant array instad of writing a similar test 8 times:



const int lines[8][3] = 
0, 1, 2 , // rows
3, 4, 5 ,
6, 7, 8 ,
0, 3, 6 , // columns
1, 4, 7 ,
2, 5, 8 ,
0, 4, 8 , // diagonals
2, 4, 6
;

bool checkForWin(char b[], char p)

for (int i = 0 ; i < 8 ; i++ )
if (b[lines[i][0]] == p && b[lines[i][1]] == p && b[lines[i][2]] == p)
return true;


// No winnig line found
return false;






share|improve this answer









$endgroup$








  • 2




    $begingroup$
    Neither of those two options are very scalable (it's nice to declare a SIZE constant, less nice if you can't change it anyhow with adapting the logic). The logic is very easy to implement for arrays of every size - check all rows, all columns and the two diagonals. No need to hardcode it.
    $endgroup$
    – Voo
    May 26 at 17:47










  • $begingroup$
    Yes, the SIZE problem needs to be addressed. If you want to honor the SIZE constant, you can generate the array programmatically (and introduce variables for the array sizes). It is clean to define the topology and then use it to drive the program. For instance, if the next step is to have the computer play, and it has to find opportunities for double-threats, it will be much simpler with such a lines array. If you change for a 3x3x3 board, the code doesn't change. There are just more lines.
    $endgroup$
    – Florian F
    May 26 at 18:20










  • $begingroup$
    But as soon as you generate the lines array programmatically, you already have the code to do the check directly (since that's basically what you're doing when creating the array). Just seems rather roundabout.
    $endgroup$
    – Voo
    May 26 at 20:18







  • 1




    $begingroup$
    It is very enlightening to see that you can abstract the board as a generic set of cells joined by a set of lines, to see that you can define the shape of the board in one place and program the gameplay regardless of that shape. It is called separation of concerns.
    $endgroup$
    – Florian F
    May 26 at 21:02






  • 4




    $begingroup$
    Hard coding those “lines” into rows columns and diagonals feels odd. Not very human readable, and at the same time not dynamic to board size. So kind of a lose lose.
    $endgroup$
    – Cooper Buckingham
    May 27 at 2:27















6












$begingroup$

The code looks clean and readable.



You could rename check to checkForWin to tell what it checks.



I would also suggest to improve that procedure by declaring the lines as a constant array instad of writing a similar test 8 times:



const int lines[8][3] = 
0, 1, 2 , // rows
3, 4, 5 ,
6, 7, 8 ,
0, 3, 6 , // columns
1, 4, 7 ,
2, 5, 8 ,
0, 4, 8 , // diagonals
2, 4, 6
;

bool checkForWin(char b[], char p)

for (int i = 0 ; i < 8 ; i++ )
if (b[lines[i][0]] == p && b[lines[i][1]] == p && b[lines[i][2]] == p)
return true;


// No winnig line found
return false;






share|improve this answer









$endgroup$








  • 2




    $begingroup$
    Neither of those two options are very scalable (it's nice to declare a SIZE constant, less nice if you can't change it anyhow with adapting the logic). The logic is very easy to implement for arrays of every size - check all rows, all columns and the two diagonals. No need to hardcode it.
    $endgroup$
    – Voo
    May 26 at 17:47










  • $begingroup$
    Yes, the SIZE problem needs to be addressed. If you want to honor the SIZE constant, you can generate the array programmatically (and introduce variables for the array sizes). It is clean to define the topology and then use it to drive the program. For instance, if the next step is to have the computer play, and it has to find opportunities for double-threats, it will be much simpler with such a lines array. If you change for a 3x3x3 board, the code doesn't change. There are just more lines.
    $endgroup$
    – Florian F
    May 26 at 18:20










  • $begingroup$
    But as soon as you generate the lines array programmatically, you already have the code to do the check directly (since that's basically what you're doing when creating the array). Just seems rather roundabout.
    $endgroup$
    – Voo
    May 26 at 20:18







  • 1




    $begingroup$
    It is very enlightening to see that you can abstract the board as a generic set of cells joined by a set of lines, to see that you can define the shape of the board in one place and program the gameplay regardless of that shape. It is called separation of concerns.
    $endgroup$
    – Florian F
    May 26 at 21:02






  • 4




    $begingroup$
    Hard coding those “lines” into rows columns and diagonals feels odd. Not very human readable, and at the same time not dynamic to board size. So kind of a lose lose.
    $endgroup$
    – Cooper Buckingham
    May 27 at 2:27













6












6








6





$begingroup$

The code looks clean and readable.



You could rename check to checkForWin to tell what it checks.



I would also suggest to improve that procedure by declaring the lines as a constant array instad of writing a similar test 8 times:



const int lines[8][3] = 
0, 1, 2 , // rows
3, 4, 5 ,
6, 7, 8 ,
0, 3, 6 , // columns
1, 4, 7 ,
2, 5, 8 ,
0, 4, 8 , // diagonals
2, 4, 6
;

bool checkForWin(char b[], char p)

for (int i = 0 ; i < 8 ; i++ )
if (b[lines[i][0]] == p && b[lines[i][1]] == p && b[lines[i][2]] == p)
return true;


// No winnig line found
return false;






share|improve this answer









$endgroup$



The code looks clean and readable.



You could rename check to checkForWin to tell what it checks.



I would also suggest to improve that procedure by declaring the lines as a constant array instad of writing a similar test 8 times:



const int lines[8][3] = 
0, 1, 2 , // rows
3, 4, 5 ,
6, 7, 8 ,
0, 3, 6 , // columns
1, 4, 7 ,
2, 5, 8 ,
0, 4, 8 , // diagonals
2, 4, 6
;

bool checkForWin(char b[], char p)

for (int i = 0 ; i < 8 ; i++ )
if (b[lines[i][0]] == p && b[lines[i][1]] == p && b[lines[i][2]] == p)
return true;


// No winnig line found
return false;







share|improve this answer












share|improve this answer



share|improve this answer










answered May 26 at 16:11









Florian FFlorian F

49728




49728







  • 2




    $begingroup$
    Neither of those two options are very scalable (it's nice to declare a SIZE constant, less nice if you can't change it anyhow with adapting the logic). The logic is very easy to implement for arrays of every size - check all rows, all columns and the two diagonals. No need to hardcode it.
    $endgroup$
    – Voo
    May 26 at 17:47










  • $begingroup$
    Yes, the SIZE problem needs to be addressed. If you want to honor the SIZE constant, you can generate the array programmatically (and introduce variables for the array sizes). It is clean to define the topology and then use it to drive the program. For instance, if the next step is to have the computer play, and it has to find opportunities for double-threats, it will be much simpler with such a lines array. If you change for a 3x3x3 board, the code doesn't change. There are just more lines.
    $endgroup$
    – Florian F
    May 26 at 18:20










  • $begingroup$
    But as soon as you generate the lines array programmatically, you already have the code to do the check directly (since that's basically what you're doing when creating the array). Just seems rather roundabout.
    $endgroup$
    – Voo
    May 26 at 20:18







  • 1




    $begingroup$
    It is very enlightening to see that you can abstract the board as a generic set of cells joined by a set of lines, to see that you can define the shape of the board in one place and program the gameplay regardless of that shape. It is called separation of concerns.
    $endgroup$
    – Florian F
    May 26 at 21:02






  • 4




    $begingroup$
    Hard coding those “lines” into rows columns and diagonals feels odd. Not very human readable, and at the same time not dynamic to board size. So kind of a lose lose.
    $endgroup$
    – Cooper Buckingham
    May 27 at 2:27












  • 2




    $begingroup$
    Neither of those two options are very scalable (it's nice to declare a SIZE constant, less nice if you can't change it anyhow with adapting the logic). The logic is very easy to implement for arrays of every size - check all rows, all columns and the two diagonals. No need to hardcode it.
    $endgroup$
    – Voo
    May 26 at 17:47










  • $begingroup$
    Yes, the SIZE problem needs to be addressed. If you want to honor the SIZE constant, you can generate the array programmatically (and introduce variables for the array sizes). It is clean to define the topology and then use it to drive the program. For instance, if the next step is to have the computer play, and it has to find opportunities for double-threats, it will be much simpler with such a lines array. If you change for a 3x3x3 board, the code doesn't change. There are just more lines.
    $endgroup$
    – Florian F
    May 26 at 18:20










  • $begingroup$
    But as soon as you generate the lines array programmatically, you already have the code to do the check directly (since that's basically what you're doing when creating the array). Just seems rather roundabout.
    $endgroup$
    – Voo
    May 26 at 20:18







  • 1




    $begingroup$
    It is very enlightening to see that you can abstract the board as a generic set of cells joined by a set of lines, to see that you can define the shape of the board in one place and program the gameplay regardless of that shape. It is called separation of concerns.
    $endgroup$
    – Florian F
    May 26 at 21:02






  • 4




    $begingroup$
    Hard coding those “lines” into rows columns and diagonals feels odd. Not very human readable, and at the same time not dynamic to board size. So kind of a lose lose.
    $endgroup$
    – Cooper Buckingham
    May 27 at 2:27







2




2




$begingroup$
Neither of those two options are very scalable (it's nice to declare a SIZE constant, less nice if you can't change it anyhow with adapting the logic). The logic is very easy to implement for arrays of every size - check all rows, all columns and the two diagonals. No need to hardcode it.
$endgroup$
– Voo
May 26 at 17:47




$begingroup$
Neither of those two options are very scalable (it's nice to declare a SIZE constant, less nice if you can't change it anyhow with adapting the logic). The logic is very easy to implement for arrays of every size - check all rows, all columns and the two diagonals. No need to hardcode it.
$endgroup$
– Voo
May 26 at 17:47












$begingroup$
Yes, the SIZE problem needs to be addressed. If you want to honor the SIZE constant, you can generate the array programmatically (and introduce variables for the array sizes). It is clean to define the topology and then use it to drive the program. For instance, if the next step is to have the computer play, and it has to find opportunities for double-threats, it will be much simpler with such a lines array. If you change for a 3x3x3 board, the code doesn't change. There are just more lines.
$endgroup$
– Florian F
May 26 at 18:20




$begingroup$
Yes, the SIZE problem needs to be addressed. If you want to honor the SIZE constant, you can generate the array programmatically (and introduce variables for the array sizes). It is clean to define the topology and then use it to drive the program. For instance, if the next step is to have the computer play, and it has to find opportunities for double-threats, it will be much simpler with such a lines array. If you change for a 3x3x3 board, the code doesn't change. There are just more lines.
$endgroup$
– Florian F
May 26 at 18:20












$begingroup$
But as soon as you generate the lines array programmatically, you already have the code to do the check directly (since that's basically what you're doing when creating the array). Just seems rather roundabout.
$endgroup$
– Voo
May 26 at 20:18





$begingroup$
But as soon as you generate the lines array programmatically, you already have the code to do the check directly (since that's basically what you're doing when creating the array). Just seems rather roundabout.
$endgroup$
– Voo
May 26 at 20:18





1




1




$begingroup$
It is very enlightening to see that you can abstract the board as a generic set of cells joined by a set of lines, to see that you can define the shape of the board in one place and program the gameplay regardless of that shape. It is called separation of concerns.
$endgroup$
– Florian F
May 26 at 21:02




$begingroup$
It is very enlightening to see that you can abstract the board as a generic set of cells joined by a set of lines, to see that you can define the shape of the board in one place and program the gameplay regardless of that shape. It is called separation of concerns.
$endgroup$
– Florian F
May 26 at 21:02




4




4




$begingroup$
Hard coding those “lines” into rows columns and diagonals feels odd. Not very human readable, and at the same time not dynamic to board size. So kind of a lose lose.
$endgroup$
– Cooper Buckingham
May 27 at 2:27




$begingroup$
Hard coding those “lines” into rows columns and diagonals feels odd. Not very human readable, and at the same time not dynamic to board size. So kind of a lose lose.
$endgroup$
– Cooper Buckingham
May 27 at 2:27











5












$begingroup$

Starting from the top, defining the SIZE macro is good - this potentially allows you to change the size of the board by modifying one number. It is especially good because it allows you to later come back and change it to the name of a global variable instead of a constant number, which then allows you to define the size of the board runtime.



You then throw away some of that utility in draw_board(), by printing the column numbers as a constant ("# 1 2 3n"), rather than by using the nice macro you defined just a few lines prior. You should print the column numbers with a loop, just like you print the board itself. Consider also writing ones for the symbols (i.e. EMPTY, PLAYER1 and PLAYER2) so you can change them if desired.



init_board() is great, but you could use memset(board, '-', SIZE * SIZE) instead of the loop.



In place(), your scanf() format should not be "%s". You're expecting two numbers (or digits), so scan for those: "%d%d" or "%c%c" will do the job and allow for improvements later down the line. In addition, you can check for whether there already is a symbol at the coordinate in a single comparison with board[pos] != '-'.



Also, you should return the index on success and a faulty value like -1 on failure, so that your check() can take that as input and only check the rows, columns and diagonals that the token is a part of. Something like this:



// In the main loop:
int index = place();
if (index >= 0)

if (check(board, index, player))
break; // Placement succeeded and resulted in a win, break loop.
/* … */



Next, in check(), you check a 3x3 board, completely ignoring that nice SIZE macro you defined. You should instead use a loop to check because then you can freely change the SIZE of the board later down the line. Combined with the index for input, you'd have something like...



bool check(char board[], int index, char player)
colCount == SIZE


Finally, you may want to define a function-like macro or a function for indexing the board to reduce the chance of having a typo cause a bug and to make it somewhat more legible. It can also reduce the amount of work you need to put in if you choose to change the indexing later on.



#define AT(X, Y) [(Y) * SIZE + (X)]
int at(int x, int y) return y * SIZE + x;
// which allow you to write something like...
if ( board AT(x, y) == '-' )
board[at(x, y)] = 'x';





share|improve this answer










New contributor



Rogem is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.





$endgroup$








  • 3




    $begingroup$
    Using a macro for something that can easily be done by a function is rather awful.
    $endgroup$
    – Voo
    May 26 at 18:38










  • $begingroup$
    @Voo Uh... What do you think macros are useful for?
    $endgroup$
    – Rogem
    May 26 at 18:52






  • 4




    $begingroup$
    Macros are for things that cannot be done by inline functions. Which, yes, means they should be used as rarely as possible.
    $endgroup$
    – Cody Gray
    May 26 at 19:58






  • 1




    $begingroup$
    macros dont follow the rules of the language. they are maintenance hazards and introduve hard to spot bugs. besides using them for constants in c you should rarely use them...
    $endgroup$
    – Sandro4912
    May 27 at 3:53






  • 1




    $begingroup$
    @PeterCordes It will also match a leading - if there is one, but reading input is a beast of its own...
    $endgroup$
    – Rogem
    May 28 at 15:04















5












$begingroup$

Starting from the top, defining the SIZE macro is good - this potentially allows you to change the size of the board by modifying one number. It is especially good because it allows you to later come back and change it to the name of a global variable instead of a constant number, which then allows you to define the size of the board runtime.



You then throw away some of that utility in draw_board(), by printing the column numbers as a constant ("# 1 2 3n"), rather than by using the nice macro you defined just a few lines prior. You should print the column numbers with a loop, just like you print the board itself. Consider also writing ones for the symbols (i.e. EMPTY, PLAYER1 and PLAYER2) so you can change them if desired.



init_board() is great, but you could use memset(board, '-', SIZE * SIZE) instead of the loop.



In place(), your scanf() format should not be "%s". You're expecting two numbers (or digits), so scan for those: "%d%d" or "%c%c" will do the job and allow for improvements later down the line. In addition, you can check for whether there already is a symbol at the coordinate in a single comparison with board[pos] != '-'.



Also, you should return the index on success and a faulty value like -1 on failure, so that your check() can take that as input and only check the rows, columns and diagonals that the token is a part of. Something like this:



// In the main loop:
int index = place();
if (index >= 0)

if (check(board, index, player))
break; // Placement succeeded and resulted in a win, break loop.
/* … */



Next, in check(), you check a 3x3 board, completely ignoring that nice SIZE macro you defined. You should instead use a loop to check because then you can freely change the SIZE of the board later down the line. Combined with the index for input, you'd have something like...



bool check(char board[], int index, char player)
colCount == SIZE


Finally, you may want to define a function-like macro or a function for indexing the board to reduce the chance of having a typo cause a bug and to make it somewhat more legible. It can also reduce the amount of work you need to put in if you choose to change the indexing later on.



#define AT(X, Y) [(Y) * SIZE + (X)]
int at(int x, int y) return y * SIZE + x;
// which allow you to write something like...
if ( board AT(x, y) == '-' )
board[at(x, y)] = 'x';





share|improve this answer










New contributor



Rogem is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.





$endgroup$








  • 3




    $begingroup$
    Using a macro for something that can easily be done by a function is rather awful.
    $endgroup$
    – Voo
    May 26 at 18:38










  • $begingroup$
    @Voo Uh... What do you think macros are useful for?
    $endgroup$
    – Rogem
    May 26 at 18:52






  • 4




    $begingroup$
    Macros are for things that cannot be done by inline functions. Which, yes, means they should be used as rarely as possible.
    $endgroup$
    – Cody Gray
    May 26 at 19:58






  • 1




    $begingroup$
    macros dont follow the rules of the language. they are maintenance hazards and introduve hard to spot bugs. besides using them for constants in c you should rarely use them...
    $endgroup$
    – Sandro4912
    May 27 at 3:53






  • 1




    $begingroup$
    @PeterCordes It will also match a leading - if there is one, but reading input is a beast of its own...
    $endgroup$
    – Rogem
    May 28 at 15:04













5












5








5





$begingroup$

Starting from the top, defining the SIZE macro is good - this potentially allows you to change the size of the board by modifying one number. It is especially good because it allows you to later come back and change it to the name of a global variable instead of a constant number, which then allows you to define the size of the board runtime.



You then throw away some of that utility in draw_board(), by printing the column numbers as a constant ("# 1 2 3n"), rather than by using the nice macro you defined just a few lines prior. You should print the column numbers with a loop, just like you print the board itself. Consider also writing ones for the symbols (i.e. EMPTY, PLAYER1 and PLAYER2) so you can change them if desired.



init_board() is great, but you could use memset(board, '-', SIZE * SIZE) instead of the loop.



In place(), your scanf() format should not be "%s". You're expecting two numbers (or digits), so scan for those: "%d%d" or "%c%c" will do the job and allow for improvements later down the line. In addition, you can check for whether there already is a symbol at the coordinate in a single comparison with board[pos] != '-'.



Also, you should return the index on success and a faulty value like -1 on failure, so that your check() can take that as input and only check the rows, columns and diagonals that the token is a part of. Something like this:



// In the main loop:
int index = place();
if (index >= 0)

if (check(board, index, player))
break; // Placement succeeded and resulted in a win, break loop.
/* … */



Next, in check(), you check a 3x3 board, completely ignoring that nice SIZE macro you defined. You should instead use a loop to check because then you can freely change the SIZE of the board later down the line. Combined with the index for input, you'd have something like...



bool check(char board[], int index, char player)
colCount == SIZE


Finally, you may want to define a function-like macro or a function for indexing the board to reduce the chance of having a typo cause a bug and to make it somewhat more legible. It can also reduce the amount of work you need to put in if you choose to change the indexing later on.



#define AT(X, Y) [(Y) * SIZE + (X)]
int at(int x, int y) return y * SIZE + x;
// which allow you to write something like...
if ( board AT(x, y) == '-' )
board[at(x, y)] = 'x';





share|improve this answer










New contributor



Rogem is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.





$endgroup$



Starting from the top, defining the SIZE macro is good - this potentially allows you to change the size of the board by modifying one number. It is especially good because it allows you to later come back and change it to the name of a global variable instead of a constant number, which then allows you to define the size of the board runtime.



You then throw away some of that utility in draw_board(), by printing the column numbers as a constant ("# 1 2 3n"), rather than by using the nice macro you defined just a few lines prior. You should print the column numbers with a loop, just like you print the board itself. Consider also writing ones for the symbols (i.e. EMPTY, PLAYER1 and PLAYER2) so you can change them if desired.



init_board() is great, but you could use memset(board, '-', SIZE * SIZE) instead of the loop.



In place(), your scanf() format should not be "%s". You're expecting two numbers (or digits), so scan for those: "%d%d" or "%c%c" will do the job and allow for improvements later down the line. In addition, you can check for whether there already is a symbol at the coordinate in a single comparison with board[pos] != '-'.



Also, you should return the index on success and a faulty value like -1 on failure, so that your check() can take that as input and only check the rows, columns and diagonals that the token is a part of. Something like this:



// In the main loop:
int index = place();
if (index >= 0)

if (check(board, index, player))
break; // Placement succeeded and resulted in a win, break loop.
/* … */



Next, in check(), you check a 3x3 board, completely ignoring that nice SIZE macro you defined. You should instead use a loop to check because then you can freely change the SIZE of the board later down the line. Combined with the index for input, you'd have something like...



bool check(char board[], int index, char player)
colCount == SIZE


Finally, you may want to define a function-like macro or a function for indexing the board to reduce the chance of having a typo cause a bug and to make it somewhat more legible. It can also reduce the amount of work you need to put in if you choose to change the indexing later on.



#define AT(X, Y) [(Y) * SIZE + (X)]
int at(int x, int y) return y * SIZE + x;
// which allow you to write something like...
if ( board AT(x, y) == '-' )
board[at(x, y)] = 'x';






share|improve this answer










New contributor



Rogem is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.








share|improve this answer



share|improve this answer








edited May 26 at 23:51





















New contributor



Rogem is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.








answered May 26 at 18:27









RogemRogem

1514




1514




New contributor



Rogem is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.




New contributor




Rogem is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.









  • 3




    $begingroup$
    Using a macro for something that can easily be done by a function is rather awful.
    $endgroup$
    – Voo
    May 26 at 18:38










  • $begingroup$
    @Voo Uh... What do you think macros are useful for?
    $endgroup$
    – Rogem
    May 26 at 18:52






  • 4




    $begingroup$
    Macros are for things that cannot be done by inline functions. Which, yes, means they should be used as rarely as possible.
    $endgroup$
    – Cody Gray
    May 26 at 19:58






  • 1




    $begingroup$
    macros dont follow the rules of the language. they are maintenance hazards and introduve hard to spot bugs. besides using them for constants in c you should rarely use them...
    $endgroup$
    – Sandro4912
    May 27 at 3:53






  • 1




    $begingroup$
    @PeterCordes It will also match a leading - if there is one, but reading input is a beast of its own...
    $endgroup$
    – Rogem
    May 28 at 15:04












  • 3




    $begingroup$
    Using a macro for something that can easily be done by a function is rather awful.
    $endgroup$
    – Voo
    May 26 at 18:38










  • $begingroup$
    @Voo Uh... What do you think macros are useful for?
    $endgroup$
    – Rogem
    May 26 at 18:52






  • 4




    $begingroup$
    Macros are for things that cannot be done by inline functions. Which, yes, means they should be used as rarely as possible.
    $endgroup$
    – Cody Gray
    May 26 at 19:58






  • 1




    $begingroup$
    macros dont follow the rules of the language. they are maintenance hazards and introduve hard to spot bugs. besides using them for constants in c you should rarely use them...
    $endgroup$
    – Sandro4912
    May 27 at 3:53






  • 1




    $begingroup$
    @PeterCordes It will also match a leading - if there is one, but reading input is a beast of its own...
    $endgroup$
    – Rogem
    May 28 at 15:04







3




3




$begingroup$
Using a macro for something that can easily be done by a function is rather awful.
$endgroup$
– Voo
May 26 at 18:38




$begingroup$
Using a macro for something that can easily be done by a function is rather awful.
$endgroup$
– Voo
May 26 at 18:38












$begingroup$
@Voo Uh... What do you think macros are useful for?
$endgroup$
– Rogem
May 26 at 18:52




$begingroup$
@Voo Uh... What do you think macros are useful for?
$endgroup$
– Rogem
May 26 at 18:52




4




4




$begingroup$
Macros are for things that cannot be done by inline functions. Which, yes, means they should be used as rarely as possible.
$endgroup$
– Cody Gray
May 26 at 19:58




$begingroup$
Macros are for things that cannot be done by inline functions. Which, yes, means they should be used as rarely as possible.
$endgroup$
– Cody Gray
May 26 at 19:58




1




1




$begingroup$
macros dont follow the rules of the language. they are maintenance hazards and introduve hard to spot bugs. besides using them for constants in c you should rarely use them...
$endgroup$
– Sandro4912
May 27 at 3:53




$begingroup$
macros dont follow the rules of the language. they are maintenance hazards and introduve hard to spot bugs. besides using them for constants in c you should rarely use them...
$endgroup$
– Sandro4912
May 27 at 3:53




1




1




$begingroup$
@PeterCordes It will also match a leading - if there is one, but reading input is a beast of its own...
$endgroup$
– Rogem
May 28 at 15:04




$begingroup$
@PeterCordes It will also match a leading - if there is one, but reading input is a beast of its own...
$endgroup$
– Rogem
May 28 at 15:04











2












$begingroup$

Overall, this is well-written code. One thing I would change besides what has already been suggested by others is your call to system("clear") which creates an OS-specific dependency.



You can instead use #ifdef to check the platform and then call the appropriate command to clear the screen. Additionally, this can be wrapped in a function to allow for code that is easy to reuse.



Example:



void clear() 
#ifdef _WIN32
system("cls");
#else
system("clear");
#endif






share|improve this answer









$endgroup$








  • 1




    $begingroup$
    Thanks, I was thinking about adding something like this, but I just ended up forgetting about it. I didn't think it was that simple!
    $endgroup$
    – J. Czekaj
    2 days ago















2












$begingroup$

Overall, this is well-written code. One thing I would change besides what has already been suggested by others is your call to system("clear") which creates an OS-specific dependency.



You can instead use #ifdef to check the platform and then call the appropriate command to clear the screen. Additionally, this can be wrapped in a function to allow for code that is easy to reuse.



Example:



void clear() 
#ifdef _WIN32
system("cls");
#else
system("clear");
#endif






share|improve this answer









$endgroup$








  • 1




    $begingroup$
    Thanks, I was thinking about adding something like this, but I just ended up forgetting about it. I didn't think it was that simple!
    $endgroup$
    – J. Czekaj
    2 days ago













2












2








2





$begingroup$

Overall, this is well-written code. One thing I would change besides what has already been suggested by others is your call to system("clear") which creates an OS-specific dependency.



You can instead use #ifdef to check the platform and then call the appropriate command to clear the screen. Additionally, this can be wrapped in a function to allow for code that is easy to reuse.



Example:



void clear() 
#ifdef _WIN32
system("cls");
#else
system("clear");
#endif






share|improve this answer









$endgroup$



Overall, this is well-written code. One thing I would change besides what has already been suggested by others is your call to system("clear") which creates an OS-specific dependency.



You can instead use #ifdef to check the platform and then call the appropriate command to clear the screen. Additionally, this can be wrapped in a function to allow for code that is easy to reuse.



Example:



void clear() 
#ifdef _WIN32
system("cls");
#else
system("clear");
#endif







share|improve this answer












share|improve this answer



share|improve this answer










answered 2 days ago









FarazFaraz

413211




413211







  • 1




    $begingroup$
    Thanks, I was thinking about adding something like this, but I just ended up forgetting about it. I didn't think it was that simple!
    $endgroup$
    – J. Czekaj
    2 days ago












  • 1




    $begingroup$
    Thanks, I was thinking about adding something like this, but I just ended up forgetting about it. I didn't think it was that simple!
    $endgroup$
    – J. Czekaj
    2 days ago







1




1




$begingroup$
Thanks, I was thinking about adding something like this, but I just ended up forgetting about it. I didn't think it was that simple!
$endgroup$
– J. Czekaj
2 days ago




$begingroup$
Thanks, I was thinking about adding something like this, but I just ended up forgetting about it. I didn't think it was that simple!
$endgroup$
– J. Czekaj
2 days ago










J. Czekaj is a new contributor. Be nice, and check out our Code of Conduct.









draft saved

draft discarded


















J. Czekaj is a new contributor. Be nice, and check out our Code of Conduct.












J. Czekaj is a new contributor. Be nice, and check out our Code of Conduct.











J. Czekaj is a new contributor. Be nice, and check out our Code of Conduct.














Thanks for contributing an answer to Code Review Stack Exchange!


  • Please be sure to answer the question. Provide details and share your research!

But avoid


  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.

Use MathJax to format equations. MathJax reference.


To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f221035%2ftic-tac-toe-for-the-terminal%23new-answer', 'question_page');

);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

Grendel Contents Story Scholarship Depictions Notes References Navigation menu10.1093/notesj/gjn112Berserkeree

Area configuration aggregation error after install Porto themeMagento 2.1 CE Installed but front/backend not loading/workingCSS not loading on page within Magento 2 pageCannot install module in Magento 2no commands defined in the “setup” namespace. in Magento2Magento 2: Static files are present but shows 404Why do i have to always run the commands to clean cache in Magento 2.1.8?Failure reason: 'Unable to unserialize value.'Error 500 after magento migrationIn production mode the site does not loadMagento 2 : Error 500 after installing

Middle Expansion Olielle Resaix Definition: Uttering songs of triumph shouting with joy triumphant exulting Sejunction Journal 붙다 달 고급 품목 외출 The stretch trades the screeching tin. Definition: The act of speaking with a drawl a drawl Cough Sand Definition: An uproar a quarrel a noisy outbreak Shake Iron Publicize Horse House Baby 사과 Resaix Flaggy Jelly Temporary Unequaled Puppet A drop in the bucket Shrew 성격 회원 성질 미팅 The burn frames the tacky quality. Materialistic The smoke reduces the way. Yammoe Nondescript Cheek 얼굴 배 약하다 날리다 타다 The illegal country shows the iron. Help Rule Drearien Smoke Teaching Meaty Wasp Abraham Lincoln Jaws 진심 수리하다 Size Cork Idea Convert Think Lark John Lennon 거울 청소 군 추천하다 아이스크림