computer virus (prank)

This sketch creates a full-screen prank animation that simulates a computer virus scanning, infecting, and then 'fixing' a system. It progresses through four dramatic states—scanning, infected alert, fixing, and system deleted—complete with alarming sounds and a clickable button, all designed as a harmless visual joke.

🧪 Try This!

Experiment with the code by making these changes:

  1. Speed up the scanning phase — Lower the scanDuration value so the progress bar fills faster and the prank moves quicker to the alarm screen
  2. Make the prank window huge — Increase the width and height constants to dominate the screen and make the fake terminal feel more imposing
  3. Change the final punchline — Edit the text in the CLEAN state to deliver your own custom ending message
  4. Change the alarm color to bright blue — Switch the INFECTED state background from red to an unsettling shade of blue for a different vibe
  5. Show more fake files at once — Increase maxScannedFiles to display more file paths simultaneously in the scanning window
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch creates a fake computer virus prank that mimics the look and feel of a real system security alert. It cycles through four dramatic states: a scanning phase with a progress bar and scrolling file list, a shocking 'VIRUS DETECTED' alert, a 'fixing' phase, and finally a humorous 'SYSTEM DELETED' screen. The prank uses state machines, progress tracking, progress bars, text animation, p5.sound audio synthesis, and interactive buttons to create a convincing (but ultimately harmless) visual gag.

The code is organized around a state variable that controls which drawing function runs each frame, plus helper functions for generating fake file paths and managing audio. By studying this sketch, you will learn how to structure multi-phase animations using state machines, how to map elapsed time to progress percentages, how to layer visual elements like windows and text, and how to use p5.sound oscillators and noise generators to create alarming sound effects.

⚙️ How It Works

  1. When the sketch loads, setup() creates a full-screen canvas, positions a simulated system scanner window in the center, initializes audio oscillators and noise generators, and generates a pool of fake file paths to display. The prankState is set to 'SCANNING' and startTime records when the state began.
  2. Every frame, draw() clears the canvas to black and calls the appropriate drawing function based on the current prankState: drawScanningScreen, drawInfectedScreen, drawFixingScreen, or drawCleanScreen.
  3. In the SCANNING state, elapsed time is mapped to a progress percentage (0–100% over 6 seconds). A progress bar fills red, text displays fake file paths that scroll up as progress increases, and an alarming oscillator tone rises in frequency. When progress reaches 100%, the state switches to INFECTED.
  4. In the INFECTED state, the entire canvas turns red and displays large warning text ('VIRUS DETECTED!') with threat statistics. The oscillator plays a low, loud alarm tone. A red 'FIX NOW' button becomes visible on screen.
  5. When the user clicks 'FIX NOW', the state changes to FIXING. A new progress bar (now green) fills over 4 seconds, displaying a fake 'Virus giver Tool' window. The oscillator stops and a white noise generator whirs. When fixing reaches 100%, the state switches to CLEAN.
  6. In the CLEAN state, the canvas turns green and displays humorous final messages ('SYSTEM DELETED!') with joke text. After 5 seconds, setup() is called to reset the entire prank and loop it again. The windowResized() function ensures the prank window and button stay centered if the browser is resized.

🎓 Concepts You'll Learn

State machinesProgress bars and progress trackingTime-based animationp5.sound oscillators and noiseInteractive buttonsCanvas resizing and centeringText rendering and scrollingConditional drawing based on state

📝 Code Breakdown

setup()

setup() runs once when the sketch starts. It's where you initialize the canvas, position UI elements, set up audio, and prepare data structures. This sketch's setup() is quite rich because it has to coordinate a full-screen canvas, centered windows, interactive buttons, and audio synthesis all at once.

🔬 This loop populates scannedFiles with fake paths. If you change 'maxScannedFiles * 5' to just 'maxScannedFiles', what happens to the scrolling file list in the scanning phase? Will it still feel smooth?

  // Generate some fake file paths for the scan
  for (let i = 0; i < maxScannedFiles * 5; i++) { // Generate more than needed to scroll
    scannedFiles.push(generateFakeFilePath());
  }
function setup() {
  // Create a full-screen canvas
  createCanvas(windowWidth, windowHeight);
  
  // Calculate position for the simulated prank window to be centered on the canvas
  windowX = (width - prankWindowWidth) / 2;
  windowY = (height - prankWindowHeight) / 2;
  progressBarWidth = prankWindowWidth - 60; // Calculate based on prankWindowWidth

  textAlign(LEFT, TOP);
  textSize(16);
  textFont('monospace'); // For a terminal-like feel

  // Initialize audio components
  alarmingOsc = new p5.Oscillator('sine');
  whirringNoise = new p5.Noise('white');
  whirringNoise.amp(0); // Start silent
  
  // Start the audio context (required for p5.sound)
  userStartAudio();

  // Generate some fake file paths for the scan
  for (let i = 0; i < maxScannedFiles * 5; i++) { // Generate more than needed to scroll
    scannedFiles.push(generateFakeFilePath());
  }

  // Initialize the prank state
  prankState = 'SCANNING';
  progress = 0;
  startTime = millis();
  alarmingOsc.start();
  alarmingOsc.amp(0.1);

  // Create and hide prank button
  // Position relative to the *actual* canvas dimensions (full screen)
  fixButton = createButton('FIX NOW');
  fixButton.position(width / 2 - 75, height / 2 + 100);
  fixButton.size(150, 50);
  fixButton.style('background-color', '#FF0000');
  fixButton.style('color', '#FFFFFF');
  fixButton.style('border', 'none');
  fixButton.style('border-radius', '5px');
  fixButton.style('font-size', '24px');
  fixButton.style('font-weight', 'bold');
  fixButton.style('cursor', 'pointer');
  fixButton.hide(); // Hide initially
  fixButton.mousePressed(() => {
    prankState = 'FIXING';
    progress = 0;
    startTime = millis();
    fixButton.hide(); // Hide the button
    alarmingOsc.stop(); // Stop alarming sound
    whirringNoise.start(); // Start whirring sound
    whirringNoise.amp(0.2);
  });
}
Line-by-line explanation (13 lines)

🔧 Subcomponents:

calculation Canvas and Window Positioning windowX = (width - prankWindowWidth) / 2;

Centers the simulated prank window horizontally on the canvas by calculating the offset

calculation Audio Oscillator Initialization alarmingOsc = new p5.Oscillator('sine');

Creates a sine wave oscillator that will play alarming tones throughout the prank

for-loop Fake File Path Generation for (let i = 0; i < maxScannedFiles * 5; i++) { scannedFiles.push(generateFakeFilePath()); }

Populates the scannedFiles array with fake file paths so there are always more files than can display at once, enabling scrolling illusion

conditional Fix Button Click Handler fixButton.mousePressed(() => { prankState = 'FIXING'; ... });

Defines what happens when the user clicks the 'FIX NOW' button—transitions to FIXING state and starts whirring audio

createCanvas(windowWidth, windowHeight);
Creates a canvas that fills the entire browser window, making the prank full-screen and immersive
windowX = (width - prankWindowWidth) / 2;
Calculates the horizontal offset needed to center the prank window on the canvas
windowY = (height - prankWindowHeight) / 2;
Calculates the vertical offset needed to center the prank window on the canvas
textFont('monospace');
Sets the font to monospace (like a terminal or command prompt) to enhance the system-scanner aesthetic
alarmingOsc = new p5.Oscillator('sine');
Creates a sine wave oscillator—this will generate the beeping/alarming tones heard during scanning and infection
whirringNoise = new p5.Noise('white');
Creates a white noise generator that will play the 'whirring' sound during the fixing phase
userStartAudio();
Initializes the Web Audio API—required by p5.sound before any audio can play in the browser
for (let i = 0; i < maxScannedFiles * 5; i++) { scannedFiles.push(generateFakeFilePath()); }
Generates 75 fake file paths (15 * 5) and stores them in the scannedFiles array—more than needed so the list can scroll convincingly
prankState = 'SCANNING';
Sets the initial state to SCANNING, which tells draw() to run drawScanningScreen() each frame
startTime = millis();
Records the current time in milliseconds—used to calculate how much time has elapsed in the current state
alarmingOsc.start();
Starts the oscillator playing—it will now generate sound until alarmingOsc.stop() is called
fixButton = createButton('FIX NOW');
Creates an HTML button element with the text 'FIX NOW'—this is what users can click to 'fix' the fake virus
fixButton.hide();
Hides the button initially so it doesn't appear until the INFECTED state, when it becomes relevant to the prank

draw()

draw() runs once per frame (60 times per second by default in p5.js). Here it acts as a dispatcher: it clears the canvas, then delegates all drawing to whichever function matches the current state. This state machine pattern is fundamental to interactive sketches—it lets you organize complex behavior into discrete, predictable phases.

🔬 This switch statement is the 'state machine' at the heart of the prank. Each case represents a different phase. What if you added a new case, say 'case "PAUSED": drawPausedScreen(); break;', and then changed prankState = 'PAUSED' in one of the drawing functions? How would that break the prank flow?

  switch (prankState) {
    case 'SCANNING':
      drawScanningScreen();
      break;
    case 'INFECTED':
      drawInfectedScreen();
      break;
    case 'FIXING':
      drawFixingScreen();
      break;
    case 'CLEAN':
      drawCleanScreen();
      break;
  }
function draw() {
  background(0); // Black background for a system console feel
  fill(255); // White text/shapes by default

  switch (prankState) {
    case 'SCANNING':
      drawScanningScreen();
      break;
    case 'INFECTED':
      drawInfectedScreen();
      break;
    case 'FIXING':
      drawFixingScreen();
      break;
    case 'CLEAN':
      drawCleanScreen();
      break;
  }
}
Line-by-line explanation (3 lines)

🔧 Subcomponents:

switch-case State Machine Switch switch (prankState) { case 'SCANNING': ... break; ... }

Routes execution to the correct drawing function based on the current prankState—this is the core of the state machine pattern

background(0);
Clears the canvas to black (RGB 0,0,0) each frame, erasing the previous frame's content and creating a fresh surface for new drawing
fill(255);
Sets the default fill color to white (RGB 255,255,255)—any shapes drawn after this will be white unless fill() is called again
switch (prankState) { case 'SCANNING': drawScanningScreen(); break; ... }
Checks the current prankState and calls the matching drawing function—this state machine pattern keeps code organized and makes state transitions simple

drawScanningScreen()

drawScanningScreen() is where the prank's first act unfolds. It demonstrates time-based animation (converting elapsed time to progress), progress bar rendering, scrolling text lists, dynamic sound pitch, and state transitions. The key learning here is how map() transforms time into visual progress, and how a blinking cursor adds faux-terminal authenticity.

🔬 This loop creates the scrolling file list. The '20' in '(i * 20)' and '(scrollOffset % 20)' controls the vertical spacing. What happens if you change it to 30 or 40? How does the scroll speed or file spacing change?

  // Display scanned files
  let scrollOffset = map(progress, 0, 100, 0, scannedFiles.length * 20 - (prankWindowHeight - 170));
  for (let i = 0; i < maxScannedFiles; i++) {
    let fileIndex = floor((scrollOffset + i * 20) / 20) % scannedFiles.length;
    let yPos = windowY + 130 + (i * 20) - (scrollOffset % 20);
    if (yPos > windowY + 130 && yPos < windowY + prankWindowHeight - 40) {
      text('Scanning: ' + scannedFiles[fileIndex], windowX + 30, yPos);
    }
  }
function drawScanningScreen() {
  // Update progress
  let elapsedTime = millis() - startTime;
  progress = map(elapsedTime, 0, scanDuration, 0, 100);
  progress = constrain(progress, 0, 100);

  // Window frame
  noFill();
  stroke(255);
  rect(windowX, windowY, prankWindowWidth, prankWindowHeight);

  // Title bar
  fill(255);
  rect(windowX, windowY, prankWindowWidth, 30);
  fill(0);
  text('System Scanner v3.0', windowX + 10, windowY + 8);

  // Content
  fill(255);
  text('Scanning system for threats...', windowX + 30, windowY + 60);

  // Progress bar background
  noFill();
  stroke(255);
  rect(windowX + 30, windowY + 90, progressBarWidth, progressBarHeight);

  // Progress bar fill
  fill(255, 0, 0); // Red fill for alarming progress
  noStroke();
  let filledWidth = map(progress, 0, 100, 0, progressBarWidth);
  rect(windowX + 30, windowY + 90, filledWidth, progressBarHeight);

  // Progress percentage text
  fill(255);
  text(floor(progress) + '%', windowX + 30 + progressBarWidth + 10, windowY + 95);

  // Display scanned files
  let scrollOffset = map(progress, 0, 100, 0, scannedFiles.length * 20 - (prankWindowHeight - 170));
  for (let i = 0; i < maxScannedFiles; i++) {
    let fileIndex = floor((scrollOffset + i * 20) / 20) % scannedFiles.length;
    let yPos = windowY + 130 + (i * 20) - (scrollOffset % 20);
    if (yPos > windowY + 130 && yPos < windowY + prankWindowHeight - 40) {
      text('Scanning: ' + scannedFiles[fileIndex], windowX + 30, yPos);
    }
  }

  // Blinking cursor
  if (frameCount % 60 < 30) {
    text('_', windowX + 30, windowY + prankWindowHeight - 40);
  }

  // Alarming sound
  let freq = map(progress, 0, 100, 400, 800);
  alarmingOsc.freq(freq);

  // Transition to INFECTED state
  if (progress >= 100) {
    prankState = 'INFECTED';
    startTime = millis();
    progress = 0;
    fixButton.show(); // Show the fix button
  }
}
Line-by-line explanation (15 lines)

🔧 Subcomponents:

calculation Progress Calculation let elapsedTime = millis() - startTime; progress = map(elapsedTime, 0, scanDuration, 0, 100);

Converts elapsed time into a 0–100 percentage, so the progress bar fills smoothly over the scanDuration

calculation Progress Bar Rendering let filledWidth = map(progress, 0, 100, 0, progressBarWidth); rect(windowX + 30, windowY + 90, filledWidth, progressBarHeight);

Maps progress (0–100) to actual pixel width and draws a red rectangle that grows as progress increases

for-loop Scrolling File List for (let i = 0; i < maxScannedFiles; i++) { ... }

Displays a window of fake file paths from the scannedFiles array, scrolling them upward as progress increases

conditional Blinking Cursor Effect if (frameCount % 60 < 30) { text('_', windowX + 30, windowY + prankWindowHeight - 40); }

Draws an underscore cursor that blinks on and off every 30 frames, mimicking an active terminal

conditional State Transition Logic if (progress >= 100) { prankState = 'INFECTED'; ... }

Detects when scanning is complete and switches to the INFECTED state

let elapsedTime = millis() - startTime;
Calculates how many milliseconds have passed since startTime was set—this is the foundation for time-based progress
progress = map(elapsedTime, 0, scanDuration, 0, 100);
Maps elapsed time from the range 0–scanDuration to 0–100, so progress smoothly goes from 0% to 100% as time passes
progress = constrain(progress, 0, 100);
Clamps progress to the range 0–100, preventing it from exceeding 100% if time goes over scanDuration
rect(windowX, windowY, prankWindowWidth, prankWindowHeight);
Draws the outer border of the prank window as a white rectangle outline
fill(255); rect(windowX, windowY, prankWindowWidth, 30);
Draws a white rectangle at the top of the window (the title bar) that is 30 pixels tall
text('System Scanner v3.0', windowX + 10, windowY + 8);
Renders the title text in the title bar, indented 10 pixels from the left and 8 pixels from the top
let filledWidth = map(progress, 0, 100, 0, progressBarWidth);
Maps the progress percentage to a pixel width so the progress bar grows smoothly from 0 to full width
rect(windowX + 30, windowY + 90, filledWidth, progressBarHeight);
Draws a red rectangle that represents the filled portion of the progress bar—its width grows as filledWidth increases
text(floor(progress) + '%', windowX + 30 + progressBarWidth + 10, windowY + 95);
Displays the progress percentage (e.g., '42%') to the right of the progress bar, using floor() to round down to whole numbers
let scrollOffset = map(progress, 0, 100, 0, scannedFiles.length * 20 - (prankWindowHeight - 170));
Maps progress to a vertical scroll position—as progress increases, the file list scrolls upward
let fileIndex = floor((scrollOffset + i * 20) / 20) % scannedFiles.length;
Calculates which file from the scannedFiles array to display at position i, wrapping around using modulo (%) if needed
if (frameCount % 60 < 30) { text('_', windowX + 30, windowY + prankWindowHeight - 40); }
Checks if the frame count modulo 60 is less than 30—this is true for 30 frames, false for the next 30, creating a blinking effect
let freq = map(progress, 0, 100, 400, 800);
Maps progress to a frequency range (400–800 Hz)—as the prank progresses, the alarm tone rises in pitch
alarmingOsc.freq(freq);
Updates the oscillator's frequency to the calculated value, creating a rising-pitch alarm effect
if (progress >= 100) { prankState = 'INFECTED'; startTime = millis(); ... fixButton.show(); }
When scanning completes, switches to INFECTED state, records the new startTime, and shows the FIX NOW button

drawInfectedScreen()

drawInfectedScreen() is the emotional climax of the prank. It abandons the fake terminal aesthetic and floods the screen with red to trigger alarm. It demonstrates how color psychology (red = danger), text size hierarchy (large = important), and loud, low-frequency audio can combine to create a convincing sense of threat. This is where the prank 'feels real' to the user.

function drawInfectedScreen() {
  background(255, 0, 0); // Red background for severe alert
  fill(255);
  textSize(72);
  textAlign(CENTER, CENTER);
  text('VIRUS DETECTED!', width / 2, height / 2 - 50);

  textSize(24);
  text('Your system has been compromised.', width / 2, height / 2 + 30);
  text('Immediate action required to prevent data loss.', width / 2, height / 2 + 60);

  textSize(18);
  text('Threat Level: CRITICAL', width / 2, height / 2 + 200);
  text('Infected Files: 1,482', width / 2, height / 2 + 225);

  // Alarming tone
  alarmingOsc.freq(100);
  alarmingOsc.amp(0.3); // Louder for infected state
}
Line-by-line explanation (8 lines)

🔧 Subcomponents:

calculation Red Alert Background background(255, 0, 0);

Sets the entire canvas to red, creating a sense of urgency and danger

calculation Multi-size Text Rendering textSize(72); text('VIRUS DETECTED!', width / 2, height / 2 - 50);

Renders large centered text that dominates the screen, emphasizing the threat

background(255, 0, 0);
Fills the entire canvas with red (RGB 255,0,0), creating an alarm-like visual effect
textAlign(CENTER, CENTER);
Centers all text both horizontally and vertically around the coordinates provided to text()
textSize(72);
Sets the font size to 72 pixels for the main warning text—very large and attention-grabbing
text('VIRUS DETECTED!', width / 2, height / 2 - 50);
Renders the main warning in huge white text, centered horizontally and slightly above the vertical center
textSize(24);
Reduces font size to 24 pixels for the secondary warning messages
text('Your system has been compromised.', width / 2, height / 2 + 30);
Renders a secondary message below the main warning, positioned relative to the screen center
textSize(18);
Further reduces font size to 18 pixels for the threat statistics
alarmingOsc.freq(100); alarmingOsc.amp(0.3);
Sets the oscillator to a very low frequency (100 Hz) and increases amplitude to 0.3—a loud, low, menacing tone

drawFixingScreen()

drawFixingScreen() mirrors drawScanningScreen() structurally but inverts the emotional tone: the progress bar turns green (reassuring), the message says 'adding threats' (absurd), and the audio shifts from high-pitched beeping to pulsing white noise. This demonstrates how the same layout can tell very different stories through color, text, and sound. The dynamic amplitude (using sin) teaches how oscillating values can create animation without position changes.

🔬 This line uses sin() to pulse the amplitude. The '0.1' controls the speed of oscillation. What happens if you change 'frameCount * 0.1' to 'frameCount * 0.01' or 'frameCount * 0.5'? Does the whirring speed up or slow down?

  // Whirring sound
  whirringNoise.amp(map(sin(frameCount * 0.1), -1, 1, 0.1, 0.3)); // Vary amplitude for whirring effect
function drawFixingScreen() {
  // Update progress
  let elapsedTime = millis() - startTime;
  progress = map(elapsedTime, 0, fixDuration, 0, 100);
  progress = constrain(progress, 0, 100);

  // Window frame
  noFill();
  stroke(255);
  rect(windowX, windowY, prankWindowWidth, prankWindowHeight);

  // Title bar
  fill(255);
  rect(windowX, windowY, prankWindowWidth, 30);
  fill(0);
  text('Virus giver Tool', windowX + 10, windowY + 8);

  // Content
  fill(255);
  text('adding threats...', windowX + 30, windowY + 60);

  // Progress bar background
  noFill();
  stroke(255);
  rect(windowX + 30, windowY + 90, progressBarWidth, progressBarHeight);

  // Progress bar fill
  fill(0, 255, 0); // Green fill for fixing progress
  noStroke();
  let filledWidth = map(progress, 0, 100, 0, progressBarWidth);
  rect(windowX + 30, windowY + 90, filledWidth, progressBarHeight);

  // Progress percentage text
  fill(255);
  text(floor(progress) + '%', windowX + 30 + progressBarWidth + 10, windowY + 95);

  // Display threats removed
  let threatsRemoved = floor(map(progress, 0, 100, 0, 1482));
  text('adding more threats: ' + threatsRemoved, windowX + 30, windowY + 130);

  // Whirring sound
  whirringNoise.amp(map(sin(frameCount * 0.1), -1, 1, 0.1, 0.3)); // Vary amplitude for whirring effect

  // Transition to CLEAN state
  if (progress >= 100) {
    prankState = 'CLEAN';
    startTime = millis();
    progress = 0;
    whirringNoise.stop(); // Stop whirring sound
  }
}
Line-by-line explanation (10 lines)

🔧 Subcomponents:

calculation Green Progress Bar fill(0, 255, 0); let filledWidth = map(progress, 0, 100, 0, progressBarWidth); rect(windowX + 30, windowY + 90, filledWidth, progressBarHeight);

Draws a green progress bar that grows as the 'fixing' process advances, inverting the red alarm of the infected state

calculation Dynamic Whirring Audio whirringNoise.amp(map(sin(frameCount * 0.1), -1, 1, 0.1, 0.3));

Modulates the noise generator's amplitude using a sine wave, creating a pulsing 'whirring' effect that sounds like active processing

conditional Completion Transition if (progress >= 100) { prankState = 'CLEAN'; ... }

Detects when fixing is complete and transitions to the final CLEAN state

let elapsedTime = millis() - startTime;
Calculates elapsed time since the FIXING state started—same pattern as drawScanningScreen()
progress = map(elapsedTime, 0, fixDuration, 0, 100);
Maps elapsed time to a 0–100 progress percentage, filling the progress bar smoothly over fixDuration milliseconds
text('Virus giver Tool', windowX + 10, windowY + 8);
Displays a fake tool name in the title bar—the tone is darker and more technical than 'System Scanner'
text('adding threats...', windowX + 30, windowY + 60);
Renders a message that the tool is 'adding threats'—a humorous twist that reveals the prank's absurdity
fill(0, 255, 0);
Sets the fill color to green (RGB 0,255,0), creating a sense of safety and progress after the red alert
let threatsRemoved = floor(map(progress, 0, 100, 0, 1482));
Maps progress to a threat count (0–1482), creating a sense that something is being processed and counted
text('adding more threats: ' + threatsRemoved, windowX + 30, windowY + 130);
Displays the threat count alongside 'adding more threats'—an ironic message that adds to the prank's humor
whirringNoise.amp(map(sin(frameCount * 0.1), -1, 1, 0.1, 0.3));
Uses sin(frameCount * 0.1) to generate a smoothly oscillating value from -1 to 1, then maps it to amplitude 0.1–0.3, creating a pulsing whirr
whirringNoise.stop();
Stops the noise generator when fixing is complete, creating silence before the final screen
if (progress >= 100) { prankState = 'CLEAN'; startTime = millis(); ... }
Triggers the state transition to CLEAN when progress reaches 100%, resetting startTime for the final phase

drawCleanScreen()

drawCleanScreen() is the punchline. It completes the prank narrative with ironic color (green for 'clean'), humorous text messages, and automatic reset. The automatic reset using millis() demonstrates how to create time-triggered events without state variables—a useful pattern for timed UI transitions. Notice that calling setup() from within draw() reinitializes everything, allowing the prank to loop infinitely.

function drawCleanScreen() {
  background(0, 255, 0); // Green background for clean system
  fill(0);
  textSize(72);
  textAlign(CENTER, CENTER);
  text('SYSTEM DELETED!', width / 2, height / 2 - 50);

  textSize(24);
  text('Your system is now broken.', width / 2, height / 2 + 30);
  text('Just deystroy your chromebook', width / 2, height / 2 + 60);
  text('Hope you had a good vpn!', width / 2, height / 2 + 90);

  // No specific sound for clean state, or a celebratory chime could be added
  alarmingOsc.stop();
  whirringNoise.stop();

  // Automatically reset the prank after cleanScreenDuration
  if (millis() - startTime >= cleanScreenDuration) {
    setup(); // Reset for another prank!
  }
}
Line-by-line explanation (9 lines)

🔧 Subcomponents:

calculation Green Victory Background background(0, 255, 0);

Fills the canvas with green to signal 'success'—ironic given the message

conditional Automatic Reset Timer if (millis() - startTime >= cleanScreenDuration) { setup(); }

After displaying the final screen for 5 seconds, automatically calls setup() to restart the entire prank loop

background(0, 255, 0);
Sets the background to bright green (RGB 0,255,0)—a color typically associated with success, creating irony with the 'SYSTEM DELETED' message
fill(0);
Sets text color to black (RGB 0,0,0) so it contrasts with the green background and remains readable
textSize(72);
Uses the largest font size for the main message, keeping it visually prominent like the INFECTED state
text('SYSTEM DELETED!', width / 2, height / 2 - 50);
Renders the punchline: 'SYSTEM DELETED!'—a humorous conclusion to the prank
text('Your system is now broken.', width / 2, height / 2 + 30);
Follows with a secondary joke message, adding to the absurdist humor of the prank
text('Just deystroy your chromebook', width / 2, height / 2 + 60);
Continues the humorous escalation (note the intentional typo 'deystroy'—part of the prank's comedic style)
text('Hope you had a good vpn!', width / 2, height / 2 + 90);
Ends with a joke about VPNs, hinting that the prank is meant to be taken lightly
alarmingOsc.stop(); whirringNoise.stop();
Stops both audio generators, creating silence and signaling the prank is over
if (millis() - startTime >= cleanScreenDuration) { setup(); }
After cleanScreenDuration milliseconds (5 seconds), calls setup() to reset the entire sketch and restart the prank loop

generateFakeFilePath()

generateFakeFilePath() is a helper function that builds randomized Windows-style file paths. It demonstrates how arrays, loops, conditionals, and string concatenation work together to create convincing fake data. By varying drive letters, folder names, depths, and extensions, every generated path looks legitimate but is entirely synthetic—a key technique in pranks, games, and procedural generation.

🔬 This loop adds 1–3 random folders to the path. The random([1, 2, 3]) picks a number from that array. What happens if you change it to random([5, 6, 7]) or even [1, 1, 1])? How does that change the believability of the fake file paths?

  for (let i = 0; i < random([1, 2, 3]); i++) {
    path += random(folders) + '\\';
  }
function generateFakeFilePath() {
  const drives = ['C:', 'D:', 'E:'];
  const folders = ['Windows', 'System32', 'Program Files', 'Users', 'AppData', 'Documents', 'Downloads'];
  const subfolders = ['temp', 'cache', 'logs', 'fonts', 'drivers', 'updates'];
  const extensions = ['.dll', '.exe', '.sys', '.tmp', '.log', '.dat', '.ini', '.js', '.css', '.html', '.txt', '.zip', '.rar'];

  let path = random(drives) + '\\';
  for (let i = 0; i < random([1, 2, 3]); i++) {
    path += random(folders) + '\\';
  }
  if (random() > 0.5) { // Add subfolder sometimes
    path += random(subfolders) + '\\';
  }
  path += randomWord() + random(extensions);
  return path;
}
Line-by-line explanation (9 lines)

🔧 Subcomponents:

for-loop Folder Path Loop for (let i = 0; i < random([1, 2, 3]); i++) { path += random(folders) + '\\'; }

Randomly adds 1–3 folder names to the path, creating varied directory structures

conditional Optional Subfolder if (random() > 0.5) { path += random(subfolders) + '\\'; }

Randomly (50% chance) adds a subfolder, increasing path variety and believability

const drives = ['C:', 'D:', 'E:'];
Defines an array of drive letters—the 'hard drives' or partitions that fake file paths will reference
const folders = ['Windows', 'System32', ...];
An array of realistic Windows system folder names that will be randomly inserted into paths
const subfolders = ['temp', 'cache', ...];
An array of subfolder names (temp files, caches, logs) that add depth and realism to paths
const extensions = ['.dll', '.exe', ...];
An array of file extensions—the suffixes that identify file types, adding authenticity to fake file names
let path = random(drives) + '\\';
Starts the path with a random drive (e.g., 'C:') and appends a backslash—the Windows path separator
for (let i = 0; i < random([1, 2, 3]); i++) { path += random(folders) + '\\'; }
Adds 1–3 randomly chosen folders to the path, building a directory structure
if (random() > 0.5) { path += random(subfolders) + '\\'; }
Flips a coin (50% chance)—if true, adds one more subfolder for extra complexity
path += randomWord() + random(extensions);
Completes the path by appending a random filename (via randomWord()) and a random file extension
return path;
Returns the completed fake file path as a string, e.g., 'C:\\Windows\\System32\\temp\\abc123.dll'

randomWord()

randomWord() is a simple but powerful utility that demonstrates how to build randomized strings. It uses a pool of allowed characters (chars) and the random() function to generate syntactically valid—but completely artificial—filenames. This pattern is fundamental to procedural content generation: you define rules (letters, digits, 4–10 chars) and let randomness fill in the details, creating infinite variety with minimal code.

🔬 This loop creates a word 4–10 characters long. What if you change [4, 5, 6, 7, 8, 9, 10] to [2, 2, 2] to always make very short filenames, or [15, 20, 25] for very long ones? How does that affect the realism of the scanning phase?

  for (let i = 0; i < random([4, 5, 6, 7, 8, 9, 10]); i++) {
    word += random(chars);
  }
function randomWord() {
  const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
  let word = '';
  for (let i = 0; i < random([4, 5, 6, 7, 8, 9, 10]); i++) {
    word += random(chars);
  }
  return word;
}
Line-by-line explanation (5 lines)

🔧 Subcomponents:

for-loop Random Character Loop for (let i = 0; i < random([4, 5, 6, 7, 8, 9, 10]); i++) { word += random(chars); }

Generates a random string of 4–10 characters by repeatedly picking random letters and digits

const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
Defines a string of all lowercase letters and digits—the pool of characters that will be randomly selected
let word = '';
Initializes an empty string that will accumulate random characters
for (let i = 0; i < random([4, 5, 6, 7, 8, 9, 10]); i++) {
Loops 4–10 times (chosen randomly), building up the word character by character
word += random(chars);
Appends one random character from the chars string to the word, concatenating it to the end
return word;
Returns the completed random string, e.g., 'abc123d' or 'x9y2z'

windowResized()

windowResized() is a built-in p5.js function that automatically runs whenever the browser window is resized. Here it keeps both the prank window and the button centered, ensuring the prank remains visually correct on screens of any size. This is crucial for full-screen sketches—without windowResized(), elements would become misaligned on resize.

function windowResized() {
  // Resize the canvas to fill the new window dimensions
  resizeCanvas(windowWidth, windowHeight);
  
  // Recalculate position for the simulated prank window to be centered
  windowX = (width - prankWindowWidth) / 2;
  windowY = (height - prankWindowHeight) / 2;
  
  // Re-center fixButton on the full-screen canvas
  fixButton.position(width / 2 - 75, height / 2 + 100);
}
Line-by-line explanation (4 lines)

🔧 Subcomponents:

calculation Canvas Resize resizeCanvas(windowWidth, windowHeight);

Resizes the p5.js canvas to match the new browser window size

calculation Window Reposition windowX = (width - prankWindowWidth) / 2; windowY = (height - prankWindowHeight) / 2;

Recalculates the prank window's position so it stays centered after the browser is resized

resizeCanvas(windowWidth, windowHeight);
Rebuilds the p5.js canvas to fill the new window dimensions, called automatically by p5.js when the browser is resized
windowX = (width - prankWindowWidth) / 2;
Recalculates the horizontal center position for the prank window based on the new canvas width
windowY = (height - prankWindowHeight) / 2;
Recalculates the vertical center position for the prank window based on the new canvas height
fixButton.position(width / 2 - 75, height / 2 + 100);
Moves the 'FIX NOW' button to the new centered position relative to the resized canvas

📦 Key Variables

prankState string

Tracks the current phase of the prank ('SCANNING', 'INFECTED', 'FIXING', or 'CLEAN')—controls which drawing function runs each frame

let prankState = 'SCANNING';
progress number

Stores the current progress percentage (0–100) for the active state's progress bar—updated each frame based on elapsed time

let progress = 0;
startTime number

Records the time (in milliseconds, via millis()) when the current state began—used to calculate elapsed time and progress

let startTime;
scanDuration number

The duration in milliseconds for the SCANNING phase (6000 = 6 seconds)—controls how long the progress bar takes to fill

const scanDuration = 6000;
fixDuration number

The duration in milliseconds for the FIXING phase (4000 = 4 seconds)—controls how long the 'fixing' progress bar takes

const fixDuration = 4000;
cleanScreenDuration number

The duration in milliseconds to display the CLEAN screen before auto-resetting (5000 = 5 seconds)

const cleanScreenDuration = 5000;
windowX number

The horizontal position (x-coordinate) of the top-left corner of the simulated prank window on the canvas

let windowX;
windowY number

The vertical position (y-coordinate) of the top-left corner of the simulated prank window on the canvas

let windowY;
prankWindowWidth number

The fixed width in pixels of the simulated prank window (600 pixels)—remains constant throughout the sketch

const prankWindowWidth = 600;
prankWindowHeight number

The fixed height in pixels of the simulated prank window (400 pixels)—remains constant throughout the sketch

const prankWindowHeight = 400;
progressBarWidth number

The calculated width of the progress bar inside the window (prankWindowWidth - 60)—used for drawing and progress mapping

let progressBarWidth;
progressBarHeight number

The fixed height of the progress bar (20 pixels)—a visual parameter for the progress bar rectangles

const progressBarHeight = 20;
scannedFiles array

An array of generated fake file paths that are displayed and scrolled during the SCANNING phase—populated in setup()

let scannedFiles = [];
maxScannedFiles number

The number of fake files displayed simultaneously on screen (15)—controls how many lines appear in the scanning window at once

const maxScannedFiles = 15;
alarmingOsc p5.Oscillator

A sine wave oscillator from p5.sound that generates the alarming beep/tone during SCANNING and INFECTED phases

let alarmingOsc;
whirringNoise p5.Noise

A white noise generator from p5.sound that plays the 'whirring' sound during the FIXING phase

let whirringNoise;
fixButton p5.Renderer (HTML button)

An HTML button element created with createButton() that users click to transition from INFECTED to FIXING state

let fixButton;

🔧 Potential Improvements (6)

Here are some ways this code could be enhanced:

BUG drawInfectedScreen() and drawFixingScreen() font/text alignment

textAlign() is set to CENTER/CENTER in drawScanningScreen() but other drawing functions don't explicitly reset it, leading to inconsistent alignment if drawing order changes

💡 Call textAlign(LEFT, TOP) at the start of drawScanningScreen() to explicitly set alignment, ensuring consistency across all draw functions

PERFORMANCE setup() - scannedFiles generation loop

Generates maxScannedFiles * 5 (75) fake file paths every time setup() is called, including during auto-reset in drawCleanScreen()—this recalculation is unnecessary if the prank loops

💡 Move the scannedFiles generation outside of setup() into a separate initialization function, or check if scannedFiles is already populated before regenerating

STYLE generateFakeFilePath() and randomWord()

Both functions use random([array]) which is correct but less idiomatic than random() with a single argument when possible

💡 For randomWord() length, consider using floor(random(4, 11)) instead of random([4, 5, 6, 7, 8, 9, 10]) for cleaner code

BUG windowResized()

Button repositioning assumes fixButton is defined, but windowResized() could theoretically be called before setup() completes

💡 Add a null check before repositioning: if (fixButton) { fixButton.position(...); } to prevent errors

FEATURE drawCleanScreen()

The auto-reset (calling setup()) is abrupt and may surprise users who want to read the joke message longer or take a screenshot

💡 Consider disabling auto-reset or adding a user-clickable area to trigger reset manually, giving users control over when the prank loops

STYLE drawScanningScreen() and drawFixingScreen()

Progress bar background is drawn twice (once with noFill, once with fill), which is redundant

💡 Combine into a single draw call: draw the outline with stroke, then draw the filled portion on top to reduce drawing calls

🔄 Code Flow

Code flow showing setup, draw, drawscanningscreen, drawinfectedscreen, drawfixingscreen, drawcleanscreen, generatefakefilepath, randomword, windowresized

💡 Click on function names in the diagram to jump to their code

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] click setup href "#fn-setup" click draw href "#fn-draw" draw --> state-switch[State Machine Switch] state-switch -->|scanning| drawscanningscreen[drawScanningScreen] state-switch -->|infected| drawinfectedscreen[drawInfectedScreen] state-switch -->|fixing| drawfixingscreen[drawFixingScreen] state-switch -->|clean| drawcleanscreen[drawCleanScreen] click drawscanningscreen href "#fn-drawscanningscreen" click drawinfectedscreen href "#fn-drawinfectedscreen" click drawfixingscreen href "#fn-drawfixingscreen" click drawcleanscreen href "#fn-drawcleanscreen" drawscanningscreen --> progress-calc[Progress Calculation] progress-calc --> progress-bar-draw[Progress Bar Rendering] progress-bar-draw --> file-scroll[Scrolling File List] file-scroll --> blinking-cursor[Blinking Cursor Effect] blinking-cursor --> state-transition[State Transition Logic] state-transition -->|complete| state-switch click progress-calc href "#sub-progress-calc" click progress-bar-draw href "#sub-progress-bar-draw" click file-scroll href "#sub-file-scroll" click blinking-cursor href "#sub-blinking-cursor" click state-transition href "#sub-state-transition" drawinfectedscreen --> red-background[Red Alert Background] red-background --> text-rendering[Multi-size Text Rendering] click red-background href "#sub-red-background" click text-rendering href "#sub-text-rendering" drawfixingscreen --> green-progress[Green Progress Bar] green-progress --> whirring-sound[Dynamic Whirring Audio] whirring-sound --> fix-completion[Completion Transition] fix-completion -->|complete| state-switch click green-progress href "#sub-green-progress" click whirring-sound href "#sub-whirring-sound" click fix-completion href "#sub-fix-completion" drawcleanscreen --> green-bg[Green Victory Background] green-bg --> auto-reset[Automatic Reset Timer] auto-reset --> setup click green-bg href "#sub-green-bg" click auto-reset href "#sub-auto-reset" setup --> canvas-setup[Canvas and Window Positioning] setup --> audio-init[Audio Oscillator Initialization] click canvas-setup href "#sub-canvas-setup" click audio-init href "#sub-audio-init" generatefakefilepath --> file-generation[Fake File Path Generation] file-generation --> path-assembly[Folder Path Loop] path-assembly --> conditional-subfolder[Optional Subfolder] conditional-subfolder --> char-loop[Random Character Loop] click file-generation href "#sub-file-generation" click path-assembly href "#sub-path-assembly" click conditional-subfolder href "#sub-conditional-subfolder" click char-loop href "#sub-char-loop" windowresized --> canvas-resize[Canvas Resize] canvas-resize --> window-recenter[Window Reposition] click canvas-resize href "#sub-canvas-resize" click window-recenter href "#sub-window-recenter"

❓ Frequently Asked Questions

What visual elements does the computer virus prank sketch feature?

The sketch creates a simulated prank window that resembles a scanning interface, complete with a progress bar and fake file paths to enhance the illusion of a computer virus.

How can users interact with the computer virus prank sketch?

Users can observe the progress of the scanning and fixing phases, but there are no interactive elements allowing them to directly influence the outcome.

What creative coding techniques are showcased in this p5.js sketch?

The sketch demonstrates the use of timed state changes, audio elements for immersive effects, and visual design to simulate a realistic computer virus experience.

Preview

computer virus (prank) - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of computer virus (prank) - Code flow showing setup, draw, drawscanningscreen, drawinfectedscreen, drawfixingscreen, drawcleanscreen, generatefakefilepath, randomword, windowresized
Code Flow Diagram