Category: Introductory Projects

  • Robotics Project – Gesture Drum Bot

    Music has always been an important part of my life, and it’s something I listen to everyday, whether I need to relax or find motivation. The rhythm and energy of beats have a way of bringing focus and emotions together, and inspired me to incorporate a music element into this project. For this project, I wanted to design a simple robot that could detect hand movements, respond dynamically, and perform an expressive action by drumming to the rhythm of my hand movements.

    Project Overview

    The Gesture Drum bot uses the user’s hand as a virtual conductor. And when their hand moves toward or away from the ultrasonic sensor in rhythm, the Arduino measures these distance changes and creates a beat.

    Each time a beat is detected:

    • The servo taps like a drumstick
    • The buzzer plays a short musical note with a random
    • The LCD screen briefly flashes “BEAT!” and then updates the BPM

    Tinkercad Design

    Image of Circuit Design in Tinkercad

    I started by designing and simulating a prototype in Tinkercad. This process helped me to first verify the logic of the code and wiring for different components.

    The wires in the circuit design are also color coded to make it more accessible:

    Green – PINs

    Yellow – Ground

    Red – Power (5V)

    Blue – SDA / SCL

    Circuit Wiring

    ComponentPIN connectionFunction
    Ultrasonic SensorVCC -> 5V,
    GND -> GND,
    SIG -> D9
    Measures hand distance
    Servo MotorSIG -> D6,
    VCC -> 5V,
    GND -> GND
    Moves motor based on the distance
    Piezo Buzzer(+) -> D3,
    (-) -> GND
    Plays random tone when beat triggered
    LCDSDA -> A4,
    SCL -> A5,
    VCC -> 5V,
    GND -> GND
    Displace distance and BPM.

    Above is the schematic diagram of the circuit design. Where it illustrates how all the components are connected to each other and the Arduino board.

    Bill of Materials (BOM)

    ComponentQuantityDescription
    Arduino UNO1Main board
    Ultrasonic Distance Sensor1Detects distance of hand movement
    Servo Motor1acts as drumstick arm
    Piezo Buzzer1plays a tone for each beat
    PCF8574-based, 32 (0x20) LCD 16 x 2 (I2C)1displays data
    LCBG LED RGB1LED that can display any color
    1 kΩ Resistor3resistor for LED
    BreadBoard + Jumper Wiresfor connections

    Code Logic

    Entire Source Code
    #include <Servo.h>
    #include <LiquidCrystal_I2C.h>
    
    LiquidCrystal_I2C lcd(0x20, 16, 2);
    Servo arm;
    
    //pins
    const int ultrasonic_pin = 9;
    const int servo_pin = 6;
    const int buzzer_pin = 3;
    
    //window
    const int min_dist = 60;
    const int max_dist = 150;
    
    //beat
    float lastDistance = 0;
    float lastVelocity = 0;
    bool approaching = false;
    
    unsigned long lastBeat = 0;
    const unsigned long min_beat_gap = 250;
    
    //BPM
    unsigned long history[4] = {0};
    int Pos = 0;
    bool Full = false;
    float bpm = 0;
    
    //Notes
    const int notes[] = {196,220,247,294,330,392,440,494};
    const int note_count = sizeof(notes) / sizeof(int);
    
    void setup() {
      Serial.begin(115200);
      
      pinMode(buzzer_pin, OUTPUT);
      digitalWrite(buzzer_pin, LOW);
      
      //setup lcd
      lcd.init();
      lcd.backlight();
      lcd.clear();
      
      //reset servo position
      arm.attach(servo_pin);
      arm.write(90);
      
      randomSeed(analogRead(A0));
      //start timer
      lastBeat = millis();
    
    }
    
    void loop(){
      unsigned long now = millis();
      
      //Read distance
      float dist = readDistanceCM();
      //positive value, continues
      bool valid = (dist>0);
      
      if (valid){
        //servo follows hand
        arm.write(distanceToAngle(dist));
      }
      
      //Detect beat
      float velocity = lastDistance - dist;
      //within the window that we defined
      bool inRange = (dist >= min_dist && dist <= max_dist);
    
      //valid hand movement, continues
      if (inRange && velocity > 0.4) {
        approaching = true;
      }
      
      //for detecting hand movement coming back
      bool reversed = (lastVelocity > 0 && velocity < 0);
      
      if (valid && approaching && reversed && (now - lastBeat > min_beat_gap)){
        triggerBeat();
        record(now);
        approaching = false;
      }
      
      lastVelocity = velocity;
      lastDistance = dist;
      
      //lcd
      static unsigned long lastLCD = 0;
      if (now - lastLCD > 200) {
        lastLCD = now;
        updateLCD(valid ? dist : -1, bpm);
      }
        
      delay(15);
    
    }
    
    
    float readDistanceCM() {
      pinMode(ultrasonic_pin, OUTPUT);
      digitalWrite(ultrasonic_pin, LOW);
      delayMicroseconds(2);
      digitalWrite(ultrasonic_pin, HIGH);
      delayMicroseconds(10);
      digitalWrite(ultrasonic_pin, LOW);
      
      //echo
      pinMode(ultrasonic_pin, INPUT);
      unsigned long dur = pulseIn(ultrasonic_pin, HIGH, 15000);
      
      if (dur==0) return -1;
      
      float cm = dur / 58.0;
      if (cm < 2 || cm > 400) return -1;
      
      return cm;
    }
    
    
    int distanceToAngle(float cm) {
      //detect distance inside the window
      if (cm < min_dist) cm = min_dist;
      if (cm > max_dist) cm = max_dist;
      
      //convert distance to servo angle
      float t = (cm - min_dist) / (max_dist - min_dist);
      return 35 + (int)(t*(120-35));
    }
    
    void triggerBeat() {
      int note = notes[random(note_count)];
      tone(buzzer_pin, note, 120);
      lastBeat = millis();
    }
    
    void record(unsigned long now) {
      unsigned long x = now - lastBeat;
      history[Pos] = x;
      Pos = (Pos + 1) % 4;
      
      //true once we have stored 4 intervals
      if (Pos == 0) Full = true;
      
      if (Full) {
        unsigned long sum = 0;
        for (int i=0; i<4; i++) {
          sum += history[i];
        }
        
        float avg = sum / 4.0;
        bpm = 60000.0 / avg;
        
      }
    }
    
    void updateLCD(float dist, float bpm) {
      //print distance
      lcd.setCursor(0, 0);
      if (dist < 0) {
        lcd.print("Dist: --       ");
      } else {
        lcd.print("Dist: ");
        lcd.print((int)dist);
        lcd.print("cm    ");
      }
    
      lcd.setCursor(0, 1);
      lcd.print("BPM: ");
      if (bpm > 0) lcd.print((int)bpm);
      else lcd.print("--");
      lcd.print("       ");
    }
    1. Read distance: The ultrasonic sensor measures hand distance.
    2. Filter Noise: A smoothing algorithm for the ultrasonic sensor to remove inconsistent values that might affect consistency
    3. Detect Beat: If the hand moves toward the sensor and then reverses direction quickly, then it’s considered to a beat.
    4. Play Beat: After a beat is detected, the system will react to it by playing a random musical note using tone(), and move the servo slightly as a drum tap.
    5. Calculate BPM: The program stores the last few beat intervals and averages them.
    6. Display Data: The LCD continuously updates to show distance and BPM.

    Code Explanation

    void setup() {
      Serial.begin(115200);
      
      pinMode(buzzer_pin, OUTPUT);
      digitalWrite(buzzer_pin, LOW);
      
      //setup lcd
      lcd.init();
      lcd.backlight();
      lcd.clear();
      
      //reset servo position
      arm.attach(servo_pin);
      arm.write(90);
      
      randomSeed(analogRead(A0));
      //start timer
      lastBeat = millis();
    
    }

    The program starts with the setup() function, which runs once when the program is uploaded to Arduino. It includes all of the libraries needed and initializes all the variables.

    Serial.begin(115200);

    This line starts a serial port at 115200, which acts as the console and allows Arduino to send debugging messages. In addition, pinMode() sets up the buzzer’s pin so it can send sound signals. The parameters of it are the pin number and the mode (output / input).

    diginalWrite(<pin>, LOW) turns off the buzzer at the start so it doesn’t make any sound when the program starts. With digitalWrite(), you are able to either set it as High (5V) or Low (0V).

    Afterwards, the LCD display get initialized. And the servo is configured at a specific pin.

    The code now proceeds to the loop() function which runs over and over again.

    unsigned long nowMs = millis();

    millis() is a built in function that returns the number of milliseconds since the program started running. And in this case, it is assigned and stored in a variable called nowMs, and is used throughout the loop for timing things like detecting the beat and stuff.

      //Read distance
      float dist = readDistanceCM();
      //positive value, continues
      bool valid = (dist>0);
    
    //read distance function
    float readDistanceCM() {
      pinMode(ultrasonic_pin, OUTPUT);
      digitalWrite(ultrasonic_pin, LOW);
      delayMicroseconds(2);
      digitalWrite(ultrasonic_pin, HIGH);
      delayMicroseconds(10);
      digitalWrite(ultrasonic_pin, LOW);
      
      //echo
      pinMode(ultrasonic_pin, INPUT);
      unsigned long dur = pulseIn(ultrasonic_pin, HIGH, 15000);
      
      if (dur==0) return -1;
      
      float cm = dur / 58.0;
      if (cm < 2 || cm > 400) return -1;
      
      return cm;
    }

    This section is in charge of reading the distance from the ultrasonic sensor and smoothening the data received. This is achieved by first creating a floating variable and calling the readDistanceCM() function to store the distance returned inside it. Afterwards, a boolean variable is created to check if the reading make sense. And if valid, the distance is passed on to convert it to an angle.

      //Servo follows
      if (valid) {
        int targetAngle = distanceToAngle(emaDist);
        arm.write(targetAngle);
      }
    
    //function to convert distance to angle
    int distanceToAngle(float cm) {
      //detect distance inside the window
      if (cm < min_dist) cm = min_dist;
      if (cm > max_dist) cm = max_dist;
      
      //convert distance to servo angle
      float t = (cm - min_dist) / (max_dist - min_dist);
      return 35 + (int)(t*(120-35));
    }

    If the distance is valid, it converts it to an angle that can be passed on to the servo using the distanceToAngle function.

     //Detect beat
      float velocity = lastDistance - dist;
      //within the window that we defined
      bool inRange = (dist >= min_dist && dist <= max_dist);
    
      //valid hand movement, continues
      if (inRange && velocity > 0.4) {
        approaching = true;
      }

    This section is responsible for detecting whether the hand is moving toward the sensor fast enough to count as a beat gesture.

    dist is the distance from the ultrasonic sensor. lastDistance is the previous loop’s distance, and is stored to compare how much the distance changed since then. In other words, if the hand moves quickly, the difference between those two values becomes bigger.

      velocity = lastDistance - dist;

    Therefore, the formula above gives an estimate of how fast the hand is moving. If the hand moves closer to the sensor, then emaDist (current distance) becomes smaller, lastDistance – dist becomes positive, and this represents positive velocity, vice versa.

    //within the window that we defined
      bool inRange = (dist >= min_dist && dist <= max_dist);
    
      //valid hand movement, continues
      if (inRange && velocity > 0.4) {
        approaching = true;
      }

    The window min and max defines the detection zone that we actually consider about, if the hand isn’t in this zone, we ignore the movement, and this is used to prevent false triggers. And approaching is simply a boolean variable that is true when the hand is inside this distance zone.

    if (valid && approaching && reversed && (now - lastBeat > min_beat_gap)){
        triggerBeat();
        record(now);
        approaching = false;
      }

    Finally, the if statement considers whether the hand is inside the valid distance zone and is moving toward the sensor fast enough, if so, it triggers a beat by calling that function.

    float readDistanceCM() {
      pinMode(ultrasonic_pin, OUTPUT);
      digitalWrite(ultrasonic_pin, LOW);
      delayMicroseconds(2);
      digitalWrite(ultrasonic_pin, HIGH);
      delayMicroseconds(10);
      digitalWrite(ultrasonic_pin, LOW);
      
      //echo
      pinMode(ultrasonic_pin, INPUT);
      unsigned long dur = pulseIn(ultrasonic_pin, HIGH, 15000);
      
      if (dur==0) return -1;
      
      float cm = dur / 58.0;
      if (cm < 2 || cm > 400) return -1;
      
      return cm;
    }

    Next up, this function is in charge of calculating the distance from the hand. The logic of it is to first send a short ultrasonic pulse, and wait for the echo to return. And by using this data, it is able to convert that time into a distance measurement.

      float dist = readDistanceCM();

    We have actually mentioned it previously, and called it to store it inside the dist variable.

    pinMode(ULTRA_PIN, OUTPUT);
    digitalWrite(ULTRA_PIN, LOW);
    delayMicroseconds(2);
    digitalWrite(ULTRA_PIN, HIGH);
    delayMicroseconds(10);
    digitalWrite(ULTRA_PIN, LOW);

    This line first allows assigns a pin and mode for the sensor. And create a pulse starting with low for 2 microseconds, high for 10 microseconds, and back to low again.

    pinMode(ULTRA_PIN, INPUT);

    After that, it immediately switched to input mode to listen for the echo returning.

    unsigned long dur = pulseIn(ULTRA_PIN, HIGH, 15000UL);
    float cm = dur / 58.0;

    Finally, this standard formula converts the echo received into distance in centimeters.

    Above, I have explained all the essential parts of the code. Most of the program’s logic is organized inside the main loop, but to keep the code cleaner and easier, I moved any repeated or commonly used operations into separate functions.

    Physical Prototype:

    While I really want to build the project out in real life on a real Arduino board, my computer unfortunately couldn’t detect the port connected to it. I plan on fixing this issue, and once it’s revolved, I’ll update this post with a demonstration of the physical prototype.

    For now, the Tinkercad prototype functions perfectly, and as a reader, you are able to replicate the project with the wiring and code provided.

    Benefits of making a physical prototype:

    Right now, when running the code online in Tinkercad, I’ve noticed some lag and unresponsiveness. And by compiling the code on a real Arduino board, this issue can be eliminated. Moreover, building a physical prototype provides a more engaging and realistic experience, as you are able to interact with it directly and can have a better user experience.

    Reflections on AI

    I used GPT-5 as my AI assistant throughout this project. I chose to use GPT not only because I am more familiar with it, but also because it knows me better in terms of my working style and preferences. Over time, it has learned about my personality and requirements stored as a memory, allowing it to provide to responses that I feel more suitable for me.

    And in the project, I used AI for a lot of several tasks. This includes: debugging, suggesting better algorithms, and polishing my wording of this blog post to make it sound a bit smoother. In addition, since it had been a while since I last programmed with arduino, I have asked it for guidance on parts of the code.

    I chose to use AI because it helps me to save time, while still allowing active learning for myself through problem solving, thinking creatively, and deepen my understanding of coding.

    Source Code
    #include <Servo.h>
    #include <LiquidCrystal_I2C.h>
    
    LiquidCrystal_I2C lcd(0x20, 16, 2);
    Servo arm;
    
    //pins
    const int ultrasonic_pin = 9;
    const int servo_pin = 6;
    const int buzzer_pin = 3;
    
    //window
    const int min_dist = 60;
    const int max_dist = 150;
    
    //beat
    float lastDistance = 0;
    float lastVelocity = 0;
    bool approaching = false;
    
    unsigned long lastBeat = 0;
    const unsigned long min_beat_gap = 250;
    
    //BPM
    unsigned long history[4] = {0};
    int Pos = 0;
    bool Full = false;
    float bpm = 0;
    
    //Notes
    const int notes[] = {196,220,247,294,330,392,440,494};
    const int note_count = sizeof(notes) / sizeof(int);
    
    void setup() {
      Serial.begin(115200);
      
      pinMode(buzzer_pin, OUTPUT);
      digitalWrite(buzzer_pin, LOW);
      
      //setup lcd
      lcd.init();
      lcd.backlight();
      lcd.clear();
      
      //reset servo position
      arm.attach(servo_pin);
      arm.write(90);
      
      randomSeed(analogRead(A0));
      //start timer
      lastBeat = millis();
    
    }
    
    void loop(){
      unsigned long now = millis();
      
      //Read distance
      float dist = readDistanceCM();
      //positive value, continues
      bool valid = (dist>0);
      
      if (valid){
        //servo follows hand
        arm.write(distanceToAngle(dist));
      }
      
      //Detect beat
      float velocity = lastDistance - dist;
      //within the window that we defined
      bool inRange = (dist >= min_dist && dist <= max_dist);
    
      //valid hand movement, continues
      if (inRange && velocity > 0.4) {
        approaching = true;
      }
      
      //for detecting hand movement coming back
      bool reversed = (lastVelocity > 0 && velocity < 0);
      
      if (valid && approaching && reversed && (now - lastBeat > min_beat_gap)){
        triggerBeat();
        record(now);
        approaching = false;
      }
      
      lastVelocity = velocity;
      lastDistance = dist;
      
      //lcd
      static unsigned long lastLCD = 0;
      if (now - lastLCD > 200) {
        lastLCD = now;
        updateLCD(valid ? dist : -1, bpm);
      }
        
      delay(15);
    
    }
    
    
    float readDistanceCM() {
      pinMode(ultrasonic_pin, OUTPUT);
      digitalWrite(ultrasonic_pin, LOW);
      delayMicroseconds(2);
      digitalWrite(ultrasonic_pin, HIGH);
      delayMicroseconds(10);
      digitalWrite(ultrasonic_pin, LOW);
      
      //echo
      pinMode(ultrasonic_pin, INPUT);
      unsigned long dur = pulseIn(ultrasonic_pin, HIGH, 15000);
      
      if (dur==0) return -1;
      
      float cm = dur / 58.0;
      if (cm < 2 || cm > 400) return -1;
      
      return cm;
    }
    
    
    int distanceToAngle(float cm) {
      //detect distance inside the window
      if (cm < min_dist) cm = min_dist;
      if (cm > max_dist) cm = max_dist;
      
      //convert distance to servo angle
      float t = (cm - min_dist) / (max_dist - min_dist);
      return 35 + (int)(t*(120-35));
    }
    
    void triggerBeat() {
      int note = notes[random(note_count)];
      tone(buzzer_pin, note, 120);
      lastBeat = millis();
    }
    
    void record(unsigned long now) {
      unsigned long x = now - lastBeat;
      history[Pos] = x;
      Pos = (Pos + 1) % 4;
      
      //true once we have stored 4 intervals
      if (Pos == 0) Full = true;
      
      if (Full) {
        unsigned long sum = 0;
        for (int i=0; i<4; i++) {
          sum += history[i];
        }
        
        float avg = sum / 4.0;
        bpm = 60000.0 / avg;
        
      }
    }
    
    void updateLCD(float dist, float bpm) {
      //print distance
      lcd.setCursor(0, 0);
      if (dist < 0) {
        lcd.print("Dist: --       ");
      } else {
        lcd.print("Dist: ");
        lcd.print((int)dist);
        lcd.print("cm    ");
      }
    
      lcd.setCursor(0, 1);
      lcd.print("BPM: ");
      if (bpm > 0) lcd.print((int)bpm);
      else lcd.print("--");
      lcd.print("       ");
    }
    
  • CAD Project – Building a Miniature Rowing Boat in onShape

    picture of the final product

    Introduction:

    In this project, I wanted to design a miniature of a rowing scull using Onshape. The goal of this assignment was to apply fundamental CAD skills like sketching, extruding, and assembling to a real world object. Rowing is a sport that requires precision, balance and symmetry, and I wanted to capture that same spirit in my CAD model.

    I chose to replicate a rowing scull because I really enjoy doing the sport and it has been a major part of my life. Where I’ve been rowing for more than five years now and always admired the design of these boats. Additionally, I’ve always wanted to 3D print a model boat to display it on my desk, and this project felt like the perfect chance to turn that into a reality.

    Sketching 2D Design

    I began the design by first creating a part studio for the hull of the boat. Then I sketched the cross sections, also called stations (STA). And in boat designs, a station represents a specific slice along the boat’s length, and it’s almost like cutting the hull into thin vertical sections to see its internal shape at different points. Each cross-section shows the outline of the hull at that point, which helps to define the overall body curves from bow (the front of a rowing boat) to stern (the back of the boat).

    Using the planes, I drew ellipses from STA_0 (the bow) to STA_90 (the stern) to present the gradual transitions in width and height.

    cross section sketches

    Alongside the hull, I also made sketches for other parts like the rigger, oarlocks, and oars.

    sketch of the rigger

    Extrusion

    Next, I used the loft and extrude tools to create a smooth 3D solid object between the sketches. I then created another sketch for the cockpit opening on the top surface and used the extrude cut tool to remove material.

    hull after loft and extrude

    For other part studios, I also mainly used the extrusion tool to turn the sketch into a 3D object.

    rigger after extrude

    oarlock after extruding and applying fillets

    Assembly

    Once I’ve completed all individual parts in different studios, I created an assembly. I inserted the scull, rigger, oarlocks, and oars, then used fastened mates to secure all the parts to the boat. I also use the revolute mates when connecting the oarlock to the rigger to allow the oars to rotate around.

    I also played around with the animation for a while, I wasn’t able to find any ways to animate the oars on both sides together, so instead I’ve just showcased what it looks like animating one oar.

    Mechanical Drawings

    After completing the assembly, I created a drawing to communicate my design. The drawing for the assembly included top, front, and isometric views.

    For other individual parts, I included views from different angles and measurements.

    Bill of materials (BOM)

    The BOM is quite straightforward, I just wanted to highlight on the choice of materials. For several parts I chose to use carbon fiber as it is lightweight, durable, and also because most boats in real life use this material to improve speed and balance.

    Conclusion

    I really enjoyed doing this assignment and create my own 3D model of a sculling boat. During the process, I definitely learned more about 3D designs and CAD skills.

    Thanks for reading!

  • Coding Project – Akinator

    For my coding assignment, I’ve created an Akinator like guessing game in Python, where the computer tries to figure out the character the user is thinking of. The program I created uses
    AI to generate simple yes/no questions, and the user responds until the AI makes a guess.

    Below is the flowchart I made for my program:

    Flowchart

    Image URL (clearer version): https://i.imgur.com/ddYnl0R.png

    Source Code: https://replit.com/@michaelhu28/Python?v=1

    1. Variables

    history = ""      # stores past questions and answers
    game_over = False # tracks if the game has ended
    question_count = 0 # counts number of questions asked

    This is part of the variables I’ve defined to keep track of the game’s progress. The history is a. string which stores a log of the entire conversation. game_over is a boolean (meaning can be assigned True of False) that stores the status of the game. And question_count keeps a total of how many questions the AI has asked.

    2. Functions

    def get_user_answer():
      #Gets user input
      while True:
        answer = input("You: ").strip().lower()
        if answer in ["yes", "y","no","n","maybe","m","stop","quit","q"]:
          if answer in ["yes", "y"]:
            return "yes"
          elif answer in ["no", "n"]:
            return "no"
          elif answer in ["maybe", "m"]:
            return "maybe"
          elif answer in ["stop", "quit", "q"]:
            return "stop"
          else:
            print("Please answer with yes, no, maybe, or quit.")

    This function validates the user’s input. Instead of crashing or accepting a random input, this function keeps asking the user until they provide a valid answer. It also takes into account different forms of input (like “y” and “yes”) into consistent outputs for the AI.

    3. Conditional Statements:

    if ai_output["type"] == "guess":
        ask = input("Is this correct? (yes/no): ").strip().lower()
        if ask in ["yes", "y"]:
            print("Great! I guessed it right.")
            print(f"It took me {question_count} questions to guess it.")
            game_over = True
        else:
            print("Sorry, I couldn't guess it right. Let's try again.")
            history += f"\nAI: {ai_output}\nYou: no (incorrect guess)"
            question_count += 1

    This portion of the code checks if the AI is making a guess or asking a question. This is made possible by prompting the AI to respond in a JSON format. If the type of response is a guess and is correct, it ends the game with a success message.

    Example response format:

    { "type": "question", "message": "Is your person real, not fictional?", 
    
    "confidence": 0.01, 
    
    "state": { "q_count": 1, "top_candidates": ["Barack Obama","Harry Potter","Elvis Presley"], 
    
    "notes": "Starting with real vs fictional split for major info gain." } 
    }

    Prompt Engineering:

    I have explored with the prompt and the different models to let the AI guess correctly using the least amount of questions. (left is the new prompt, right is the old one)

    Pros and Cons of the New Prompt

    ProsCons
    Improves the accuracy of the response.A longer prompt would result in longer response times.
    Specifies the structure of the response, making it readable by the program.Generating structured JSON with all that information can increase the token load.

    Result:

    When thinking of the same character(Mr. Beast), by using the new prompt, the program is able to guess it correctly using less amount of questions (15 questions – 22 questions). The new prompt also shows a confidence score when asking the question.

    What I Learned

    • How to use flowcharting to plan my logic before coding.
    • How to connect Python with the OpenAI API and structure prompts.
    • How to use loops, conditionals, and functions to make code readable and organized.

    Future Improvements

    • Add a confidence score so the AI can explain how sure it is about a guess. (Added)
    • Save the transcript of each game to a file so I can analyze failures.
    • Build a version with GUI

    Update (Sep. 28):

    After receiving feedback that my original setup required a paid API and wasn’t directly runnable, I updated my project to use the Google Gemini API. This change allows anyone to clone the project and run the program for free without extra setup. To make the project accessible over time, I also recorded a short walk-through video demonstrating how the game works in action. This way, even if the API service changes or keys expire, readers can still clearly see the program’s functionality.

    Thanks for Reading my First Blog Post!