Programming in the era of ChatGPT

ChatGPT was released for free public use in November 2022 by OpenAI. I'd heard and read about how well it works not only as a chatbot, but for writing code.

While new for me, AIs writing code is not a new concept. GitHub Copilot (which I also haven't used) was released for public use more than a full year before ChatGPT. As I understand it, GitHub Copilot is built upon a similar sort of AI service, also from OpenAI, but is less generically conversational and more of a pure code-generation tool.

Asking an AI to write code, with a back-and-forth dialog, and actually have it generate something useful, seemed so far-fetched to me I had to try it out. As you'll see below, I was blown away by how well it performed!

With some back and forth fixing and incremental improvements (I hardly had to change anything myself) ChatGPT wrote several programs for me, and the following webpages:

In this article I'll share my chats with ChatGPT and most of the code it wrote for me. I close with my rambling thoughts on the onset of this technology.

After creating an OpenAI account (which is required, presumably, to try to minimize misuse of the system) I tried some basic conversational chatting. It of course did very well but gave formal, verbose answers to things. Next, I tried to get it to write a program.

Below you'll see my inputs indented 1 block level, and preceded with a "Phil:", and ChatGPT's responses indented 2 block levels, for example:

Phil: my input

The response given by ChatGPT

The code written by ChatGPT will be formatted as a regular code block here without further indentation:

code written by ChatGPT

Number Sorting

For the first test, I went with a very simple concept, and specified just about everything needed to write it:

Phil: write a python 3 program to sort a list of numbers separated by newline characters, read from stdin, and write the result to a file called "sorted.txt"

This was a simple example, and the output here looks surprisingly good to me (I haven't tried it):

import sys

numbers = []
for line in sys.stdin:
    numbers.append(int(line.strip()))

numbers.sort()

with open("sorted.txt", "w") as f:
    for number in numbers:
        f.write(str(number) + '\n')

This program will take a list of numbers separated by newline characters from standard input, sort them using the built-in sort() function, and write the sorted list to a file called "sorted.txt".

Here is how this program works:

First, the program imports the sys module. Next, it creates an empty list called numbers. Then, it reads the standard input using a for loop, and for each line, it converts the line to an integer using int() and strip() functions and appends to the numbers list. After that, it sorts the numbers list in ascending order using the built-in sort() function. Finally, it opens the file "sorted.txt" in write mode, then for each number in the numbers list, it writes the number to the file and add a new line character.

Please note that the user needs to have write access to the directory where this code is executed, otherwise, it will raise a permission error.

Now, let's see if OpenAI's claims that it "Remembers what user said earlier in the conversation" and "Allows user to provide follow-up corrections" work as expected:

Phil: give me a program that does the same thing, but using python list comprehensions

import sys

numbers = [int(line.strip()) for line in sys.stdin]
numbers.sort()

with open("sorted.txt", "w") as f:
    f.writelines(str(number) + '\n' for number in numbers)

This program is similar to the previous one but it uses python list comprehension which allows you to create a list using a single line of code... [output truncated here by me]

Again, this response looks great! I can incrementally change the program by referring to what's already been discussed!

Now, let's go another step:

Phil: port the previous program to rust

Again, I haven't tried it, but it looks convincing and like it should at least be mostly correct:

use std::io;
use std::io::prelude::*;
use std::fs::File;

fn main() {
    let mut numbers: Vec<i32> = Vec::new();
    for line in io::stdin().lock().lines() {
        let line = line.unwrap();
        numbers.push(line.trim().parse().unwrap());
    }

    numbers.sort();

    let mut file = File::create("sorted.txt").unwrap();
    for number in numbers {
        writeln!(file, "{}", number).unwrap();
    }
}

This program does the same thing as the previous python programs, but it's written in Rust... [output truncated here by me]

Phil: port the previous program to a bash shell script for use on a linux computer

#!/bin/bash

numbers=()
while read line
do
  numbers+=($line)
done

sorted_numbers=($(printf '%s\n' "${numbers[@]}" | sort -n))

echo ${sorted_numbers[@]} > sorted.txt

This program will take a list of numbers separated by newline characters from standard input, sort them using the sort command, and write the sorted list to a file called "sorted.txt"... [output truncated here by me]

This looks like it should work, but could be much simpler, using a one-liner: sort -n > sorted.txt

A More Complex Concept

I tried a prompt for another simple script, but without specifying exactly how to accomplish it:

Phil: write a python program to give the number of bits of entropy represented by the diceware passphrase read from standard input

I was again surprised to see how nicely this turned out! Wow!

import math

def entropy(passphrase):
    word_list = passphrase.strip().split()
    word_count = len(word_list)
    log2_word_count = math.log2(word_count)
    entropy_per_word = log2_word_count + math.log2(7776)
    return entropy_per_word * word_count

passphrase = input()
print(entropy(passphrase))

This program calculates the number of bits of entropy represented by the diceware passphrase read from standard input... [output truncated here by me]

I am impressed by the variable names, the clear description it provides following each program, and just overall how easy this is to use. It even embeds code comments!

Number Guessing

Here's another simple program ChatGPT got mostly correct on the first try:

Phil: write a python command line script that prompts the user to guess a random number between 1 and 1 million, inclusive. if the guess is incorrect, allow the user to guess again. when the correct number is guessed, tell the user how many guesses the user made versus how many an optimal binary search algorithm would have used.

Here is a Python script that prompts the user to guess a random number between 1 and 1 million, inclusive and compares the number of guesses made by the user to the optimal number of guesses in a binary search algorithm:

import random

# Generate a random number between 1 and 1 million
answer = random.randint(1, 1000000)

# Initialize the number of guesses
guesses = 0

while True:
    # Get the user's guess
    guess = int(input("Guess a number between 1 and 1 million: "))
    guesses += 1

    # Check if the guess is correct
    if guess == answer:
        print("Congratulations! You guessed the correct number.")
        print("You made", guesses, "guesses.")
        print("An optimal binary search algorithm would have made", len(str(answer)), "guesses.")
        break
    elif guess < answer:
        print("Too low, try again.")
    else:
        print("Too high, try again.")

You can adjust the range of numbers by changing the lower and upper bounds in the random.randint() function.

This script prompts the user to guess a number and compares it to a randomly generated number between 1 and 1 million. If the user's guess is incorrect, the script tells the user whether the guess was too low or too high and prompts them to guess again. Once the user guesses the correct number, the script tells the user how many guesses they made and how many guesses an optimal binary search algorithm would have made.

Please note that the optimal number of guesses for binary search is equal to the log base 2 of the number of items in the list, rounded up to the next integer. So in this case we are using len(str(answer)) which is the number of digits of the number and assuming that the number is uniformly distributed, which is not the case with random numbers. But this is a simple way to give a rough idea of the number of optimal guesses.

It got the estimation for the number of binary search guesses wrong.

Phil: your estimate for the optimal number of guesses is incorrect. for example, log base 2 of 1 million is around 19, not 6. if the range were 1-100, again your estimate is incorrect. log base 2 of 100 is around 6, not 2.

I apologize for the confusion, you are correct that the estimate of optimal number of guesses I provided is incorrect. The correct way to calculate the number of guesses in a binary search algorithm is to use the log base 2 of the range of numbers being searched, rounded up to the next integer.

For example:

... snipped here by me ...
optimal_guesses = math.ceil(math.log(1000000, 2))
...

In this example, the range is 1-1000000 so the optimal number of guesses would be math.ceil(math.log(1000000, 2)) which is around 20 guesses.

I apologize again for any confusion I may have caused, and thank you for bringing this to my attention.

Phil: you omitted the necessary line "import random"

I apologize, you're correct that I omitted the necessary "import random" line in my previous code snippet. Here's the correct version of the script:

(It gave the same script but with correct import statements.)

Haiku Webpage

For this example, I tested generating HTML, CSS, and JavaScript. Again, this demonstrates how nicely this works for iterative programming. With a better interface to the filesystem this could legitimately work for prototyping, bug fixing, etc.

I am embedding most of the conversation here, mostly for posterity, but also because it shows how nicely ChatGPT handles requests in the context of the ongoing conversation.

Phil: write an html file, with embedded javascript, that writes a haiku with a button labeled "write haiku"

Here is an example of an HTML file with embedded JavaScript that writes a haiku with a button labeled "Write Haiku":

<!DOCTYPE html>
<html>
<head>
<title>Haiku Generator</title>
</head>
<body>

<h1>Haiku Generator</h1>

<div id="haiku"></div>

<button onclick="writeHaiku()">Write Haiku</button>

<script>
function writeHaiku() {
    var haiku = "Autumn moonlight -\na worm digs silently\ninto the chestnut.";
    document.getElementById("haiku").innerHTML = haiku;
}
</script>

</body>
</html>

In this example, JavaScript function writeHaiku() is called when the button is clicked, it assigns the haiku as a string to the innerHTML of a div element with id "haiku". The haiku will be displayed in the div element. This is a basic example that always displays the same haiku. You can replace the hardcoded haiku with a dynamic one generated by a haiku generator library or API, or you can use random haiku from an array of haikus.

Phil: give the previous html page a muted dark mode color theme

Here is an example of how you might give the previous HTML page a muted dark mode color theme:

... snipped here by me ...
<style>
  /* add dark mode styles */
  body {
    background-color: #3f3f3f; /* muted dark gray */
    color: #d9d9d9; /* muted light gray */
  }
  button {
    background-color: #8a8a8a; /* muted dark gray */
    color: white;
  }
</style>
...

In this example, I added a style element in the head section of the HTML page and defined some CSS styles for the body and button elements. The background-color and color properties are set to muted dark gray and muted light gray respectively to give a muted dark mode theme. You can also use CSS preprocessors or frameworks like Bootstrap to handle color themes.

Phil: for the previous html page, when the button is pressed, use a javascript ajax request to load another haiku from an online haiku service, or scrape one from a website. also, show all previously-output haikus.

(it generated code that used an example URL, and explained how to actually put it to use)

Phil: modify the previous script to remove the ajax request on the button click. instead, randomly show one of 10 haikus embedded into the javacsript.

Here is an example of how you might modify the previous script to remove the AJAX request on the button click and instead randomly show one of 10 haikus embedded into the JavaScript

... snipped here by me ...
<script>
  var previousHaikus = [];
  var haikus = [
    "Autumn moonlight -\na worm digs silently\ninto the chestnut.",
    "Winter twilight -\na world of dew,\nand within every dewdrop\na world of struggle.",
    "Over the wintry\nForest, winds howl in rage\nWith no leaves to blow.",
    "A world of dew,\nAnd within every dewdrop\nA world of struggle.",
    "The light of a candle\nIs transferred to another candle—\nSpring twilight",
    "Over the sea\nMountains rise in the distance—\nAutumn moon",
    "Winter seclusion -\nListening, that evening,\nTo the rain in the mountain.",
    "A world of dew,\nAnd within every dewdrop\nA world of struggle.",
    "life’s little, our heads\nsad. Redeemed and wasting clay\nthis chance. Be of use.",
    "life’s little, our heads\nsad. Redeemed and wasting clay\nthis chance. Be of use."
  ];

  function loadHaiku() {
    var randomHaiku = haikus[Math.floor(Math.random()*haikus.length)];
    displayHaiku(randomHaiku);
  }

  function displayHaiku(haiku) {
    previousHaikus.push(haiku);
    var haikuHTML = "";
    for (var i = 0; i < previousHaikus.length; i++) {
      haikuHTML += "<p>" + previousHaikus[i] + "</p>";
    }
    document.getElementById("haiku-container").innerHTML = haikuHTML;
  }

</script>
...

In this example, I removed the loadHaiku() function's XMLHttpRequest and replaced it with an array haikus that contains 10 haikus. The loadHaiku() function generates a random number between 0 and the number of haikus in the array haikus using Math.random() function, this random number is used to select one of the haikus from the array. The selected haiku is passed to the displayHaiku(haiku) function, which is similar to the previous version and stores

Phil: for the previous html page, display line breaks in the expected places in each haiku.

Here is an example of how you might modify the previous script to display line breaks in the expected places in each haiku

... snipped here by me ...
function displayHaiku(haiku) {
  previousHaikus.push(haiku);
  var haikuHTML = "";
  for (var i = 0; i < previousHaikus.length; i++) {
    haikuHTML += "<p>" + previousHaikus[i].replace(/\n/g, "<br>") + "</p>";
  }
  document.getElementById("haiku-container").innerHTML = haikuHTML;
}
...

In this example, I added the replace(/\n/g, "<br>") statement in the displayHaiku(haiku) function. This statement replaces the new line characters ('\n') in each haiku with <br> tags, which are [the output was truncated here]

Phil: for the previous html page, instead of displaying the new haiku instantly, display it with a "fade in" effect. also, move the entire user interface into a centered scrolling box that's 50% the height of the window.

Here is an example of how you might modify the previous script to display the new haiku with a "fade in" effect, and also move the entire user interface into a centered scrolling box that's 50% the height of the window:

... snipped here by me ...
<style>
  /* add dark mode styles */
  body {
    background-color: #3f3f3f; /* muted dark gray */
    color: #d9d9d9; /* muted light gray */
  }
  button {
    background-color: #8a8a8a; /* muted dark gray */
    color: white;
  }
  /* add styles for the scrolling box */
  .scrolling-box {
    height: 50%; /* set the height to 50% of the window */
    overflow-y: scroll; /* enable vertical scrolling */
    margin: auto; /* center the box horizontally */
    text-align: center; /* center the text horizontally */
  }
  /* add styles for the fade in effect */
  .fade-in {
    opacity: 0; /* initially set the opacity to 0 */
    animation: fade-in 1s linear; /* define the animation */
  }
  @keyframes fade-in {
    from { opacity: 0; }
    to { opacity: 1; } /* increase the opacity to 1 over 1 second */
  }
</style>
...

(the 50% height request didn't work as intended. CSS is hard!)

Phil: modify the previous html page to remove the 'fade-in' class from the <p> elements created by the button

Here is an example of how you might modify the previous HTML page to remove the 'fade-in' class from the <p> elements created by the button:

(it generated code, but stopped before showing the changes)

Phil: show me only the updated "displayHaiku" function

Here is the updated displayHaiku function that removes the 'fade-in' class from the <p> elements created by the button:

function displayHaiku(haiku) {
    previousHaikus.push(haiku);
    var haikuHTML = "";
    for (var i = 0; i < previousHaikus.length; i++) {
      haikuHTML += "<p>" + previousHaikus[i].replace(/\n/g, "<br>") + "</p>";
    }
    document.getElementById("haiku-container").innerHTML = haikuHTML;
}

You can see that I removed the class attribute from the <p> element that was creating the fade in effect.

For this request, ChatGPT was not able to properly use JavaScript to add/remove the CSS class with JavaScript. I asked it to do an alternative instead:

modify the .fade-in css class above to only affect the last sibling <p> element

Here is an example of how you might modify the .fade-in CSS class to only affect the last sibling

element:

.fade-in:last-child {
    opacity: 0; /* initially set the opacity to 0 */
    animation: fade-in 1s linear; /* define the animation */
}

In this example, I added the :last-child selector to the .fade-in class. This selector targets the last child element of its parent, which in this case is the last

element in the haiku-container div. This means that only the last

element will have the opacity: 0 and animation: fade-in 1s linear styles applied to it, causing the fade-in effect.

Also, you should note that the previous JavaScript code that added the class fade-in was removed, so this css class is not used anymore.

It did a great job, overall, with this. (The haikus themselves are not very good, but that's to be expected, I think!) I was especially pleased by the CSS "fade in" effect. You can view the HTML page here.

Emoji Aquarium

Next, I tried to go over the top.

Phil: write an html file, with embedded javascript, that displays 2D graphics rendered onto a canvas at 30fps. the canvas should represent the side view of an aquarium, containing fish, and the canvas should take the full width and height of the window. the fish themselves can be rendered with fish emoji characters. the fish should move around randomly in the window.

Here is an example of an HTML file that displays 2D graphics of fish emoji characters moving around randomly on a canvas filling the width and height of the window at 30fps:

<!DOCTYPE html>
<html>
<head>
  <title>Aquarium</title>
</head>
<body>

<canvas id="aquarium"></canvas>

<script>
  // Get the canvas element
  var canvas = document.getElementById("aquarium");

  // Set the canvas size to the full width and height of the window
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;

  // Get the canvas context
  var ctx = canvas.getContext("2d");

  // Set the number of fish
  var numFish = 10;

  // Create an array to store the fish data
  var fish = [];

  // Initialize the fish data
  for (var i = 0; i < numFish; i++) {
    fish[i] = {
      x: Math.random() * canvas.width,
      y: Math.random() * canvas.height,
      speed: Math.random() * 2 + 1,
      angle: Math.random() * 360
    };
  }

  // Function to update the fish position and angle
  function updateFish() {
    for (var i = 0; i < numFish; i++) {
      fish[i].x += Math.cos(fish[i].angle * Math.PI / 180) * fish[i].speed;
      fish[i].y += Math.sin(fish[i].angle * Math.PI / 180) * fish[i].speed;

      if (fish[i].x > canvas.width + 50) {
        fish[i].x = -50;
      } else if (fish[i].x < -50) {
        fish[i].x = canvas.width + 50;
      }

      if (fish[i].y > canvas.height + 50) {
        fish[i].y = -50;
      } else if (fish[i].y < -50) {
        fish[i].y = canvas.height + 50;
      }

      fish[i].angle += Math.random() * 20 - 10;
    }
  }

  // Function to draw the fish on the canvas
  function drawFish() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    for (var i = 0; i < numFish; i++) {
      ctx.save();
      ctx.translate(fish[i].x, fish[i].y);
      ctx.rotate(fish[i].angle * Math.PI / 180);
      ctx.fillText("🐟", 0, 0);
      ctx.restore();
    }
  }

  // Function to run the animation
  function animate() {
    updateFish();
    drawFish();
    window.requestAnimationFrame(animate);
  }

  // Start the animation
  animate();
</script>

</body>
</html>

Note that this code will only work in a browser and it uses requestAnimationFrame method to achieve 30fps.

Also, you may need to adjust the number of [the output was truncated here]

Phil: update the webpage to make the fish swim slower, and mostly just horizontally left and right (with occasional up/down movement), and don't jump the fish to the opposite edge when they move over the edge of the screen -- instead, stop them at the edge so they have to turn around.

Here is an updated version of the previous code that makes the fish swim slower, mostly horizontally left and right, and stops them at the edge of the screen instead of jumping to the opposite edge:

... snipped here by me ...

  // Initialize the fish data
  for (var i = 0; i < numFish; i++) {
    fish[i] = {
      x: Math.random() * canvas.width,
      y: Math.random() * canvas.height,
      speed: Math.random() * 0.5 + 0.5, // slower speed
      angle: Math.random() * 90 - 45 // mostly horizontal movement
    };
  }

  // Function to update the fish position and angle
  function updateFish() {
    for (var i = 0; i < numFish; i++) {
      fish[i].x += Math.cos(fish[i].angle * Math.PI / 180) * fish[i].speed;
      fish[i].y += Math.sin(fish[i].angle * Math.PI / 180) * fish[i].speed;

      if (fish[i].x > canvas.width) {
        fish[i].x = canvas.width;
        fish[i].angle = (180 - fish[i].angle) + (Math.random() * 20 - 10); //change direction
      } else if (fish[i].x < 0) {
        fish[i].x = 0;
        fish[i].angle = (180 - fish[i].angle) + (Math.random() * 20 - 10); //change direction
      }

      if (fish[i].y > canvas.height) {
        fish[i].y = canvas.height;
        fish[i].angle = (360 - fish[i].angle) + (Math.random() * 20 - 10); //change direction
      } else if (fish[i].y < 0) {
        fish[i].y = 0;
        fish[i].angle = (360 - fish[i].angle) + (Math.random() * 20 - 10); //change direction
      }
    }
  }

...

Phil: make the background dark blue, and make the fish appear about 5 times bigger

Here is the updated version of the previous code that changes the background to dark blue and makes the fish appear about 5 times bigger:

(it again typed out most of the html file, and the output was truncated)

Phil: show me just the drawFish() function

Sure, here is the updated version of the drawFish() function that changes the size of the fish and sets the background color:

function drawFish() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = "rgb(0, 0, 128)";
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    ctx.font = "50px Arial"; // set the font size
    for (var i = 0; i < numFish; i++) {
      ctx.save();
      ctx.translate(fish[i].x, fish[i].y);
      ctx.rotate(fish[i].angle * Math.PI / 180);
      ctx.fillText("🐟", 0, 0);
      ctx.restore();
    }
  }

The key changes here are setting the font size to 50px and the background color before drawing the fish.

Phil: the fish appear to be swimming backward! make them swim the other way, or flip them around when drawing them. show me just the functions that are updated.

Here is an updated version of the updateFish() function that makes the fish swim in the opposite direction, by changing the angle of fish by 180 degrees:

We went back and forth a number of times on this. It was having trouble getting the fish to be drawn facing in the direction they are moving, and not drawn upside down.

Phil: when the fish are moving left, they can be rendered normally, and do not need to be flipped. the text (and fish emoji character) only need to be flipped horizontally when they are moving to the right. i think just the drawFish() function can be updated to make this change.

You are correct, my apologies for the confusion. Here is an updated version of the drawFish() function that only flips the fish horizontally when they are moving to the right:

function drawFish() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = "rgb(0, 0, 128)";
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    ctx.font = "50px Arial";
    for (var i = 0; i < numFish; i++) {
        ctx.save();
        ctx.translate(fish[i].x, fish[i].y);
        if (Math.cos(fish[i].angle * Math.PI / 180) > 0) {
            ctx.scale(-1, 1);
        }
        ctx.fillText("🐟", 0, 0);
        ctx.restore();
    }
}

This code uses the cosine of the angle of the fish to check the direction of movement, if it's greater than 0, the fish is facing right side, so it flips the fish horizontally using the scale method. This will make sure that the fish are always facing the right direction, based on their movement.

Phil: that works great. now, give each fish about a random 1/20 chance, every second, to change their direction of motion from leftwards to rightwards or vice versa.

Sure, here is an updated version of the updateFish() function that gives each fish a random 1/20 chance to change their direction of motion every second:

function updateFish() {
    for (var i = 0; i < numFish; i++) {
        fish[i].x += Math.cos(fish[i].angle * Math.PI / 180) * fish[i].speed;
        fish[i].y += Math.sin(fish[i].angle * Math.PI / 180) * fish[i].speed;

        if (fish[i].x > canvas.width) {
            fish[i].x = canvas.width;
            fish[i].angle = (360 - fish[i].angle) + (Math.random() * 90 - 45);
        } else if (fish[i].x < 0) {
            fish[i].x = 0;
            fish[i].angle = (360 - fish[i].angle) + (Math.random() * 90 - 45);
        }

        if (fish[i].y > canvas.height) {
            fish[i].y = canvas.height;
            fish[i].angle = (360 - fish[i].angle) + (Math.random() * 90 - 45);
        } else if (fish[i].y < 0) {
            fish[i].y = 0;
            fish[i].angle = (360 - fish[i].angle) + (Math.random() * 90 - 45);
        }
        fish[i].angle = fish[i].angle % 360;

        if (Math.random() < 1/20) {
            fish[i].angle = (fish[i].angle + 180) % 360;
        }
    }
}

This code uses the random() function to generate a random number between 0 and 1, if the random number is less than 1/20, it will add 180 degrees to the angle of the fish, this will make the fish change direction. And with this, the fish will have a 1/20 chance to change its direction of motion every second, this will make the fish move in a more random and natural way.

While correct-looking at first glance, there's a logical error here with ChatGPT's code.

Phil: the updateFish() function is called 30 times per second, so the current code applies that 1/20th chance 30 times per second, not once per second. so the random chance needs to be reduced by that factor in order to give a 1/20th chance per second. give me just the updated version of the line containing "Math.random() < 1/20" that fixes this.

You are correct, my apologies for the confusion. The current code applies the 1/20 chance 30 times per second, not once per second. To fix this, the random chance needs to be reduced by that factor, so it should be 1/20th chance per second. Here is the updated version of the line containing "Math.random() < 1/20" that fixes this:

if (Math.random() < 1/(20*30)) {
    fish[i].angle = (fish[i].angle + 180) % 360;
}

This code uses the random() function to generate a random number between 0 and 1, if the random number is less than 1/(20*30), which is 1/600, it will add 180 degrees to the angle of the fish, this will make the fish change direction.

It's worth mentioning that this value of 1/600 is an approximation, as the actual time the updateFish() function is called can fluctuate and may not be exactly 30 times per second, but in practice this value should be close enough to give a 1/20th chance per second.

Wow! It can give me just the updated line(s) I asked for!

Phil: that's much better. but to make it even less likely, about 1/100 odds per second, what should that line be changed to?

You can change that line to

if (Math.random() < 1/(100*30)) {
    fish[i].angle = (fish[i].angle + 180) % 360;
}

This will give each fish about 1/100 odds of changing their direction of motion per second.

This is because the updateFish() function is called 30 times per second and the new probability of fish direction change is 1/100, so the chance of fish direction change is 1/(100*30) which is 1/3000

So, this line of code will give each fish about 1/3000 chance of changing their direction of motion every time the updateFish() function is called.

This is where ChatGPT has a bit of problem:

Phil: ok very nice, thanks. add two more fish, and use the blowfish emoji character for them, but otherwise make them behave identically to the fish we already have.

Sure, to add two more fish, you will need to update the init() function where the number of fish is defined and the fish array is created. First, you will need to increase the number of fish by 2, like this:

var numFish = 5;

then, you will need to add two more fish objects to the fish array in the init() function, like this:

fish[3] = {x: canvas.width / 2, y: canvas.height / 2, angle: Math.random() * 360, speed: 2};
fish[4] = {x: canvas.width / 2, y: canvas.height / 2, angle: Math.random() * 360, speed: 2};

Finally, you will need to update the drawFish() function to use the blowfish emoji character instead of the fish emoji character when drawing the new fish. you can do this by replacing the following line of code:

ctx.fillText("🐟", 0, 0);

with this:

if (i < 3) {
    ctx.fillText("🐟", 0, 0);
} else {
    ctx.fillText("🐡", 0, 0);
}

This way the first three fish will be drawn using the fish emoji character, and the last two fish will be drawn using the blowfish emoji character. and all the fish will behave identically otherwise, and will be updated by the same updateFish() function.

ChatGPT got a number of things wrong with this request to add two new fish using a different emoji character. I'm not sure how or why it thinks there's an init() function, but there isn't one. I told it the rest of its mistakes, and it wasn't able to correct them all. It was easy enough however for me to fix them.

Phil: that's somewhat correct. a few corrections here: first, the numFish variable should be set to 12, not 5. secondly, we don't need to set any specific indexes of the fish array. lastly, the if statement's condition should be "i > 1" not "i < 3". that will render only the 0th and 1st fish as blowfish.

You are correct, I apologize for the confusion. Here are the corrected versions of the init() and drawFish() functions:

var numFish = 12;

(This is now correct.)

for (var i = 0; i < numFish; i++) {
    fish[i] = {x: canvas.width / 2, y: canvas.height / 2, angle: Math.random() * 360, speed: 2};
}

(I believe this was also corrected by just reverting to what it was before.)

if (i < 2) {
    ctx.fillText("🐡", 0, 0);
} else {
    ctx.fillText("🐟", 0, 0);
}

(There's still an off-by-one error here.)

in this way, the first two fish will be rendered as blowfish and the rest will be rendered as fish.

And also you should increase the number of fish to 12 instead of 5.

Again, I apologize for the confusion, I hope this clears things up.

Here, I tried to paste in some code for ChatGPT to read, and I think it worked:

Phil: that will work but i will leave the fish array initialized as the following: // Initialize the fish data for (var i = 0; i < numFish; i++) { fish[i] = { x: Math.random() * canvas.width, y: Math.random() * canvas.height, speed: Math.random() * 0.5 + 0.5, // slower speed angle: Math.random() * 90 - 45 // mostly horizontal movement }; }

Phil: You are correct, the fish array should be initialized as you described. Here is the corrected version of the init() function that initializes the fish array with the random initial positions, speed and angle:

(it gave me back the code again)

Once again, my expectations were exceeded with this request. It actually rendered a fish emoji swimming around on the first try! There were some hiccups, and a lot of back and forth, for a total of 15 revisions of the page. You can view the final page here.

Hurdles

ChatGPT gets basic facts wrong, often. This doesn't seem uncorrectable to me. Another AI could be trained to fact-check ChatGPT (transparently) before the user ever sees any incorrect output. But in general, I think after a few years of progression, these types of AIs will start to get all their facts correct more often than humans do.

Another interesting problem is malware generation. In the short term, I don't know how to get around this. Human users can already fool ChatGPT into bypassing restrictions on what kind of code writes. Perhaps it will have to keep a huge database of small snippets of all code it generates so the origins of any ChatGPT-written malware can be traced and corrected. But then again, it could be very useful for software companies, and companies in general, to use ChatGPT for penetration testing and for other cyber security purposes.

A New Era?

I am blown away by ChatGPT. I asked it to write a few scripts and webpages and it didn't get everything correct on the first try, but it largely understood what I wanted and made sensible choices where my specifications were lacking. I could describe, conversationally, where it made mistakes in its code and it could make the corrections! Or, again conversationally, I could ask it to change or add things to its code. It's like talking to a programmer, but it's an AI! This is so far beyond what I thought was possible with today's technology — it's like a surreal window into the future.

Interacting with AIs could be the inevitable future of software development. In the near term, I can envision AIs being used for prototyping and writing or running unit tests to fix software errors. Taking this concept a bit further, I believe that given enough proper training and computing power it could read and understand all the code for a software project (of any scale), and make improvements or add new features just as a programmer employee would. In the longer term, software development could be an interactive conversation with an AI, where humans sketch out GUIs or APIs and the AI then rapidly iterates on a series of code prototypes to get the product built quickly. The AIs could even tell the human software engineers where their specifications are incomplete or contain logical errors.

And this will forever change the way I think about AI in general. I was very skeptical of AI after seeing how poorly Siri and Alexa perform, how much trouble Tesla is having with their self-driving cars, how finicky Photoshop's "content-aware fill" is, how hard it is to wrangle StableDiffusion into creating an image that isn't a bizarre mess, etc. But ChatGPT feels entirely different. To me, it demonstrates the disruptive power of AI.

This raises a lot of issues around the kinds of work people do for a living, and not just for programmers. Writers in general, including sports writers, technical writers, and to some degree journalists, must be concerned about this technology. This and similar AIs can already do much of their job. It can even make corrections as suggested by an editor. Perhaps professional writers will some day soon work with an AI like this to craft a final product, where much of the grunt work (typing, spellchecking, summarizing a set of facts) is handled by the AI.

Other information-based occupations, aside from jobs focused on writing, are also potentially about to be disrupted by this technology. Examples include data and financial analysis jobs. Another that comes to mind: it could read and understand all documentation at any company and flawlessly handle customer service! Computer interfaces, and interfaces to information in general (voice assistants and search engines), will probably end up using technology similar to this.

It's easy to get carried away thinking about the possibilities, but this feels like a turning point to me on par with the invention of the computer or the Internet. I'm excited to see where this technology goes over the next few years.