AI Mind Reader - xelsed.ai

This sketch creates an interactive AI mind reader game where users submit cryptic hints and an AI guesses what they're thinking. A glowing brain with pulsing neural connections animates in the center, while AI guesses float upward as thought bubbles. The sketch uses the OpenAI API to generate intelligent guesses based on accumulated hints.

๐ŸŽ“ Concepts You'll Learn

API integrationAsynchronous programmingClasses and objectsAnimation loopsBezier curvesBlend modesDOM manipulationJSON parsingEvent handlingVector math

๐Ÿ”„ Code Flow

Code flow showing getapikey, preload, setup, draw, definebrainpoints, drawbrain, neuralconnection, guessbubble, handlesubmit, speakguess, windowresized

๐Ÿ’ก Click on function names in the diagram to jump to their code

graph TD start[Start] --> setup[setup] setup --> canvas-setup[Canvas Initialization] setup --> color-setup[Color Definitions] setup --> input-creation[Input Element Creation] setup --> button-creation[Button Element Creation] setup --> neural-loop[Neural Connection Initialization] setup --> definebrainpoints[definebrainpoints] definebrainpoints --> size-calc[Brain Size Calculation] size-calc --> point-array[Control Points Array] setup --> draw[draw loop] click setup href "#fn-setup" click canvas-setup href "#sub-canvas-setup" click color-setup href "#sub-color-setup" click input-creation href "#sub-input-creation" click button-creation href "#sub-button-creation" click neural-loop href "#sub-neural-loop" click definebrainpoints href "#fn-definebrainpoints" click size-calc href "#sub-size-calc" click point-array href "#sub-point-array" draw --> background-clear[Background Clear] background-clear --> neural-update-loop[Neural Connection Update Loop] neural-update-loop --> neuralconnection[neuralconnection] neuralconnection --> constructor[Constructor Initialization] constructor --> random-point-method[Random Point Generation] random-point-method --> update-method[Update Method] update-method --> display-method[Display Method] neural-update-loop --> guess-update-loop[Guess Bubble Update Loop] guess-update-loop --> guessbubble[guessbubble] guessbubble --> constructor-bubble[Constructor Initialization] constructor-bubble --> update-bubble[Update Method] update-bubble --> display-bubble[Display Method] guess-update-loop --> fade-removal[Fade-Out Removal] fade-removal --> draw click draw href "#fn-draw" click background-clear href "#sub-background-clear" click neural-update-loop href "#sub-neural-update-loop" click neuralconnection href "#fn-neuralconnection" click constructor href "#sub-constructor" click random-point-method href "#sub-random-point-method" click update-method href "#sub-update-method" click display-method href "#sub-display-method" click guess-update-loop href "#sub-guess-update-loop" click guessbubble href "#fn-guessbubble" click constructor-bubble href "#sub-constructor-bubble" click update-bubble href "#sub-update-bubble" click display-bubble href "#sub-display-bubble" click fade-removal href "#sub-fade-removal" handlesubmit[handlesubmit] --> input-validation[Input Validation] input-validation -->|valid| hint-storage[Hint Storage] hint-storage --> ui-disable[UI Disable] ui-disable --> api-call[OpenAI API Call] api-call --> error-handling[Error Handling] error-handling -->|success| json-parsing[JSON Parsing] json-parsing --> guess-creation[Guess Bubble Creation] guess-creation --> speakguess[speakguess] speakguess --> browser-check[Browser Capability Check] browser-check --> utterance-creation[Utterance Creation] utterance-creation --> utterance-config[Utterance Configuration] utterance-config --> draw click handlesubmit href "#fn-handlesubmit" click input-validation href "#sub-input-validation" click hint-storage href "#sub-hint-storage" click ui-disable href "#sub-ui-disable" click api-call href "#sub-api-call" click error-handling href "#sub-error-handling" click json-parsing href "#sub-json-parsing" click guess-creation href "#sub-guess-creation" click speakguess href "#fn-speakguess" click browser-check href "#sub-browser-check" click utterance-creation href "#sub-utterance-creation" click utterance-config href "#sub-utterance-config" windowresized[windowresized] --> canvas-resize[Canvas Resize] canvas-resize --> brain-recalc[Brain Points Recalculation] brain-recalc --> draw click windowresized href "#fn-windowresized" click canvas-resize href "#sub-canvas-resize" click brain-recalc href "#sub-brain-recalc"

๐Ÿ“ Code Breakdown

getApiKey()

This function decodes an XOR-encrypted API key. XOR encryption is a simple cipher where each character is combined with a key using the XOR operator (^). This prevents exposing the API key in plain text in the source code, though it's not cryptographically secure for real applications.

function getApiKey() {
  return atob(encoded).split('').map(c => String.fromCharCode(c.charCodeAt(0) ^ key)).join('');
}

๐Ÿ”ง Subcomponents:

calculation Base64 Decode atob(encoded)

Converts the base64-encoded API key string back to its original binary form

for-loop XOR Decryption .split('').map(c => String.fromCharCode(c.charCodeAt(0) ^ key)).join('')

XORs each character with the key value to decrypt the API key

Line by Line:

return atob(encoded)
atob() decodes the base64-encoded string stored in the 'encoded' variable
.split('')
Converts the decoded string into an array of individual characters
.map(c => String.fromCharCode(c.charCodeAt(0) ^ key))
For each character, gets its character code, XORs it with the key value (0x5A), and converts back to a character
.join('')
Combines all decrypted characters back into a single string and returns it

preload()

preload() is a special p5.js function that runs before setup(). It's used here to decode the API key before the sketch initializes, ensuring the key is ready when needed for API calls.

function preload() {
  openaiApiKey = getApiKey();
  console.log("OpenAI API Key decoded.");
}

Line by Line:

openaiApiKey = getApiKey()
Calls getApiKey() to decode the XOR-encrypted API key and stores it in the global variable openaiApiKey
console.log("OpenAI API Key decoded.")
Prints a message to the browser console confirming the API key has been decoded

setup()

setup() runs once when the sketch starts. It initializes the canvas, creates UI elements, defines colors, and sets up the neural connections that will animate throughout the sketch. The brain points are calculated based on the canvas size so they scale properly on different screen sizes.

function setup() {
  createCanvas(windowWidth, windowHeight);
  brainColor = color(0, 150, 255);
  neonColor = color(0, 255, 255);
  inputElement = createInput('');
  inputElement.attribute('placeholder', 'What are you thinking?');
  inputElement.attribute('maxlength', '100');
  inputElement.changed(handleSubmit);
  inputElement.parent('ui-container');
  buttonElement = createButton('Submit Hint');
  buttonElement.mousePressed(handleSubmit);
  buttonElement.parent('ui-container');
  defineBrainPoints();
  for (let i = 0; i < 25; i++) {
    neuralConnections.push(new NeuralConnection());
  }
  textAlign(CENTER, CENTER);
  textFont('Monospace');
  noStroke();
}

๐Ÿ”ง Subcomponents:

calculation Canvas Initialization createCanvas(windowWidth, windowHeight)

Creates a full-window canvas that responds to the browser size

calculation Color Definitions brainColor = color(0, 150, 255); neonColor = color(0, 255, 255);

Defines the blue brain color and cyan neon color used throughout the sketch

calculation Input Element Creation inputElement = createInput(''); inputElement.attribute('placeholder', 'What are you thinking?');

Creates a text input field with placeholder text for users to enter hints

calculation Button Element Creation buttonElement = createButton('Submit Hint'); buttonElement.mousePressed(handleSubmit);

Creates a submit button that triggers the handleSubmit function when clicked

for-loop Neural Connection Initialization for (let i = 0; i < 25; i++) { neuralConnections.push(new NeuralConnection()); }

Creates 25 neural connection objects that will animate throughout the sketch

Line by Line:

createCanvas(windowWidth, windowHeight)
Creates a canvas that fills the entire browser window
brainColor = color(0, 150, 255)
Defines a blue color (RGB: 0, 150, 255) for the brain glow effect
neonColor = color(0, 255, 255)
Defines a cyan color (RGB: 0, 255, 255) for neural connections and text
inputElement = createInput('')
Creates an empty text input field using p5.js DOM functions
inputElement.attribute('placeholder', 'What are you thinking?')
Sets the placeholder text that appears in the input field when it's empty
inputElement.attribute('maxlength', '100')
Limits the input to 100 characters maximum
inputElement.changed(handleSubmit)
Registers the handleSubmit function to run when the user presses Enter in the input field
inputElement.parent('ui-container')
Adds the input element to the HTML container with id 'ui-container'
buttonElement = createButton('Submit Hint')
Creates a button with the label 'Submit Hint'
buttonElement.mousePressed(handleSubmit)
Registers the handleSubmit function to run when the button is clicked
buttonElement.parent('ui-container')
Adds the button to the same HTML container as the input
defineBrainPoints()
Calls the function to calculate the bezier control points for the brain shape
for (let i = 0; i < 25; i++) { neuralConnections.push(new NeuralConnection()); }
Creates 25 NeuralConnection objects and adds them to the neuralConnections array
textAlign(CENTER, CENTER)
Sets text alignment to center both horizontally and vertically
textFont('Monospace')
Sets the font to monospace for a technical/futuristic look
noStroke()
Disables stroke (outline) for text by default

draw()

draw() runs 60 times per second, creating the animation. Each frame, it clears the background, updates and displays all neural connections, draws the brain, and updates/displays all floating guesses. The loop iterates backwards through guesses so removing items doesn't skip any elements.

function draw() {
  background(20, 20, 40);
  for (let nc of neuralConnections) {
    nc.update();
    nc.display();
  }
  drawBrain();
  for (let i = guesses.length - 1; i >= 0; i--) {
    let guessBubble = guesses[i];
    guessBubble.update();
    guessBubble.display();
    if (guessBubble.alpha <= 0) {
      guesses.splice(i, 1);
    }
  }
}

๐Ÿ”ง Subcomponents:

calculation Background Clear background(20, 20, 40)

Fills the canvas with a dark blue color each frame, clearing previous drawings

for-loop Neural Connection Update Loop for (let nc of neuralConnections) { nc.update(); nc.display(); }

Updates and displays each neural connection animation

for-loop Guess Bubble Update Loop for (let i = guesses.length - 1; i >= 0; i--) { ... }

Updates and displays floating guess bubbles, removing them when they fade out

conditional Fade-Out Removal if (guessBubble.alpha <= 0) { guesses.splice(i, 1); }

Removes completely faded guesses from the array to free memory

Line by Line:

background(20, 20, 40)
Fills the entire canvas with a dark blue color (RGB: 20, 20, 40), clearing all previous drawings each frame
for (let nc of neuralConnections)
Loops through each neural connection object in the neuralConnections array
nc.update()
Updates the neural connection's pulse position and animation state
nc.display()
Draws the neural connection line and its pulsing glow effect
drawBrain()
Calls the function to draw the glowing brain shape in the center
for (let i = guesses.length - 1; i >= 0; i--)
Loops through the guesses array backwards (from end to start) to safely remove items during iteration
guessBubble.update()
Updates the guess bubble's position and alpha (opacity) for fade-out effect
guessBubble.display()
Draws the guess text at its current position with its current opacity
if (guessBubble.alpha <= 0)
Checks if the guess bubble has completely faded out
guesses.splice(i, 1)
Removes the completely faded guess from the array to prevent memory waste

defineBrainPoints()

This function calculates the control points for bezier curves that draw the brain shape. By basing the size on the smaller canvas dimension and using percentages, the brain scales responsively to different screen sizes. The 9 points create a symmetrical brain-like shape with left and right lobes.

function defineBrainPoints() {
  const cx = width / 2;
  const cy = height / 2;
  const bw = min(width, height) * 0.35;
  const bh = min(width, height) * 0.45;
  brainPoints = [
    createVector(cx, cy - bh * 0.5),
    createVector(cx - bw * 0.4, cy - bh * 0.4),
    createVector(cx - bw * 0.5, cy - bh * 0.1),
    createVector(cx - bw * 0.4, cy + bh * 0.3),
    createVector(cx, cy + bh * 0.5),
    createVector(cx + bw * 0.4, cy + bh * 0.3),
    createVector(cx + bw * 0.5, cy - bh * 0.1),
    createVector(cx + bw * 0.4, cy - bh * 0.4),
    createVector(cx, cy - bh * 0.5)
  ];
}

๐Ÿ”ง Subcomponents:

calculation Center Calculation const cx = width / 2; const cy = height / 2;

Calculates the center point of the canvas

calculation Brain Size Calculation const bw = min(width, height) * 0.35; const bh = min(width, height) * 0.45;

Calculates brain width and height as percentages of the smaller canvas dimension for responsive scaling

calculation Control Points Array brainPoints = [createVector(...), ...]

Creates an array of 9 vectors that define the bezier curve control points for the brain shape

Line by Line:

const cx = width / 2
Calculates the horizontal center of the canvas
const cy = height / 2
Calculates the vertical center of the canvas
const bw = min(width, height) * 0.35
Brain width is 35% of the smaller dimension (width or height), ensuring it scales proportionally
const bh = min(width, height) * 0.45
Brain height is 45% of the smaller dimension, making it slightly taller than wide
brainPoints = [createVector(...), ...]
Creates an array of 9 vector points that form the outline of the brain shape using bezier curves

drawBrain()

This function creates a glowing brain effect by drawing the brain outline 6 times with decreasing thickness and increasing opacity. The ADD blend mode adds the colors together, creating a bright neon glow. Bezier curves create smooth, organic curves between the control points defined in brainPoints.

function drawBrain() {
  noFill();
  strokeWeight(2);
  for (let i = 0; i < 6; i++) {
    blendMode(ADD);
    const glowAlpha = map(i, 0, 5, 20, 100);
    const glowWeight = map(i, 0, 5, 10, 2);
    stroke(brainColor.r, brainColor.g, brainColor.b, glowAlpha);
    strokeWeight(glowWeight);
    bezier(
      brainPoints[0].x, brainPoints[0].y,
      brainPoints[1].x, brainPoints[1].y,
      brainPoints[2].x, brainPoints[2].y,
      brainPoints[3].x, brainPoints[3].y
    );
    bezier(
      brainPoints[3].x, brainPoints[3].y,
      brainPoints[4].x, brainPoints[4].y,
      brainPoints[5].x, brainPoints[5].y,
      brainPoints[6].x, brainPoints[6].y
    );
    bezier(
      brainPoints[6].x, brainPoints[6].y,
      brainPoints[7].x, brainPoints[7].y,
      brainPoints[8].x, brainPoints[8].y,
      brainPoints[8].x, brainPoints[8].y
    );
  }
  blendMode(BLEND);
}

๐Ÿ”ง Subcomponents:

for-loop Glow Layer Loop for (let i = 0; i < 6; i++)

Draws 6 passes of the brain outline with varying thickness and opacity to create a bloom/glow effect

calculation Alpha Mapping const glowAlpha = map(i, 0, 5, 20, 100)

Maps the loop counter to opacity values, creating a gradient from dim to bright

calculation Weight Mapping const glowWeight = map(i, 0, 5, 10, 2)

Maps the loop counter to stroke weights, creating thicker outer glows and thinner inner lines

calculation Bezier Curve Drawing bezier(...) three times

Draws three connected bezier curves using the control points to form the complete brain outline

Line by Line:

noFill()
Disables fill so only the outline is drawn
strokeWeight(2)
Sets initial stroke weight to 2 pixels
for (let i = 0; i < 6; i++)
Loops 6 times to draw multiple passes of the brain outline for a glow effect
blendMode(ADD)
Sets blend mode to ADD, which adds colors together, creating a bright glow effect
const glowAlpha = map(i, 0, 5, 20, 100)
Maps loop counter (0-5) to opacity (20-100), making later passes brighter
const glowWeight = map(i, 0, 5, 10, 2)
Maps loop counter (0-5) to stroke weight (10-2), making later passes thinner
stroke(brainColor.r, brainColor.g, brainColor.b, glowAlpha)
Sets the stroke color to the brain color with the calculated alpha value
strokeWeight(glowWeight)
Sets the stroke weight to the calculated value
bezier(...)
Draws a bezier curve using 4 control points (start, control1, control2, end)
blendMode(BLEND)
Resets blend mode to default BLEND mode for subsequent drawings

class NeuralConnection

NeuralConnection represents animated lines connecting around the brain. Each connection has a pulsing glow that travels along the line. When the pulse reaches the end, new random endpoints are generated, creating the illusion of neural activity. The sine wave animation makes the pulse smoothly grow and shrink.

class NeuralConnection {
  constructor() {
    this.start = this.randomPointNearBrain();
    this.end = this.randomPointNearBrain();
    this.pulsePosition = random(1);
    this.pulseSpeed = random(0.005, 0.02);
    this.pulseSize = random(5, 15);
  }
  randomPointNearBrain() {
    const cx = width / 2;
    const cy = height / 2;
    const radius = min(width, height) * 0.25;
    const angle = random(TWO_PI);
    return createVector(cx + radius * cos(angle) * random(0.8, 1.3), cy + radius * sin(angle) * random(0.8, 1.3));
  }
  update() {
    this.pulsePosition += this.pulseSpeed;
    if (this.pulsePosition > 1) {
      this.pulsePosition = 0;
      this.start = this.randomPointNearBrain();
      this.end = this.randomPointNearBrain();
    }
  }
  display() {
    stroke(neonColor.r, neonColor.g, neonColor.b, 30);
    strokeWeight(1);
    line(this.start.x, this.start.y, this.end.x, this.end.y);
    const pulseX = lerp(this.start.x, this.end.x, this.pulsePosition);
    const pulseY = lerp(this.start.y, this.end.y, this.pulsePosition);
    blendMode(ADD);
    stroke(neonColor.r, neonColor.g, neonColor.b, 150);
    strokeWeight(this.pulseSize * sin(this.pulsePosition * PI));
    line(pulseX, pulseY, pulseX, pulseY);
    blendMode(BLEND);
  }
}

๐Ÿ”ง Subcomponents:

calculation Constructor Initialization constructor() { ... }

Initializes a neural connection with random start/end points and pulse properties

calculation Random Point Generation randomPointNearBrain() { ... }

Generates random points in a circular area around the brain

calculation Update Method update() { ... }

Updates the pulse position and regenerates endpoints when pulse completes

calculation Display Method display() { ... }

Draws the connection line and the pulsing glow effect

Line by Line:

this.start = this.randomPointNearBrain()
Sets the starting point of the neural connection to a random point near the brain
this.end = this.randomPointNearBrain()
Sets the ending point of the neural connection to another random point near the brain
this.pulsePosition = random(1)
Initializes pulse position to a random value between 0 and 1
this.pulseSpeed = random(0.005, 0.02)
Sets how fast the pulse moves along the line (0.005 to 0.02 per frame)
this.pulseSize = random(5, 15)
Sets the maximum size of the pulsing glow (5 to 15 pixels)
const radius = min(width, height) * 0.25
Defines the radius of the circular area where random points are generated
const angle = random(TWO_PI)
Generates a random angle (0 to 2ฯ€ radians) for the point
return createVector(cx + radius * cos(angle) * random(0.8, 1.3), ...)
Creates a vector at the calculated position using trigonometry, with slight randomization
this.pulsePosition += this.pulseSpeed
Moves the pulse along the line by incrementing its position
if (this.pulsePosition > 1)
Checks if the pulse has reached the end of the line
this.pulsePosition = 0
Resets the pulse to the start for continuous looping
stroke(neonColor.r, neonColor.g, neonColor.b, 30)
Sets a faint cyan color for the background connection line
line(this.start.x, this.start.y, this.end.x, this.end.y)
Draws the faint background line connecting the two points
const pulseX = lerp(this.start.x, this.end.x, this.pulsePosition)
Interpolates the X position of the pulse between start and end based on pulse position
const pulseY = lerp(this.start.y, this.end.y, this.pulsePosition)
Interpolates the Y position of the pulse between start and end based on pulse position
strokeWeight(this.pulseSize * sin(this.pulsePosition * PI))
Animates the pulse size using a sine wave, creating a pulsing effect from 0 to max and back

class GuessBubble

GuessBubble represents a floating thought bubble showing an AI guess. Each bubble starts near the center with random position variation, moves slowly in a random direction, and fades out over time. The varying text sizes and fade speeds create a natural, organic appearance.

class GuessBubble {
  constructor(guessData) {
    this.guess = guessData.guess;
    this.confidence = guessData.confidence;
    this.reasoning = guessData.reasoning;
    this.x = width / 2 + random(-min(width, height) * 0.2, min(width, height) * 0.2);
    this.y = height / 2 + random(-min(width, height) * 0.2, min(width, height) * 0.2);
    this.size = random(18, 24);
    this.alpha = 255;
    this.speed = createVector(random(-0.5, 0.5), random(-0.5, 0.5));
    this.fadeSpeed = random(0.5, 1.5);
  }
  update() {
    this.x += this.speed.x;
    this.y += this.speed.y;
    this.alpha -= this.fadeSpeed;
  }
  display() {
    fill(neonColor.r, neonColor.g, neonColor.b, this.alpha);
    noStroke();
    textSize(this.size);
    text(this.guess, this.x, this.y);
  }
}

๐Ÿ”ง Subcomponents:

calculation Constructor Initialization constructor(guessData) { ... }

Initializes a guess bubble with position, size, and animation properties

calculation Update Method update() { ... }

Updates the bubble's position and fades it out

calculation Display Method display() { ... }

Draws the guess text at its current position and opacity

Line by Line:

this.guess = guessData.guess
Stores the AI's guess text from the guessData object
this.confidence = guessData.confidence
Stores the AI's confidence level (1-100) for the guess
this.reasoning = guessData.reasoning
Stores the AI's reasoning for why it made this guess
this.x = width / 2 + random(-min(width, height) * 0.2, min(width, height) * 0.2)
Sets initial X position near the center with random offset (ยฑ20% of smaller dimension)
this.y = height / 2 + random(-min(width, height) * 0.2, min(width, height) * 0.2)
Sets initial Y position near the center with random offset (ยฑ20% of smaller dimension)
this.size = random(18, 24)
Sets the text size to a random value between 18 and 24 pixels
this.alpha = 255
Sets initial opacity to fully opaque (255 = fully visible)
this.speed = createVector(random(-0.5, 0.5), random(-0.5, 0.5))
Creates a velocity vector with random X and Y speeds (-0.5 to 0.5 pixels per frame)
this.fadeSpeed = random(0.5, 1.5)
Sets how quickly the bubble fades out (0.5 to 1.5 opacity units per frame)
this.x += this.speed.x
Moves the bubble horizontally by its X velocity
this.y += this.speed.y
Moves the bubble vertically by its Y velocity
this.alpha -= this.fadeSpeed
Reduces opacity each frame, making the bubble fade out
fill(neonColor.r, neonColor.g, neonColor.b, this.alpha)
Sets the text color to cyan with the current opacity
text(this.guess, this.x, this.y)
Draws the guess text at its current position

async function handleSubmit()

handleSubmit() is the core function that handles user input and communicates with OpenAI's API. It uses async/await to handle the asynchronous API call, validates input, manages UI state, and handles both successful responses and errors. The try/catch/finally structure ensures the UI is always re-enabled even if an error occurs.

async function handleSubmit() {
  const hint = inputElement.value().trim();
  if (hint === '') {
    alert('Please enter a hint!');
    return;
  }
  hints.push(hint);
  inputElement.value('');
  buttonElement.attribute('disabled', true);
  inputElement.attribute('disabled', true);
  console.log("Submitting hints to OpenAI:", hints);
  const prompt = `Based on these clues, guess what I am thinking: ${hints.join(', ')}. Return JSON: {guess:string, confidence:1-100, reasoning:string}`;
  try {
    const response = await fetch('https://api.openai.com/v1/chat/completions', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + openaiApiKey
      },
      body: JSON.stringify({
        model: 'gpt-3.5-turbo',
        messages: [{ role: 'user', content: prompt }],
        max_tokens: 150,
        temperature: 0.7
      })
    });
    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(`OpenAI API error: ${response.status} - ${errorData.error.message}`);
    }
    const data = await response.json();
    const content = data.choices[0].message.content;
    console.log("OpenAI raw response:", content);
    let guessData;
    try {
      guessData = JSON.parse(content);
    } catch (e) {
      const jsonMatch = content.match(/```json\n([\s\S]*?)\n```/);
      if (jsonMatch) {
        guessData = JSON.parse(jsonMatch[1]);
      } else {
        throw new Error('Could not parse AI response as JSON');
      }
    }
    guesses.push(new GuessBubble(guessData));
    speakGuess(guessData.guess);
    console.log("AI Guess:", guessData);
  } catch (error) {
    console.error('Error calling OpenAI API:', error);
    alert('Error: ' + error.message);
  } finally {
    buttonElement.attribute('disabled', false);
    inputElement.attribute('disabled', false);
  }
}

๐Ÿ”ง Subcomponents:

conditional Input Validation if (hint === '') { alert('Please enter a hint!'); return; }

Checks that the user entered a hint before proceeding

calculation Hint Storage hints.push(hint); inputElement.value('');

Adds the hint to the array and clears the input field

calculation UI Disable buttonElement.attribute('disabled', true); inputElement.attribute('disabled', true);

Disables input and button during API call to prevent multiple submissions

calculation OpenAI API Call const response = await fetch('https://api.openai.com/v1/chat/completions', { ... })

Sends the hints to OpenAI's API and waits for a response

conditional Error Handling if (!response.ok) { ... throw new Error(...) }

Checks for API errors and throws an error if the request failed

conditional JSON Parsing try { guessData = JSON.parse(content); } catch (e) { ... }

Attempts to parse the AI response as JSON, with fallback for markdown code blocks

calculation Guess Bubble Creation guesses.push(new GuessBubble(guessData))

Creates a new guess bubble with the AI's response

calculation Speech Output speakGuess(guessData.guess)

Speaks the guess aloud using text-to-speech

Line by Line:

const hint = inputElement.value().trim()
Gets the text from the input field and removes whitespace from both ends
if (hint === '') { alert('Please enter a hint!'); return; }
If the hint is empty, shows an alert and exits the function
hints.push(hint)
Adds the new hint to the hints array
inputElement.value('')
Clears the input field for the next hint
buttonElement.attribute('disabled', true)
Disables the submit button to prevent multiple simultaneous API calls
inputElement.attribute('disabled', true)
Disables the input field during the API call
const prompt = `Based on these clues...`
Creates the prompt text that will be sent to OpenAI, including all accumulated hints
const response = await fetch('https://api.openai.com/v1/chat/completions', { ... })
Sends a POST request to OpenAI's API with the prompt and waits for the response
method: 'POST'
Specifies this is a POST request (sending data to the server)
'Authorization': 'Bearer ' + openaiApiKey
Includes the API key in the Authorization header for authentication
model: 'gpt-3.5-turbo'
Specifies using GPT-3.5 Turbo model, which is fast and cost-effective
max_tokens: 150
Limits the response to 150 tokens (roughly 100-150 words) to keep it concise
temperature: 0.7
Sets creativity level: 0.7 is balanced between deterministic and creative responses
if (!response.ok) { ... throw new Error(...) }
If the API request failed, extracts the error message and throws an error
const data = await response.json()
Converts the response from JSON text to a JavaScript object
const content = data.choices[0].message.content
Extracts the AI's response text from the nested response structure
guessData = JSON.parse(content)
Attempts to parse the response as JSON to extract guess, confidence, and reasoning
const jsonMatch = content.match(/```json\n([\s\S]*?)\n```/)
If direct parsing fails, searches for JSON inside markdown code blocks
guesses.push(new GuessBubble(guessData))
Creates a new GuessBubble with the parsed guess data and adds it to the guesses array
speakGuess(guessData.guess)
Calls the speakGuess function to speak the guess aloud
catch (error) { ... alert('Error: ' + error.message) }
Catches any errors and displays them to the user
finally { buttonElement.attribute('disabled', false); ... }
Re-enables the input and button after the API call completes (whether successful or not)

function speakGuess(text)

speakGuess() uses the Web Speech API to speak the AI's guess aloud. It checks browser compatibility first, then creates and configures a speech utterance with the guess text. This adds an audio dimension to the AI's responses, making the interaction more immersive.

function speakGuess(text) {
  if ('speechSynthesis' in window) {
    const utterance = new SpeechSynthesisUtterance(text);
    utterance.pitch = 1;
    utterance.rate = 1;
    utterance.volume = 1;
    speechSynthesis.speak(utterance);
    console.log("Speaking guess:", text);
  } else {
    console.warn("Speech Synthesis not supported in this browser.");
  }
}

๐Ÿ”ง Subcomponents:

conditional Browser Capability Check if ('speechSynthesis' in window)

Checks if the browser supports the Web Speech API

calculation Utterance Creation const utterance = new SpeechSynthesisUtterance(text)

Creates a speech utterance object with the guess text

calculation Utterance Configuration utterance.pitch = 1; utterance.rate = 1; utterance.volume = 1;

Sets speech properties to standard values

Line by Line:

if ('speechSynthesis' in window)
Checks if the browser supports the Web Speech API for text-to-speech
const utterance = new SpeechSynthesisUtterance(text)
Creates a speech utterance object containing the text to be spoken
utterance.pitch = 1
Sets the pitch to normal (1 is standard, higher values are higher pitch)
utterance.rate = 1
Sets the speaking rate to normal (1 is standard, higher values are faster)
utterance.volume = 1
Sets the volume to maximum (1 is full volume, 0 is silent)
speechSynthesis.speak(utterance)
Tells the browser to speak the utterance using the system's text-to-speech engine
console.log("Speaking guess:", text)
Logs to console for debugging purposes
console.warn("Speech Synthesis not supported...")
Logs a warning if the browser doesn't support text-to-speech

function windowResized()

windowResized() is a special p5.js function that runs whenever the browser window is resized. It ensures the canvas and all visual elements scale properly to fit the new window size, maintaining responsiveness across different screen sizes.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  defineBrainPoints();
}

๐Ÿ”ง Subcomponents:

calculation Canvas Resize resizeCanvas(windowWidth, windowHeight)

Resizes the p5.js canvas to match the new window dimensions

calculation Brain Points Recalculation defineBrainPoints()

Recalculates brain control points based on the new canvas size

Line by Line:

resizeCanvas(windowWidth, windowHeight)
Resizes the canvas to match the current window width and height
defineBrainPoints()
Recalculates the brain shape control points so the brain scales proportionally to the new canvas size

๐Ÿ“ฆ Key Variables

inputElement p5.Renderer (DOM element)

Stores reference to the text input field where users type hints

let inputElement;
buttonElement p5.Renderer (DOM element)

Stores reference to the submit button that triggers hint submission

let buttonElement;
hints array

Stores all hints submitted by the user as strings, accumulating over time

let hints = [];
guesses array

Stores all GuessBubble objects representing AI guesses that are currently displayed

let guesses = [];
neuralConnections array

Stores all NeuralConnection objects that animate around the brain

let neuralConnections = [];
brainPoints array

Stores p5.Vector objects that define the control points for the bezier brain shape

let brainPoints = [];
brainColor p5.Color

Stores the blue color used for the glowing brain outline

let brainColor;
neonColor p5.Color

Stores the cyan neon color used for neural connections and guess text

let neonColor;
openaiApiKey string

Stores the decoded OpenAI API key used for authentication with the API

let openaiApiKey;
encoded string (constant)

Stores the XOR-encoded OpenAI API key that gets decoded at startup

const encoded = 'KTF3Kig1MHcIaDhqCms+OA0AYhEADSo9NDEbLDxjKwMTPW0fdwgLMQ8wahcvFy4UIDAxYiI4CWgwdy0fLGgsDgwcLCkDAgsuNh40OxwcN24LbA5pGDY4MRwQKR0+AigiLDATP2piFwwqLzk3agorOBM2ExkvLTMeDmg8OwoTGCMpLhQXOWMuNBIPDRc2bRlpFA0THT0RIh8NETsRKyADAykUKRs=';
key number (constant)

Stores the XOR key (0x5A) used to decrypt the encoded API key

const key = 0x5A;

๐Ÿงช Try This!

Experiment with the code by making these changes:

  1. Change the brainColor in setup() to a different color like color(255, 0, 255) for magenta and see how the brain glow changes
  2. Increase the number of neural connections from 25 to 50 in the setup() loop to create a denser neural network effect
  3. Modify the temperature parameter in handleSubmit() from 0.7 to 0.3 (more deterministic) or 1.0 (more creative) and see how the AI's guesses change
  4. Change the fadeSpeed range in GuessBubble constructor from random(0.5, 1.5) to random(0.1, 0.3) to make guesses fade out much more slowly
  5. Adjust the brain size in defineBrainPoints() by changing the multipliers (0.35 and 0.45) to larger values like 0.5 and 0.6 to create a bigger brain
  6. Modify the max_tokens in handleSubmit() from 150 to 300 to allow longer, more detailed AI responses
  7. Change the neonColor from cyan (0, 255, 255) to green (0, 255, 0) and see the entire visual theme transform
Open in Editor & Experiment โ†’

๐Ÿ”ง Potential Improvements

Here are some ways this code could be enhanced:

BUG handleSubmit() - JSON parsing

The regex pattern for extracting JSON from markdown code blocks uses a specific format that may not match all variations (e.g., with different whitespace or without the 'json' language specifier)

๐Ÿ’ก Make the regex more flexible: `const jsonMatch = content.match(/```(?:json)?\n?([\s\S]*?)\n?```/)` to handle variations in markdown formatting

BUG NeuralConnection.display()

Drawing a line from a point to itself (pulseX, pulseY to pulseX, pulseY) with a stroke weight creates a point, but this is inefficient and unintuitive

๐Ÿ’ก Use circle(pulseX, pulseY, this.pulseSize * sin(this.pulsePosition * PI)) instead for a clearer visual representation of the pulsing glow

PERFORMANCE drawBrain()

Drawing the brain outline 6 times per frame with multiple bezier curves is computationally expensive, especially on lower-end devices

๐Ÿ’ก Consider reducing the number of glow passes from 6 to 3-4, or use a pre-rendered graphics buffer (createGraphics) to draw the brain once and reuse it

PERFORMANCE handleSubmit()

The API key is decoded every time the sketch runs, and the encoded key is visible in the source code (though obfuscated)

๐Ÿ’ก For production, use environment variables or a backend service to handle API authentication instead of embedding keys in client-side code

STYLE handleSubmit()

The prompt construction is a single long string that's hard to read and modify

๐Ÿ’ก Use template literals with better formatting: `const prompt = \`Based on these clues, guess what I'm thinking: ${hints.join(', ')}. Return JSON: {guess: string, confidence: 1-100, reasoning: string}\`;`

FEATURE GuessBubble

The confidence level is stored but never displayed to the user, missing useful feedback about how confident the AI is

๐Ÿ’ก Modify the display() method to show confidence as a percentage or visual indicator, e.g., `text(this.guess + ' (' + this.confidence + '%)', this.x, this.y)`

FEATURE handleSubmit()

There's no indication to the user that the AI is thinking/processing their hint

๐Ÿ’ก Add a loading message or animation (e.g., 'AI is thinking...') that displays while waiting for the API response

FEATURE speakGuess()

The speech synthesis always uses default voice and settings, which may not be ideal for all users

๐Ÿ’ก Add options to select voice, pitch, and rate, or detect the user's language preference to use an appropriate voice

Preview

AI Mind Reader - xelsed.ai - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of AI Mind Reader - xelsed.ai - Code flow showing getapikey, preload, setup, draw, definebrainpoints, drawbrain, neuralconnection, guessbubble, handlesubmit, speakguess, windowresized
Code Flow Diagram