Making an app with ChatGPT

Late last year, I decided to try a little experiment: could I build an app using ChatGPT?

I had already used ChatGPT many times for work and side projects, so I knew it could help. However, this time I wanted to go a bit further and build a full app.

To achieve this, I defined some rules:

For context, all of this project occurred in the same ChatGPT discussion, except for the ideation phase, which was done a few days earlier in a separate conversation.

Finding an idea

Finding the right app was straightforward. If ChatGPT was going to do the coding, it should also help me find the idea. My first prompt was a bit vague:

I'm looking for an app idea, can you give me some?

It gave me 10 ideas of apps in various categories, but none of them seemed “simple” enough to build in a short period of time and only required an app. I refined my question:

Any idea of simple games? Like minesweeper?

The results were much better, and among the 10 ideas, I found it: TileSwap.

  1. Tile Swap: A puzzle game where players must swap tiles to complete a picture or pattern. This could include landscapes, abstract patterns, or famous artworks.

Building

When prompted to help me build a TileSwap game, ChatGPT’s answer was great. It outlined the game’s mechanics and the steps required to reach our goal.

This game typically involves a grid of tiles where one tile is missing, allowing the player to slide adjacent tiles into the empty space to rearrange them, usually with the goal of achieving a specific pattern or order.

To build this game in SwiftUI, we'll need to follow these steps:

  1. Setup the Game Board: Define the grid size and create an array to represent the tiles and their positions.
  2. User Interface: Use SwiftUI views to display the tiles as a grid. Each tile can be a Button or a View that responds to user interactions.
  3. Game Logic: Implement the logic to handle tile movements. This involves updating the array when a tile is moved and checking if the tile can be moved to the empty space.
  4. Shuffling: Before the game starts, shuffle the tiles to create a solvable puzzle.
  5. Winning Condition: Implement logic to determine when the player has successfully arranged the tiles in the correct order.
  6. Additional Features: Add features like a reset button, move counter, or timer to enhance the game.

My following questions didn’t adhere to this particular order, so I don’t know if it would have been able to iterate over each step by itself. However, it was nice to have this outline and use it as a base to start working. Another nice touch from ChatGPT was that it directly provided me with sample code to lay out a 3x3 grid. The code compiled without any issues, but unfortunately, it crashed on the first run. This was a bit earlier than I expected. After pointing out what seemed to be the error, ChatGPT gave me corrected code, which worked fine this time.

TileSwap first launch

We iterated from there. I asked if it could help me with the rest of the game, which it split into three tasks: the tile movement logic, the shuffling, and the win condition. The newly generated code was based on the previous code, with empty methods for each of the tasks. One point I should mention is that most of the time ChatGPT gave me empty methods, so I had to repeat that I wanted it to provide the full code.

First surprise: when I asked for help with the shuffling of tiles, it automatically added a method to ensure the puzzle was solvable, along with an explanation of how to solve such a puzzle. I didn’t ask for it, and I learned something new, as I didn’t know the algorithm behind it.

For a standard 3x3 puzzle, if the number of inversions is even, then the puzzle is solvable.

private func isPuzzleSolvable() -> Bool {
    var inversions = 0
    for i in 0..<(tiles.count - 1) {
        for j in (i + 1)..<tiles.count {
            if tiles[i] > tiles[j] && tiles[j] != 0 {
                inversions += 1
            }
        }
    }
    // In a 3x3 puzzle, if the number of inversions is even, then the puzzle is solvable
    return inversions % 2 == 0
}

At this point, we had a basic TileSwap game with a 3x3 solvable grid, which was playable. Not bad.

Options

Next, I wanted to add a game settings screen. What’s interesting is the decisions ChatGPT made on its own. Again, I didn’t ask for specifics.

Now continuing on this, I would like to add a menu in the upper left cornet to allow me to configure the game.

Looking at the generated code, it took some initiative and decided to add two options: Reset Game and Change Grid Size, which seemed perfect in the context of our game.

After some tinkering—asking for a navigation bar, switching from NavigationView to NavigationStack, presenting it modally, adding presentationDetents, coding the reset game logic, and various handlers—we started working on changing the grid size.

This time, I asked a "big" question:

Perfect.

Now the "Grid Size Change" button. I would like to replace it.

Instead I would like to have a specific section, named "difficulties". Which would have 4 levels:

  • Easy
  • Medium
  • Hard
  • Extreme

And:

  • Easy should be 3x3
  • Medium should be 4x4
  • Hard should be 6x6
  • Extreme should be 8x8

For each difficulty row I would like the following:

  • On the left, an "icon" showing a miniature of the grid the size
  • In the middle the difficulty name
  • On the right the grid size.

I wanted to see if it could handle all of this. In response, it provided me with all the code to add the entries in the settings, define the difficulties in an enum, etc. The only part left empty was how to create the new grid once the difficulty was selected.

So here is a before and after:

Game settings before and after the question

Design-wise, it was not quite there. I asked ChatGPT for a visual representation of the grid to replace the 3x3, 4x4, etc., text on the left of the row. The original code caused the grid visualization to go out of bounds, but ChatGPT fixed this with a GeometryReader, and it worked.

Game difficulties row redesigned with a visual representation of the grid.

We proceeded to code the updating of the grid depending on the difficulties. One point it missed, which surprised me since it proposed it at the beginning, was ensuring the puzzle was still solvable1.

So I provided the code we used to check that a puzzle was solvable and asked if it was supposed to work for the new difficulties. ChatGPT explained that even grids (4x4, etc.) were not solvable in the same way as odd ones, and our algorithm would need to be changed.

  • For Even Grids (4x4, 6x6, 8x8, etc.): The puzzle's solvability depends on the sum of the inversions and the row number (counting from the bottom) where the blank tile resides in the solved state.
  • For Odd Grids (like 3x3): The solvability depends solely on whether the number of inversions is even, as you've implemented.

I copied and pasted the new code, selected a difficulty, and tried to solve it. Unfortunately, the code was not working2. So I took the numbers in my grid and asked ChatGPT to resolve it. The answer ended with:

Since we have 59 inversions (odd) and the blank tile is in the 1st row (odd), their sum is 60, which is even. Therefore, this particular puzzle configuration is not solvable.

I promptly told it that the grid had been provided by its own code and had passed the check of isPuzzleSolvable. It then asked me for the current code3 and tried to analyze it. What I find really crazy is that it can understand some parts of the code.

The calculation let blankRow = (tiles.firstIndex(of: 0)! / columns) + 1 finds the row of the blank tile, but it counts from the top, not from the bottom. In a grid counting rows from the top, the bottom row is row 1, the next one up is row 2, and so on. However, for solvability, we need the row number from the bottom.

I mean, this kind of answer is impressive. It didn’t just try something else; it read the code, kept in mind its purpose, and found the issue.

Paywall

During the development of the app, I decided which parts should be behind a paywall. So, each time I added or finished a feature, I asked ChatGPT to provide me with a version that would need to be unlocked with a purchase. We did this for the game difficulties, using images and accent colors.

Later, while developing the premium option for the settings screen, I asked for a Purchase view4.

Those are example for the PurchasePromptView.

Can you create a screen for me inspired by those?

Keep in mind that a I would like to show:

  • A list of things that will be unlocked
  • That we only have one purchase option
  • User should be able to restore the purchase

To my greatest surprise, it nailed the list of unlockable features without having to remind it:

// Dummy list of features for display purposes
let features = [
    "Unlock all difficulties: Hard and Extreme",
    "Use your own pictures for tiles",
    "Access to all accent colors",
    "Remove all ads",
    "Future premium features"
]

The fourth one is clearly an error, but the first three really matched! And yes, Hard and Extreme were the only two difficulties behind the paywall.

Game premium screen, before and after

Here is a side-by-side comparison of the first version of the screen it generated and the one we shipped after some iterations.

I was really impressed that it was able to retrieve the features. So many times I had to remind it of some things we did before that I wasn’t expecting it to provide anything useful. I thought it would only give me “generated” results from everything used for its training.

Misc

I won’t go into details of everything we did; the discussion is really long, with a lot of back and forth.

Conclusion

It’s impressive. It’s not ready to take developers’ jobs yet; it has lots of difficulties keeping in mind the architecture of the app, the names of the classes, or properties. This means we could not use it to develop full features of an existing app, even if we gave it the whole code. It would work at first, but rapidly, we would need to send it back the code to keep it in sync with what we already have.

I tried to port the app to tvOS, which is really different from iOS and iPadOS, and it started giving code that had no relation to what we had already done5.

It really struggled with image generation. I think the new version is better now (I haven’t tried to regenerate a hero image or app icon), but it took several iterations before landing on something usable.

However, it was really good at solving issues, giving me the correct algorithms, updating them for new grid sizes, and correcting compilation errors or crashes.


  1. As I guessed, solving a 3x3 grid is not the same as solving a 4x4 one. 

  2. I first, I thought I was just bad at this game. To be honest, even after the fix, I still had a hard time finishing some grids, thinking there was still an issue. 

  3. I thought it would find this by itself 

  4. I provided some example screenshots. 

  5. The visionOS version was fully ported by myself; at the time, it still thought it was a rumor.