Today’s lab will ask you to use arrays and strings to implement a basic two-player game: tic-tac-toe. This lab will be less structured than the previous labs we used to practice using strings and arrays, but there are still specific requirements for the lab that you should be sure to follow. Follow the instructions below to get started:
Go to https://git.cs.grinnell.edu/csc161/tic-tac-toe/ and create a fork of the repository.
Add your group members as collaborators on the repository.
Navigate to ~/csc161/labs in a terminal and use git to clone your fork of the repository.
Open ~/csc161/labs/tic-tac-toe in VSCode.
If you’re unsure how to complete any of these steps, you can go back to our previous labs for more details.
The first step in implementing a game like tic-tac-toe is to decide how we are going to represent the game state. There are two important pieces of state we need to track in tic-tac-toe:
We will use a char to represent the current player’s turn.
This char will always be set to either 'X' or 'O', starting with 'X' at the beginning of each game.
The complete game board will be char array with a length of nine.
You may wonder why we aren’t using a 2D array;
this is partly just for practice, but C also has very limited support for 2D arrays so you’ll end up needing to represent square or rectangular arrays with a single dimension in the future.
The board array will be laid out in row major form: the first three elements are the top row of the board, the next three are the middle row, and the last three are the bottom row.
We’ll allow three values in our char array: 'X', 'O', and ' ' for blank spaces.
The board should initially be set to all blanks.
Create two variables inside your main function: current_turn to store which player’s turn it is, and board to store the state of the board.
Write a function void print_board(char board[]) that takes the board as a parameter and prints it in a nicely readable form like this:
O | | X
---+---+---
O | X |
---+---+---
| |
Test your print_board function by passing in different
The next step in your implementation is to ask the user to input moves. You’ll do this with a prompt, but the options correspond to cells in the board instead of a printed menu. You’ll eventually display a prompt like this:
It's player X's turn.
1 | 2 | X
---+---+---
4 | O | 6
---+---+---
7 | 8 | 9
Enter the number of the cell to place an X:
Notice that the board is printed with numbers in all blank spaces, but occupied spaces print as an X or O depending on which player claimed them.
The top left cell is index 0 in our board array, but we’ll print it as a option 1 to avoid any ambiguity between 0 and O characters.
We are not going to use scanf to prompt for user input.
Intstead, you can use the fgets function to read a line of input from the user and store it in an array.
Then, you can use the atoi function to turn it into a number.
Here is an example of how you can do this:
#include <stdio.h>
#include <stdlib.h>
#define INPUT_LENGTH 10
int main() {
// As the user to type a number
printf("Type a number:\n");
// Create an array to hold user input
char input[INPUT_LENGTH];
// Read user input and check for errors
if (fgets(input, INPUT_LENGTH, stdin) == NULL) {
fprintf(stderr, "Failed to read input from user.\n");
exit(EXIT_FAILURE);
}
// Convert the input to an integer
int value = atoi(input);
printf("You typed the number %d\n", value);
return 0;
}
Note that this example code does not do any validation of the value the user provided, but you will need to validate user input in your work below.
The provided code does check to see if fgets returned NULL.
The manual page says fgets will return NULL if something goes wrong, so we need to check for that.
When an error occurs we use fprintf function to print an error message before exiting.
Reglar printf prints to stdout, but this call to fprintf writes the error to stderr, which is where C programs are meant to diagnostic messages like this.
Your terminal will display text written to stdout and stderr together.
Then we can call exit(EXIT_FAILURE) to immediately exit the program with an exit code that indicates something failed (there is also an EXIT_SUCCESS constant you can use if the program finished successfully).
Make sure you read and understand the possible return values from standard C functions when you use them; if they can return a value to indicate an error then you must check for the error and deal with it.
Write a funtion void print_choices(char board[]) that prints the board with blank cells numbered starting from 1 in the upper left corner.
Write a function int get_move(char board[]) that shows the board with numbered cells (using print_choices), and prompts the user to make a move.
The function should not return until the user types a valid number for a blank cell.
If the user types an invalid number, the number for an occupied cell, or any input that contains non-numeric characters you must reject it and ask for input again.
Note that atoi will not tell you if the user types something that stars with a number like "1x", so you’ll need to check the length of the user input to detect invalid inputs like this one.
The return value from get_move should be a valid index into the board array.
If the user types “1” and that cell is open, get_move should return 0 because that is the index of the cell numbered “1” in the prompt.
Add a loop to your main function to run the game.
The loop should do the following:
print_boardget_move to get the next moveFor now, your loop will just run forever. We’ll check for wins in the next part of the lab.
The next step is to check whether a player has won or if the game results in a tie.
You’ll do this with two new functions: bool check_win(char board[]) and bool check_tie(char board[]).
To implement these functions you’re going to need to deal with the representation we chose for the board.
In get_move, you were able to use one-dimensional array indices to loop through the board.
Printing options for the user just required basic addition to give numbers starting at 1 instead of 0.
But now we’re going to interact with the game board as if it was two-dimensional.
Remember, we are represeng the board in row-major form. The first three indices are the top row, the next three are the middle row, and the last three are the bottom row. Keep this representation in mind as you complete the exercises below.
Write the check_tie function.
It should return false as long as there are open spaces left on the board.
Your check_tie function should not print anything to the terminal;
we’ll leave that to the caller.
Add code to your game loop to call check_tie after each move.
If the game results in a tie, print the result and break out of the loop.
Write the check_win function.
It should return true if either player has placed three of their symbols in a row, column, or diagonal.
Your check_win function should not print anything to the terminal.
Add code to your game loop to call check_win after each move.
If a player wins, print the result and break out of the loop.
Play a few games to test your check_tie and check_win functions.
Make sure you can correctly detect wins by either player using rows, columns, and diagonals.
Finally, you’ll add an outer loop to your game to make it easy to play again after you finish a round of tic tac toe. The prompt you use to ask the user if they want to keep playing should look like this:
X wins.
Do you want to play again? (Y/N)
Notice that this prompt asks the user to type 'Y' or 'N' (followed by enter) instead of a numbered menu.
You can use fgets to read the input as a string and compare it to the expected options.
Add the outer loop that asks whether the user would like to play another round of the game.
Don’t forget that you’ll need to use strcmp to compare strings.
You’ll need to clear the board array to all blank spaces after each round of the game.
There are several ways to do this depending on where you declare the variable, but one good option is to use the memset function to fill the array with ' ' characters.