Tic-Tac-Toe
To conclude Part II, you’ll apply everything you’ve learned so far to build a game of Tic-Tac-Toe. In this game, players take turns claiming a spot on a 3x3 grid and marking it with their symbol: X or O. The first player to fill a row, column, or diagonal with their symbol wins the game:
Requirements
This section describes how your game should work. Your output needn’t be identical, but you should have the same functionality.
When the game starts, it greets the players with a welcome message and an empty game board (the 3x3 grid):
Welcome to Tic-Tac-Toe!
1 | 2 | 3
-----------
4 | 5 | 6
-----------
7 | 8 | 9
The positions on the board are numbered for clarity. If you prefer an empty board, you can replace the numbers with spaces.
Next, the game picks a random starting player and asks this player to select a position:
Player O, it’s your turn.
Select a position (1-9): 2
The game then marks this position with the player’s symbol and shows the board as confirmation:
1 | O | 3
-----------
4 | 5 | 6
-----------
7 | 8 | 9
Play now moves to the other player, who is asked to select a position. This process repeats itself until the game ends. The game then prints the final board and the winner:
X | O | O
-----------
4 | X | O
-----------
7 | 8 | X
Player X has won!
Finally, the game asks the players if they want to play again:
Would you like to play again? (y/n): y
If they answer y, the game starts over. If they answer n, the program ends.
Prerequisites
To complete this challenge, you’ll need to learn a few new things:
- How to read input from the terminal
- How to generate random values
Reading input
The Standard Library includes a readLine
function that reads input and returns a String
. By default, readLine
reads from the terminal, but it can also read from a file.
You call readLine
as follows:
let input = readLine()!
When reading from a file, readLine
may run out of input, in which case it can’t return a String
. The exclamation point after the function call indicates that you acknowledge this risk but that you assume everything will be fine. This assumption is fairly safe when reading from a terminal, because readLine
waits for more input from the user.
WARNING
The user can type Ctrl+D (on macOS and Linux) or Ctrl+Z (on Windows) to send an end-of-file signal to your program, which will cause readLine
to crash.
Note
The exclamation point is part of a feature called Optionals that you’ll learn about in Part III.
If you want to read a number, you can convert the String
you get from readLine
to the desired type. Here’s how you convert it to an Int
:
let number = Int(input)!
Again, note the exclamation point. If the input is not an integer, the conversion from String
to Int
will fail and crash your program.
Random values
Most games involve randomness. Fortunately, Swift makes it easy to generate random values. Types such as Int
, Double
, and Bool
include a random
function that returns a random value of that type.
For example, here’s how you generate a random integer between one and ten, and a random Boolean:
let randomInt = Int.random(in: 1...10)
let randomBool = Bool.random()
Implementation
This section guides you through the process of building your game. As this is your first challenge, you’ll get some help, such as how to split your code into functions and how to group these functions into files. However, this isn’t a tutorial, so implementing the functions is entirely up to you.
Working on a project of this size can feel overwhelming. Don’t worry about how much time you need or if you get stuck and have to start over. The important part is not to give up; keep at it until you have a working solution.
Project setup
Start by creating an executable package using the Swift Package Manager. Refer back to Getting Started if you need a refresher on how to do that.
Delete the source file that came with your project, and create the following files instead:
- main.swift will start the game.
- input.swift will hold functions that read input.
- board.swift will hold functions and variables related to the game board.
- game.swift will hold the core functionality of the game, such as taking turns and checking if the game is over.
Using multiple files and functions keeps your code manageable. Programs can quickly get out of hand if you put all your code in a single file. Splitting your code into smaller parts lets you focus on one part at a time.
The game board
The first part you should implement is the game board. In board.swift, do the following:
- Declare one or more variables to hold the state of the board (which player has claimed which position).
- Declare a function to print the board.
Call this function from main.swift and run the program to try it out.
Taking a turn
Next, implement taking a turn.
In input.swift, declare a function that asks the player to select a position. If the player enters an invalid number or an already claimed position, the function should repeat the question until the player enters a valid position.
In game.swift, declare a function to take a single turn. Use the previous function to read a position, then mark this position on the board.
In main.swift, take a few turns and print the board after every turn to confirm the player’s selection.
Alternating players
The next step is to alternate the players.
In game.swift, add a variable to keep track of the current player. Then, declare a function to switch between the players.
In main.swift, switch the current player after every turn.
Ending the game
You’re almost ready to play a full game. The last remaining issue is to determine when the game is over.
In board.swift, declare a function that checks if the board is full.
In game.swift, declare a function that checks if the current player completed a row, column, or diagonal.
With these functions in place, you’re ready to add a game loop.
Still in game.swift, declare a function that plays an entire game. This function contains a loop that lets the players take turns until the board is full, or until a player makes three-in-a-row. When the game is over, print the result. You may need additional variables to support this function.
When you’re done, call your function from main.swift to try it out.
Playing multiple games
There’s only one requirement left to implement: when the game ends, the players should be able to start over and play a new game.
In input.swift, declare a function that asks a yes/no question.
In board.swift, declare a function to reset the board to its initial state.
In game.swift, declare a function to initialize or reset the game, and call this function before you start the game loop.
Finally, modify main.swift to support playing multiple games.
Evaluation
Congratulations on completing the first challenge. If it felt like a struggle at times, that’s normal. The goal of this challenge is not only to practice what you’ve learned so far, but also to prepare you for what’s coming next. The experience you’ve gained here will help you understand the topics in the upcoming chapters.
In fact, some of these chapters use the solution for this challenge as a starting point. This solution is as much a part of this course as the chapters, exercises, and challenges, so study it carefully. Compare it to your solution and learn from the differences.
Up next
Take some well-deserved time off to rest and recharge, then continue to Part III, where you’ll learn how to store and process collections of data.