AI Reaction Time Test - How Fast Are Your Reflexes - xelsed.ai

This sketch creates an interactive reaction time test game where players wait for the screen to turn green, then click as fast as possible. The game measures reaction time in milliseconds, tracks the best score, and prevents cheating by penalizing early clicks. It's a fun way to test and challenge your reflexes.

๐ŸŽ“ Concepts You'll Learn

Game state managementEvent handlingTime measurementConditional logicCanvas resizingCustom fontsSwitch statements

๐Ÿ”„ Code Flow

Code flow showing preload, setup, draw, mousepressed, resetgame, windowresized

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

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> bestscore[best-score-display] draw --> waitstate[wait-state] draw --> readystate[ready-state] draw --> resultstate[result-state] draw --> earlystate[early-state] draw --> waitclick[wait-click] draw --> readyclick[ready-click] draw --> resultearlyclick[result-early-click] draw --> statereset[state-reset] draw --> targettimecalc[target-time-calc] waitstate -->|random delay expires| readystate readystate -->|player clicks| readyclick resultstate -->|player clicks| resultearlyclick earlystate -->|player clicks| resultearlyclick waitclick -->|player clicks too early| earlystate readyclick -->|calculates reaction time| resultstate statereset -->|sets game back to waiting state| waitstate targettimecalc -->|calculates random delay| readystate click setup href "#fn-setup" click draw href "#fn-draw" click bestscore href "#sub-best-score-display" click waitstate href "#sub-wait-state" click readystate href "#sub-ready-state" click resultstate href "#sub-result-state" click earlystate href "#sub-early-state" click waitclick href "#sub-wait-click" click readyclick href "#sub-ready-click" click resultearlyclick href "#sub-result-early-click" click statereset href "#sub-state-reset" click targettimecalc href "#sub-target-time-calc"

๐Ÿ“ Code Breakdown

preload()

preload() is a special p5.js function that runs before setup(). Use it to load fonts, images, and sounds so they're available when your sketch needs them. Without preload(), text might appear in the default font before the custom font loads.

function preload() {
  myFont = loadFont('https://unpkg.com/@fontsource/roboto@latest/files/roboto-latin-400-normal.woff');
}

Line by Line:

myFont = loadFont('https://unpkg.com/@fontsource/roboto@latest/files/roboto-latin-400-normal.woff')
Loads the Roboto font from a CDN (online source) before the sketch starts. preload() runs before setup() to ensure assets are ready.

setup()

setup() runs once when the sketch starts. It's where you initialize your canvas, set up fonts, colors, and other settings that should happen only once. Think of it as the 'preparation' phase before the game begins.

function setup() {
  createCanvas(windowWidth, windowHeight);
  textFont(myFont);
  textAlign(CENTER, CENTER);
  noStroke();
  resetGame();
}

Line by Line:

createCanvas(windowWidth, windowHeight)
Creates a canvas that fills the entire browser window. windowWidth and windowHeight are p5.js variables that automatically detect the window size.
textFont(myFont)
Tells p5.js to use the custom Roboto font (loaded in preload()) for all text drawn in the sketch.
textAlign(CENTER, CENTER)
Sets text alignment so that text is centered both horizontally and vertically around the coordinates you specify.
noStroke()
Removes outlines from shapes. This means rectangles and other shapes will only have fill color, no border.
resetGame()
Calls the resetGame() function to initialize the game state and set up the first round.

draw()

draw() runs repeatedly (typically 60 times per second) and is where all animation and visual updates happen. The switch statement is a clean way to manage different game states - each case handles what should be displayed and what should happen in that state. The millis() function returns the number of milliseconds since the sketch started, which is perfect for measuring reaction time.

function draw() {
  fill(0);
  textSize(24);
  textAlign(LEFT, TOP);
  text(`Best: ${bestScore === Infinity ? 'N/A' : bestScore + ' ms'}`, 20, 20);

  textAlign(CENTER, CENTER);

  switch (gameState) {
    case "WAIT":
      background(255, 0, 0);
      fill(255);
      textSize(64);
      text("Wait for green...", width / 2, height / 2);
      if (millis() >= targetTime) {
        gameState = "READY";
        startTime = millis();
      }
      break;

    case "READY":
      background(0, 255, 0);
      fill(0);
      textSize(96);
      text("CLICK NOW!", width / 2, height / 2);
      break;

    case "RESULT":
      background(255);
      fill(0);
      textSize(128);
      text(`${reactionTime} ms`, width / 2, height / 2 - 50);
      textSize(32);
      text("Click to try again", width / 2, height / 2 + 50);
      break;

    case "EARLY":
      background(255);
      fill(0);
      textSize(64);
      text("Too early!", width / 2, height / 2 - 50);
      textSize(32);
      text("Click to try again", width / 2, height / 2 + 50);
      break;
  }
}

๐Ÿ”ง Subcomponents:

conditional Best Score Display text(`Best: ${bestScore === Infinity ? 'N/A' : bestScore + ' ms'}`, 20, 20)

Shows the best reaction time in the top-left corner, displaying 'N/A' if no successful attempt has been made yet

switch-case WAIT State Handler case "WAIT": ... if (millis() >= targetTime) { gameState = "READY"; startTime = millis(); }

Displays red screen with 'Wait for green...' message and transitions to READY state when the random delay expires

switch-case READY State Handler case "READY": background(0, 255, 0); fill(0); textSize(96); text("CLICK NOW!", width / 2, height / 2);

Displays green screen with 'CLICK NOW!' message, waiting for the player to click

switch-case RESULT State Handler case "RESULT": ... text(`${reactionTime} ms`, width / 2, height / 2 - 50); text("Click to try again", width / 2, height / 2 + 50);

Displays white screen showing the reaction time in large text and prompts to try again

switch-case EARLY State Handler case "EARLY": ... text("Too early!", width / 2, height / 2 - 50); text("Click to try again", width / 2, height / 2 + 50);

Displays white screen with 'Too early!' message when player clicks before the screen turns green

Line by Line:

fill(0)
Sets the fill color to black (0, 0, 0 in RGB) for the best score text.
textSize(24)
Sets the font size to 24 pixels for the best score display.
textAlign(LEFT, TOP)
Changes text alignment to top-left so the best score displays in the corner correctly.
text(`Best: ${bestScore === Infinity ? 'N/A' : bestScore + ' ms'}`, 20, 20)
Displays the best score at position (20, 20). Uses a ternary operator to show 'N/A' if no successful attempt yet, otherwise shows the time in milliseconds.
textAlign(CENTER, CENTER)
Resets text alignment back to center for the main game messages.
switch (gameState)
Uses a switch statement to handle different game states. Each case represents a different screen/phase of the game.
case "WAIT": background(255, 0, 0)
When waiting, fill the entire canvas with red (255, 0, 0 in RGB).
if (millis() >= targetTime)
Checks if the current time in milliseconds has reached the target time. When true, it's time to show the green screen.
gameState = "READY"; startTime = millis()
Transitions to the READY state and records the exact moment the green screen appeared, which will be used to calculate reaction time.
case "READY": background(0, 255, 0)
When ready, fill the canvas with green (0, 255, 0 in RGB) - this is the signal for the player to click.
text("CLICK NOW!", width / 2, height / 2)
Displays 'CLICK NOW!' in large text (96px) at the center of the screen.
case "RESULT": text(`${reactionTime} ms`, width / 2, height / 2 - 50)
Shows the reaction time in very large text (128px) slightly above center when the player successfully clicks.
case "EARLY": text("Too early!", width / 2, height / 2 - 50)
Displays 'Too early!' message when the player clicks before the green screen appears.

mousePressed()

mousePressed() is a p5.js event handler that responds to user clicks. Notice how the same click event is handled differently depending on gameState - this is the power of state-based game design. It keeps your code organized and makes it easy to understand what should happen in each phase of the game.

function mousePressed() {
  switch (gameState) {
    case "WAIT":
      gameState = "EARLY";
      break;

    case "READY":
      reactionTime = millis() - startTime;
      bestScore = min(bestScore, reactionTime);
      gameState = "RESULT";
      break;

    case "RESULT":
    case "EARLY":
      resetGame();
      break;
  }
}

๐Ÿ”ง Subcomponents:

switch-case WAIT State Click Handler case "WAIT": gameState = "EARLY";

Detects when player clicks too early and transitions to EARLY state

switch-case READY State Click Handler case "READY": reactionTime = millis() - startTime; bestScore = min(bestScore, reactionTime); gameState = "RESULT";

Calculates reaction time, updates best score if needed, and transitions to RESULT state

switch-case RESULT/EARLY State Click Handler case "RESULT": case "EARLY": resetGame();

Starts a new game round when player clicks after seeing result or early message

Line by Line:

function mousePressed()
This special p5.js function is called automatically whenever the mouse is clicked or the screen is touched.
switch (gameState)
Uses a switch statement to handle clicks differently depending on the current game state.
case "WAIT": gameState = "EARLY"
If the player clicks while the screen is red (WAIT state), transition to EARLY state to show 'Too early!' message.
case "READY": reactionTime = millis() - startTime
If the player clicks while the screen is green (READY state), calculate reaction time by subtracting the time the green screen appeared from the current time.
bestScore = min(bestScore, reactionTime)
Updates bestScore to the smaller of the current bestScore and the new reactionTime. The min() function returns the minimum value, so this keeps track of the fastest reaction.
gameState = "RESULT"
Transitions to RESULT state to display the reaction time.
case "RESULT": case "EARLY": resetGame()
When the player clicks after seeing either the result or the 'Too early!' message, start a new round by calling resetGame().

resetGame()

resetGame() is a helper function that prepares the game for a new round. It's called from setup() to start the first game and from mousePressed() when the player wants to try again. By centralizing the reset logic in one function, you avoid repeating code and make it easier to maintain. The random(2000, 5000) creates unpredictability, which is essential for a fair reaction time test.

function resetGame() {
  gameState = "WAIT";
  targetTime = millis() + random(2000, 5000);
  reactionTime = 0;
  startTime = 0;
}

๐Ÿ”ง Subcomponents:

calculation Game State Reset gameState = "WAIT"

Sets the game back to the waiting state with a red screen

calculation Random Delay Calculation targetTime = millis() + random(2000, 5000)

Calculates when the screen should turn green by adding a random delay between 2-5 seconds to the current time

Line by Line:

gameState = "WAIT"
Resets the game state to WAIT, which displays the red screen with 'Wait for green...' message.
targetTime = millis() + random(2000, 5000)
Calculates a new target time by taking the current time in milliseconds and adding a random number between 2000 and 5000 milliseconds (2-5 seconds). This unpredictable delay prevents players from anticipating when the green screen will appear.
reactionTime = 0
Clears the previous reaction time so it doesn't carry over to the next round.
startTime = 0
Clears the previous start time so it doesn't affect the next round's calculation.

windowResized()

windowResized() is a p5.js event handler that responds to window resize events. Without this function, the canvas would stay its original size and not adapt to window changes. This is important for responsive design - making sure your sketch works well on different screen sizes and when users resize their browser.

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

Line by Line:

function windowResized()
This special p5.js function is called automatically whenever the browser window is resized.
resizeCanvas(windowWidth, windowHeight)
Resizes the canvas to match the new window dimensions, ensuring the game fills the entire window at all times.

๐Ÿ“ฆ Key Variables

gameState string

Tracks the current phase of the game. Can be 'WAIT' (red screen), 'READY' (green screen), 'RESULT' (showing reaction time), or 'EARLY' (clicked too soon). Controls what's displayed and how clicks are handled.

let gameState = "WAIT";
targetTime number

Stores the millisecond timestamp when the screen should turn green. Calculated as current time plus a random delay between 2-5 seconds.

let targetTime = millis() + random(2000, 5000);
startTime number

Stores the millisecond timestamp when the green screen appears. Used to calculate reaction time by subtracting from the click time.

let startTime = millis();
reactionTime number

Stores the player's reaction time in milliseconds, calculated as the time between when the green screen appeared and when the player clicked.

let reactionTime = millis() - startTime;
bestScore number

Stores the fastest (lowest) reaction time achieved so far. Initialized to Infinity so any successful attempt will be faster. Updated using the min() function.

let bestScore = Infinity;
myFont object

Stores the custom Roboto font loaded from the CDN in preload(). Used by textFont() to display text in a consistent, attractive font.

let myFont; // Loaded in preload()

๐Ÿงช Try This!

Experiment with the code by making these changes:

  1. Change the random delay range in resetGame() from random(2000, 5000) to random(1000, 3000) to make the game faster-paced with shorter wait times.
  2. Modify the text sizes in the draw() function - try changing textSize(96) to textSize(120) for 'CLICK NOW!' to make it even more prominent.
  3. Add a sound effect when the screen turns green by loading a sound in preload() and playing it when gameState changes to 'READY'. This would give players an audio cue.
  4. Change the background colors - try background(100, 150, 255) for a blue WAIT state instead of red, or background(255, 200, 0) for a yellow READY state.
  5. Add a counter that tracks how many attempts the player has made by incrementing a variable each time resetGame() is called, and display it on screen.
  6. Experiment with the reaction time calculation by adding a small penalty: instead of just reactionTime = millis() - startTime, try reactionTime = millis() - startTime + 50 to see how it affects your best score.
Open in Editor & Experiment โ†’

๐Ÿ”ง Potential Improvements

Here are some ways this code could be enhanced:

FEATURE draw() function

No visual feedback or animation when transitioning between states

๐Ÿ’ก Add a fade effect or color transition when moving from WAIT to READY state. For example, use lerpColor() to smoothly transition from red to green over a few frames.

STYLE mousePressed() function

The switch statement has two cases (RESULT and EARLY) that do the same thing but are written separately

๐Ÿ’ก Combine them as shown in the code (case "RESULT": case "EARLY": resetGame();) to avoid code duplication. This is already done correctly in the sketch.

FEATURE Global variables and draw() function

No way to see a history of recent reaction times, only the best score

๐Ÿ’ก Create an array to store the last 5-10 reaction times and display them on screen. For example: let reactionHistory = []; and push new times to it, displaying them as a list.

PERFORMANCE preload() function

Font is loaded from an external CDN, which could be slow or fail on poor connections

๐Ÿ’ก Add error handling or a fallback font. You could use a try-catch block or check if the font loaded successfully before using it.

BUG draw() function - WAIT state

If the random delay is very long (close to 5 seconds), players might think the sketch is frozen

๐Ÿ’ก Add a countdown timer showing how many seconds until the screen turns green. For example: let secondsLeft = (targetTime - millis()) / 1000; and display it on the red screen.

FEATURE Global variables

No difficulty levels or modes for variety

๐Ÿ’ก Add a difficulty system where 'Easy' has longer delays (3-6 seconds), 'Normal' has 2-5 seconds, and 'Hard' has 1-3 seconds. Let players switch modes by pressing number keys.

Preview

AI Reaction Time Test - How Fast Are Your Reflexes - xelsed.ai - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of AI Reaction Time Test - How Fast Are Your Reflexes - xelsed.ai - Code flow showing preload, setup, draw, mousepressed, resetgame, windowresized
Code Flow Diagram