Assignment 9: Final project Proposal

Title: Catch the Horse
Mechanical Horse.mp4 – Google Drive

Concept:

“Catch the Horse” is an interactive two-player game that blends physical and virtual gameplay to create an engaging experience. A physical mechanical horse and a digital cowboy chase are brought to life through Arduino and p5.js integration. The game revolves around strategy, reflexes, and decision-making, where one player controls a cowboy trying to catch a runaway horse, while the other player actively defends the horse using tools like mud, fences, and boosters. With immersive elements like crouching to dodge obstacles and precise timing challenges to throw a lasso, the game offers an interactive and entertaining experience.

Overview:

In “Catch the Horse,” a virtual horse escapes its stable and leaves the screen, triggering a motor that animates a physical mechanical horse placed beside the monitor. The cowboy avatar must chase after the horse, dodging obstacles and using a lasso to attempt capture. The horse player can make the chase challenging with mud, fences, and boosters, while the cowboy must rely on strategic decision-making and skill to win.

Gameplay Mechanics

The Cowboy’s Role (Player 1)

  • Chasing the Horse:
    • The cowboy avatar moves left and right using a joystick to dodge mud and other obstacles.
    • Physical crouching (detected by an ultrasonic sensor) is required to avoid fences thrown by the horse player.
    • At set intervals, the cowboy can attempt to throw a lasso to capture the horse or a bag of treats.
  • Lasso Mechanic:
    The cowboy has two options when throwing the lasso:

    1. Catch the Horse (Luck-Based):
      • A meter appears with an oscillating needle that moves slower, making it easier to time the throw.
      • If successful, the player rolls a virtual dice. A roll of 5 or 6 captures the horse; otherwise, the chase continues.
    2. Catch the Treats (Skill-Based):
      • The meter’s needle oscillates faster, requiring greater precision to time the throw.
      • Success guarantees the bag of treats, which stops the horse and ensures a win.
      • Failure results in no capture, and the chase continues. Speedometer meter with arrow for dashboard. - Vector. 30715312 Vector ...

The Horse’s Role (Player 2)

  • The horse player uses three buttons to evade capture:
    1. Mud Throw: Throws mud obstacles that the cowboy must dodge with the joystick.
    2. Fence Throw: Launches fences that the cowboy must crouch to avoid.
    3. Booster: Temporarily speeds up the horse, preventing the cowboy from throwing a lasso.
  • All buttons have cooldown periods to maintain fairness and balance. Robot Unicorn Attack 2 (Gameplay) Android / iOS - YouTube

Challenges and Solutions

  1. Mechanical Horse Durability:
    • The motor runs at a controlled speed to prevent stress on the fragile body.
  2. Button Cooldowns and Fairness:
    • Cooldown timers for the horse player’s buttons ensure balanced gameplay.
  3. Accurate Crouch Detection:
    • Calibrate the ultrasonic sensor to detect movement precisely while avoiding false triggers.

Winning Conditions

  • The cowboy wins if they:
    • Successfully throw the lasso and roll a 5 or 6.
    • Successfully capture the bag of treats.
  • The horse wins if:
    • The cowboy fails all lasso attempts, or the timer runs out.

 

Assignment 8: In class Exercises (Serial Communication)

Exercise 1 – Moving Ellipse

Prompt: Make something that uses only one sensor on Arduino and makes the ellipse in p5 move on the horizontal axis, in the middle of the screen, and nothing on Arduino is controlled by p5.
Arduino Code
Demonstration:

Exercise 2 – LED Brightness

Prompt: Make something that controls the LED brightness from p5

Arduino Code

P5.js Sketch

Demonstration:

Exercise 3 – Bouncing Ball

Prompt: Take the gravity wind example (https://editor.p5js.org/aaronsherwood/sketches/I7iQrNCul) and make it so every time the ball bounces one led lights up and then turns off, and you can control the wind from one analog sensor

Arduino Code

P5.js Sketch

Demonstration:

Assignment 7: How fairies play the piano

Concept:

For this assignment, my partner Fasya and I designed a musical instrument that merges digital and analog elements to create an interactive, light-based piano. Our idea centers around a glowing wand that functions as a control interface. When moved over the keys, the wand activates notes from the C major scale (C, D, E, F, and G) like magic, and the brightness of its glow determines the octave. We integrated a potentiometer to adjust brightness, allowing users to easily shift the octave up or down. Additionally, we added a switch to toggle the instrument off, which prevents accidental note activation—particularly useful to avoid unintended sounds from ambient light sources like flashlights.

Highlight:

To bring our vision to life, we used five photoresistors to detect light from the wand and mapped each sensor’s range to specific notes (C, D, E, F, and G) and their octave scales. By setting sensor thresholds from a default minimum to a maximum value that a flashlight might produce, we could dynamically adjust the octave based on the brightness the photoresistor detects. Essentially, the brighter the wand, the higher the octave, allowing for an expressive range in tone.

For the wand itself, we created a purple glow using a tricolored LED, giving the instrument an ethereal fairy-like visual quality. A potentiometer is attached to the LED to control brightness, making it easy for users to adjust octaves on the fly. The setup includes separate circuits for the keyboard and wand, creating flexibility for future enhancements, such as adding multiple wands for collaborative play.

Keyboard Code:

#include "pitches.h"
bool buttonState = false;
// Define the piezo buzzer pin
const int buzzerPin = 8;

// Define frequencies for each note across multiple octaves
const int fNotes[] = {NOTE_F4, NOTE_F5, NOTE_F6, NOTE_F7};  // Octaves of A
const int gNotes[] = {NOTE_G4, NOTE_G5, NOTE_G6, NOTE_G7};  // Octaves of B
const int cNotes[] = {NOTE_C4, NOTE_C5, NOTE_C6, NOTE_C7};  // Octaves of C
const int dNotes[] = {NOTE_D4, NOTE_D5, NOTE_D6, NOTE_D7};  // Octaves of D
const int eNotes[] = {NOTE_E4, NOTE_E5, NOTE_E6, NOTE_E7};  // Octaves of E

void setup() {
  // Initialize serial communication at 9600 bps for debugging
  Serial.begin(9600);
  pinMode(7,INPUT);
}

void loop() {
  // Array to store sensor values
  int sensorValues[5];
  int switchValue = digitalRead(7);
  if (switchValue == HIGH){
      buttonState = true;
  }

 
  // Read each sensor value and store in the array
  sensorValues[0] = analogRead(A3);  // f note
  sensorValues[1] = analogRead(A4);  // g note
  sensorValues[2] = analogRead(A0);  // C note
  sensorValues[3] = analogRead(A1);  // D note
  sensorValues[4] = analogRead(A2);  // E note

  // Play a note based on each sensor value
  for (int i = 0; i < 5; i++) {
    int note;
    if (sensorValues[i] < 850 || !buttonState) {
      // Stop any sound if the sensor value is below 900
      noTone(buzzerPin);
      continue;
    } else {
      // Map the sensor value (900 to 1100) to an index (0 to 3) for each note array
      int noteIndex = map(sensorValues[i], 850, 1100, 0, 3);

      // Assign the note based on the sensor index
      switch(i) {
        case 0: note = fNotes[noteIndex]; break;
        case 1: note = gNotes[noteIndex]; break;
        case 2: note = cNotes[noteIndex]; break;
        case 3: note = dNotes[noteIndex]; break;
        case 4: note = eNotes[noteIndex]; break;
      }
      
      // Play the mapped frequency on the piezo buzzer
      tone(buzzerPin, note);
    }
    
    // Delay to control the speed of tone change
    delay(100);
  }
}

Wand Code:

// *Interfacing RGB LED with Arduino 
// * Author: Osama Ahmed 

//Defining  variable and the GPIO pin on Arduino
int redPin= 9;
int greenPin = 10;
int  bluePin = 11;
int potPin = A2;
int sensorVal = 0;
double brightness = 0;

void setup() {
  Serial.begin(9600);
  //Defining the pins as OUTPUT
  pinMode(redPin,  OUTPUT);              
  pinMode(greenPin, OUTPUT);
  pinMode(bluePin, OUTPUT);
}
void  loop() {
  sensorVal = analogRead(potPin);
  brightness = (double)sensorVal / 1023;
  Serial.println(brightness);
  
  setColor(170, 0, 255, brightness); // Purple Color
  // delay(1000);
}
void setColor(int redValue, int greenValue,  int blueValue, double brightValue) {
  analogWrite(redPin, (double) redValue * brightValue);
  analogWrite(greenPin,  (double) greenValue  * brightValue);
  analogWrite(bluePin, (double) blueValue  * brightValue);
}

Demonstration:

Keyboard Circuit Schematic
Wand Circuit Schematic

 

Reading Reflection 7: Rant Continued

Thoughts on Pictures under Glass:

I share Bret Victor’s perspective on “pictures under glass” technology, and I believe touchscreen LEDs have overstayed their welcome in many fields. This includes devices like the Kindle—a non-glare touchscreen designed for reading that now also accommodates audiobooks. While these devices offer practical benefits, such as saving storage space and promoting a paperless world, they come at the cost of the rich sensory experience that traditional books provide.

Touchscreens have permeated so many areas of our lives, saturating us with constant notifications, flashing updates, and endless scrolling. This widespread use of touchscreens has left us overstimulated, as we’re constantly pulled in multiple directions by the same technology that now powers even our reading devices. This overstimulation detracts from the act of reading itself, making it harder to slow down and engage in the immersive, focused way that physical books invite. In fact, the Kindle’s interface, with its flat screen and standardized e-book format, feels more like another digital task than an invitation to read.

A physical book, by contrast, invites us to unplug, slow down, and focus. Its varied textures, the scent of the pages, and even the way it changes over time as it’s passed from one reader to the next all contribute to the act of reading as a sensory experience. Books are unique; each one feels different to the touch and engages the senses in ways that screens simply can’t emulate.

More than that, a well-worn book carries visible signs of love and use—cracked spines, dog-eared corners, or a few scribbled notes. These details create a sense of shared history and connection between readers across generations. No matter how advanced, a Kindle can never replicate that. Physical books leave an imprint not only on us but also of us, and in that way, they foster a relationship with readers that digital devices lack.

Reading Reflection 6

Thoughts on physical computing and how to do it:

Physical Computing’s Greatest hits and misses
Making Interactive Art: Set the Stage, Then Shut Up and Listen

In reading Physical Computing’s Greatest Hits and Misses and Making Interactive Art: Set the Stage, Then Shut Up and Listen, I got a sense that while physical computing has been around for a long time, the approach to creating interactive art has evolved. Rather than focusing on creating fixed meanings, artists are now encouraged to let meaning emerge through interaction, giving audiences more freedom in their interpretation.

From my own experiences visiting new media art installations in Paris and London, I’ve noticed that many installations still tend to be defined by the artist’s initial inspiration, which can limit the ways audiences experience them. One example is an installation I saw in the meditation room at the Museum of the Future. The setup involved placing your hands over a column that emitted vibrations, designed to create a relaxing, full-body sensation. However, instead of allowing us to engage directly with the sensations, an interpreter was there to tell us how to think and feel as we experienced it, even instructing us to close our eyes and envision a door. This guidance controlled our interpretation, making it harder to form a personal connection with the piece.

This experience reinforced what the readings suggest: interactive art is most impactful when artists “set the stage” but avoid overly directing the audience’s interpretation. By allowing viewers to find their own meaning in the experience, the connection to the art becomes more personal and engaging.

Assignment 6: Overspending Warning

Concept:

For this assignment, we were asked to control one LED in an analog manner and another in a digital manner. I chose to use a button switch to control one LED and a potentiometer for the other. For the digital component, I attached a button switch to my wallet’s card-ejection button so that whenever I try to access my credit card, a red LED lights up as a gentle warning to consider my spending. For the analog component, I connected a potentiometer to a blinking LED, allowing the speed of the LED’s blinking to be adjusted by turning the potentiometer. This setup demonstrates both analog and digital LED control in a creative, practical application.

Highlight:

A key highlight of this project is my approach to keeping the analog and digital circuits distinct from each other. By treating them as separate circuits, I chose to use the Arduino’s 5V and 3.3V power outputs individually powering the digital circuit with 5V and the analog circuit with 3.3V. Additionally, I set up each circuit on separate breadboards, which makes it easy to distinguish between the two and ensures a clear, organized layout. This setup not only reinforces the conceptual differences between analog and digital control but also simplifies troubleshooting and testing.

Hand-Drawn Schematic

For the coding aspect of this project, I organized the code by designating separate blocks for the analog and digital controls, clearly separated by comments. This structure makes the code easier to navigate and understand, as each section is dedicated to controlling one LED independently.

In the blinking LED project, I utilized the map function, as we covered in class, to control the blinking speed with the potentiometer. By mapping the potentiometer’s analog input range (0–1023) to a delay range (e.g., 50–1000 milliseconds), I was able to adjust the blink rate based on the potentiometer’s position.

int led = 11;

void setup() {
 Serial.begin(9600);

 pinMode(led, OUTPUT);
 pinMode(13, OUTPUT);
 pinMode(A2, INPUT);

}

void loop() {
//controlling led with a potentiometer
    int sensorValue = analogRead(A1);
    Serial.println(sensorValue);

    // Map the potentiometer value (0–1023) to a delay time (e.g., 50–1000 ms)
    int blinkDelay = map(sensorValue, 0, 1023, 50, 1000);

    // Blink the LED at a speed controlled by the potentiometer
    digitalWrite(led, HIGH); 
    delay(blinkDelay);        
    digitalWrite(led, LOW);    
    delay(blinkDelay);         

//Controlling Led with a Button
int buttonState = digitalRead(A2);
if (buttonState == LOW) {
digitalWrite(13, LOW);
} else {
digitalWrite(13, HIGH);
}

}

Demonstration:

Digital Circuit

Analog Circuit

Complete Setup

Assignment 5: Unusual Switch

Concept:

The inspiration for this project came from a common issue in my home: my siblings often leave kitchen drawers and cabinet doors open after grabbing snacks. This habit leads to my cat sneaking into these spaces, where she can hide for hours. To solve this, I came up with the idea of creating a simple sensor-based system that alerts my siblings when they forget to close a drawer or cabinet. By using a light sensor, this system can detect when a drawer or door is left open and activate a notification, such as an LED, to remind them to close it. This project combines basic electronics with a practical problem-solving approach to keep both the kitchen organized and my cat safe.

Highlight:

The highlight of this project was developing a functional switch system that alerts users when a drawer is left open. I began by connecting a light sensor to a 10k resistor in a voltage divider circuit, which allowed me to monitor light changes accurately. I added a red LED and a green LED, each with its own 330-ohm resistor, connected to digital pins 10 and 11.

void setup() {
  // initialize serial communication at 9600 bits per second:
  Serial.begin(9600);
  
  // Set pin modes for LEDs
  pinMode(10, OUTPUT); // Red LED
  pinMode(11, OUTPUT); // Green LED
}

// the loop routine runs over and over again forever:
void loop() {
  // read the input on analog pin A2:
  int sensorValue = analogRead(A2);
  
  // print out the value you read:
  Serial.println(sensorValue);
  delay(1);  // delay in between reads for stability

  if (sensorValue > 600) {
    // Turn on red LED and turn off green LED
    digitalWrite(10, HIGH);
    digitalWrite(11, LOW);
  } else {
    // Turn on green LED and turn off red LED
    digitalWrite(10, LOW);
    digitalWrite(11, HIGH);
  }
}

Then, using code adapted from Week 9 lecture slides, I programmed the LEDs to respond to light levels: the red LED illuminates when the sensor detects that a drawer is open, and the green LED lights up when the drawer is closed. Finally, I mounted the light sensor inside the drawer and tested the setup. As designed, the red LED serves as an alert when the drawer is open, while the green LED confirms it is securely closed. This project successfully provided a practical solution to alerting users to close drawers, helping prevent pets from accessing open spaces.

Setup

Demonstration:

 

 

Reading Reflection 5

The utilitarian Design vs Aesthetics:

Norman,“Emotion & Design: Attractive things work better”
Her Code Got Humans on the Moon

In reflecting on Don Norman’s “Emotion & Design: Attractive Things Work Better” and the article “Her Code Got Humans on the Moon”, I’ve gained a deeper appreciation for the role of user-centered design, particularly in high-stakes environments. Norman’s insights on emotional engagement in design highlight how well-designed, intuitive products improve user experience, functionality, and even safety. This principle aligns with Margaret Hamilton’s story in the article, where her recommendation to include a warning note in the Apollo software was initially dismissed but could have prevented a critical error that later occurred.

Both Norman and Hamilton emphasize that design must go beyond the technical requirements and account for human unpredictability. In high-stress situations—such as a lunar landing or, more broadly, any critical application—users may act differently than designers anticipate. Hamilton’s experience reflects Norman’s point about designing not only for ideal circumstances but also for scenarios where things go wrong. This reinforces the importance of creating safeguards in design to prevent errors, support users under pressure, and mitigate risks, demonstrating that effective design is as much about empathy and foresight as it is about functionality.

In reflecting on how design impacts safety and usability, an example that comes to mind is the design of fire extinguishers. While essential in emergencies, many fire extinguishers are not immediately intuitive to use, especially in high-stress situations. The sequence—pull the pin, aim the nozzle, squeeze the handle, and sweep—may seem simple, but in a crisis, it can be easy to forget steps or become disoriented, particularly for those who haven’t received training.

Midterm Project: Labyrinth Race

Concept:

Sketch Link: p5.js Web Editor | Labyrinth Final (p5js.org)

Inspired by the Greek myth of Theseus, who navigated the labyrinth to defeat the Minotaur, I envisioned a game that captures the thrill of navigating a maze to reach a goal. In this two-player game, only one can survive the labyrinth. Players must race to the safe zone at the center of the maze while avoiding touching the walls of the maze. The first player to reach the center wins, while the other faces death. Alternatively, if a player touches the walls at any point, they are immediately eliminated, granting victory to their opponent by default. This intense competition fosters a sense of urgency and strategy as players navigate the ever-changing labyrinth.

Design features:

  • Dynamic Maze Generation: Each time a new game begins, the maze layout changes, ensuring that no two playthroughs are the same. This feature keeps the gameplay fresh, challenging players to adapt to new environments every time they enter the labyrinth.
  • Boundary Enforcement: Players are confined to the game canvas, ensuring they stay within the limits of the maze. Exiting the bounds is not an option.
  • Customizable Player Names: Players can personalize their experience by entering their own names before starting a match. For those who prefer to jump right into the action, the game also offers default character names, maintaining a smooth and accessible start.
  • Animated Player Sprites and Movement Freedom: Each player is represented by a sprite that shows movement animations. The game allows Movement in 8 directions.
    EightDirMovement21
  • Fullscreen Mode: For a more immersive experience, players can opt to play the game in Fullscreen mode.
  • Collision Detection: The game includes collision detection, where touching the maze walls results in instant disqualification. Players must carefully navigate the labyrinth, avoiding any contact with the walls or face elimination.

Process:

The most challenging and time-consuming aspect of this game was designing the maze. The logic used to ensure that a new maze is generated with every iteration involves several key steps:

Maze Initialization: The process begins by drawing six concentric white circles at the center of a black canvas, with each subsequent circle having a radius that decreases by 60pt. This creates the foundational layout of the maze.

let numCircles = 6; // Number of white circles in the maze
let spacing = 50; // Spacing between circles

// Loop to draw white circles
for (let i = 0; i < numCircles; i++) {
    let radius = (i + 1) * spacing; // Calculate radius for each circle
    noFill(); 
    stroke(255); 
    strokeWeight(2); 
    ellipse(width / 2, height / 2, radius * 2, radius * 2); // Draw each circle
}

Black Circle Placement: Black circles are then placed randomly on top of the white circles. The innermost black circle is erased to create a clear entrance. The number of black circles on each white circle increases as we move outward, adding complexity to the maze design.

// Function to generate positions for the black circles
function generateBlackCircles() {
    // Loop through each circle in the maze
    for (let i = 0; i < numCircles; i++) {
        let radius = (numCircles - i) * spacing; // Start with the outermost circle first
        let maxBlackCircles = numCircles - i; // Outermost gets 6 circles, innermost gets 1
        
        // Generate random positions for the black circles
        for (let j = 0; j < maxBlackCircles; j++) {
            let validPosition = false;
            let angle;

            // Keep generating a random angle until it is far enough from other circles
            while (!validPosition) {
                angle = random(TWO_PI); // Generate a random angle
                validPosition = true; // Assume it's valid

                // Check if the angle is far enough from previously placed angles
                for (let k = 0; k < placedAngles.length; k++) {
                    let angleDifference = abs(angle - placedAngles[k]);
                    angleDifference = min(angleDifference, TWO_PI - angleDifference);
                    if (angleDifference < minAngleDifference) {
                        validPosition = false; // Too close, generate again
                        break;
                    }
                }
            }
            // Calculate the coordinates and store them
            let x = width / 2 + radius * cos(angle); 
            let y = height / 2 + radius * sin(angle);
            blackCircles.push({ x: x, y: y, angle: angle, radius: radius });
            placedAngles.push(angle); // Save the angle for future spacing
        }
    }
}

Collision Detection for Black Circles: An overlap function checks to ensure that no two black circles spawn on top of one another. This is crucial for maintaining the integrity of the maze entrances.

// Function to check if the midpoint of a line is overlapping with a black circle
function isOverlapping(circle, x1, y1, x2, y2) {
    let { midX, midY } = calculateMidpoint(x1, y1, x2, y2);
    let distance = dist(midX, midY, circle.x, circle.y);
    return distance < circleRadius; // Return true if overlapping
}

Maze Line Generation: For each black circle, a white maze line is generated that is perpendicular to the white circles. Another overlap function checks whether the maze lines overlap with the black circles, adjusting their positions as necessary.

// Function to draw the lines from the edge of the black circle
function drawMazeLines() {
    stroke(255); // Set the stroke color to white
    for (let i = 1; i < blackCircles.length - 2; i++) {
        let circle = blackCircles[i];
        // Calculate starting and ending points for the line
        let startX = circle.x - (50 * cos(circle.angle)); 
        let startY = circle.y - (50 * sin(circle.angle));
        let endX = startX + (50 * cos(circle.angle));
        let endY = startY + (50 * sin(circle.angle));
        // Check for overlap and adjust rotation if necessary
        while (isAnyCircleOverlapping(startX, startY, endX, endY)) {
            // Rotate the line around the origin
            let startVector = createVector(startX - width / 2, startY - height / 2);
            let endVector = createVector(endX - width / 2, endY - height / 2);
            startVector.rotate(rotationStep);
            endVector.rotate(rotationStep);
            // Update the coordinates
            startX = startVector.x + width / 2;
            startY = startVector.y + height / 2;
            endX = endVector.x + width / 2;
            endY = endVector.y + height / 2;
        }
        line(startX, startY, endX, endY); // Draw the line
    }
}

Test Sketch:

 Final Additions:

  • Player Integration: I introduced two player characters to the canvas. Each player was assigned different movement keys and given 8 degrees of movement as players needed to utilize their movement skills effectively while avoiding collisions with the maze walls.
  • Collision Detection: With the players in place, I implemented collision detection between the players and the maze structure.
  • Game States: I established various game states to manage different phases of the gameplay, such as the start screen, gameplay, and game over conditions. This structure allowed for a more organized flow and made it easier to implement additional features like restarting the game.
  • Player Name Input: To personalize the gaming experience further, I incorporated a mechanism for players to input their names before starting the game. Additionally, I created default names for the characters, ensuring that players could quickly jump into the action without needing to set their names.
  • Sprite Movement: I dedicated time to separately test the sprite sheet movement in a different sketch to ensure smooth animations. Once the movement was working perfectly, I replaced the placeholder player shapes with the finalized sprites, enhancing the visual appeal and immersion of the game.
  • Audio and Visual Enhancements: Finally, I added background music to create an engaging atmosphere, along with a game-ending video to show player death. Additionally, I included background image to the game screen.

Final Sketch:

Future Improvements:

One significant challenge I encountered with the game was implementing the fullscreen option. Initially, the maze generation logic relied on the canvas being a perfect square, which restricted its adaptability. To address this, I had to modify the maze generation logic to allow for resizing the canvas.

However, this issue remains partially unresolved. While the canvas no longer needs to be a perfect square, it still cannot be dynamically adjusted for a truly responsive fullscreen mode. To accommodate this limitation, I hardcoded a larger overall canvas size of 1500 by 800 pixels. As a result, the game can only be played in fullscreen, which may detract from the user experience on smaller screens or varying aspect ratios.

Moving forward, I aim to refine the canvas resizing capabilities to enable a fully responsive fullscreen mode.

 

Midterm Progress: Defeat the Minotaur

Concept:

I’ve been reading books on Greek mythology lately, and I’ve noticed how certain tropes keep resurfacing in these tales. Anyone familiar with Greek myths knows that labyrinths are a huge aspect of these stories. Like, take Orpheus, who went into the underworld to bring back his dead wife. His story brought people to tears, and it really showed how much of a maze the underworld was and how insane it was for him to brave that journey.

Since I’m a literature major too, I thought I had the thought of working on a project that would be a fusion of both of my majors for once. That’s how I got the idea to create my own labyrinth game, which if the name of the project does not already give away— is an adaptation of the Greek myth of Theseus, who went into the Minotaur’s labyrinth to defeat him.

Knossos Palace in Assassin's Creed Odyssey: A Historian-Scientist ...

 

But, honestly, if I were in Theseus’s shoes, my strategy wouldn’t be to kill the Minotaur. With my very human physique, I’m not cut out for that. I’d be focused on getting to safety instead. So, the idea for this game is simple: you start with a circular maze that has multiple entrances around the edge. There are two players — one using the arrow keys to move and the other using WASD. At the start of the game, each player must choose a separate entrance to the maze, and the goal is to find their way to the safe zone in the center before the Minotaur catches them. Once a player reaches the safe zone, both players can’t move anymore, and the one who didn’t make it gets eaten by the Minotaur.

MYTHS OF THE LABYRINTH | Ashmolean Museum

Players can choose to play as Theseus or Orpheus, and the game also ends if anyone touches the boundary walls of the maze.

Design:

The maze itself will be created using vectors. Each segment of the labyrinth will be drawn programmatically, ensuring that the structure remains consistent while allowing for flexibility in the maze’s layout. For the characters, I’ll use a sprite sheet to handle movement animations, enabling smooth transitions between different frames as the characters navigate through the maze.

In terms of the pseudo-algorithm for the game:

1. Maze Construction: The maze will be created using vector functions. I’ll define an array of vectors to represent the maze walls and entrances. This will allow for flexible maze design. The setup() function will initialize the canvas and call a function to draw the maze based on pre-defined parameters.

2. Player Movement: Two player objects will be created, each with properties for position and speed. The players will respond to keyboard inputs, with Player 1 controlled by the arrow keys and Player 2 using WASD. The draw() function will include a movement handler that updates each player’s position based on key presses.

3. Safe Zone Mechanism: A function will check if one of the players reaches the safe zone. When this occurs, the game will stop updating the players’ positions. This can be handled with a simple boolean flag that controls movement.

function checkSafeZone() {
    if (dist(player1.x, player1.y, safeZone.x, safeZone.y) < safeZone.radius) {
        gameActive = false; // Stops player movement
    }
}

4. Minotaur Appearance: The Minotaur will be an object with properties for position and movement speed. When the safe zone is reached, a function will activate the Minotaur, setting its initial position off-screen. The Minotaur will then move towards the player outside the safe zone using a straightforward pathfinding algorithm or direct movement logic.

5. Game Over Condition: A function will determine if the Minotaur reaches the player outside the safe zone. If they collide, the game will trigger a game over state, displaying who won and include restart button.

The Challange:

 

Making the Minotaur move toward the player outside the safe zone feels like a challenging task for a few reasons. First, I need to allow the Minotaur to override the boundary wall constraints, which means it would glide over any obstacles on the screen to reach the player. Since the Minotaur won’t be restricted by the usual collision detection that applies to the maze walls, it should make it a little simpler to implement the tracking mechanism.

To implement this, I envision starting with a function that checks which player is outside the safe zone. From there, I’ll need to determine how to return the coordinates of that player so the Minotaur can track them effectively. The tricky part is figuring out how to make the Minotaur move towards those coordinates. I’ll need to increment or decrement its x and y positions based on the player’s location.

The real challenge lies in determining when to increment or decrement. I need to devise a way for the program to recognize the Minotaur’s position relative to the player’s coordinates. This means creating conditions that account for whether the Minotaur is to the left, right, above, or below the player. Getting that logic right will be crucial for smooth movement, and I can already tell it’s going to take some trial and error to nail down the precise mechanics.

Risk Management:

To reduce the risk of encountering major issues while programming the Minotaur’s movement, I’ve taken several proactive measures. First, I’ve broken down the implementation into manageable tasks, focusing on one aspect at a time, such as determining the player’s position outside the safe zone before programming the Minotaur’s movement. This incremental approach allows me to test each function independently and ensure that everything works as expected before integrating it into the game.

Additionally, I’ve researched pathfinding algorithms and movement logic used in similar games, which has given me insights into best practices for implementing smooth character movement. I’m also making use of clear comments in my code to keep track of my thought process and logic, which should help me debug any issues that arise more easily.

Finally, I’ve set up a simple testing framework where I can run the game at various stages of development. This way, I can identify potential problems early on and address them before they become larger obstacles.