An interactive quiz that determins your moral philosophy profile // ICM W3

Sep 23, 2024

The Idea

Last week in ICM, we explored conditionals like if, else, &&, and ||. I wanted to use these concepts to create a project that not only incorporates them but also allows the choices made to yield different outcomes based on how users interact with them.

This was the assignment : CREATE • CHOICES

  • Two roads diverged in a yellow wood, And sorry I could not travel both... Life is full of difficult choices, use conditional statements to control the flow of your programs. Create a sketch that asks people to make difficult choices that have surprising consequences.

  • Which choices are easier, harder? Which choices are false choices?

  • What internal or external factors influence the choice? How do others’ choices affect your choices?

  • What choices surprise you with unexpected outcomes?

Can you combine choices to create hard-to-predict results? (Hint: Use && and ||

Concept Overview

I had an idea for an interactive game that would present players with ethical dilemmas and determine their moral philosophy profile based on their choices. I started by sketching out the idea on paper and then some wireframes in Figma to visualize the flow more coherently.

The concept was to have multiple screens: an intro, three ethical dilemmas, and then a result screen showing the player's ethical profile, based on the choices they made.

Initially, I wanted to include tons of features and complex interactions, but I quickly realized I needed to simplify things to actually get a working prototype.

here are the main differences between the first and second iterations and why I made those changes.

First design:

  • Each screen had unique components and interactions based around buttons.

  • Required programming mouse X and Y coordinates.

  • Used the "mouseIsPressed" function to check if the mouse was within the button's area.

  • Made the code more complex and harder to manage.

Second design:

  • Simplified by removing the need for mouse interaction.

  • Switched to the "keyPressed" function to check for specific key inputs.

  • Created a uniform layout across all screens to maintain consistency.

  • Nested everything into objects and functions, streamlining the code structure.

  • Allowed for a functioning prototype without focusing on intricate details.

Foundations

Initially, I approached the project by creating individual functions for each screen. This seemed logical at first, but it quickly became apparent that it wasn't the most efficient method.

function draw() {
  background("#222222");
  
  // Title
  textAlign(CENTER);
  textSize(30);
  fill('#EE8838');
  text("what's your moral profile?", 300, 80);
  
  // Welcome text
  textAlign(LEFT);
  textSize(22);
  fill('#577455');
  text("Welcome! This interactive experience will...", 30, 140, 540, 200);
  
  // Begin button
  noStroke();
  fill('#EE8838');
  rect(225, 460, 150, 50, 10);
  fill(255);
  textAlign(CENTER, CENTER);
  textSize(24);
  text("Begin", 300, 485);
}

This approach worked for a single screen, but as I added more screens and complexity, I realized I needed a more scalable solution, as handling multiple screens and sorting through the code to insert conditional logic became quite hectic.

Embracing Objects and Modularity

After discussing my project with Nikolai, I learned about using objects to structure my code more effectively. This was a game-changer! I created objects for each screen, containing all the necessary text and instructions:

const screen_1 = {
 // title text key and string as its value , 
  tt: "What's your moral profile?",
 // Body text 
  bt: "Welcome! This interactive experience will...",
 // Body text 
  ii: "Press [Spacebar] to Continue"
};

Then, I created corresponding draw functions for each screen:

function drawScreen1() {
  textAlign(LEFT);
  textSize(36);
  fill('#EE8838');
  text(screen_1.tt, 30, 80);
  // ... more drawing code ...
}

Implementing Game Logic

In the game, each choice the player makes influences their ethical profile by adjusting the U, V, and D scores. Here's how the scoring system works for each dilemma:

Dilemma 1: The Trolley Problem

  • Option A (Do nothing): Increases D, decreases U

  • Option B (Push the stranger): Increases U, decreases V

This dilemma pits utilitarian thinking (saving more lives) against deontological ethics (not using someone as a means) and virtue ethics (the character implications of choosing to end a life directly).

Dilemma 2: The White Lie

  • Option A (Tell the truth): Increases D, decreases V

  • Option B (Lie to protect feelings): Increases V, decreases D

This scenario explores the tension between deontological ethics (the duty to tell the truth) and virtue ethics (being kind and considerate).

Dilemma 3: Corporate Whistleblowing

  • Option A (Report illegal activities): Increases U and V

  • Option B (Stay silent): Decreases D and V

This dilemma balances utilitarian ethics (preventing environmental damage) against potential negative consequences (job losses). It also involves virtue ethics (integrity and courage) and touches on deontological considerations (the duty to obey the law and prevent harm).

Here's a pseudocode representation of this scoring system:

FUNCTION updateScores(dilemma, choice):
    IF dilemma is 1 (Trolley Problem):
        IF choice is A:
            Increase D by 1
            Decrease U by 1
        ELSE IF choice is B:
            Increase U by 1
            Decrease V by 1
    
    ELSE IF dilemma is 2 (White Lie):
        IF choice is A:
            Increase D by 1
            Decrease V by 1
        ELSE IF choice is B:
            Increase V by 1
            Decrease D by 1
    
    ELSE IF dilemma is 3 (Whistleblowing):
        IF choice is A:
            Increase U by 1
            Increase V by 1
        ELSE IF choice is B:
            Decrease D by 1
            Decrease V by 1

    Ensure U, V, and D stay within range -3 to 3

This scoring system allows for nuanced ethical profiles based on the player's choices. For example:

  • A player who consistently chooses utilitarian options will end up with a high U score and lower V and D scores.

  • A player who balances different ethical considerations might have more even scores across U, V, and D.

  • A player who strongly adheres to one ethical framework while rejecting others might end up with one very high score and two low scores.

Implementing this scoring system required careful consideration of how each choice reflects different ethical frameworks. It also involved deciding how much each choice should impact the scores and ensuring that the final scores would lead to meaningful and varied ethical profiles.

In the actual code, this logic was implemented within the keyPressed() function:

function keyPressed() {
    if (state === 1) { // Trolley Problem
        if (key === 'A' || key === 'a') {
            d++;
            u--;
        } else if (key === 'B' || key === 'b') {
            u++;
            v--;
        }
        state = 2; // Move to next dilemma
    }
    // Similar logic for other dilemmas...

    // Ensure scores stay within range
    u = constrain(u, -3, 3);
    v = constrain(v, -3, 3);
    d = constrain(d, -3, 3);
}

This scoring system forms the foundation of the game's ethical profiling mechanism. It translates the player's choices into a quantifiable ethical stance, which is then used by the profile() function to determine the player's final ethical profile. This was the most challenging part, creating the profile() function to determine the player's ethical profile based on their scores. This required careful consideration of how to define each ethical profile. I did use Claude to help me with the math because I was not able to do the math logic myself but it gave me something that looked like this.

which when translated to code looked something like this -

function profile(u, v, d) {
  // Principled Utilitarian
  if (u >= 2 && u > d && u > v && d >= 0 && v >= 0) {
    drawScreen5();
  }
  // Compassionate Deontologist
  else if (d >= 2 && d > u && d > v && u >= 0 && v >= 0) {
    drawScreen6();
  }
  // Pragmatic Virtue Ethicist
  else if (v >= 2 && v > u && v > d && u >= 0 && d >= 0) {
    drawScreen7();
  }
  // Balanced Ethicist
  else if (u > 0 && v > 0 && d > 0 && abs(u - v) <= 1 && abs(u - d) <= 1 && abs(v - d) <= 1) {
    drawScreen8();
  }
  // Consequentialist Extremist
  else if (u >= 2 && v <= 0 && d <= 0) {
    drawScreen9();
  }
  // Rule Absolutist
  else if (d >= 2 && u <= 0 && v <= 0) {
    drawScreen10();
  }
  // Virtue Purist
  else if (v >= 2 && u <= 0 && d <= 0) {
    drawScreen11();
  }
  // Default case if no specific profile is matched
  else {
    let maxScore = max(u, v, d);
    if (maxScore == u) {
      drawScreen5(); // Default to Principled Utilitarian
    } else if (maxScore == v) {
      drawScreen7(); // Default to Pragmatic Virtue Ethicist
    } else {
      drawScreen6(); // Default to Compassionate Deontologist
    }
  }
}

The Final Product

After a lot of trial and error(and endlessly bothering Nikolai to sit thorough my pseudo code logic) I finally got everything working! Here's the Pseudo code and the complete code:

Final Quiz!

Initialize state, u, v, d

FUNCTION draw:
    Clear background
    IF state is 0-3:
        Draw respective screen (intro or dilemmas)
    ELSE IF state is 4:
        Calculate and display ethical profile

FUNCTION keyPressed:
    IF on intro screen:
        Move to first dilemma
    ELSE IF on dilemma screen:
        Update u, v, d based on choice
        Move to next screen
    ELSE IF on final screen:
        Reset game

FUNCTION profile(u, v, d):
    Determine ethical profile based on u, v, d scores
    Display corresponding profile screen

FUNCTION resetGame:
    Reset state, u, v, d to initial values
let v = 0,
  d = 0,
  u = 0;
let state = 0;

const screen_1 = {
  tt: "What is your moral profile?",
  bt:
    "Welcome! This interactive experience will present you with three challenging ethical scenarios. Your choices will help determine your moral philosophy profile. The profiles will be based on the following:\n\n1. Utilitarianism    2. Deontology    3. Virtue Ethics",
  ii: "Press [Spacebar] to Continue",
};

const screen_2 = {
  tt: "The Trolley Problem",
  bt:
    "A trolley is headed toward five people. You can push a stranger off a bridge to stop the trolley, killing the stranger but saving the five. What do you do?\n\nA) Do nothing, letting the trolley kill the five people.\nB) Push the stranger, killing them but saving the five.",
  ii: "Press [A] or [B] on your Keyboard",
};

const screen_3 = {
  tt: "The White Lie",
  bt:
    "Your friend asks if you liked their novel, which you found disappointing. What do you say?\n\nA) Tell the truth: You didn't enjoy it, but offer feedback.\nB) Lie to protect their feelings: Say you liked it.",
  ii: "Press [A] or [B] on your Keyboard",
};

const screen_4 = {
  tt: "Corporate Whistleblowing",
  bt:
    "You discover your company is illegally dumping toxic waste. Reporting it may destroy the company and cause job loss, but staying silent allows environmental harm. What do you do?\n\nA) Report the illegal activities.\nB) Stay silent to protect jobs.",
  ii: "Press [A] or [B] on your Keyboard",
};

const screen_5 = {
  tt: "Principled Utilitarian",
  bt:
    "You're a Principled Utilitarian. You believe in maximizing the greatest good for the greatest number of people, but you also respect certain moral rules. You're willing to make tough choices for the benefit of many, but you have lines you won't cross.\n\n(High Utilitarian, Medium Deontological)",
  ii: "Press [Spacebar] to Restart",
};

const screen_6 = {
  tt: "Compassionate Deontologist",
  bt:
    "You're a Compassionate Deontologist. You have a strong sense of duty and believe in following moral rules, but you also value character and virtue. You strive to do what's right according to universal principles, while also considering the impact on people's well-being.\n\n(High Deontological, Medium Virtue Ethics)",
  ii: "Press [Spacebar] to Restart",
};

const screen_7 = {
  tt: "Pragmatic Virtue Ethicist",
  bt:
    "You're a Pragmatic Virtue Ethicist. You prioritize being a good person and cultivating virtuous character traits, but you're also practical about achieving positive outcomes. You believe that good intentions and good consequences often go hand in hand.\n\n(High Virtue Ethics, Medium Utilitarian)",
  ii: "Press [Spacebar] to Restart",
};

const screen_8 = {
  tt: "Balanced Ethicist",
  bt:
    "You're a Balanced Ethicist. You show a nuanced understanding of ethical decision-making, drawing from multiple ethical frameworks. You consider rules, consequences, and character equally, adapting your approach based on the specific situation.\n\n(Relatively Equal Scores)",
  ii: "Press [Spacebar] to Restart",
};

const screen_9 = {
  tt: "Consequentialist Extremist",
  bt:
    "You're a Consequentialist Extremist. You strongly believe that the ends justify the means and are willing to make difficult choices if they lead to the best overall outcome. Be cautious of overlooking individual rights or personal integrity in pursuit of the greater good.\n\n(Very High Utilitarian, Low Others)",
  ii: "Press [Spacebar] to Restart",
};

const screen_10 = {
  tt: "Rule Absolutist",
  bt:
    "You're a Rule Absolutist. You have an unwavering commitment to moral rules and principles, regardless of consequences. While this ensures consistency in your actions, consider that sometimes strict adherence to rules might lead to suboptimal outcomes.\n\n(Very High Deontological, Low Others)",
  ii: "Press [Spacebar] to Restart",
};

const screen_11 = {
  tt: "Virtue Purist",
  bt:
    "You're a Virtue Purist. You place the highest value on personal character and moral excellence. You believe that by being a good person, good actions will naturally follow. Remember that sometimes difficult situations might require considering outcomes or universal rules as well.\n\n(Very High Virtue Ethics, Low Others)",
  ii: "Press [Spacebar] to Restart",
};

function drawScreen1() {
  textAlign(LEFT);
  textSize(36);
  fill("#EE8838");
  text(screen_1.tt, 30, 80);

  textSize(24);
  fill("#577455");
  text(screen_1.bt, 30, 140, 540, 400);

  textSize(24);
  fill(255);
  text(screen_1.ii, 30, 550);
}

function drawScreen2() {
  textAlign(LEFT);
  textSize(36);
  fill("#EE8838");
  text(screen_2.tt, 30, 80);

  textSize(24);
  fill("#577455");
  text(screen_2.bt, 30, 140, 540, 400);

  textSize(24);
  fill(255);
  text(screen_2.ii, 30, 550);
}

function drawScreen3() {
  textAlign(LEFT);
  textSize(36);
  fill("#EE8838");
  text(screen_3.tt, 30, 80);

  textSize(24);
  fill("#577455");
  text(screen_3.bt, 30, 140, 540, 400);

  textSize(24);
  fill(255);
  text(screen_3.ii, 30, 550);
}

function drawScreen4() {
  textAlign(LEFT);
  textSize(36);
  fill("#EE8838");
  text(screen_4.tt, 30, 80);

  textSize(24);
  fill("#577455");
  text(screen_4.bt, 30, 140, 540, 400);

  textSize(24);
  fill(255);
  text(screen_4.ii, 30, 550);
}

function drawScreen5() {
  textAlign(LEFT);
  textSize(36);
  fill("#EE8838");
  text(screen_5.tt, 30, 80);

  textSize(24);
  fill("#577455");
  text(screen_5.bt, 30, 140, 540, 400);

  textSize(24);
  fill(255);
  text(screen_5.ii, 30, 550);
}

function drawScreen6() {
  textAlign(LEFT);
  textSize(36);
  fill("#EE8838");
  text(screen_6.tt, 30, 80);

  textSize(24);
  fill("#577455");
  text(screen_6.bt, 30, 140, 540, 400);

  textSize(24);
  fill(255);
  text(screen_6.ii, 30, 550);
}

function drawScreen7() {
  textAlign(LEFT);
  textSize(36);
  fill("#EE8838");
  text(screen_7.tt, 30, 80);

  textSize(24);
  fill("#577455");
  text(screen_7.bt, 30, 140, 540, 400);

  textSize(24);
  fill(255);
  text(screen_7.ii, 30, 550);
}

function drawScreen8() {
  textAlign(LEFT);
  textSize(36);
  fill("#EE8838");
  text(screen_8.tt, 30, 80);

  textSize(24);
  fill("#577455");
  text(screen_8.bt, 30, 140, 540, 400);

  textSize(24);
  fill(255);
  text(screen_8.ii, 30, 550);
}

function drawScreen9() {
  textAlign(LEFT);
  textSize(36);
  fill("#EE8838");
  text(screen_9.tt, 30, 80);

  textSize(24);
  fill("#577455");
  text(screen_9.bt, 30, 140, 540, 400);

  textSize(24);
  fill(255);
  text(screen_9.ii, 30, 550);
}

function drawScreen10() {
  textAlign(LEFT);
  textSize(36);
  fill("#EE8838");
  text(screen_10.tt, 30, 80);

  textSize(24);
  fill("#577455");
  text(screen_10.bt, 30, 140, 540, 400);

  textSize(24);
  fill(255);
  text(screen_10.ii, 30, 550);
}

function drawScreen11() {
  textAlign(LEFT);
  textSize(36);
  fill("#EE8838");
  text(screen_11.tt, 30, 80);

  textSize(24);
  fill("#577455");
  text(screen_11.bt, 30, 140, 540, 400);

  textSize(24);
  fill(255);
  text(screen_11.ii, 30, 550);
}

function profile(u, v, d) {
  // Principled Utilitarian
  if (u >= 2 && u > d && u > v && d >= 0 && v >= 0) {
    drawScreen5();
  }
  // Compassionate Deontologist
  else if (d >= 2 && d > u && d > v && u >= 0 && v >= 0) {
    drawScreen6();
  }
  // Pragmatic Virtue Ethicist
  else if (v >= 2 && v > u && v > d && u >= 0 && d >= 0) {
    drawScreen7();
  }
  // Balanced Ethicist
  else if (u > 0 && v > 0 && d > 0 && abs(u - v) <= 1 && abs(u - d) <= 1 && abs(v - d) <= 1) {
    drawScreen8();
  }
  // Consequentialist Extremist
  else if (u >= 2 && v <= 0 && d <= 0) {
    drawScreen9();
  }
  // Rule Absolutist
  else if (d >= 2 && u <= 0 && v <= 0) {
    drawScreen10();
  }
  // Virtue Purist
  else if (v >= 2 && u <= 0 && d <= 0) {
    drawScreen11();
  }
  // Default case if no specific profile is matched
  else {
    let maxScore = max(u, v, d);
    if (maxScore == u) {
      drawScreen5(); // Default to Principled Utilitarian
    } else if (maxScore == v) {
      drawScreen7(); // Default to Pragmatic Virtue Ethicist
    } else {
      drawScreen6(); // Default to Compassionate Deontologist
    }
  }
}

function setup() {
  createCanvas(600, 600);
}

function keyPressed() {
  if (key === " " && state === 0) {
    state++; // for spacebar on intro screen
  } else if (state === 1) {
    if (key === "A" || key === "a") {
      d++;
      u--;
      state++;
    } else if (key === "B" || key === "b") {
      v++;
      u++;
      state++;
    }
    console.log(state, u, v, d);
  } else if (
    //(key === "A" || key === "a" || key === "B" || key === "b") &&
    state === 2
  ) {
    if (key === "A" || key === "a") {
      d++;
      v--;
      state++;
    } else if (key === "B" || key === "b") {
      v++;
      d--;
      state++;
    }
    console.log(state, u, v, d);
  } else if (
    //(key === "A" || key === "a" || key === "B" || key === "b") &&
    state === 3
  ) {
    if (key === "A" || key === "a") {
      u++;
      v++;
      state++;
    } else if (key === "B" || key === "b") {
      v--;
      d--;
      state++;
    }
    console.log(state, u, v, d);
  } else if (key === " " && state >= 4) {
    console.log(state, u, v, d);
    resetGame(); // Reset game on final screen
  }

  function resetGame() {
    state = 0;
    v = 0;
    u = 0;
    d = 0;
  }
}

function draw() {
  background("#222222");
  if (state == 0) {
    drawScreen1();
  } else if (state == 1) {
    drawScreen2();
  } else if (state == 2) {
    drawScreen3();
  } else if (state == 3) {
    drawScreen4();
  } else {
    profile(u, v, d);  
  }
}

Reflections and Learnings

This project has been an incredible learning experience. I've learned about object-oriented programming, state management, and the importance of systematic debugging. It's amazing how breaking down complex problems into smaller, manageable parts can make such a difference in the development process.

One of the key realizations I had was about the limitations of P5.js when it comes to building more complex applications. While it's great for creating interactive sketches, I found myself struggling as my project grew in scale and complexity. Trying to manage game state, user interactions, and data flow all within a single P5.js sketch made me appreciate why larger applications are often split into backend, frontend, and data structure components. I can now see how separating these concerns into distinct layers could have made this project more manageable and scalable.

References
  1. Nik's Blog
  2. Claude by Anthropic
  3. Ellen for being such a great teacher

©2019-2025 SURYA NARREDDI.

©2019-2025 SURYA NARREDDI.