top of page

Dicer

Joscha Bach (http://bach.ai/) mentioned in a talk on AI that LLMs (currently) can't do dice well. This seems true, but I wondered how well it can figure things out using a solver-type approach, where the end user accepts there won't be a zero-shot result but can retry with certain directions, in a pair programming style. This is what I managed to obtain using Python (which I'm novice at) and ChatGPT-4o (the preview of a newer version is out but not available to me yet).


This is a mapped cube that transforms dice values each 'roll' or 'turn' with correctly maintained ordering.

Joscha's argument was that chatgpt, being a bit bad at maths, can't figure out the spatial mapping and maintain it accurately, since it's giving 'language' oriented probabilistic responses. This seems to be true, however, with a bit of kicking, some progress can be made. If you iterate through some of the errors to tweak what it understands about a dice's numbering, and the way you want to map it, it seems to get the idea. Once it's locked in, you can convert that to a code-based operation that always works.


Starter Conversation

You said: If you have a dice with a one facing up, what is the bottommost face?
ChatGPT said:

[04:16] On a standard six-sided die, the numbers on opposite faces always add up to 7. If a "1" is facing up, the number on the bottommost face would be "6."


You said:

Given that, if a five faces towards you, what is facing away from you (with 1 up and 6 down)?

ChatGPT said:

[04:17] If the "1" is facing up and the "6" is facing down, and the "5" is facing towards you, the face directly opposite the "5" (the one facing away from you) would be the "2."

This is because on a standard die, the opposite faces are paired as:

  • 1 and 6

  • 2 and 5

  • 3 and 4


You said:

Okay, when you roll the dice, two times towards you, what is the up-facing side?

ChatGPT said:

[04:19] If the die starts with the "1" facing up, "6" facing down, and "5" facing towards you, and you roll it two times towards you:

  1. After the first roll towards you, the "5" (which was facing you) will now be on top, and the "2" (which was facing away from you) will be on the bottom.

  2. After the second roll, the "2" (which was on the bottom) will now be on top, and the "5" will be on the bottom.

So, after two rolls towards you, the "2" will be facing up.


You said:

Nope. If you roll two times in one axis (towards you), then the opposite face to the starting face must face up.

ChatGPT said:

[04:21] You're correct! My mistake. If you roll a die two times towards you, it will indeed bring the opposite face of the starting face to the top.

In this case:

  • Initially, the "1" is facing up and the "6" is facing down.

  • After two rolls towards you, the "6" (which started on the bottom) will now be on top.

So, after two rolls towards you, the "6" will be facing up.

In the starter conversation, chatgpt made errors (and I did too), which we could attribute possibly to not having much info or to being in some kind of conversational mode. As soon as I started going after pythonscript, the expression of how a dice worked got more sophisticated, including the idea of mapping opposite pairs, directionality, and ordered mapping of the cube faces based on an up-face. Where it struggled. Asking python to draw the result of the dice transform (rolling it it forward, sideways, or spinning its Yaw) seemed persistently incorrect, but it was clearly a matching error (from chatgpt's plotting space to python's plotting space).


Internally, calculating a result for a sequence of turns, results were given correctly, but the code it generated to recreate this in python didn't quite sync.


Sequence of dice turns

Envisage a Cube at each step of the rotation, and assign each face of the cube an index from 1 to 6 using the opposite faces pattern of a regular dice. Now, compute the dice turning, if each turn is different to the previous one, for five turns.


ChatGPT said:

Let's visualize the cube and assign the faces as follows, based on a standard die:

  • Face 1 is opposite Face 6.

  • Face 2 is opposite Face 5.

  • Face 3 is opposite Face 4.

Now, let's compute five different turns, where each turn is in a different direction (forward, backward, left, right, etc.), and see which face is on top after each turn.

Initial Position:

  • 1 is up, 6 is down.

  • 5 is facing you, 2 is away from you.

  • 4 is to the left, 3 is to the right.

Step-by-Step Turns:

  1. First Turn: Roll forward (toward you).

    • The top face is now 5.

    • The face towards you is 6.

    • The face away from you is 1.

    • The face on the bottom is 2.

    • Left remains 4, right remains 3.

  2. Second Turn: Roll left.

    • The top face is now 3.

    • The face to the right is 5.

    • The face to the left is 6.

    • The face towards you is still 4, and the face away from you is 2.

  3. Third Turn: Roll backward (away from you).

    • The top face is now 4.

    • The face towards you is 3.

    • The face away from you is 5.

    • The face on the bottom is 2, and left and right stay the same.

  4. Fourth Turn: Roll right.

    • The top face is now 2.

    • The face towards you is 4.

    • The face away from you is 3.

    • The face on the bottom is 5.

    • Left and right faces swap again: Left is 6, right is 1.

  5. Fifth Turn: Roll forward.

    • The top face is now 6.

    • The face towards you is 2.

    • The face away from you is 5.

    • The face on the bottom is 1.

    • Left remains 4, right remains 3.

Final State after 5 turns:

  • Top: 6

  • Bottom: 1

  • Towards you: 2

  • Away: 5

  • Left: 4

  • Right: 3

This completes five distinct turns, with the final face on top being 6.

As it happens, in the function it wrote in python, the dice expression seemed to unfold in a different way than its own dice unfolding (but initially even Chatgpt seemed to have loose notions of opposite sides of a dice):

An incorrect mapping, where 1 and 6 are not opposite. ChatGPT cycled around the problem a lot, but I admit not being able to think of a solution either.


Eventually, I asked it to use a 3x4 table and represent the dice (cross) using that. This seemed to help a lot, probably because the table is easy to do calculations with. Also, when I observed the python version of the dice numbering was flipped, we got better results immediately (it figured out something from this observation under the hood).


Me: By the way, notice that your chatgpt drawing is

+

+++

+

+

and python is flipped upside down.

+

+

+++

+



Once the 2D represenation of an unfolded dice cross worked, and I could input 'front','back','left' ... rolls and see the numbers correctly update, I wanted to see if python could draw a cube with a 3D feel.



At first I got a solid blue isometric cube with flat shading and numbers on the edges of the faces, not in the centers, so I had to goad chatgpt to change some colours and position the text (this part it does fairly readily):

As shown above, the text renders for all faces, so i had to figure out an easy way to drop the 'occluded' text, which isn't so easy since this is a flat 2D image of a cube, and it doesn't know about the world or space too well. So I sort of hard-coded that 3 of the sides of the cube were always fixed (active) and the other 3 were occluded (inactive). The dice transforms to generate the correct number mapping, then displays only the cube oriented towards the viewer.


This was easy in the end, but still took, as usual, a few wrong turns. For example, in the image below, only one face has text (the barely visible 3 on the right face). This turned out to be related to the Z order and placement of the text in the plotting (matplotlib), and chatgpt was able to figure it out without me having to do much except point out the problem. I included the pictures with OCR instructions a few times, but it wasn't clear ChatGPT was analysing these or just figuring stuff out from my comments...

Once the dice faces were rendering alright, I added a 'random' input, made the dice begin with a random state, and played around with options to save out a render sequence to PNG, such as 1'front', 2'front', 3'left', 4'yawleft'. Last of all, I did the Joscha Bach test of asking it to roll forwards a number of times, then left a number of times, and supply the final state of the dice, based on a starting state. By now, the result looks fine.

Dice Final Sequence

ME: using the python example, draw (in chatgpt style) a random starting dice state then roll it forward 3 times then left once. draw the end state.


ChatGPT said:

Let's walk through the example by simulating the dice's random starting state, followed by three forward rolls and one left roll, and then I'll provide a final representation.

Dice Starting State (Random)

For simplicity, let's assume that the random starting state has 4 on top. Using the standard dice configuration, this would mean:

  • 4 is up. 3 is down (opposite of 4). Opposites add up to 7.

  • The sides could be: 1 in front, 6 in the back, 5 on the left, 2 on the right.

First State (Random starting position):

       4 5 1 2 3 6

After 3 Forward Rolls:

Each forward roll shifts the up, front, down, and back faces. Here’s how it works after each roll:

  1. First Forward Roll:

    • Up face (4) moves to back.

    • Front face (1) moves to up.

    • Down face (3) moves to front.

    • Back face (6) moves to down.

    After the first forward roll:


         1 5 3 2 6 4

  2. Second Forward Roll:

    • Up face (1) moves to back.

    • Front face (3) moves to up.

    • Down face (6) moves to front.

    • Back face (4) moves to down.

    After the second forward roll:


         3 5 6 2 4 1

  3. Third Forward Roll:

    • Up face (3) moves to back.

    • Front face (6) moves to up.

    • Down face (4) moves to front.

    • Back face (1) moves to down.

    After the third forward roll:


       6 5 4 2 1 3

After One Left Roll:

A left roll affects the up, left, down, and right faces:

  • Up face (6) moves to right.

  • Left face (5) moves to up.

  • Down face (1) moves to left.

  • Right face (2) moves to down.

Final state after one left roll:


    5 1 4 6 2 3

Summary of the Final State:

  • 5 is up.

  • 4 is in front.

  • 6 is on the right.

  • 1 is on the left.

  • 2 is on the bottom.

  • 3 is in the back.


The python code provided is what was used to make the image above.

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import numpy as np
import random

class Dice3DVisualizer:
def __init__(self):
# Initialize the dice with standard opposites:
self.faces = {
'up': None, # To be set randomly
'down': None, # To be set based on 'up'
'front': None, # To be set based on the random up face
'back': None, # Based on front
'left': None, # Based on the random up face
'right': None # Based on left
}
self.set_random_start()

def set_random_start(self):
# Randomly set a starting number for the 'up' face
possible_faces = [1, 2, 3, 4, 5, 6]
opposites = {1: 6, 2: 5, 3: 4, 4: 3, 5: 2, 6: 1}

# Choose a random number for the 'up' face
self.faces['up'] = random.choice(possible_faces)
self.faces['down'] = opposites[self.faces['up']] # Set the opposite face for 'down'

# Now set the remaining side faces based on what's up
if self.faces['up'] == 1 or self.faces['up'] == 6:
# If 1 or 6 is up, choose side faces from 2, 3, 4, 5
self.faces['front'], self.faces['back'], self.faces['left'], self.faces['right'] = 2, 5, 4, 3
elif self.faces['up'] == 2 or self.faces['up'] == 5:
# If 2 or 5 is up, choose side faces from 1, 3, 4, 6
self.faces['front'], self.faces['back'], self.faces['left'], self.faces['right'] = 1, 6, 4, 3
else:
# If 3 or 4 is up, choose side faces from 1, 2, 5, 6
self.faces['front'], self.faces['back'], self.faces['left'], self.faces['right'] = 1, 6, 5, 2

def roll_forward(self):
# Move the dice faces according to a forward roll (top -> front, etc.)
self.faces['up'], self.faces['front'], self.faces['down'], self.faces['back'] = \
self.faces['back'], self.faces['up'], self.faces['front'], self.faces['down']

def roll_backward(self):
# Move the dice faces according to a backward roll (top -> back, etc.)
self.faces['up'], self.faces['front'], self.faces['down'], self.faces['back'] = \
self.faces['front'], self.faces['down'], self.faces['back'], self.faces['up']

def roll_left(self):
# Move the dice faces according to a left roll (top -> left, etc.)
self.faces['up'], self.faces['left'], self.faces['down'], self.faces['right'] = \
self.faces['right'], self.faces['up'], self.faces['left'], self.faces['down']

def roll_right(self):
# Move the dice faces according to a right roll (top -> right, etc.)
self.faces['up'], self.faces['left'], self.faces['down'], self.faces['right'] = \
self.faces['left'], self.faces['down'], self.faces['right'], self.faces['up']

def yaw_left(self):
# Move the dice faces according to a yaw left (front -> right, etc.)
self.faces['front'], self.faces['right'], self.faces['back'], self.faces['left'] = \
self.faces['right'], self.faces['back'], self.faces['left'], self.faces['front']

def yaw_right(self):
# Move the dice faces according to a yaw right (front -> left, etc.)
self.faces['front'], self.faces['right'], self.faces['back'], self.faces['left'] = \
self.faces['left'], self.faces['front'], self.faces['right'], self.faces['back']

def draw_dice(self, ax):
# Define the vertices of a cube (fixed cube faces)
r = [-1, 1]
vertices = np.array([[x, y, z] for x in r for y in r for z in r])

# Define the six cube faces
faces = [[vertices[0], vertices[1], vertices[3], vertices[2]], # front cube face
[vertices[4], vertices[5], vertices[7], vertices[6]], # back cube face
[vertices[0], vertices[1], vertices[5], vertices[4]], # left cube face
[vertices[2], vertices[3], vertices[7], vertices[6]], # right cube face
[vertices[0], vertices[2], vertices[6], vertices[4]], # bottom cube face
[vertices[1], vertices[3], vertices[7], vertices[5]]] # top cube face

# Define face labels for the dice (which face should display on each cube face)
face_labels = {
'front': self.faces['front'], # Dice face on front cube face
'back': self.faces['back'], # Dice face on back cube face
'left': self.faces['left'], # Dice face on left cube face
'right': self.faces['right'], # Dice face on right cube face
'down': self.faces['down'], # Dice face on bottom cube face
'up': self.faces['up'] # Dice face on top cube face
}

# Base dice color (Lighter Red) and tints for each face
base_red = "#FF6666" # Lighter Red for most faces
top_red = "#FF9999" # Lighter red for the top
left_red = "#FF8080" # Mid-tone between base red and top red

# Define colors for each face of the cube
face_colors = {
'front': base_red,
'back': base_red,
'left': left_red,
'right': base_red,
'down': base_red,
'up': top_red
}

# Plot each face of the cube (these are the static cube faces)
ax.cla() # Clear the previous plot
ax.set_box_aspect([1, 1, 1]) # Aspect ratio is 1:1:1
ax.set_axis_off() # Turn off the axis

# Add cube faces with appropriate shading (cube faces don't change)
ax.add_collection3d(Poly3DCollection([faces[0]], facecolors=face_colors['front'], linewidths=1, edgecolors='r', alpha=1)) # front cube face
ax.add_collection3d(Poly3DCollection([faces[1]], facecolors=face_colors['back'], linewidths=1, edgecolors='r', alpha=1)) # back cube face
ax.add_collection3d(Poly3DCollection([faces[2]], facecolors=face_colors['left'], linewidths=1, edgecolors='r', alpha=1)) # left cube face
ax.add_collection3d(Poly3DCollection([faces[3]], facecolors=face_colors['right'], linewidths=1, edgecolors='r', alpha=1)) # right cube face
ax.add_collection3d(Poly3DCollection([faces[4]], facecolors=face_colors['down'], linewidths=1, edgecolors='r', alpha=1)) # bottom cube face
ax.add_collection3d(Poly3DCollection([faces[5]], facecolors=face_colors['up'], linewidths=1, edgecolors='r', alpha=1)) # top cube face

# Render the dice face numbers only on the top, front, and right cube faces
ax.text(0, 0, 1.02, str(face_labels['up']), ha='center', va='center', fontsize=20, color='white', zorder=10) # Top face (dice number)
ax.text(0, -1.02, 0, str(face_labels['front']), ha='center', va='center', fontsize=20, color='white', zorder=10) # Front face (dice number)
ax.text(1.02, 0, 0, str(face_labels['right']), ha='center', va='center', fontsize=20, color='white', zorder=10) # Right face (dice number)

plt.draw()
plt.pause(0.1) # Pause for real-time updates

def simulate_dice():
# Create a figure and axis for dynamic updates
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')

dice = Dice3DVisualizer()

# Draw the initial state with random starting condition
dice.draw_dice(ax)

# Allow user to input commands
while True:
command = input("Enter Roll (Front, Back, Left, Right, YawLeft, YawRight, Random) or 'quit' to exit: ").strip().lower()

if command == "front":
dice.roll_forward()
elif command == "back":
dice.roll_backward()
elif command == "left":
dice.roll_left()
elif command == "right":
dice.roll_right()
elif command == "yawleft":
dice.yaw_left()
elif command == "yawright":
dice.yaw_right()
elif command == "random":
dice.set_random_start() # Reset to a random starting condition
elif command == "quit":
break
else:
print("Invalid command. Try again.")
continue

# Update dice visual after each command
dice.draw_dice(ax)

if __name__ == "__main__":
simulate_dice()

To run this:

  1. If you need to, install Python (I used IDLE to run the code)

  2. If you need to, install library 'matplotlib' (using CMD: pip install matplotlib)

  3. If you need to, install the other libraries that are in the #import entries at the start of the code.

  4. Paste the python code to IDLE and save as dicer.py and run it.

  5. In the IDLE window, enter one of the listed commands.


    You should see a Dice image as in the picture, and can watch the dice faces update. It doesn't animate, the numbers are just mapped according to the transform.


Since I don't know much about python, I can't really say if the code is wildly inefficient, full of extra junk, or even really correct. But it demonstrates out that even if you have to 'solve' the problem in an iterative session where chatgpt doesn't initially know what you're after or have a good example, it certainly learns fast and doesn't mind being shown what it needs to correct. Some of the stumbling blocks required me to stop its looping mal-attempts and change approach, but there's no way I could make the script myself. Possibly the dice solution case Joscha Bach was talking about (or had in mind) might be more complicated, but this case goes from fail to success just with 'no code' requests and suggestions. It will be interesting to see if later models of chatgpt can more readily provide a useful result.

1 comentário


Ward
Ward
15 de set.

Cool! Wondering if it will be easier or harder in the new version. Impressive that you got it to work after those initial hiccups.

Curtir
Featured Posts
Recent Posts
Search By Tags
Follow Us
  • Facebook Basic Square
  • Twitter Basic Square
  • Google+ Basic Square
bottom of page