The State of AI Coding Today

Assessing AI-Powered Coding: Will Developers Become Obsolete?

22 minutes
The State of AI Coding Today

In the ever-evolving world of technology, few advancements have captured our collective imagination quite like the meteoric rise of generative AI. Especially with behemoths like OpenAI's GPT series and Google's Bard leading the charge, the landscape of what machines can achieve has been redrawn. Among the most tantalizing feats? Their ability to conjure up code snippets, rectify pesky bugs, and even draft more extensive coding sequences from the merest hint of a prompt. The allure is undeniable: imagine a world of streamlined efficiency, diminished human error, and coding knowledge that isn’t locked behind the formidable gates of complex jargon and years of experience.

Yet, for every proponent touting AI as the heir apparent to the coding throne, there are naysayers who caution against getting caught up in the hype. Can AI really supplant the intricate dance of human reasoning, contextual understanding, and innovative problem-solving that underpins programming? Or are we staring at a future where developers and AI tools collaborate in newfound harmony?

Before we discard our programming manuals and embrace an AI-led utopia, it's crucial to dissect the nuances, promises, and potential overstatements surrounding this debate. Welcome to the intersection of code and consciousness, where we demystify the myth and dive into the tangible magic of AI in the world of programming.

AI as a Game Developer: A GPT Challenge

It's one thing to ruminate on the potential of AI, but another entirely to put it through its paces. What better way to challenge the generative prowess of ChatGPT than to task it with creating an iconic game from scratch? Our mission for the AI? Draft the source code for a classic – the game of Snake. We're not just talking any variant; we want this built in Python using the tkinter GUI framework. Picture this: a 20x20 grid, a three-segment snake navigated with the W, S, A, D keys, and randomly placed food that, when consumed, increases the snake's length. The catch? The game concludes if the snake collides with itself or the grid's boundaries. And of course, a score that ticks up with each morsel consumed. Let's dive in and see if ChatGPT can deliver a blast from the past with a futuristic twist.

From Prompt to Playable: ChatGPT’s Foray into Game Development

Navigating the world of AI-generated code can sometimes feel like stepping into uncharted waters. As we seek to demystify the capabilities of ChatGPT, we turn to a familiar environment for many developers: the MU editor. With Python 3.x and its assortment of common libraries at our fingertips, MU offers a solid foundation for our exploration. The old saying goes, "the proof of the pudding is in the eating," and we're eager to see firsthand what ChatGPT can produce. Armed with our straightforward prompt, can the generated Python code truly encapsulate the magic of the classic Snake game? Or will it merely be a labyrinth of complex lines? Let's delve in.

The prompt

Please provide the source code for a game using Python and the tkinter GUI framework. The game is "Snake." In this game:

  • The player moves a snake with three segments on a 20x20 square playing area using the W, S, A, D keys.
  • Food for the snake appears randomly in the playing area.
  • When the snake moves over the square with food, it grows by one segment.
  • The game ends if the snake collides with itself or the borders of the playing area.
  • The score increases by one for each piece of food eaten and is displayed on the screen.

ChatGPT:

Certainly! Here's a simple implementation of the Snake game using Python's tkinter framework:

import tkinter as tk
from random import randint

class SnakeGame:
    def __init__(self, master):
        self.master = master
        self.master.title('Snake Game')

        self.score = 0
        self.cell_size = 20
        self.width = 20
        self.height = 20

        self.canvas = tk.Canvas(self.master, width=self.width * self.cell_size, height=self.height * self.cell_size)
        self.canvas.pack()

        self.snake = [(5, 5), (5, 6), (5, 7)]
        self.food = self.generate_food()

        self.canvas.bind("<Key>", self.key_press)
        self.canvas.focus_set()

        self.draw()
        self.move_snake()

    def draw(self):
        self.canvas.delete(tk.ALL)

        # Draw snake
        for segment in self.snake:
            x, y = segment
            self.canvas.create_rectangle(x * self.cell_size, y * self.cell_size,
                                         x * self.cell_size + self.cell_size, y * self.cell_size + self.cell_size,
                                         fill='green')

        # Draw food
        x, y = self.food
        self.canvas.create_rectangle(x * self.cell_size, y * self.cell_size,
                                     x * self.cell_size + self.cell_size, y * self.cell_size + self.cell_size,
                                     fill='red')

        # Display score
        self.canvas.create_text(10, 10, anchor=tk.NW, text=f"Score: {self.score}", font=("Arial", 14), fill="black")

    def key_press(self, event):
        direction = event.keysym
        last_move = self.snake[-1]
        if direction == 'W':
            self.snake.append((last_move[0], last_move[1] - 1))
        elif direction == 'S':
            self.snake.append((last_move[0], last_move[1] + 1))
        elif direction == 'A':
            self.snake.append((last_move[0] - 1, last_move[1]))
        elif direction == 'D':
            self.snake.append((last_move[0] + 1, last_move[1]))

    def generate_food(self):
        while True:
            x = randint(0, self.width - 1)
            y = randint(0, self.height - 1)
            if (x, y) not in self.snake:
                return (x, y)

    def move_snake(self):
        head = self.snake[-1]
        x, y = head

        if head == self.food:
            self.score += 1
            self.food = self.generate_food()
        else:
            self.snake.pop(0)

        if (x < 0 or x >= self.width or y < 0 or y >= self.height or
            head in self.snake[:-1]):
            self.end_game()
            return

        self.draw()
        self.master.after(100, self.move_snake)

    def end_game(self):
        self.canvas.delete(tk.ALL)
        self.canvas.create_text(self.width * self.cell_size / 2, self.height * self.cell_size / 2,
                                text=f"Game Over\nScore: {self.score}", font=("Arial", 14), fill="black")

if __name__ == "__main__":
    root = tk.Tk()
    game = SnakeGame(root)
    root.mainloop()

You can run the code above, and the Snake game will be displayed. Use the W, S, A, and D keys to control the snake's movement. The score is displayed at the top left of the playing area. The game ends if the snake hits the walls or runs into itself.

The Result:

The code supplied was pasted into the MU Editor and run, but it did not seem to work, producing the following error:

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Users\main\AppData\Local\Programs\Mu Editor\Python\lib\tkinter\__init__.py", line 1892, in __call__
    return self.func(*args)
  File "C:\Users\main\AppData\Local\Programs\Mu Editor\Python\lib\tkinter\__init__.py", line 814, in callit
    func(*args)
  File "c:\develop\python\snake\snake1.py", line 65, in move_snake
    head = self.snake[-1]
IndexError: list index out of range

Fixing Errors

Once notified of the issue ChatGPT produced a modified method for the move_snake method, making the following comments:

ChatGPT:

I apologize for the oversight. The error you're seeing arises from trying to access the last element of an empty list. This situation can arise in the given code if the snake is reduced to zero segments.

To fix this issue, we should avoid moving the snake if its length is less than or equal to zero.

I'll make a small modification to the move_snake function to account for this:

def move_snake(self):
    if len(self.snake) == 0:  # Check if the snake has any segments left
        return

    head = self.snake[-1]
    x, y = head

    if head == self.food:
        self.score += 1
        self.food = self.generate_food()
    else:
        self.snake.pop(0)

    if (x < 0 or x >= self.width or y < 0 or y >= self.height or
        head in self.snake[:-1]):
        self.end_game()
        return

    self.draw()
    self.master.after(100, self.move_snake)

With this fix, the code will no longer try to access the last element of an empty snake list. However, I must note that under normal gameplay conditions, the snake should never be empty as it starts with 3 segments and only loses a segment when it hits a wall or itself – at which point the game ends.

snake1

I finally had a running game. Or did I?

More Issues

The game ran, but the body of the snake disappeared into the head of the snake and then the snake ceased to exist. It was not evident what the issue was initially. I had to slow the game down to one move every 500 milliseconds. Then I could see the issue. I told ChatGPT the following:

I slowed the snake down and noticed the snake segments move into the head but the head never moves. After the last segment moves into the head the snake is gone.

ChatGPT:

I apologize for the oversight. You're right; the game as originally designed isn't updating the snake's head based on the direction pressed.

Let's fix this oversight by updating the position of the snake's head based on the key pressed.

In the key_press function, after adding the new head position, we should remove the tail (unless the snake ate food). This way, the snake moves forward in the chosen direction. The current code only adds new segments but doesn't remove the old ones unless it eats food, which is causing the snake to 'consume' itself.

Here's the updated key_press function:

def key_press(self, event):
    direction = event.keysym
    last_move = self.snake[-1]
    new_head = last_move
    if direction == 'W':
        new_head = (last_move[0], last_move[1] - 1)
    elif direction == 'S':
        new_head = (last_move[0], last_move[1] + 1)
    elif direction == 'A':
        new_head = (last_move[0] - 1, last_move[1])
    elif direction == 'D':
        new_head = (last_move[0] + 1, last_move[1])

    self.snake.append(new_head)
    # Remove the tail if not eating food
    if new_head != self.food:
        self.snake.pop(0)

This change ensures that the snake moves in the direction of the pressed key by adding a segment in that direction and removing the tail segment, giving the illusion of movement. If the snake eats food, the tail segment won't be removed, making the snake grow.

Considering the code I had another question for ChatGPT:

The head also needs an initital direction - did you set one up?

ChatGPT:

You're right; the snake needs an initial direction to start moving automatically even if no keys are pressed. I apologize for missing that detail.

Let's set up an initial direction for the snake and ensure it keeps moving in that direction until another key is pressed.

We'll make the following changes:

Add an initial_direction attribute to the SnakeGame class. Update the move_snake method to use this direction when no keys are pressed. Here's the code:

class SnakeGame:
    def __init__(self, master):
        # ... [previous code]
        self.direction = 'D'  # Let's start by moving right
        # ... [rest of the code]

    def key_press(self, event):
        self.direction = event.keysym
        self.move_based_on_direction()

    def move_based_on_direction(self):
        last_move = self.snake[-1]
        new_head = last_move
        if self.direction == 'W':
            new_head = (last_move[0], last_move[1] - 1)
        elif self.direction == 'S':
            new_head = (last_move[0], last_move[1] + 1)
        elif self.direction == 'A':
            new_head = (last_move[0] - 1, last_move[1])
        elif self.direction == 'D':
            new_head = (last_move[0] + 1, last_move[1])

        self.snake.append(new_head)
        # Remove the tail if not eating food
        if new_head != self.food:
            self.snake.pop(0)

    def move_snake(self):
        if len(self.snake) == 0:  # Check if the snake has any segments left
            return

        self.move_based_on_direction()

        head = self.snake[-1]
        x, y = head

        if (x < 0 or x >= self.width or y < 0 or y >= self.height or
            head in self.snake[:-1]):
            self.end_game()
            return

        self.draw()
        self.master.after(200, self.move_snake)  # Adjusted speed here

With these modifications, the snake will start moving to the right initially ('D' direction). If you press any of the W, A, S, or D keys, it will then change direction accordingly.

Game Over

Running the game at this stage as developed by ChatGPT looked quite good. I quickly realized there was an issue - after pressing any key the game abruptly ended:

snake2

I reported this to my developer – ChatGPT:

No matter which key I press an end of game condition is generated.

ChatGPT:

I apologize for the oversight. The problem is likely due to the ordering of operations. In the current logic, the snake is moved as soon as a key is pressed, then it is moved again immediately during the automatic move process. This can result in situations where the snake moves twice in rapid succession, causing it to collide with itself or the walls.

Let's make some adjustments:

Remove the self.move_based_on_direction() call from the key_press method to prevent immediate movement when a key is pressed. Instead, we'll rely solely on the move_snake method to handle the snake's movement. Adjust the logic for the direction to ensure that the snake doesn't move in the opposite direction of its current movement (this can cause it to run into itself). Here's the revised code:

class SnakeGame:
    def __init__(self, master):
        # ... [previous code]
        self.direction = 'D'  # Let's start by moving right
        # ... [rest of the code]

    def key_press(self, event):
        new_direction = event.keysym
        if self.direction == 'W' and new_direction != 'S':
            self.direction = new_direction
        elif self.direction == 'S' and new_direction != 'W':
            self.direction = new_direction
        elif self.direction == 'A' and new_direction != 'D':
            self.direction = new_direction
        elif self.direction == 'D' and new_direction != 'A':
            self.direction = new_direction

    def move_based_on_direction(self):
        last_move = self.snake[-1]
        new_head = last_move
        if self.direction == 'W':
            new_head = (last_move[0], last_move[1] - 1)
        elif self.direction == 'S':
            new_head = (last_move[0], last_move[1] + 1)
        elif self.direction == 'A':
            new_head = (last_move[0] - 1, last_move[1])
        elif self.direction == 'D':
            new_head = (last_move[0] + 1, last_move[1])

        self.snake.append(new_head)
        # Remove the tail if not eating food
        if new_head != self.food:
            self.snake.pop(0)

    def move_snake(self):
        if len(self.snake) == 0:  # Check if the snake has any segments left
            return

        self.move_based_on_direction()

        head = self.snake[-1]
        x, y = head

        if (x < 0 or x >= self.width or y < 0 or y >= self.height or
            head in self.snake[:-1]):
            self.end_game()
            return

        self.draw()
        self.master.after(200, self.move_snake)  # Adjusted speed here

The adjustments in the key_press method ensure that the snake doesn't move immediately upon a key press, and also that it doesn't reverse direction into itself. The move_snake method remains the primary mechanism for the snake's movement.

Digging in Personally

These changes were applied, unfortunately, this did not correct the issue. I decided to do some basic troubleshooting on behalf of the developer to see if I could figure out where the problem was instead of going in circles with the AI as I have experienced in cases like this before. After adding several print statements I was able to track the issue to the key_press method. It expected four specific characters A, W, S or D, in uppercase only. Any other keys pressed caused the program to crash. This was relayed to ChatGPT for a correction to the code:

I found that invalid keypresses cause the game to end. Also the keypress is not getting converted to uppercase, so even a valid keypress is invalid if it is not converted to upper case.

ChatGPT:

I understand the issues you're facing. Let's address them.

To ensure that the game doesn't end on invalid keypresses, we need to check if the key pressed is one of the allowed keys before processing it. To handle both lowercase and uppercase keypresses, we can convert the event.keysym value to uppercase using the upper() method. Here's the modified key_press method:

def key_press(self, event):
    new_direction = event.keysym.upper()  # Convert to uppercase

    # Check if the key pressed is one of the allowed keys
    if new_direction not in ['W', 'S', 'A', 'D']:
        return  # Exit without making any changes

    if self.direction == 'W' and new_direction != 'S':
        self.direction = new_direction
    elif self.direction == 'S' and new_direction != 'W':
        self.direction = new_direction
    elif self.direction == 'A' and new_direction != 'D':
        self.direction = new_direction
    elif self.direction == 'D' and new_direction != 'A':
        self.direction = new_direction

With these modifications, the game will only respond to the W, A, S, and D keys (both lowercase and uppercase) and will ignore other keypresses without causing the game to end.

Growth without Eating

snake3

With the snake moving correctly the next issue encountered was a problem around the food not being consumed and the score not getting updated when the snake moved over the food. I identified to ChatGPT where I thought the issue might be this time and let the AI fix the problem:

It appears the food is not being eaten (although the snake is growing in length) - I think we need code here:

    if new_head != self.food:
    self.snake.pop(0)

...which removes the food when eaten and generates a new food morsel.

ChatGPT:

You're correct, I must've overlooked that part. When the snake's head is on the same cell as the food, the food should be "eaten" and a new piece of food should be generated.

Let's correct this in the move_based_on_direction method. When the snake's new head position matches the food position, we should not only grow the snake but also generate a new piece of food and update the score.

Here's the corrected code for the move_based_on_direction method:

def move_based_on_direction(self):
    last_move = self.snake[-1]
    new_head = last_move

    if self.direction == 'W':
        new_head = (last_move[0], last_move[1] - 1)
    elif self.direction == 'S':
        new_head = (last_move[0], last_move[1] + 1)
    elif self.direction == 'A':
        new_head = (last_move[0] - 1, last_move[1])
    elif self.direction == 'D':
        new_head = (last_move[0] + 1, last_move[1])

    self.snake.append(new_head)

    # Check if the snake has eaten the food
    if new_head == self.food:
        self.score += 1
        self.food = self.generate_food()
    else:
        self.snake.pop(0)

Now, when the snake's head overlaps with the food, the snake will grow, the score will increase by one, and a new piece of food will be generated. If not, the snake will just move forward, and its tail segment will be removed.

These changes were incorporated into the code and some game testing was done. It seemed that with these last changes things were working really well. The game was playable and the score was being updated. Now there were a few improvements I wanted to make.

Making the Game Better

I wanted to start the game nice and slow and make the snake move faster as the player has more success adding to the challenge of the game. I asked ChatGPT:

Let’s set the initial speed to 400 which is nice and slow. Make sure the keyboard processing does not get too slow though. Change the code to make the initial snake speed 400ms and increase speed by 5% with ever food eaten. Never let it drop below 50ms.

ChatGPT:

Certainly! We can achieve this by decoupling the keyboard input processing from the snake's movement.

We'll maintain a timer for processing the snake movement which will initially be set to 400ms and will decrease with every food eaten. We'll set a faster timer for checking and processing the keyboard buffer so that key presses are responsive.

Here's how you can implement this: Modify the move_snake method to adjust the timer based on the food eaten.

def move_snake(self):
    if len(self.snake) == 0:  # Check if the snake has any segments left
        return

    self.move_based_on_direction()

    head = self.snake[-1]
    x, y = head

    if (x < 0 or x >= self.width or y < 0 or y >= self.height or
        head in self.snake[:-1]):
        self.end_game()
        return

    self.draw()

    speed = max(50, int(400 * (0.95 ** self.score)))
    self.master.after(speed, self.move_snake)

With the above modification, the snake's speed will start at 400ms per move, and for every piece of food eaten, it will become 5% faster. To set a minimum threshold for the speed (i.e., ensure it never goes below 50ms), use the max() function. In this case even if the calculated speed goes below 50 due to the score being very high, the speed will never be set below 50ms. The key presses, being event-driven, will be instantly responsive irrespective of the snake's movement speed.

I also wanted to see the speed (a measurement of difficulty) so I asked ChatGPT:

Also add a print out of the snake speed calculated as 450 - speed. Put that in the upper right corner.

ChatGPT:

To display the snake speed in the upper right corner, you need to make use of the Canvas's text drawing capabilities.

Here's what you should do:

Add an attribute to SnakeGame to hold the speed text item ID. This way, we can update the text each time the snake moves. Update the draw method to display the speed text on the canvas. Let's walk through the changes:

Step 1: Add an attribute for the speed text item ID In the init method of the SnakeGame class:

self.speed_text_id = None

Step 2: Update the draw method to display the speed text In the draw method, before the line that starts the loop to draw the snake, add the following:

# Delete previous speed text
if self.speed_text_id:
    self.canvas.delete(self.speed_text_id)

# Display the snake speed
snake_speed = 450 - speed
self.speed_text_id = self.canvas.create_text(self.width * self.cell_size - 10, 10, anchor="ne", 
                                            text=f"Speed: {snake_speed}", fill="red")

The create_text method is used to place text on the canvas. The anchor argument "ne" ensures that the text is right-aligned in the top right corner. We're subtracting 10 from the width to ensure a small margin.

With these changes, the speed of the snake (calculated as 450 - speed) will be displayed in the upper right corner of the canvas. The speed text will update every time the snake moves.

Making Beginner Mistakes

I find that ChatGPT makes some basic errors that a new programmer to the language would make. In this case it was a scope issue. I pointed this out:

I think that the variable "speed" is out of scope for the draw method - yes?

ChatGPT:

You're correct. My apologies for the oversight.

A solution would be to update the speed as an attribute of the SnakeGame class each time it's recalculated, and then you can access it directly in the draw method.

Here's how you can adjust:

  1. Add the speed as an attribute in the init method:
self.speed = 400  # Initial speed
  1. Update the speed in the move_snake method: Change the speed calculation line to:
self.speed = max(50, int(400 * (0.95 ** self.score)))

Then, use self.speed for scheduling the after method:

self.master.after(self.speed, self.move_snake)
  1. Use self.speed in the draw method: In the draw method, you can now use:
snake_speed = 450 - self.speed
self.speed_text_id = self.canvas.create_text(self.width * self.cell_size - 10, 10, anchor="ne", 
                                            text=f"Speed: {snake_speed}", font=("Arial", 14), fill="black")

By making these adjustments, the speed will be accessible both in move_snake and draw, ensuring that the speed is updated correctly and can be displayed on the canvas.

We Have Arrived!

At this point the game was complete enough to enjoy and show off. ChatGPT wrote a snake game as planned, but as it turned out it required an experienced programmer to help locate some issues and guide the development.

snake

The Trials and Triumphs: A Deep Dive into ChatGPT's Game Development Journey

Taking on the challenge of letting ChatGPT shoulder the programming responsibilities for our Snake game was not without its hurdles. The journey unfolded in a series of iterations, each refining the product but also exposing the model's limitations.

Our initial enthusiasm met a bump when the first version ran aground with an out-of-bounds error. Even as we fine-tuned our prompts, further iterations unveiled logic mishaps. The snake's movements, steered by the A, W, D, and S keys, weren't responsive. While ChatGPT grappled with pinpointing the hiccup, it was the human touch—that of the assisting programmer—that shone a light on the underlying issues. By strategically placing print statements, it became evident that ChatGPT had overlooked filtering out invalid keystrokes and converting them to uppercase for comparison.

And there were more oversights. The model faltered at managing the game's score, handling the food consumption, and integrating a global speed display—overlooking a classic pitfall of variable scopes.

So, let's address the elephant in the room: Could ChatGPT single-handedly craft a full-fledged game from a lone prompt? Candidly, no. It needed a nudge, or rather, several nudges. And did it, after these series of prompts, whip up an error-free, immersive game without human intervention? The truth is, while ChatGPT paved much of the way, the human programmer played the critical role of refining and debugging.

Yet, it's worth noting that with ChatGPT in the mix, the game development was significantly expedited. From inception to a fully functional game, the process took mere minutes, with the lion's share of time invested in ironing out the movement logic.

In conclusion, as of now, ChatGPT shines as a collaborative ally in the programming arena, accelerating processes and offering insights. However, the notion of it replacing human programmers entirely? That remains a distant future, if at all feasible.

Accessing the Source Code

This project is available on Github here: https://github.com/bjazmoore/Python-Snake. If you do not have Python installed you can download a windows executable to play the game.
It is licensed with the standard MIT Open Source license

About the author


Brad Moore

Brad Moore

Brad Moore is an on-again, off-again web developer; hobbyist programmer; writer; blogger; deacon and Sunday school teacher. He lives with his wife along the Ohio river in Kentucky, USA. He has three grown kids and one cat named Arthur.