The code's structure has been changed to make the program more readable. I've also commented and split up the program into blocks for better readability as well. The recursion in the player and computer moves have been replaced with a loop instead. The user can now determine the first player. The algorithm to determine game end by a win has also been made much more concise by re-using code and using loops. User input is also now validated, throwing up an error for invalid inputs.
I still have not made a smarter computer, or used a minmax algorithm, and I am working on extending the program to allow user to make choices about players. However regarding the code at hand, are there any mistakes I am making in making certain choices, or any further optimisations I can make to improve structure, readability, efficiency, safety, etc?
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//----------------------------------------------------------------------------------
// Structure Definition
//----------------------------------------------------------------------------------
struct game_data {
int win; // Either 0 or 1.
int turns; // Ranges from 0 to 9(game end).
int turn; // Either 0 or 1 where 0 is human player
char grid[3][3]; // Grid
};
//------------------------------------------------------------------------------------
// Function Declarations
//------------------------------------------------------------------------------------
void Intro(void); // Game intro
void GameSetup(struct game_data *game); // Determines first player
void PlayerOneMove(struct game_data *game); // Player one input
void RandomComputerMove(struct game_data *game); // Computer move
void DrawUpdatedGrid(const struct game_data *game); // Draw updated grid
void GameEventWon(struct game_data *game); // Checks for winners
void GameEventDrawn(struct game_data *game); // Checks for draws
char ThreeInARow(char grid[][3]); // Checks for three-in-a-row
//------------------------------------------------------------------------------------
// Main Loop
//------------------------------------------------------------------------------------
int main(void) {
// Initialises Random Number Generator
srand((unsigned)time(0));
// Initialising Game State Variables
struct game_data game = {
0,
0,
0,
{{' ', ' ', ' '},
{' ', ' ', ' '},
{' ', ' ', ' '}}
};
Intro();
GameSetup(&game);
while (game.win == 0) {
if (game.turn == 0) {
PlayerOneMove(&game);
// RandomComputerMoveTwo(&game);
game.turns++;
game.turn = 1;
}
else {
RandomComputerMove(&game);
game.turns++;
game.turn = 0;
}
DrawUpdatedGrid(&game);
GameEventWon(&game);
GameEventDrawn(&game);
}
return 0;
}
//------------------------------------------------------------------------------------
// Short Intro and Setup
//------------------------------------------------------------------------------------
void Intro(void) {
printf("\nWelcome to NOUGHTS AND CROSSES\n\n");
printf("The grid you will be playing on is 3x3 and your input will be "
"determined by the co ordinates you put in, in the form 'row "
"column'.\n\n");
printf("For example an input of '1 1' will put a 'Z' on the first row "
"on the "
"first column. Like so:\n\n");
printf(" 1 2 3 \n"
" +---+---+---+\n"
"1 | Z | | |\n"
" +---+---+---+\n"
"2 | | | |\n"
" +---+---+---|\n"
"3 | | | |\n"
" +---+---+---+\n"
"\n");
}
void GameSetup(struct game_data *game) {
char answer;
// Allow user to determine first player
printf("Would you like to play first? y/n \n");
answer = getchar();
if (answer == 'y') {
game->turn = 0;
printf("\nYou have chosen to go first\n");
} else if (answer == 'n') {
game->turn = 1;
printf("\nYou have chosen to go second\n");
}
else {
puts("Invalid input");
exit(EXIT_FAILURE);
}
}
//------------------------------------------------------------------------------------
// Player One Move
//------------------------------------------------------------------------------------
void PlayerOneMove(struct game_data *game) {
int row, column;
do {
printf("You are 'Crosses'. Please input co-ordinates ...\n");
if (scanf(" %d %d", &row, &column) != 2) {
puts("Invalid input");
exit(EXIT_FAILURE);
}
if (row < 1 || row > 3 || column < 1 || column > 3) {
puts("Out of range input");
exit(EXIT_FAILURE);
}
} while (game->grid[row - 1][column - 1] != ' ');
game->grid[row - 1][column - 1] = 'X'; // Places player move
printf("\nYour turn:\n\n");
}
//------------------------------------------------------------------------------------
// Computer Move
//------------------------------------------------------------------------------------
void RandomComputerMove(struct game_data *game) {
int column;
int row;
do {
row = rand() % 3;
column = rand() % 3;
} while (game->grid[row][column] != ' ');
printf("\nComputer turn:\n\n");
game->grid[row][column] = 'O'; // Places computer move
}
//------------------------------------------------------------------------------------
// Display Updated Grid
//------------------------------------------------------------------------------------
void DrawUpdatedGrid(const struct game_data *game) {
printf(
"+---+---+---+\n"
"| %c | %c | %c |\n"
"+---+---+---+\n"
"| %c | %c | %c |\n"
"----+---+---|\n"
"| %c | %c | %c |\n"
"+---+---+---+\n"
"\n",
game->grid[0][0], game->grid[0][1], game->grid[0][2], game->grid[1][0],
game->grid[1][1], game->grid[1][2], game->grid[2][0], game->grid[2][1],
game->grid[2][2]);
}
//------------------------------------------------------------------------------------
// Checks for Three In A Row
//------------------------------------------------------------------------------------
char ThreeInARow(char grid[][3]) {
for (int i = 0; i < 3; i++) {
// Rows
if (grid[i][0] == grid[i][1] && grid[i][1] == grid[i][2]) {
return grid[i][0];
}
// Columns
else if (grid[0][i] == grid[1][i] && grid[1][i] == grid[2][i]) {
return grid[0][i];
}
}
// Diagonals
if ((grid[0][0] == grid[1][1] && grid[1][1] == grid[2][2]) ||
(grid[0][2] == grid[1][1] && grid[1][1] == grid[2][0])) {
return grid[1][1];
}
return ' ';
}
//------------------------------------------------------------------------------------
// Check if Game Won
//------------------------------------------------------------------------------------
void GameEventWon(struct game_data *game) {
switch (ThreeInARow(game->grid)) {
case 'X':
game->win = 1;
printf("CROSSES has WON\n");
break;
case 'O':
game->win = 1;
printf("NOUGHTS has WON\n");
break;
}
}
//------------------------------------------------------------------------------------
// Check if Game Drawn
//------------------------------------------------------------------------------------
void GameEventDrawn(struct game_data *game) {
if (game->turns == 9 && game->win == 0) {
game->win = 1;
printf("GAME is a DRAW\n");
}
}