How to Make a Game Like Wordle in SwiftUI: Part Two

Extend your Wordle word-game clone with animation, accessibility, statistics and shareable results, all in SwiftUI. By Bill Morefield.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Saving Game Results

To track the player’s long-term trends, you need a way to save the results of previous games. You’ll only store the turn on which the player successfully guessed the word or lost.

Open GuessingGame.swift and add the following new property to the class:

@AppStorage("GameRecord") var gameRecord = ""

The @AppStorage property wrapper ties a variable to the UserDefaults, a place you can store key-value pairs consistently across launches of your app. You tie the gameRecord variable to a key named GameRecord and set it as an empty string if the key does not currently exist in UserDefaults. If the variable changes, SwiftUI will update the value in UserDefaults, and vice-versa.

Now, find the checkGuess() method in the GuessingGame class. Look for where you mark a game as won, which reads status = .won, and add the following code directly before the return:

gameRecord += "\(currentGuess + 1)"

This will add the number of the turn when the player won the game. Remember that currentGuess is a zero-based index but the first possible turn is number one. To handle this, you add one to currentGuess. Move to the next if-else statement and add the following code after status = .lost:

gameRecord += "L"

When the player loses a game, this will mark the result with an L. Over time, your app will now track the player’s game history. In the next section, you’ll use that history to show the player how they’re doing.

Displaying Statistics

The work of translating the string stored in the gameRecord in the previous section will be handled by the GameStatistics struct found in GameStatistics.swift. Open this and you’ll see it expects a string with the history you stored in the last section.

Open StatisticsView.swift in the ResultsViews group. It expects a GameStatistics struct when you show the view. You’ll use the data calculated by this struct to build a view showing the player’s game results. Replace the body of this view with:

VStack(spacing: 15.0) {
  VStack {
    Text("Game Statistics")
      .font(.title)
    Text("Played: \(stats.gamesPlayed) ") +
    Text("Won: \(stats.gamesWon) (\(stats.percentageWon) %)")
    Text("Win Streak: \(stats.currentWinStreak) ") +
    Text("Max Streak: \(stats.maxWinStreak)")
  }
  // Next VStack here
}

This will create a pair of nested VStack views, with the outer one specifying spacing between the inner ones. This section shows the number of games played, the number of games won and the winning percentage. It also shows the player their current and longest winning streaks. This data all comes from the GameStatistics property passed into the view.

Building a Bar Chart

Now replace the // Next VStack here comment with the following:

// 1
VStack(alignment: .leading) {
  Text("Winning Guess Distribution")
    .font(.title)
  
  // 2
  let maxDistribution = Double(stats.winRound.max() ?? 1)
  // 3
  ForEach(stats.winRound.indices, id: \.self) { index in
    // 4
    let barCount = stats.winRound[index]
    let barLength = barCount > 0 ?
      Double(barCount) / maxDistribution * 250.0 : 1
    HStack {
      // 5
      Text("\(index + 1):")
      Rectangle()
        .frame(
          width: barLength,
          height: 20.0
        )
      Text("\(barCount)")
    }
  }
}

This code will produce a bar chart showing the rounds where the player has won games:

  1. You set the alignment of the VStack to leading to align all views against the leading edge of the parent view.
  2. The winRound property contains an array indicating how many times the player won in the specified number of guesses. Again, remember that winning on the first guess would be at index zero. You get the greatest number in the array to scale your bar chart. If there are no entries, then you use the value 1.
  3. Now you loop through all indices of the winRound property. The current index will be passed to the closure as index.
  4. You get the number of games won with guesses corresponding to the current index. You check if this value is greater than zero. If so, you calculate the ratio between this number and the largest number in the array. You multiply that ratio by 250.0 points to get the length of the bar. If the value for this index was zero, then you use 1 to show a thin line instead of nothing.
  5. Inside an HStack you show three views. First, you show the number of guesses this bar represents (adding one to convert from a zero-based array). You next create a rectangle with the width calculated in barLength in step four and a height of 20 points. Last, you display the actual count.

To see the result, change the preview to provide a history:

StatisticsView(stats: GameStatistics(gameRecord: "4L652234L643"))

Show the preview to see the results.

Preview of new game statistics view

Now you can add this information to the game result view. Open GameResultView.swift in GameBoardViews. Since we’re adding more information, Command + Click on the ShowResultView and select Embed in VStack. If you don’t see this option, make sure you’re displaying the preview canvas alongside the editor. Change the newly created VStack to be a ScrollView. This handles cases where the additional information won’t fit on the screen of smaller devices. Finally, add the following code at the bottom of the ScrollView:

StatisticsView(
  stats: GameStatistics(gameRecord: game.gameRecord)
)

Build and run the app, then play a few games to create a history so you can appreciate the new statistics view.

View showing game guesses plus winning guess distribution

Adding a Show Stats Button

You’ll now add a button to let the player view these stats anytime. Open ActionBarView.swift. Add the following code before the Spacer() in the view:

Button {
  showStats = true
} label: {
  Image(systemName: "chart.bar")
  .imageScale(.large)
  .accessibilityLabel("Show Stats")
}

This adds a button that, when tapped, will set showStats to true.

Open ContentView.swift and add the following after the existing sheet:

.sheet(isPresented: $showStats) {
  StatisticsView(stats: GameStatistics(gameRecord: game.gameRecord))
}

This displays the statistics in a modal view when showStats is true.

Build and run the app, then tap the new button to see the stats page.

Finished game statistics view

Congratulations! You’ve now replicated the most visible features of the original Wordle. In the next section, you’ll add a few finishing touches around managing the game state.