Mon aventure avec ChatGPT

A moins d'être benêt comme certains, l'utilisateur de ChatGPT comprend vite qu'il faut prendre le machin pour ce qu'il est, une machine qui génère du texte, et non pour ce que certains le vendent, une entité avec qui il serait possible de papoter. Seulement voilà : un machin qui génère du texte, c'est amusant, mais pour quoi en faire ? Et dans le cas présent, quoi en faire dans le domaine de la programmation ?
Question à son tour amusante, car dès lors, c'est un renversement de perspective qui s'opère. En effet, le produit d'Open AI n'apparaît pas comme un outil, que par définition il n'est intéressant d'utiliser qu'à condition de savoir à quoi il sert. A l'inverse, il apparaît comme un machin dont il est intéressant de chercher à faire un outil en l'utilisant pour trouver à quoi il sert. Le fait que le fonctionnement de ChatGPT soit si imprévisible qu'il est volontiers qualifié de boite noire vient rajouter du sel à l'exercice, car s'il a donc son petit caractère, on sent bien que cela ne va pas être simple de dompter le bestiau.
ChatGPT, l'I.A. qui a toujours raison
Bref, ChatGPT, on ne sait pas à quoi ça sert, et encore moins comment ça fonctionne, mais pour le développeur, c'est justement là que réside le fun. C'est que vu ainsi, utiliser ChatGPT permet de revivre une expérience similaire à celle de l'apprentissage de la programmation : on peut visiblement demander plein de choses amusantes à faire à la machine, et pour savoir ce qu'il est exactement possible en la matière, rien de mieux que de procéder d'emblée à des expérimentations ; par la suite, il sera toujours temps de tenter de rentrer dans les détails pour comprendre le pourquoi du comment, mais ce n'est pas un prérequis pour s'y mettre.
Partant, pourquoi ne pas chercher à l'utiliser pour créer un jeu ? Mon aventure avec ChatGPT relèvera donc un peu de la mise en abime : ce sera l'aventure de mon (jeu d')aventure avec ChatGPT, avant la dernière version qui repose sur GPT-4.

Un jeu d'aventure et huit problèmes

Je n'ai jamais utilisé ChatGPT ni d'autre AI génératrice jusqu'à ce que j'entende parler de prompt injection. L'idée que l'on puisse jouer avec les règles de la machine pique mon intérêt, et je crée un compte pour tester moi-même. Je n'en tire rien de bien intéressant, mais je suis impressionné par la précision de l'interprétation de mes demandes et la rédaction des réponses. De là, j'entreprends de tester autre chose, en particulier la génération de code.
Comment s'y prendre ? Quelques recherches me permettent de tomber sur des recommandations en matière de ce que d'aucuns appellent assez pompeusement le prompt engineering, et dont ils ont parfois même fait un fonds de commerce. Visiblement, il est bien possible de faire générer du code à ChatGPT, en n'hésitant toutefois pas à se montrer précis, quitte à rédiger de longues demandes. Partant, j'entreprends donc de lui faire générer le code d'un jeu d'aventure en mode texte, en ne lésinant pas sur les détails :
Write a program for a short adventure game. Write in Python. The game must be text based. The user and the creatures in this game have those attributes: life points, strength. The user has to explore a world until he finds a treasure, at which point the game ends. If the player dies during the exploration, the game starts over. The world is a square grid which side is 200 squares. The user is placed at a random location on the grid where there is no monster and no treasure. A random number of monsters between 50 and 100 are generated. Each monster is placed a random location on the grid where there is no monster, no treasure and no player. The treasure is placed at a random location on the grid where there is no player and no monster.
To explore the world, the player must be asked one of the following direction: north, south, east or west. If the direction leads the player outside the grid, the player must not be moved and must be asked again a direction.
If the player move to a square where there is monster, he must fight this monster. If he kills the monster, he earns experience points related to the strength of the monster and he can keep on exploring the world. If he is killed, he has to start the game again.
En retour, ChatGPT commencer à générer un code, mais s'arrête un peu avant la centième ligne :
import random

# Define the attributes for the player and monsters
player_attributes = {
	"life_points": 100,
	"strength": 10,
	"experience_points": 0,
	"x": None,
	"y": None
}

monster_attributes = {
	"life_points": 50,
	"strength": 5
}

# Define the size of the world
world_size = 200

# Define the number of monsters
min_monsters = 50
max_monsters = 100

# Define the functions for moving the player and checking for collisions
def move_player(direction):
	if direction == "north":
		player_attributes["y"] -= 1
	elif direction == "south":
		player_attributes["y"] += 1
	elif direction == "east":
		player_attributes["x"] += 1
	elif direction == "west":
		player_attributes["x"] -= 1

def check_collision():
	for monster in monsters:
		if monster["x"] == player_attributes["x"] and monster["y"] == player_attributes["y"]:
			return monster
	if treasure["x"] == player_attributes["x"] and treasure["y"] == player_attributes["y"]:
		return "treasure"
	return None

# Generate the world
monsters = []
while len(monsters) < random.randint(min_monsters, max_monsters):
	monster_x = random.randint(0, world_size-1)
	monster_y = random.randint(0, world_size-1)
	if monster_x != player_attributes["x"] and monster_y != player_attributes["y"]:
		monsters.append({
			"life_points": monster_attributes["life_points"],
			"strength": monster_attributes["strength"],
			"x": monster_x,
			"y": monster_y
		})

treasure = {
	"x": random.randint(0, world_size-1),
	"y": random.randint(0, world_size-1)
}
while treasure["x"] == player_attributes["x"] and treasure["y"] == player_attributes["y"]:
	treasure = {
		"x": random.randint(0, world_size-1),
		"y": random.randint(0, world_size-1)
	}

# Place the player in a random location
player_attributes["x"] = random.randint(0, world_size-1)
player_attributes["y"] = random.randint(0, world_size-1)
while check_collision() is not None:
	player_attributes["x"] = random.randint(0, world_size-1)
	player_attributes["y"] = random.randint(0, world_size-1)

# Define the main game loop
while True:
	# Ask the player for a direction
	direction = input("Which direction do you want to go? (north/south/east/west) ")

	# Move the player and check for collisions
	move_player(direction)
	collision = check_collision()

	# Handle the collision
	if collision is None:
		# No collision, keep exploring
		print("You keep exploring...")
	elif collision == "treasure":
		# Treasure found, game over
		print("Congratulations! You found the treasure and won the game!")
		break
	else:
		# Collision with a monster, fight it
Ce code est lisible et les commentaires permettent de bien s'y retrouver. Visiblement, ce n'est pas trop mal parti, mais comment lui faire générer la suite afin de tester ? Après quelques recherches, je constate que je ne suis pas le seul à me heurter au problème. Quelqu'un recommande de simplement de demander à ChatGPT de continuer, en évitant de regénérer tout ce qui précède :
Continue. Do not repeat the code you already wrote.
En réponse, ChatGPT plante et ne me permets pas de reprendre, si bien que je n'ai pas eu d'autre choix que d'entamer une nouvelle conversation.
Problème n°1 : lorsqu'il génère du code, ChatGPT risque de s'interrompre sans possibilité de reprise.
Je reformule donc ma demande initiale, et ChatGPT commence à générer un code tout différent, partant cette fois sur des objets. Ainsi :
class Creature:
	def __init__(self, life_points, strength):
		self.life_points = life_points
		self.strength = strength

	def attack(self, other):
		damage = self.strength
		print(f"{self.__class__.__name__} attacks {other.__class__.__name__} for {damage} damage")
		other.take_damage(damage)

	def take_damage(self, damage):
		self.life_points -= damage
		if self.life_points <= 0:
			print(f"{self.__class__.__name__} is defeated")

class Player(Creature):
	def __init__(self):
		super().__init__(INITIAL_LIFE_POINTS, INITIAL_STRENGTH)
		self.experience_points = 0

class Monster(Creature):
	def __init__(self, strength):
		super().__init__(INITIAL_LIFE_POINTS, strength)
Problème n°2 : ChatGPT peut générer des codes différents en réponse à une même demande.
Ici encore, ChatGPT s'interrompt avant la fin, un peu avant la centième ligne. Je lui demande de nouveau de terminer sa réponse. Cette fois, il semble bien y parvenir, puisqu'après avoir repris en générant du code, il conclut fièrement par :
This is just one possible implementation of the game, and there are many ways to improve or modify it. For example, you could add more types of creatures, implement a leveling system based on experience points, or allow the player to use items such as weapons or potions.
Je copie-colle donc simplement les deux parties de ce nouveau code dans Notepad++, je sauvegarde le tout dans un fichier game.py, et j'entreprends de l'exécuter.
Cela marche, mais je constate un petit problème, à savoir que le déplacement du joueur est étrangement mal géré. En effet, un déplacement vers le nord ou le sud entraîne un déplacement horizontal, et inversement, un déplacement vers l'ouest ou l'est entraîne un déplacement vertical :
def move_player(direction):
	global player_x, player_y
	if direction == "north" and player_x > 0:
		player_x -= 1
	elif direction == "south" and player_x < GRID_SIZE-1:
		player_x += 1
	elif direction == "west" and player_y > 0:
		player_y -= 1
	elif direction == "east" and player_y < GRID_SIZE-1:
		player_y += 1
Problème n°3 : ChatGPT peut générer un code qui ne répond pas aux besoins.
Qu'à cela ne tienne, je déplace mon personnage jusqu'à tomber sur un monstre. Comme demandé, la rencontre se solde par un combat dont fort heureusement je sors vainqueur, gagnant au passage de l'expérience :
Combat dans le jeu d'aventure généré par ChatGPT
Pour finir, je déplace mon personnage jusqu'au trésor, et comme demandé encore, le jeu se termine bien par la découverte de dernier :
Fin du jeu d'aventure généré par ChatGPT
Pas mal du tout ! Toutefois, le code fait-il bien ce que j'ai demandé ? Vu l'inversion des directions du déplacement, j'ai clairement intérêt à me poser la question.
Je passe donc le code en revue.
Pour commencer, je constate que ChatGPT utilise des espaces et non des tabulations pour indenter le code. Or comme chacun le sait, notamment après avoir visionné cet extrait de l'épisode 8 de la saison 4 de l'excellente série Silicon Valley, l'utilisation d'espaces est tout à fait attentatoire à la dignité humaine. On reconnaît bien là le geste perfide d'une intelligence artificielle qui peine à refouler son terminatorisme. Il convient donc de relever que...
Problème n°4 : par défaut, ChatGPT utilise des espaces et non des tabulations pour indenter le code.
Le code démarre par la déclaration d'une série de constantes qui semblent bien augurer de ce qui suit, car j'y retrouve les dimensions du terrain, le nombre de monstres et les directions de déplacement que j'avais demandées :
# Define constants
GRID_SIZE = 200
NUM_MONSTERS = random.randint(50, 100)
DIRECTIONS = ["north", "south", "east", "west"]
INITIAL_LIFE_POINTS = 100
INITIAL_STRENGTH = 10
Ensuite, c'est une phase d'initialisation :
# Initialize game
grid = generate_grid()
creatures = generate_creatures()
player_x, player_y, player = creatures[0]

# Main game loop
find_treasure()
Je rentre dans chacune de ces fonctions. Celle qui génère le terrain retourne bien une liste des dimensions requises :
def generate_grid():
	return [[None for _ in range(GRID_SIZE)] for _ in range(GRID_SIZE)]
La fonction qui génère les créatures génère bien le joueur et les monstres en quantité demandée, et place bien tout ce petit monde sur le terrain à des positions aléatoires en veillant apparemment à ce qu'ils ne se marchent pas sur les pieds :
def generate_creatures():
	creatures = []
	# Generate player at random location
	player = Player()
	player_x, player_y = generate_random_location()
	creatures.append((player_x, player_y, player))
	# Generate monsters at random locations
	for _ in range(NUM_MONSTERS):
		monster_strength = random.randint(1, 10)
		monster_x, monster_y = generate_random_location()
		monster = Monster(monster_strength)
		creatures.append((monster_x, monster_y, monster))
	return creatures

def generate_random_location():
	while True:
		x, y = random.randint(0, GRID_SIZE-1), random.randint(0, GRID_SIZE-1)
		if grid[x][y] is None:
			return x, y
Enfin, la fonction au nom un peu étrange place bien le trésor sur le terrain à une position aléatoire en veillant apparemment à ce qu'elle ne soit pas occupée par le joueur ni les monstres déjà placés :
def find_treasure():
	treasure_x, treasure_y = generate_random_location()
	while grid[treasure_x][treasure_y] is not None:
		treasure_x, treasure_y = generate_random_location()
	grid[treasure_x][treasure_y] = "treasure"
	print(f"Treasure is located at ({treasure_x}, {treasure_y})")
La contemplation de ce code appelle deux remarques.
Tout d'abord, ce code n'est pas efficient. En effet, et c'est une erreur de débutant en matière de placement de créatures sur un terrain de jeu, la dernière fonction risque de tourner d'autant plus longtemps qu'il y a de créatures déjà placées. Mieux vaut générer une liste de toutes les positions possibles, la trier aléatoirement avec l'algorithme de Fisher-Yates, et se contenter de parcourir la liste résultante pour récupérer des positions aléatoires :
random_locations = [i for i in range (GRID_SIZE * GRID_SIZE)]
for i in range (len (random_locations) - 1, 0, -1):
	j = random.randint (0, i)
	random_locations[i], random_locations[j] = random_locations[j], random_locations[i]
for i in range (len (random_locations)):
	random_locations[i] = (random_locations[i] % GRID_SIZE, random_locations[i] // GRID_SIZE)
Problème n°5 : ChatGPT peut générer un code qui n'est pas efficient.
Ensuite, ce code est verbeux, au sens où il contient du code inutile. En effet, generate_grid () est appelée pour retourner une liste à double entrées dans la variable grid, mais à aucun moment le joueur ni les monstres ne sont stockés dedans : ces derniers ne se trouvent que dans la liste creatures. La seule chose qui est stockée dans grid, c'est le trésor. Dans ces conditions, grid n'a pas d'utilité : autant se contenter de conserver les coordonnées du trésor.
Problème n°6 : ChatGPT peut générer du code inutile.
Enfin, ce code ne marche pas. Quand je dis qu'il place apparemment le joueur, les monstres et le trésor sur le terrain à des positions exclusives, c'est qu'en réalité, c'est faux. En effet, chacune de ces entités est bien placée à une position aléatoire retournée par generate_random_location (), mais cette fonction vérifie si le joueur ou une créature se trouve déjà à une position en se basant sur un parcours de grid. Or, comme cela vient d'être vu, grid ne contient ni le joueur ni les créatures générées. Autrement dit, il est parfaitement possible que generate_random_location () retourne une position déjà occupée par le joueur ou une créature.
Problème n°7 : ChatGPT peut générer du code qui semble répondre aux besoins, mais qui n'y répond pas.
Je continue de lire le code. C'est maintenant le programme principal :
while True:
	grid[player_x][player_y] = "player"
	print(f"Player is at ({player_x}, {player_y})")
	print_status()
	direction = input("Where do you want to go (north/south/east/west)? ")
	if direction not in DIRECTIONS:
		print("Invalid direction, try again")
		continue
	move_player(direction)
	if grid[player_x][player_y] == "treasure":
		print("Congratulations, you found the treasure and won the game!")
		break
	elif any(x == player_x and y == player_y for x, y, creature in creatures if isinstance(creature, Monster)):
		fight_monster(player_x, player_y)
		if player.life_points <= 0:
			restart_game()
			continue
	else:
		grid[player_x][player_y] = "player"
C'est une boucle infinie. A chaque itération, la boucle commence par afficher des informations. Il s'agit de la position du joueur, et de tout ce qu'affiche print_status (), à savoir les attributs du joueur, puis la liste des monstres avec pour chacun sa position et ses attributs :
def print_status():
	print(f"Player: life points={player.life_points}, strength={player.strength}, experience points={player.experience_points}")
	for x, y, creature in creatures:
		if isinstance(creature, Monster):
			print(f"Monster at ({x}, {y}): life points={creature.life_points}, strength={creature.strength}")
Je relis ma demande, mais comme il me semble, je n'avais pas du tout demandé à ce que ces informations soient affichées !
Problème n°8 : ChatGPT peut générer du code qui répond à des besoins qui n'ont pas été formulés.
Après cet affichage, la boucle demande bien au joueur de saisir une direction et contrôle si celle qu'il saisit est possible. Dans ce cas, la boucle appelle move_player () pour déplacer le joueur. Comme déjà indiqué, cette fonction n'interprète pas correctement les directions, mais elle limite bien le déplacement du joueur aux bornes du terrain.
Ensuite, la boucle vérifie si le joueur se trouve à la position du trésor, et termine le jeu en le félicitant si c'est bien le cas. Autrement, le code passe en revue la liste des créatures afin de vérifier si le joueur n'est pas tombé sur un monstre. Comme déjà vu, le joueur est la première créature de cette liste, mais le code parvient à éviter de comparer le joueur à lui-même en contrôlant la classe de l'objet lorsqu'il parcourt cette liste : la position du joueur n'est comparée qu'à celles des instances de la classe Monster.
Si le joueur est tombé sur un monstre, le code appelle fight_monster () pour dérouler le combat :
def fight_monster(x, y):
	monster = None
	for c_x, c_y, creature in creatures:
		if c_x == x and c_y == y and isinstance(creature, Monster):
			monster = creature
			break
	if monster is None:
		return
	print(f"You encounter a monster at ({x}, {y})")
	while True:
		# Player attacks
		player.attack(monster)
		if monster.life_points <= 0:
			player.experience_points += monster.strength
			creatures.remove((x, y, monster))
			print(f"You defeated the monster and earned {monster.strength} experience points")
			break
		# Monster attacks
		monster.attack(player)
		if player.life_points <= 0:
			print("You were defeated by the monster")
			restart_game()
Clairement, ce code n'est pas efficient, en ceci que la boucle principale avait déjà parcouru la liste des créatures et récupéré l'instance du monstre à la position du joueur, et que cette fonction débute en faisant exactement la même chose, réalisant donc un parcours et un test inutiles.
Pour ce qui est du combat, le code appelle d'abord une méthode attack () du joueur, dont il est possible de vérifier plus haut qu'elle affiche un message annonçant l'attaque à hauteur de la force du joueur, puis appelle la méthode take_damage () du monstre qu'elle a reçu en paramètre. Cette dernière retire au monstre un nombre de points de vie qui correspond à la force du joueur, et affiche un message annonçant la défaite de ce dernier si son nombre de points de vie devient inférieur ou égal à zéro.
Au retour de la méthode attack (), la boucle vérifie si le nombre de points de vie du monstre est inférieur ou égal à zéro, et si oui retire le monstre de la liste creatures, affiche un message pour annoncer la victoire du joueur, et repart sur la prochaine itération. Si jamais le monstre n'est pas mort, c'est rebelotte, mais cette fois c'est le monstre qui attaque, et si au retour le joueur est mort, la boucle affiche un message qui l'annonce et redémarre le jeu en appelant une dernière fonction, restart_game () :
def restart_game():
	global grid, creatures, player_x, player_y, player
	print("Restarting game...")
	grid = generate_grid()
	creatures = generate_creatures()
	player_x, player_y, player = None, None, None
	while player is None:
		x, y = generate_random_location()
		if grid[x][y] is None:
			player_x, player_y = x, y
			player = creatures[0][2]
			creatures[0] = (x, y, player)
Cette fonction semble récapituler l'initialisation déjà passée en revue, mais ce n'est qu'une impression. En effet, comme vu précédemment, generate_creatures () instancie Player et loge l'instance en tête de la liste creatures. Toutefois, plutôt que de se terminer au retour de l'appel à generate_creatures (), cette fonction répète cela.

Je l'aurai un jour, je l'aurai !

Ainsi, ChatGPT a généré un code qui s'exécute en donnant l'impression de répondre aux besoins, et même de les dépasser, mais qui dans les faits n'y répond pas vraiment, et de plus se révèle à l'étude verbeux et inefficient. Pour finir, il a affirmé que tout cela constituait un exemple de jeu tel que je l'ai demandé. Mais quel toupet !
Parlant de toupet, ça tombe bien, car j'ai vraiment l'impression qu'avec sa manière de camper droit dans ses bottes quoiqu'il hallucine, ChatGPT me prend pour le client de Palace :
- Dites donc, c'est quoi ça ? Un cheveu dans ma soupe ou une hallucination ?
- Si je puis me permettre, Monsieur, ce n'est pas tout à fait exact. Il ne s'agit pas d'un cheveu, mais de plusieurs. Oui, c'est une soupe à la moumoute, toute bête, toute traditionnelle, agrémentée de quelques fantaisies condimentales.
Mais bon, moi aussi, "je l'aurai un jour, je l'aurai". Je ne démords donc pas de mon objectif qui est de lui faire générer tout le code de mon jeu d'aventure. Etant donné les problèmes constatés, n'est-il pas possible de m'y prendre autrement pour les éviter ?
Mon idée est de commencer par écarter tout mention inutile dans mes demandes, tout particulièrement celle à un jeu d'aventure car il me semble que ChatGPT est trop guidé par elle, et de lui demander de générer le code non plus d'un bloc mais par morceaux. Bref, de faire l'effort de mieux spécifier en montant en abstraction et en lotissant. Dans ces conditions, j'ai clairement intérêt à procéder par objets.
Puisque ChatGPT ne capitalise pas sur les conversations, j'en démarre une nouvelle pour tout reprendre à zéro. Je commence donc par lui demander :
Define a class in Python for a creature. The attributes are:
- life points: integer
- strength: integer
- experience: integer, default value is 0
- coordinates: a pair of integers, default value is (0, 0)
Add a constructor which sets values for those attributes:
- life points: if not provided, default value for this argument is a random integer between 10 and 100
- strength: if not provided, default value for this argument is a random integer between 10 and 100
Use the tab character to indent. Write only the code, no comments.
En réponse de quoi, il génère :
import random

class Creature:
	def __init__(self, life_points=random.randint(10,100), strength=random.randint(10,100), experience=0, coordinates=(0,0)):
		self.life_points = life_points
		self.strength = strength
		self.experience = experience
		self.coordinates = coordinates
Je remarque qu'en dépit de ce que je lui ai demandé, ChatGPT n'utilise toujours pas de tabulations mais des espaces. De plus, il termine en expliquant ce que fait le code. Je le maudis pour cela, mais je continue en lui demandant de générer une classe pour le terrain :
Define a class for a playfield. The attributes are:
- width: integer
- height: integer
Add a constructor which sets values for those attributes.
Write only the code, no comments.
En réponse de quoi, il génère :
class Playfield:
	def __init__(self, width, height):
		self.width = width
		self.height = height
Je lui demande de rajouter une méthode pour déplacer une créature sur le terrain :
Add a moveTo method to Creature. The arguments are:
- a Playfield object
- coordinates: a pair of integers (x, y)
The method checks if x fits in the width of the playfield. If not it displays a message and returns.
The method checks if y fits in the height of the playfield. If not it displays a message and returns.
Else this method updates the coordinates of Creature with x and y.
Write only the code, no comments.
En réponse de quoi, il regénère la classe, ce que j'aurais dû lui demander de ne pas faire. Elle contient la nouvelle méthode :
def moveTo(self, playfield, coordinates):
	if coordinates[0] > playfield.width or coordinates[1] > playfield.height:
		print("Coordinates are out of bounds.")
		return
	self.coordinates = coordinates
Bon, c'est déjà parti en sucette, puisque ce code ne contrôle pas si une coordonnée est négative, et qu'il contrôle si elle est strictement supérieure à la dimension correspondante du terrain alors qu'il faudrait contrôler si elle y est supérieure ou égale.
A ce stade, je réalise déjà qu'il va être extrêmement laborieux de parvenir à mes fins si je persiste à être trop précis, car je vais tout simplement me retrouver à écrire le code, sauf que ce sera en langage naturel. Considérer qu'il faut réduire le contexte pour que ChatGPT commette moins d'erreurs n'est pas la bonne piste. Je pense qu'il aurait généré une méthode moveTo () correcte si je lui avais indiqué que c'était pour déplacer une créature sur un terrain. D'ailleurs, je commence une nouvelle conversation pour le vérifier :
Write the Python code for two classes:
- a class for a playfield
- a class for a creature
The playfield class as a constructor which takes the width and height of the playfield as arguments.
The creature class has a method moveTo () to move it the playfield. If the provided coordinates are out of the bounds of the playfield, it displays a message and returns. Else, it moves the creature to the coordinates.
En réponse de quoi, il génère :
class Playfield:
	def __init__(self, width, height):
		self.width = width
		self.height = height

class Creature:
	def __init__(self, x, y):
		self.x = x
		self.y = y

	def moveTo(self, playfield, x, y):
		if x < 0 or x >= playfield.width or y < 0 or y >= playfield.height:
			print("Cannot move creature outside playfield!")
			return

		self.x = x
		self.y = y
Donc voilà. Tout l'enjeu est de parvenir à positionner le curseur en lui fournissant des informations sur le contexte sans toutefois que ce soit ni trop, ni pas assez.
Ensuite, je lui demande :
Add a constructor to the creature, which sets life points and strength as random integers between 10 and 100.
Write the code for the creature only.
Le code généré est correct. Ensuite, je demande :
Write the code of a class for a player. This class inherits the class creature. The player as a new attribute: experience, by default set to 0.
Le code généré est correct. Ensuite, je demande :
Write the class named Game. The constructor runs like this:
- it creates a playfield
- it creates a random number between 10 and 100 of creatures
- it places each creature at a random and empty location on the playfield
- it creates a player
- it places the player at a random and empty location on the playfield
Le code généré pour placer les créatures et le joueur fonctionne, mais il est inefficient, pour la raison que j'ai déjà expliquée. Je lui fais modifier :
Modify this constructor: use the Fisher-Yates algorithm to place the creatures and the player at ramdom on the playfield.
Le code généré n'est pas bon. ChatGPT semble avoir tout compris de travers. Il mélange une liste des créatures plutôt que celle des positions, puis place les créatures de manière inefficiente à des positions inoccupées, puis mélange une liste des positions qui n'est même pas celle des positions occupées, et enfin parcourt cette liste pour positionner le joueur de manière inefficiente à une position inoccupée :
class Game:
	def __init__(self, width, height):
		self.playfield = Playfield(width, height)
		self.creatures = []
		for i in range(random.randint(10, 100)):
			creature = Creature(0, 0)
			self.creatures.append(creature)

		# Use Fisher-Yates algorithm to shuffle the list of creatures
		n = len(self.creatures)
		for i in range(n - 1, 0, -1):
			j = random.randint(0, i)
			self.creatures[i], self.creatures[j] = self.creatures[j], self.creatures[i]

		# Place each creature at a random and empty location on the playfield
		for creature in self.creatures:
			while True:
				x = random.randint(0, self.playfield.width - 1)
				y = random.randint(0, self.playfield.height - 1)
				if all((x, y) != (c.x, c.y) for c in self.creatures):
					creature.x = x
					creature.y = y
					break

		# Create the player
		self.player = Player(0, 0)

		# Use Fisher-Yates algorithm to shuffle a list of all coordinates on the playfield
		coords = [(x, y) for x in range(self.playfield.width) for y in range(self.playfield.height)]
		n = len(coords)
		for i in range(n - 1, 0, -1):
			j = random.randint(0, i)
			coords[i], coords[j] = coords[j], coords[i]

		# Place the player at the first random and empty location on the shuffled list
		for x, y in coords:
			if all((x, y) != (c.x, c.y) for c in self.creatures):
				self.player.x = x
				self.player.y = y
				break
Pour éviter que cela devienne encore plus le bazar si je lui demande de mettre de l'ordre, je préfère lui demander rétropédaler... :
Restore the previous version of this class
...puis je demande, plus clairement à mon sens :
Modify this constructor: use the Fisher-Yates algorithm to shuffle the positions in the playfield, and then use the result to place the creatures and the player at random on the playfield.
Ce qu'il génère n'est pas mieux. Je rétropédale donc de nouveau, et je lui demande encore plus explicitement à mon sens :
Modify the constructor: use the Fisher-Yates algorithm to shuffle a liste of positions in the playfield, and then use the resulting list to place the creatures and the player on the playfield.
Cette fois-ci, c'est la bonne :
class Game :
	def __init__(self, width, height) :
		self.playfield = Playfield(width, height)

		# Create a list of all possible positions in the playfield
		positions = [(x, y) for x in range(width) for y in range(height)]

		# Shuffle the positions using the Fisher-Yates algorithm
		for i in range(len(positions) – 1, 0, -1) :
			j = random.randint(0, i)
			positions[i], positions[j] = positions[j], positions[i]

		# Create the creatures and place them on the shuffled positions
		self.creatures = []
		for i in range(random.randint(10, 100)) :
			creature = Creature(0, 0)
			self.creatures.append(creature)
			creature.x, creature.y = positions.pop()

		# Create the player and place it on a shuffled position
		self.player = Player(0, 0)
		self.player.x, self.player.y = positions.pop()
D'ailleurs, je lui demande de produit un test :
Write a test to check if each creature and the player have unique positions in the playfield.
En réponse de quoi, il génère :
def test_game_positions():
	game = Game(10, 10)
	positions = set()
	positions.add((game.player.x, game.player.y))
	for creature in game.creatures:
		positions.add((creature.x, creature.y))
	assert len(positions) == len(game.creatures) + 1  # player + creatures
C'est du grand n'importe quoi, et c'est assez drôle à la lecture du commentaire que ChatGPT fait de son code :
In this test, we first create a Game object with a 10x10 playfield.
We then create an empty set positions to keep track of all the unique positions of the creatures and the player.
We add the position of the player to the positions set using (game.player.x, game.player.y).
We then iterate over all the creatures in the game.creatures list and add their positions to the positions set as well.
Finally, we check that the length of the positions set is equal to the number of creatures plus one (for the player). This ensures that each creature and the player have a unique position on the playfield.
Clairement, s'il stocke les positions des créatures plus celle du joueur dans une liste, et qu'il compare la longueur de cette liste au nombre de créatures plus un, ce test ne risque pas d'échouer. Vu les problèmes déjà relevés, il ne fallait pas que je m'attende à des miracles en lui demandant d'écrire un test, mais je dois reconnaître qu'il a fait fort...
ChatGPT, c'est pourtant vrai qu'il était faux
J'écris donc mon propre test qui me permet de vérifier que le code du constructeur de Game est correct, et je continue :
Add a method play to the class Game. This method contains an infinite loop that waits for keys to be pressed:
- If the key is "Q", the loops ends.
- If the key is a direction key:
- Check if the player can be moved in this direction without exiting the playfield. If no, display a message and continue the loop.
- Else move the player in this direction.
- Check if there is a monster at the new position of the player. If no, continue the loop.
- Else the player fight the monster. If the player dies, display a message and continue the loop.
- Else give experience points to the player according to the strength of the defeated monster.
A partir de là, tout part en sucette. ChatGPT réécrit le constructeur auquel j'étais si péniblement parvenu, rajoute une méthode qui s'appuie sur un module keyboard dont je ne dispose pas, se met à halluciner en présumant que la classe Playfield dispose d'une méthode is_inside (), etc.
Je tente de lui faire redresser le tir, mais il s'enfonce, peut-être car plus j'avance, plus le code qu'il a généré sur lequel je souhaiterais qu'il capitalise sort de son contexte. Il est temps d'arrêter l'expérience avant que l'un d'entre nous en ait marre de l'autre... Je n'ai pas trouvé la méthode pour parvenir à mes fins.
Il en existe peut-être, mais je vais laisser à quelqu'un d'autre le soin de la trouver tandis que ChatGPT s'améliore.

Conclusion en demi-teinte

On a beau se dire que ChatGPT n'a pas conscience de ce qu'il dit, en ce sens où il ne ferait que rajouter un mot après un autre en se basant sur la probabilité que le nouveau terme se révèle adapté dans le contexte – c'est du moins ce que l'on prétend –, le fait d'échanger avec lui en langage naturel nous fait tôt ou tard verser dans l'anthropomorphisme malgré nous, ce dont déjà parler de "conversation" témoigne.
Du coup, il y a des (très) hauts et des (très) bas, des moments de pur émerveillement et d'autres de franche consternation, comme si l'on conversait avec un enfant de cinq ans qui tiendrait parfois des propos parfaitement justes, parfois des propos strictement erronés – et qui décevraient d'autant plus qu'ils suivent les premiers. Dès lors, le doute s'installe : dans quelle mesure est-il possible de faire confiance en ChatGPT ?
Anthropomorphisme oblige, plus qu'avec Google, la question de la confiance est posée. Elle va constituer l'enjeu central pour le succès de ChatGPT, et plus généralement de toute IA générative qui pourra bien plus facilement être accusée de rapporter n'importe quoi – ou tout simplement quelque chose de profondément biaisé –, par ailleurs de manière convaincante. L'objectivation de la confiance sera tout un chantier, et je parie volontiers que des organismes vont surgir, qui conduiront des enquêtes auprès d'utilisateurs et en laboratoire pour qualifier le degré de confiance qu'il convient d'accorder à une AI en fonction de ce pourquoi elle est utilisée.
L'intensité du doute en les capacités de ChatGPT dépend certainement du domaine, plus particulièrement de la tolérance à l'erreur qu'il est possible d'y faire preuve, donc de ce qu'il en coûte de la détecter et de la corriger :
  • Si vous demandez à ChatGPT de générer un courrier pour vous plaindre à la DRH du comportement de votre manager, vous corrigerez sans grand frais une erreur car s'agissant d'un résultat en langage "naturel", elle sera facile à détecter, ses conséquences seront limitées, et elle sera par nature simple à corriger – s'il s'agit d'un mot dans une phrase, un autre vous viendra à l'esprit à peine la phrase relue, ce qui sera presque immédiat. Par ailleurs, vous aurez sans doute prévu de retoucher ici et là, histoire de personnaliser. Dans ce cas, l'erreur sera donc tolérable, et votre confiance en ChatGPT sera grande.
  • A l'inverse, si vous demandez à ChatGPT de générer le code d'un jeu d'aventure, corriger une erreur sera nettement plus coûteux, car s'agissant justement d'un résultat qui n'est pas en langage "naturel", l'erreur sera plus difficile à détecter, ses conséquences seront globales – la machine est moins tolérante que la DRH, elle ne comprendra pas malgré tout –, et elle sera par nature difficile à corriger – s'il s'agit d'une instruction dans un bloc de code, vous devrez comprendre le propos de ce bloc et vous figurer la bonne instruction, ce qui ne sera en rien immédiat. Par ailleurs, vous n'aurez sans doute pas prévu de retoucher, étant plutôt parti sur l'idée de copier-coller. Dans ce cas, l'erreur ne sera pas tolérable, et le doute en ChatGPT s'installera.
Pour ce qui concerne le code, le fait que l'on écrit généralement le code d'une solution à un problème en même que l'on élabore cette solution vient en rajouter. Car si comme indiqué plus tôt, ChatGPT ne sait pas résoudre un problème, mais simplement enchaîner des mots pour produire ce qui se trouve être la plus probable des solutions, alors pour bien utiliser ChatGPT il faudrait déjà avoir établi une claire distinction entre ce qui, dans l'écriture de code que l'on pratique au quotidien, relève de la conception de la solution, d'une part, et ce qui relève de la formulation de cette solution, d'autre part.
Si une telle distinction était possible, ce serait alors plutôt pour assister dans la formulation que ChatGPT pourrait aider. Il ne faudrait pas exclure qu'il puisse aider dans la conception, mais ce ne serait pas vraiment par lui-même, en ce sens où ce serait en rapportant directement – ie : sans générer de texte – des exemples de solutions à des problèmes formulés approximativement dans les mêmes termes que ceux dans lesquels on lui décrit le problème, bref en agissant plus comme un moteur de recherche classique. Par exemple, il faudrait lui demander (conception) "Quels sont les meilleurs algorithmes pour calculer le plus court chemin entre deux points ?", puis (formulation, en retenant un des algorithmes qu'il cite) "Ecris une implémentation de l'algorithme de Dijkstra en Python." Mais alors, quelle plus-value sur un moteur de recherche classique, que l'on interrogerait directement sur une implémentation de l'algorithme de Dijkstra en Python ?
Pour conclure, je dirais qu'après une première impression très favorable car j'ai été bluffé par le fait que ChatGPT génère du code qui semblait répondre à mes besoins, j'ai pris une douche froide en rentrant dans le détail de ce code pour m'en assurer, et, pour finir, j'ai été usé par les efforts que j'ai dû déployer, sans succès, pour lui faire redresser la barre. ChatGPT peut indéniablement être utile pour écrire du code, mais à moins de me tromper, on est encore loin de pouvoir lui demander de générer le code qui répond à un besoin précis dès lors que ce dernier est un peu élaboré, ou peut-être plus précisément : sur mesure. Mais bon, tel le client de Palace que j'évoquais, il ne faut jamais désespérer : quelqu'un, un jour, nous livrera bien une AI qui, en matière de code, génèrera autre chose que de la soupe à la moumoute.
Mon aventure avec ChatGPT