12
\$\begingroup\$

I made this Tic Tac Toe game in C. It works but I thinks it could be programmed better. You have the option to play alone or between two players. It also lets the player select if he uses 'X' or 'O'. Please check it out and review.

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

char marks[3][3]; /*stores each move the players make*/
int counter = 0; /*counter to check if space available*/

void initialize_rdm(void);  /*initializes random number generator*/
void initialize_grid(void); /*resets the array marks to numbers*/
void grid(void); /*prints the grid and the array*/
int checker(char c, int player); /*checks if the grid selected is available*/
void result(void); /*checks if there is a winner and prints result*/
void retry(void); /*after game finished, can call main function again*/
void ai_opponent(int n); /*randomly fills the grid, used as opponent for player*/
void singleplayer(int n); /*calls all funtion used to play alone, against computer*/
void multiplayer(void); /*calls all funtions used to play between to people*/

int main(void){
int op, x;
initialize_rdm();
initialize_grid();
grid();
printf("\tGame Options:\n\n\t1 - Player 1 vs. Player 2.");
printf("\n\n\t2 - Player vs. Computer.\n\n\t3 - Computer vs. Computer (EXTRA MODE).\n\n\t4 - Exit Game.");
printf("\n\nSelect an option: ");

for(;;){
    scanf("%d", &op);
    
    switch(op){
    
        case 1:
            multiplayer();
            
        case 2:
            grid();
            printf("\tHow would you like to play?.\n\n");
            printf("\t1 - Play using 'X' (Player 1).\n\n\t2 - Play using 'O' (Player 2).");
            printf("\n\n\nSelect an option: ");
            scanf("%d", &x);
            
            while(x != 1 && x != 2){
                printf("\nERROR! Please select a valid option: ");
                scanf("%d", &x);
            }
            singleplayer(x);
            
        case 3:
            for(;;){
                ai_opponent(1);
                result();
                ai_opponent(2);
                result();
            }
            case 4:
                printf("\nThanks for Playing! :D");
                printf("\n\nMade by:\n\nMe XDXDXDXDXD.");
                exit (EXIT_SUCCESS);
                
        default:
            printf("\nERROR! Please select a valid option: ");
    }
}
}
void initialize_rdm(void){
srand((unsigned) time(NULL));
}
void initialize_grid(void){
int i, j;
char k = '1';

for(i = 0; i < 3; i++){
    for(j = 0; j < 3; j++){
        marks[i][j] = k++;
    }
}
}
void grid(void){
                 system("CLS");
                 printf("Tic-Tac-Toe: The Game / Ta-Te-Ti: El Juego\n");
                 printf("\n\t        |       |\n");
                 printf("\t      %c |   %c   | %c\n", marks[0][0], marks[0][1], marks[0][2]);
                 printf("\t        |       |\n");
                 printf("\t-------------------------\n");
                 printf("\t        |       |\n");
                 printf("\t      %c |   %c   | %c\n", marks[1][0], marks[1][1], marks[1][2]);
                 printf("\t        |       |\n");
                 printf("\t-------------------------\n");
                 printf("\t        |       |\n");
                 printf("\t      %c |   %c   | %c\n", marks[2][0], marks[2][1], marks[2][2]);
                 printf("\t        |       |\n\n");
}
int checker(char c, int player){
int i,j;

if(c < '1' || c > '9'){
    printf("\nERROR! Please select a valid grid: ");
    return 0;
}

for(i = 0; i < 3; i++){
    for(j = 0; j < 3; j++){
        if(marks[i][j] == c){
            switch(player){
                
                case 1: 
                    counter++; 
                    marks[i][j] = 'X'; 
                    return 1;
                    
                case 2: 
                    counter++; 
                    marks[i][j] = 'O'; 
                    return 1;
            }
        }
    }
}
printf("\nGRID ALREADY FILLED!\n\nPlease select another grid: ");
return 0;
}
void result(void){
float condition;
int i, j, winner = 3;
grid();

for(i = 0; i < 3; i++){
    for(j = 0, condition = 0; j < 3; j++){ /*checks rows*/
    
        if(marks[i][j] == 'X' || marks[i][j] == 'O'){
            condition += marks[i][j];
        }
        if((condition / 'X') == 3.0){
            winner = 1;
        } else if((condition / 'O') == 3.0){
            winner = 2;
        }
    }
}
for(j = 0; j < 3; j++){
    for(i = 0, condition = 0; i < 3; i++){ /*checks columns*/
        
        if(marks[i][j] == 'X' || marks[i][j] == 'O'){
            condition += marks[i][j];
        } 
        if((condition / 'X') == 3.0){
            winner = 1;
        } else if((condition / 'O') == 3.0){
            winner = 2;
        }
    }
}
for(i = 0, j = 0, condition = 0; i < 3; i++, j++){ /*checks diagonally*/
    
    if(marks[i][j] == 'X' || marks[i][j] == 'O'){
        condition += marks[i][j];
    }
    if((condition / 'X') == 3.0){
        winner = 1;
    } else if((condition / 'O') == 3.0){
        winner = 2;
    }
}
for(i = 2, j = 0, condition = 0; j < 3; i--, j++){ /*checks diagonally*/
    
    if(marks[i][j] == 'X' || marks[i][j] == 'O'){
        condition += marks[i][j];
    }
    if((condition / 'X') == 3.0){
        winner = 1;
    } else if((condition / 'O') == 3.0){
        winner = 2;
    }
}
if(counter >= 9 && winner == 3)
    winner = 0;

switch(winner){
    case 0:
        printf("\a\nIT'S A DRAW!");
        retry();
        
    case 1:
        printf("\aPLAYER 1 WINS!");
        retry();
        
    case 2:
        printf("\aPLAYER 2 WINS!");
        retry();
        
    default: return;
    }
}
void retry(void){
char c;
counter = 0;
printf("\n\nWould you like to play again?(Y/N): ");
scanf(" %c", &c);

if(c == 'Y' || c == 'y'){
    main(); 
} else{
    printf("\n\nThanks for Playing! :)");
    printf("\n\nMade by:\n\nInsert Students names xd.");
    exit(EXIT_SUCCESS);
}
}
void ai_opponent(int n){
int a, b, i;
for(;;){
    a = rand() % 3;
    b = rand() % 3;
    
    if(marks[a][b] != 'X' && marks[a][b] != 'O'){
        switch(n){
            case 1:
                marks[a][b] = 'X';
                counter++;
                return; 
                
            case 2:
                marks[a][b] = 'O';
                counter++;
                return; 
        }
    }
}
}
void singleplayer(int n){
char c;
grid();

for(;;){
    if(n == 1){
        printf("\nPlease select a grid: ");
    
        do{
            scanf(" %c", &c);   
        } while(checker(c, n) != 1);
    
        result();
        ai_opponent(2 / n);
    } else if(n == 2){
        ai_opponent(2 / n);
        result();
        printf("\nPlease select a grid: ");
        do{
            scanf(" %c", &c);   
        } while(checker(c, n) != 1);
    }
    result();
}
}
           void multiplayer(void){
           char c;
           grid();

for(;;){
    printf("\nPlayer 1: Please select a grid: ");
    
    do{
        scanf(" %c", &c);   
    } while(checker(c, 1) != 1);

    result();
    printf("\nPlayer 2: Please select a grid: ");
    
    do{
        scanf(" %c", &c);   
    } while(checker(c, 2) != 1);
    result();
}
}
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Not enough for a review, but definitely work on your indentation. It took me a bit to see where certain functions ended. \$\endgroup\$
    – Linny
    Jun 29 '20 at 18:19
11
\$\begingroup\$

Please check it if you can, and give your opinion!

Enable more warnings

warning: this statement may fall through [-Wimplicit-fallthrough=]

unused variables `int a, b, i;`

Spell check

funtion

Do not re-enter main()

Yes, it is possible, yet makes code review and debug a bear and hard to spin this code off into its own TicTacToe() function. Don't do that.

if (c == 'Y' || c == 'y') {
  // main();
  alternative code

Flush when you are done

To insure output is seen when expected, usual a final '\n' is enough for line buffered stdout.

// printf("\nThanks for Playing! :D");
printf("\nThanks for Playing! :D\n");
//                              ^^

Pedantically, could use fflush(stdout); instead.

printf("\nThanks for Playing! :D");
fflush(stdout);

This advice applies to all the printf()s.

Format

Always a personal preference, yet at a minimum, add a blank line between functions.

Why float?

Use of floating point here makes little sense.

// float condition;
// if((condition / 'X') == 3.0){

int condition;
if (condition == 3*'X') {

Further:

Accumulating with condition += marks[i][j]; and testing with (condition / 'X') == 3.0 or condition == 3*'X' could incorrectly be true under select character coding and alternative values for the "empty" squares.

Alternative:

int32_t condition = 0;
...
condition = condition*256 + marks[i][j];
... 
if (condition == 'X'*(65536 + 256 + 1))) { 

Test evil user input

Robust code would check the return value of scanf() before using scanned results.

//scanf(...
if (scanf(...) != Expected_Scan_Count) Handle_Error();
\$\endgroup\$
5
  • 2
    \$\begingroup\$ Overall agree. However, flushing the final string before the program exits is really not necessary. There will be an implicit flush on exit. \$\endgroup\$
    – Reinderien
    Jun 26 '20 at 3:46
  • 3
    \$\begingroup\$ @Reinderien printf() is not only used here just before exit. This answer's advise applies to all printf()s. Answer amended to reflect that idea. In particular, a printf() as part of a prompt before a scanf() can get hung up in buffering. For maximum portability, fflush(). For common portability, end the printf() with a '\n' before a scanf(). \$\endgroup\$ Jun 26 '20 at 3:51
  • 4
    \$\begingroup\$ re: code formatting: I'd definitely also recommend indenting function bodies at least some, so it's easier to spot top-level lines. Every level of {} should be a new level of indentation, including the outer-most. \$\endgroup\$ Jun 26 '20 at 7:46
  • 1
    \$\begingroup\$ @PeterCordes Concerning horizontal white-space idea. I agree. Since I cut/paste/auto format, the code I reviewed is nicely formatted - to my liking - except for the vertical intra-function spacing mentioned. IMO, well formatted code is good when it can auto re-format easily. \$\endgroup\$ Jun 26 '20 at 8:22
  • 1
    \$\begingroup\$ @PeterCordes except for switch-case according to at least some style guides where the statements are indented, the case labels are not. \$\endgroup\$
    – ljrk
    Jun 26 '20 at 8:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.