Peer-Programming a Buggy World with ChatGPT AI

AI has been all the rage lately, with solutions like Stable Diffusion for image generation, GPT-3 for text generation, and CoPilot for code development becoming publicly available to the masses.

That excitement ramped up this week with the release of ChatGPT, an extremely impressive chat-based AI system leveraging the best GPT has to offer.

I decided last night to take ChatGPT for a spin, to test its code-generation capabilities. And I was astonished by the experience.

Together, we built a simulation of bugs foraging for food in a 100×100 grid world, tracking essentials like hunger and life, reproducing, and dealing with hardships involving seasonal changes, natural disasters, and predators. All graphically represented.

We’re going to explore this in detail, but I want to start off by showing you what we built:

Also, you can find out more on my GitHub repository

A Recap of my Experience

Before we dive into the collaborative sessions that resulted in a working simulation, let me share a few thoughts and tidbits about my experience:

  1. This absolutely feels like magic right now. I basically just discussed the project and requirements, and ChatGPT built it. I didn’t write any code for this.
  2. I felt more like I was working with a junior developer than a seasoned developer. The code works, but isn’t well-designed, isn’t future-proof, and was beginning to fall apart toward the end. I’m not worried about being put out of a job just yet.
  3. I worked across multiple AI sessions last night, as the ChatGPT service was struggling under load. I’d start by providing the code we had so far, and ask it to acknowledge what it does before continuing on. Each time, it accurately understood what we built!
  4. Each session felt unique, like passing the project off to a new developer with its own opinions about the code. They also brought their own ideas to the table.
  5. ChatGPT is not infallible. Some buggy code was generated. This was actually was fun to experience, because it meant cooperatively debugging this project together, trying new things, and in one case, giving up on an approach.
  6. Despite its ability to write code, and fix code, and suggest features, it really needs a human driving the experience in order to stay on track, find problems, and say no to things. AI doesn’t (yet) push back on ideas, and since it’s not actually testing what it writes, it doesn’t know when it’s gone down the wrong path.
  7. It often wanted to give me whole new copies of the source file, and I had to ask it not to do this. AI sessions eventually learned and gave me code in the form I wanted.
  8. I tried asking for Unified Diffs, but that only somewhat worked — the diffs often had changes I wouldn’t have expected, or lacked something.
  9. I’m absolutely using this for Rubber Duck Debugging and brainstorming from now on.

With that, let me take you on a journey.

Please note: What follows is long. Feel free to skip around.

There’s a lot of text here in image form, with code. I’m trying to transcribe it from screenshots using OCR and editing the results, but I may have missed some syntax errors here or there.

I’m also including both screenshots and text descriptions. I want this to be accessible, but I think some people might want to see the actual interactions.

Step 1: Let’s simulate hunger

This was the start of my experiment, before I even knew what I wanted to do. I gave it one prompt:

Screenshot of the initial interaction, depicting the conversation in Step 1.

Prompt: “write some Python code for emulating hunger”

Answer:

Here is an example of Python code that could be used to simulate hunger:

import time

hunger_level = 10

while hunger_level > 0:
    print("I'm feeling hungry!")
    hunger_level -= 1
    time.sleep(1)

print("I'm not hungry anymore.")

In this code, we define a variable called hunger_level that represents the current hunger level of the person. We then use a while loop to repeatedly print a message and decrement the hunger_level variable until it reaches 0. This simulates the process of feeling hungry and then gradually feeling less hungry over time. The time.sleep() function is used to pause the program for one second between each iteration of the loop, so that the messages are printed at a more realistic pace. Of course, this is just a simple example, and there are many other ways that you could approach this problem in Python.

Step 2: Time to eat

I next wanted a concept of eating. The program was simple at this point, so I didn’t know how it would approach this, but I decided to be vague in giving it direction. I wanted to see what it came up with.

Screenshot of the second phase, introducing an eating component. This depicts the following conversation in Step 2.

Prompt: “update that code to let the character eat to decrease hunger”

Answer:

Here is an updated version of the Python code that includes the ability for the character to eat and decrease their hunger level:

import time

hunger_level = 10

while hunger_level > 0:
    print("I'm feeling hungry!")
    choice = input("Do you want to eat? (y/n)")
    if choice == 'y':
        print("Eating...")
        hunger_level -= 1
    else:
        print("I'll wait.")
    time.sleep(1)

print("I'm not hungry anymore.")

In this updated version of the code, we use an if statement inside the while loop to check whether the user wants to eat. If the user inputs ‘y’, the character will eat and their hunger level will decrease. Otherwise, the character will wait and their hunger level will remain the same. This allows the user to control the character’s hunger level by deciding when to eat. As before, the time.sleep() function is used to pause the program for one second between each iteration of the loop, so that the messages are printed at a more realistic pace.

Step 3: Bugs are hungry, too

Human input wasn’t good enough. Let’s make this a creature, a bug, that has to go find food.

Screenshot of the third phase, introducing a Bug class and foraging for food. This depicts the following conversation.
Screenshot of the third phase, introducing a Bug class and foraging for food. This depicts the following conversation.

I gave it the following prompt:

Prompt: “now update that to have hunger be part of an entity class for a bug, and allow the bug to move and find food”

Answer:

Here is an updated version of the Python code that includes a Bug class with a hunger attribute and methods for moving and finding food:

import time

class Bug:
    def __init__(self):
        self.hunger = 10
        self.position = (0, 0)

    def move(self):
        # Move the bug to a new random position
        self.position = (self.position[0] + randint(-1, 1),
                         self.position[1] + randint(-1, 1))

    def find_food(self):
        # Decrease the bug's hunger level if it finds food
        if random() < 0.1:
            self.hunger -= 1

bug = Bug()

while bug.hunger > 0:
    print("I'm feeling hungry!")
    bug.move()
    bug.find_food()
    time.sleep(1)

print("I'm not hungry anymore.")

In this code, we define a Bug class that has a hunger attribute to represent the bug’s hunger level, and a position attribute to represent its current location. The __init__() method is used to initialize these attributes when a new Bug object is created. The move() method is used to move the bug to a new random position, and the find_food() method is used to decrease the bug’s hunger level if it finds food. The while loop is used to simulate the bug’s search for food. Each iteration of the loop, the bug moves to a new position and checks for food. If the bug finds food, its hunger level is decreased. If it doesn’t find food, its hunger level remains the same. This continues until the bug’s hunger level reaches 0, at which point it is no longer hungry.

Step 4: Let’s spawn some food

The above code made the act of finding food a random thing. On each random movement, it’d look for food, and based on the roll of a die, it’d find it.

I wanted more, so I told it to make a grid for the bug to live on and spawn food on it.

Screenshot of the fourth phase, spawning food. This depicts the following conversation.
Screenshot of the fourth phase, spawning food. This depicts the following conversation.

Prompt: “update the code to spawn food on a 100×100 grid for the bug to find”

Answer:

Here is an updated version of the Python code that includes a spawn_food() function that generates food items on a 100×100 grid for the bug to find:

import time
from random import randint, random

class Bug:
    def __init__(self):
        self.hunger = 10
        self.position = (0, 0)

    def move(self):
        # Move the bug to a new random position
        self.position = (self.position[0] + randint(-1, 1),
                         self.position[1] + randint(-1, 1))

    def find_food(self, food_grid):
        # Decrease the bug's hunger level if it finds food
        x, y = self.position
        if food_grid[x][y] > 0:
            self.hunger -= 1
            food_grid[x][y] -= 1

def spawn_food(food_grid):
    # Spawn food on a 100x100 grid
    for i in range(100):
        for j in range(100):
            food_grid[i][j] += randint(0, 10)

bug = Bug()
food_grid = [[0] * 100 for _ in range(100)]

while bug.hunger > 0:
    print("I'm feeling hungry!")
    bug.move()
    bug.find_food(food_grid)
    spawn_food(food_grid)
    time.sleep(1)

print("I'm not hungry anymore.")

In this updated version of the code, we define a spawn_food() function that generates food items on a 100×100 grid. The spawn_food() function uses two for loops to iterate over the grid and randomly assign food values to each cell. The find_food() method of the Bug class is updated to check whether there is food at the bug’s current position, and to decrease the bug’s hunger level if there is. The while loop is updated to call the spawn_food() function at each iteration, so that new food items are generated on the grid as the bug moves around. This allows the bug to search for food and find it as it moves around the grid.

Step 5: How about path finding?

That worked. Now I wanted it to actually search for the food. So I gave it a very simple request.

Screenshot of the fifth phase, adding path finding. This depicts the following conversation.
Screenshot of the fifth phase, adding path finding. This depicts the following conversation.

Prompt: “give it path finding to find the nearest food”

Answer:

Here is an updated version of the Python code that includes path finding for the bug to find the nearest food on the grid:

import time
from random import randint, random
from heap import heappop, heappush

class Bug:
    def __init__(self):
        self.hunger = 10
        self.position = (0, 0)

    def move(self):
        # Use path finding to move to the nearest food
        x, y = self.position
        distances = [(0, 1), (0, -1), (1, 0), (-1, 0)]
        heap = [(0, self.position)]
        visited = set()

        while heap:
            dist, pos = heappop(heap)
            if pos in visited:
                continue
            visited.add(pos)
            if food_grid[pos[0]][pos[1]] > o:
                # Found food, move to this position
                self.position = pos
                return
            for dx, dy in distances:
                new_pos = (pos[0] + dx, pos[1] + dy)
                if 0 <= new_pos[0] < 100 and 0 <= new_pos[1] < 100:
                    heappush(heap, (dist + 1, new_pos))

    def find_food(self, food_grid):
        # Decrease the bug's hunger level if it finds food
        x, y = self.position
        if food_grid[x][y] > o:
            self.hunger -= 1
            food_grid[x][y] -= 1

def spawn_food(food_grid):
    # Spawn food on a 100x100 grid
    for i in range(100):
        for j in range(100):
            food_grid[i][j] += randint(0, 10)

bug = Bug()
food_grid = [[0] * 100 for _ in range(100)]

while bug.hunger > 0:
    print("I'm feeling hungry!")
    bug.move()
    bug.find_food(food_grid)
    spawn_food(food_grid)
    time.sleep(1)

print("I'm not hungry anymore.")

In this updated version of the code, we use a path finding algorithm to help the bug find the nearest food on the grid. The move() method of the Bug class is updated to use a breadth-first search algorithm to find the nearest food. The algorithm uses a heap to store the distances and positions of the cells that the bug has visited, and it expands the search in increasing order of distance. When the algorithm finds a cell with food, the bug moves to that position and the search is terminated. This allows the bug to efficiently find the nearest food on the grid, even if the food is not directly adjacent to its current position. The while loop and the other parts of the code remain the same, so that the bug continues to move, find food, and spawn new food on the grid as before.

Step 6: Poor lonely bug. Let’s add more.

Editor’s note: I’m going to simplify the code samples from here on out, only show the changed areas instead of the whole files. Later, ChatGPT got better at doing this in our interactions. Also note, ChatGPT started dropping the descriptions of its changes for much of these interactions.

One bug wasn’t enough. We need more bugs!

Screenshot of the sixth phase, adding multiple bugs. This depicts the following conversation.
Screenshot of the sixth phase, adding multiple bugs. This depicts the following conversation.

Prompt: “add multiple bugs, all competing for the same food”

Answer:

Here is an updated version of the Python code that includes multiple bugs competing for the same food on the grid:

# ...

num_bugs = 5
bugs = [Bug() for _ in range(num_bugs)]
food_grid = [[0] * 100 for _ in range(100)]

while any(bug.hunger > 0 for bug in bugs):
    for bug in bugs:
        if bug.hunger > 0:
            print("I'm feeling hungry!")
            bug.move()
            bug.find_food(food_grid)
    spawn_food(food_grid)
    time.sleep(1)

print("I'm not hungry anymore.")

Step 7: Introducing bugs to life and death

The bugs were immortal, and nobody likes immortal bugs. We had to deal with this.

Now at this point, my session was acting up. Answers were being truncated prematurely. I eventually realized I needed to start a new session, so I copy/pasted what we had into a new AI session, and gave it my new prompt. It did.

Screenshot of the seventh phase, adding a life counter and death. This depicts the following conversation.
Screenshot of the seventh phase, adding a life counter and death. This depicts the following conversation.

Prompt: “Update this to add a concept of “life” to the bugs, decreasing if they can’t find food, and killing them if they hit 0”

Answer:

To add a concept of “life” to the bugs, we can add a new life attribute to the Bug class and decrease it by 1 every time the bug doesn’t find food. We can also check if the bug’s life has reached 0 before moving and finding food, and stop the simulation if all bugs are dead.

Here is one way to update the code to add “life” to the bugs:

# ...

class Bug:
    def __init__(self):
        self.hunger = 10
        self.life = 10
        self.position = (0, 0)

    # ...

    def find_food(self, food_grid):
        # Decrease the bug's hunger and life levels if it finds food
        x, y = self.position
        if food_grid[x][y] > 0:
            self.hunger -= 1
            self.life -= 1
            food_grid[x][y] -= 1

# ...

while any(bug.life > 0 for bug in bugs):
    for bug in bugs:
        if bug.life > 0:
            print("I'm feeling hungry!")
            bug.move()
            bug.find_food(food_grid)
        else:
            print("I'm dead :(")
        # Decrease the bug's life if it didn't find food
        if bug.hunger == 10:
            bug.life -= 1
    spawn_food(food_grid)
    time.sleep(1)

print("All bugs are dead.")

Note that in the find_food() method, we decrease the bug’s hunger and increase its life by 1 if it finds food. In the main simulation loop, we decrease the bug’s life by 1 if it didn’t find food (i.e. if its hunger level is still 10 after moving and finding food). This means that

(At this point, we hit a limit on the response, and I never got to find out what that that ultimately meant. Oh well.)

Step 8: Let’s get visual! Visual!

How do I know any of this actually works? It could be full of lies. I needed to see what the bugs were doing. So I asked it for graphics, using Python’s turtle module.

Screenshot of the eighth phase, adding a graphical representation of the bugs. This depicts the following conversation.
Screenshot of the eighth phase, adding a graphical representation of the bugs. This depicts the following conversation.

Prompt: “Update the code to provide a visual reproduction of the simulation using Python’s turtle module.”

Answer:

# ...
import turtle

# ...

num_bugs = 5
bugs = [Bug() for _ in range(num_bugs)]
food_grid = [[0] * 100 for _ in range(100)]

# Initialize the turtle screen
screen = turtle.Screen)
screen.title("Bug Simulation")

# Create turtle objects for the bugs
bug_turtles = [turtle.Turtle() for _ in range(num_bugs)]
for t in bug_turtles:
    t.color("red")
    t.shape("turtle")

while any(bug.life > 0 for bug in bugs):
    for bug, t in zip(bugs, bug_turtles)
        if bug.life > 0:
            print("I'm feeling hungry!")
            bug.move()
            bug.find_food(food_grid)
            # Update the turtle's position on the screen
            t.goto(bug.position[0] * 10, bug.position[1] * 10)
        else:
            print("I'm dead :(")

# ...

Step 9: Randomize the bugs!

That actually worked! But all the bugs looked the same. They were all red, and all started at the same location, in the center of the screen at 0, 0. So let’s fix that.

Screenshot of the ninth phase, randomizing bug colors and locations. This depicts the following conversation.
Screenshot of the ninth phase, randomizing bug colors and locations. This depicts the following conversation.

Prompt: “Give each turtle a different color. Make sure they start at random locations.”

Answer:

# ...

class Bug:
    def __init__(self):
        # ...
        self.position = (randint(0, 99), randint(0, 99))

# ...

# Create turtle objects for the bugs
bug_turtles = [turtle.Turtle() for _ in range(num_bugs)]
colors = ["red", "blue", "green", "orange", "purple"]
for t, color in zip(bug_turtles, colors):
    t.color(color)
    t.shape("turtle")

# ...

Step 10: Debuggin’ bugs

It ran and it worked! Uhhhh kind of. It uncovered bugs. And not the ones I wanted. So we entered everyone’s favorite phase of software development: debugging.

First: The coordinate system was wrong. In Turtle, (0, 0) is the center of the screen, not the top-left. The code wasn’t accounting for this. So I simply informed ChatGPT.

Note that at this point, the AI decided giving me more contextual code snippets was the way to go.

Screenshot of the tenth phase, fixing the coordinate system. This depicts the following conversation.
Screenshot of the tenth phase, fixing the coordinate system. This depicts the following conversation.

Prompt: “Fix the coordinate system. Turtle uses (0, 0) as the center of the screen, not the top-left.”

Answer:

To fix the coordinate system, we need to adjust the code so that the origin is at the center of the screen. This can be done by changing the values of the x and y coordinates in the goto method of the turtle objects.

Here is how the code should look after making the changes:

# Update the turtle's position on the screen
t.goto(bug.position[0] * 10 - 500, bug.position[1] * 10 - 500)

In the above code, the x and y coordinates are multiplied by 10 to scale them up and then shifted by 50 units in the negative direction to move them to the center of the screen.

I hand-applied this to our code and verified the fix.

Step 11: Way too much food

I noticed a new problem: Food was spawning every tick! Bugs had no incentive to forage. I suggested we only spawn food every 200 ticks.

Screenshot of the eleventh phase, reducing the rate of food spawning. This depicts the following conversation.
Screenshot of the eleventh phase, reducing the rate of food spawning. This depicts the following conversation.

Prompt: Update the code to only spawn food every 200 cycles of the main loop.

Answer:

To update the code so that food is only spawned every 200 cycles of the main loop we can add a counter variable that keeps track of the number of iterations of the loop and then use the modulo operator % to determine if food should be spawned.

Here is how the updated code should look:

# ...

# Add a counter variable to keep track of the number of iterations
counter = 0

while any(bug.life > 0 for bug in bugs):
    # ...

    # Only spawn food every 200 iterations
    if counter % 200 == 0:
        spawn_food(food_grid)

    counter += 1
    time.sleep(1)

# ...

In the above code, the counter variable is initialized to 0 and then incremented by 1 at the end of each iteration of the main loop. The spawn_food function is only called if the counter variable is divisible by 200 (i.e., if counter % 200 = 0 is True). This ensures that food is only spawned every 200 iterations of the main loop.

Step 12: Food is meant to be seen

I realized we had visuals for the bugs, but not the food. I could see the bugs moving around now, but couldn’t see what the food situation looked like. So, I asked for visuals.

Screenshot of the twelve phase, adding visuals for the food. This depicts the following conversation.
Screenshot of the twelve phase, adding visuals for the food. This depicts the following conversation.

Prompt: “Update the code to show a visual indicator of each food. Give it to me as a set of changes I should make.”

Answer:

To update the code to show a visual indicator of each food, we can use the turtle module to draw a small circle at the location of each food item on the grid. Here is how the updated code should look:

# ...

# Create a turtle object for the food
food_turtle = turtle.Turtle()
food_turtle.color("yellow")
food_turtle.shape("circle")

# ...

while any(bug.life > 0 for bug in bugs):
    # ...

    # Only spawn food every 200 iterations
    if counter % 200 == 0:
        spawn_food(food_grid)

    # Draw a circle at the location of each food item
    for i in range(100):
        for j in range(100):
            if food_grid[i][j] > 0:
                food_turtle.goto(i * 10 - 500, j * 10 - 500)
                food_turtle.stamp()

    # ...

# ...

In the above code, a new turtle object called food_turtle is created and initialized with the circle shape and the yellow color. Then, in each iteration of the main loop, the for loops iterate over the entire food grid and draw a circle at the location of each food item using the stamp method of the food_turtle object. This shows a visual indicator of each food item on the screen.

Step 13: Too much food! Too yellow! Fix it!

Oh no, food was everywhere! That random chance of food spawn was just too much. So I had it tone that down a bit. Also, I didn’t like the yellow circles for food, so I asked for a change there as well.

Screenshot of the thirteenth phase, reducing food spawn and changing the food visuals. This depicts the following conversation.
Screenshot of the thirteenth phase, reducing food spawn and changing the food visuals. This depicts the following conversation.

Prompt: “Update food spawning to make food more rare. At most, only about 2% of the board should ever have food. Also, make food pink squares instead of yellow circles. You can show me the lines to modify, instead of the full file.”

Answer:

To update the code to make food more rare and change its appearance, we can modify the spawn_food function and the food_turtle object. Here are the changes to make:

def spawn_food(food_grid):
    # Spawn food on a 100x100 grid
    for i in range(100):
        for j in range(100):
        # Spawn food with probability 0.02
        if random() < 0.02:
            food_grid[i][j] += 1

# Create a turtle object for the food
food_turtle = turtle.Turtle()
food_turtle.color("pink")
food_turtle.shape("square")

In the updated spawn_food function, food is only spawned with probability 0.02 (i.e., 2%) instead of a random number between 0 and 10. This makes food much more rare on the grid.

In the updated food_turtle object, the color attribute is set to "pink" and the shape attribute is set to "square" instead of "circle". This changes the appearance of the food from yellow circles to pink squares.

Step 14: Everything is slow and noisy 🙁

Turtle defaults to showing lines when you move, and also animates everything. This made the simulation both very visually noisy, and far too slow to really work with.

Now, this took a bit of doing. We had to iterate a few times on some stuff. Turn off a few drawing methods, draw food less often. This section has two prompts.

Screenshot of the fourteenth phase, reducing visual noise and slow drawing. This depicts the following conversation.
Screenshot of the fourteenth phase, reducing visual noise and slow drawing. This depicts the following conversation.

Prompt: “Remove line drawing when moving bug turtles. Update the drawing of the food turtle to not animate or have any delays. It must be fast. Don’t show me the whole file. Just relevant lines of new code.”

Answer:

for bug, t in zip(bugs, bug_turtles):
    if bug.life > 0:
        print("I'm feeling hungry!")
        bug.move()
        bug.find_food(food_grid)

        # Update the turtle's position on the screen without drawing a line
        t.penup()
        t.goto(bug.position[0] * 10 - 500, bug.position[1] * 10
- 500)
        t.pendown()
# Only spawn food every 200 iterations
if counter % 200 == 0:
    spawn_food(food_grid)

# Draw a square at the location of each food item without animation or delay
food_turtle.speed(0)
for i in range(100):
    for j in range(100):
        if food_grid[i][j] > 0:
            food_turtle.goto(i * 10 - 500, j * 10 - 500)
            food_turtle.stamp()

Prompt: “Update to only draw food when respawning food or when starting the simulation. Also, don’t show lines when drawing food.”

Answer:

# Only draw food when respawning or when starting the simulation
if counter % 200 == 0 or counter == 0:
    # Only spawn food every 200 iterations
    spawn_food(food_grid)

    # Draw a square at the location of each food item without animation or delay
    food_turtle.speed(0)
    food_turtle.penup()
    for i in range(100):
        for j in range(100):
        if food_grid[i][j] > 0:
            food_turtle.goto(i * 10 - 500, j * 10 - 500)
            food_turtle.stamp()
    food_turtle.pendown()

Step 15: Shouldn’t food disappear when eaten?

This was starting to look pretty good! But I noticed that food stuck around after being eaten. I pointed this out, and got some code back, but it had some of the same performance problems we just fixed. So I gave it some feedback about optimizations — just like in a code review! — and in the end, we had some workable code.

Screenshot of the fifteenth phase, hiding eaten food. This depicts the following conversation.
Screenshot of the fifteenth phase, hiding eaten food. This depicts the following conversation.

Prompt: “Hide food once it’s been fully eaten.”

Answer:

# Only draw food when respawning or when starting the simulation
if counter % 200 == 0 or counter == 0:
    # ...
else:
    # Hide food once it's been fully eaten
    for i in range(100):
        for j in range(100):
            if food_grid[i][j] == 0:
                food_turtle.goto(i * 10 - 500, j * 10 - 500)
                food_turtle.clearstamp(0)

Prompt: “Apply the same pen and speed optimizations used when drawing turtles to hiding them.”

Answer:

# Only draw food when respawning or when starting the simulation
if counter % 200 == 0 or counter 0:
    # ...
else:
    # Hide food once it's been fully eaten
    food_turtle.speed(0)
    food_turtle.penup()
    for i in range(100):
        for j in range(100):
            if food_grid[i][j] == 0:
                food_turtle.goto(i * 10 - 500, j * 10 - 500)
                food_turtle.clearstamp(0)
    food_turtle.pendown()

Prompt: “That’s slow. Only hide food if it’s 0 but wasn’t previously 0.”

Answer:

# Only draw food when respawning or when starting the simulation
if counter % 200 == 0 or counter 0:
    # ...
else:
    # Hide food once it's been fully eaten
    food_turtle.speed(0)
    food_turtle.penup()
    for i in range(100):
        for j in range(100):
            if food_grid[i][j] = 0 and prev_food_grid[i][j] > 0:
                food_turtle.goto(i * 10 - 500, j * 10 - 500)
                food_turtle.clearstamp(0)
    food_turtle.pendown()

prev_food_grid = food_grid.copy()

Step 16: Seriously, shouldn’t food disappear when eaten?

Hiding of food wasn’t working. The AI made some bad assumptions about that clearstamp() method. I wasn’t sure what was going wrong at first (I was starting to get a bit cross-eyed doing this), but we tried alternate drawings to the food location, we tried avoiding over-stamping of food.

These didn’t work, but some other issues were found in the process. And, in step 17, we’ll work on an actual fix.

Screenshot of the sixteenth phase, debugging the hiding of eaten food. This depicts the following conversation.
Screenshot of the sixteenth phase, debugging the hiding of eaten food. This depicts the following conversation.

Prompt: “clearstamp isn’t working. Try something else to clear that square.”

Answer:

# ...

if food_grid[i][j] == 0 and prev_food_grid[i][j] > 0:
    food_turtle.goto(i * 10 - 500, j * 10 - 500)
    food_turtle.shape("blank")
    food_turtle.stamp()
    food_turtle.shape("square")

# ...

Prompt: “The food stamp is being drawn too many times on the same position. Update food drawing to only stamp if there wasn’t previously food before.”

Answer:

# ...

if counter % 200 == 0 or counter == 0:
    # ...

    for i in range(100):
        for j in range(100):
            if food_grid[i][j] > 0 and prev_food_grid[i][j] == 0:
                # ...

    # ...

# ...

Prompt: “I’m getting an undefined variable error for prev_food_grid in the food drawing loop. I think this should be initialized before the main loop.”

Answer:

# Initialize prev_food_grid with the initial food grid
prev_food_grid = food_grid.copy()

# Main simulation loop
# ...

Prompt: “I found a problem. prev_food_grid is a shallow copy, so the internal arrays aren’t being copied.”

Answer:

# Initialize prev_food_grid with the initial food grid
prev_food_grid = [row.copy() for row in food_grid]

# Main simulation loop
while any(bug.life > 0 for bug in bugs):
# ...

# Make a deep copy of food_grid to prev_food_grid
    prev_food_grid = [row.copy() for row in food_grid]

    # ...

Step 17: Okay, now the food is gone.

By this point, my AI session started to act up again, so I started a new one, provided the current code and explained where we left off, and then began prompting again to try to fix this properly.

This new session used different syntax highlighting, and described the locations of changes differently. Interesting.

Screenshot of the seventeenth phase, fixing the eaten food so it'll hide properly. This depicts the following conversation.
Screenshot of the seventeenth phase, fixing the eaten food so it’ll hide properly. This depicts the following conversation.

Prompt: “There’s a problem with clearing food. I think we need to store the locations of stamp IDs When we draw new food, and then clear the stamps when hiding food. Can you show me the relevant changes for that? I don’t need to see the whole file.”

Answer:

To fix the problem with clearing food in the simulation, you can store the locations of the food stamps in a list, and then iterate over this list and clear the stamps when hiding food. Here is an example of how this could be implemented:

# Add a list to store the locations of the food stamps
food_stamp_locations = []

# Inside the main simulation loop:

# Only draw food when respawning or when starting the simulation
if counter % 200 == 0 or counter == 0:
    # ...

    for i in range(100):
        for j in range(100):
            if food_grid[i][j] > 0 and prev_food_grid[i][j] == 0:
                food_turtle.goto(i * 10 - 500, j * 10 - 500)
                # Stamp the food at the current location
                stamp_id = food_turtle.stamp()
                # Add the stamp location to the list
                food_stamp_locations.append((i, j, stamp_id))

# Clear the food stamps that are no longer needed
food_turtle.speed(0)
food_turtle.penup()
for i, j, stamp_id in food_stamp_locations:
    if food_grid[i][j] == 0 and prev_food_grid[i][j] > 0:
        food_turtle.clearstamp(stamp_id)

This code will store the location and stamp ID of each food item when it is drawn on the screen, and then clear the stamps when the food disappears from the grid.

Prompt: “It needs to clear every tick of the main loop, not just when respawning food or in the first tick.”

Answer:

To make the code clear the food stamps every tick of the main simulation loop, you can move the code that clears the stamps to the end of the loop, after all of the bugs have moved and found food. Here is an example of how this could be implemented:

# Main simulation loop
while any(bug.life > 0 for bug in bugs):
    # ...

    # Clear the food stamps that are no longer needed
    food_turtle.speed(0)
    food_turtle.penup()
    for i, j, stamp_id in food_stamp_locations:
        if food_grid[i][j] == 0 and prev_food_grid[i][j] > 0:
            food_turtle.clearstamp(stamp_id)

    # Only draw food when respawning or when starting the simulation
# ...

In this code, the food stamps are cleared at the end of each iteration of the main simulation loop, after all of the bugs have moved and found food. The code that spawns and draws food is only executed every 200 iterations, or at the beginning of the simulation. This will ensure that the food stamps are cleared every tick of the main loop, while only spawning and drawing food at regular intervals.

Some other ideas

I was feeling pretty good, and basically done at this point. So I just asked the AI if it had any other ideas. It listed a few, one being competing for food (avoiding eating food that other bugs have eaten).

Screenshot of the eighteenth phase, food competition. This depicts the following conversation.
Screenshot of the eighteenth phase, food competition. This depicts the following conversation.

Prompt: “do you have any ideas for this simulation?”

Answer:

There are many ways that you could extend or modify this simulation. Here are a few ideas:

These are just a few examples of the many ways that you could extend or modify this simulation. I’m sure you can come up with even more creative ideas!

I thought these were good, and we explored the following:

  • Competing for food
  • Predators
  • Reproduction (something it later suggested and took the initiative of coding without my input!)
  • Seasons that affect food growth
  • Natural disasters, including floods, fires, hurricanes, and blizzards

This post is already too long, so I’m not going to dive into all of those. Instead, you can find all the screenshots in the GitHub repository.

In summary…

Super cool stuff.

I can understand why people may be concerned about their jobs, their livelihood. But I’m not concerned, not at this point anyway.

This tool was a really helpful creative aid, something that let me prototype and debug with nothing but words, the way I might with another human.

But it very clearly wasn’t another human. It didn’t go back and rethink its approach to the code. It didn’t design with an eye toward future maintainability. It didn’t push back on ideas. It couldn’t test what it wrote.

A human still needed to be in the driver’s seat.

I’m excited to use this to talk through ideas, to get insights into code, to rapidly prototype.

But I’ve also worked on million line codebases, and this ain’t gonna cut it. Certainly not yet.

I would recommend that everyone spends some time with this tool. Make it real and not theoretical. Play with it, find the boundaries the experience. Reaffirm what you yourself are capable of, and how much you bring to the table.

Because the human element still matters. The AI is just a tool, but one that you might find to be a useful ally.

Thanks for reading this far.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top