conye – clock

Here are the gifs of the finished piece, which is a robot that displays when you will need your next 8 ounces of coffee. When you press “REFUEL” it will record the current time and set a 6-hour timer (the internet says the average time it takes for caffeine to wear off is 4 – 6 hours). During the 6 hour period, the robot will first be awake and energetic, and then it will slowly become sleepier until the percent of “fuel” reaches zero.

Stage one: Sleepy and not ready for anything.

Stage two: Just caffeinated. Ready for everything!!!!! Overstimulated!! While doing research (aka an extensive google search) I read multiple times that caffeine is supposed to induce a temporary euphoria, but I personally have never experienced that.

Note that in these gifs I sped up the countdown from 6 hours to 60 seconds so that the decreasing fuel meter would show.

Stage three: 50% caffeinated. Falling back into sleepiness and general dissatisfaction with being tired, but also sadness over caffeine dependency. You can see briefly at the beginning the transition from awake to not awake; I switch the animations when the percentage dips below 50%.

You can try it here!

 

Here are some sketches for this:

My inspiration for this “clock” came from the idea that for some people, coffee in the morning is like their fuel for the day. I was hoping to display a progression of time by showing the gradual progression of the effects of caffeine. This is pretty trivial, but I was also really excited to use a car gas meter to represent the amount of caffeine left, mostly because I imagined that it would be a cool aesthetic.

The process:

I had originally planned to use a variety of factors, such as caffeine tolerance levels and the strength and amount of the coffee to influence the countdown time. However, after looking for studies about caffeine tolerance, I couldn’t find any solid numbers to use to predict how long caffeine would last for different tolerance levels, so I had to use 6 hours, which seemed to be the norm for normal, non-caffeine immune humans.

The original, naked-looking javascript sketch:

After putting together my clock, I asked Golan what he thought about it, and he asked me why specifically I had chosen a robot to represent the levels of caffeine. It was a really good point because in my mind the association was clear (caffeine -> fuel -> robot), but it probably wasn’t a universal association, which I hadn’t considered. In the future, before I begin, I’ll spend more time looking at my concept critically instead of getting overly excited and attached and beginning immediately.

It was too late to switch it from a robot at that point, but I realized afterward that I could use the expressiveness of a robot to convey different “fuel levels.” I had included it in my sketches but hadn’t fully maximized the movements. So I made two different eye and mouth animations (one for the sleepy state, one for awake), and also made the arms swing faster when the fuel levels were higher.

If I had more time, I would like to add a way for it to take different amounts of coffee as input. I thought it would be fun to have the meter go beyond full (110% caffeinated) and to have the robot go a little crazy if the user “refueled” too much. I had also considered using cookies or a username/database (firebase maybe) system to store the number of cups consumed overall, and the amount would be displayed in tally marks in the background. This would also mean that if you refueled, left the page and came back 3 hours later, the counter would still be intact.

The p5 code is below.

var button4, button8, prevSec;
//times expressed in seconds
var refuelTime, expireTime, tick;
var totalSeconds;
var percent;
var initializing = false;
//the moving bits
var head, body, eyes, eyestired, thought, bubble1, bubble2, arm, leg, middle, mouthawake, mouthtired;
var myFont;
var cloudOffset;
var ticks = [];
 
//info collected
//for a normal person, the half life is 4-6 hours
//you'll feel the effects most prominently 30 - 60 min after consumption
function preload(){
    myFont = loadFont("Jura-Bold.ttf");
    head = loadImage("head.png");
    body = loadImage("body.png");
    eyes = loadAnimation("eyes-open.png", 
        "eyes-open.png", "eyes-open.png",
        "eyes-open.png", "eyes-open.png",
        "eyes-open.png", "eyes-open.png",
        "eyes-open.png", "eyes-open.png",
        "eyes-open.png", "eyes-open.png",
        "eyes-open.png", "eyes-open.png",
        "eyes-open.png", "eyes-open.png",
        "eyes-open.png", "eyes-blink.png");
 
    eyestired = loadAnimation("eyes-blink.png", 
        "eyes-blink2.png", "eyes-blink2.png",
        "eyes-blink2.png", "eyes-blink2.png",
        "eyes-blink.png", "eyes-blink.png",
        "eyes-blink.png", "eyes-blink.png",
        "eyes-blink.png", "eyes-blink.png",
        "eyes-blink.png", "eyes-blink.png",
        "eyes-blink.png", "eyes-blink.png",
        "eyes-blink.png", "eyes-blink.png");
 
    thought = loadAnimation("thought1.png", "thought2.png");
    thought.frameDelay = 30;
    bubble1 = loadImage("smallthought.png");
    bubble2 = loadImage("smallthought2.png");
    arm = loadAnimation("arms1.png", "arms2.png", "arms3.png", "arms4.png", "arms3.png", "arms2.png");
    leg = loadImage("legs.png");
    middle = loadImage("clock-middle.png");
    mouthawake = loadAnimation("awake-mouth1.png", "awake-mouth2.png");
    mouthawake.frameDelay = 30;
    mouthtired = loadAnimation("unawake-mouth1.png", "unawake-mouth2.png");
    mouthtired.frameDelay = 30;
}  
 
function setup() {
    createCanvas(600, 600);
    scale(600.0/720.0, 600.0/720.0);
    refuelTime = null;
    expireTime = 6 * 60 * 60; //6 hours
    percent = 0;
    textFont(myFont);
    initializeTicks();
 
    button8 = createButton('R E F U E L');
    button8.position(317, 367);
    button8.mousePressed(refuel8);
    button8.style("font-size", "15px");
    button8.style("font-weight", "700");
    button8.style("width", "100px");
    button8.style("background-color", "#d8cecd");
 
    totalSeconds = 60 * 60 * hour() + 60 * minute() + second();
    drawBot();
}
 
function draw() {
    scale(600.0/720.0, 600.0/720.0);
    background("#C2D9D6");
    drawBot();
 
    totalSeconds = 60 * 60 * hour() + 60 * minute() + second();
 
    //if refuel time was before midnight, but it turns into a new day
    if(refuelTime != null && refuelTime.totalSeconds > totalSeconds){
        refuelTime -= 86400;
    }
 
    displayStats();
 
    //stop clock if out of caffeine
    if (percent < 0) { percent = -0.02; refuelTime = null; } if (initializing) { percent += 0.01; if (percent >= 1) {
            initializing = false;
            percent = 1 - (totalSeconds - refuelTime.totalSeconds) / (expireTime + 0.0);
        }
    } else if (refuelTime != null) {
        percent = 1 - (totalSeconds - refuelTime.totalSeconds) / (expireTime + 0.0);
    }
    strokeWeight(10);
    stroke(255, 100, 100);
    line(440, 450, 440 + 90 * cos(radians(ang())), 450 + 90 * sin(radians(ang())));
}
 
function ang() {
    return -90 + (1 - percent) * 210.0;
}
 
function displayStats(){
    cloudOffset = map(Math.abs(frameCount % 50 - 25), 0, 50, 0, 10);
    //display stats
    animation(thought, 150, 120  + cloudOffset);
    image(bubble1, 180, 210 + cloudOffset, 60, 60);
    image(bubble2, 220, 260 + cloudOffset, 40, 40);
    textSize(18);
    strokeWeight(2);
    noStroke();
    fill(0);
    text("You are " + Math.floor(percent * 100) + "% caffeinated.", 34, 150 + cloudOffset);
    if(refuelTime != null){
        if(refuelTime.minute < 10){
            text("Time Of Last Coffee: ", 70, 90 + cloudOffset);
            if(refuelTime.hour < 10){
                text("0" + refuelTime.hour + " : 0" + refuelTime.minute, 110, 110 + cloudOffset);
            }
            else 
                text(refuelTime.hour + " : 0" + refuelTime.minute, 110, 110 + cloudOffset);
        }
        else{
            text("Time Of Last Coffee: ", 70, 90 + cloudOffset);
            if(refuelTime.hour < 10){
                text("0" + refuelTime.hour + " : " + refuelTime.minute, 110, 110 + cloudOffset);
            }
            else{
                text(refuelTime.hour + " : " + refuelTime.minute, 110, 110 + cloudOffset);
            }
        }
    }
    else{
        text("Uh oh.. looks like you", 50, 90 + cloudOffset);
        text("need more coffee :'|", 50, 110 + cloudOffset);
    }
}
 
function refuel8() {
    if(percent < 0) percent = 0;
    initializing = true;
    tick = 0;
    refuelTime = new Time(hour(), minute(), second());
}
 
function drawBot() {
    if(refuelTime == null){
        arm.stop();
    }
    else{
        arm.play();
    }
    arm.frameDelay = Math.floor(map(percent, 0, 1, 30, 3));
    noStroke();
    image(leg, 233, 550);
    image(body, 243, 268);
    animation(arm, 438, 400);
    fill("#1b0036");
    image(middle, 290, 300, 300, 300);
    strokeCap(SQUARE);
    strokeWeight(7);
    for(var i = 1; i < 15; i++){ if(ticks[i].angle > ang()){
            stroke(ticks[i].col);  
        }
        else{
            stroke(100);
        }    
        line(440 + 115 * cos(radians(ticks[i].angle)), 450 + 115* sin(radians(ticks[i].angle)), 440 + 140 * cos(radians(ticks[i].angle)), 450 + 140 *sin(radians(ticks[i].angle)));
    }
    noStroke();
    fill("#1b0036");
    //ellipse(440, 450, 250, 250);
    fill(255);
    textSize(30);
    text("F", 437, 335);
    text("E", 360, 560);
    image(head, 280, 80);
 
    if(percent > 0.5){
        animation(eyes, 435, 170);
        animation(mouthawake, 435, 200);
    }
    else{
        animation(eyestired, 435, 170);
        animation(mouthtired, 435, 200);
    }
 
    stroke(255, 100, 100);
}
 
function Time(hour, minute, second) {
    this.totalSeconds = hour * 3600 + minute * 60 + second;
    this.hour = hour;
    this.minute = minute;
    this.second = second;
}
 
function initializeTicks(){
    for(var i = 0; i < 15; i++){
        var r = 156 + (355 - 156)*(i / 20.0);
        var g = 203 + (166 - 203)*(i / 20.0);
        var b = 138 + (166 - 138)*(i / 20.0);
        ticks.push(new Meter(r, g, b, -90 + i * (210/15)));
    }
}
 
function Meter(r, g, b, angle){
    this.col = color(r, g, b);
    this.angle = angle;
}