More selected projects

Twitches

Twitches is a generative creature creator that hooks into active Twitch.tv streams. Each user in the chat seeds their own creature and influences its growth and personality through chatting and common Twitch emotes.

produced by: Andrew Thompson

 

The Idea

Twitch chat, like any other social media platform, has grown into a unique and vibrant community. Although not much more than a simple IRC chat, Twitch chat has developed its own social code on the basis of emotes. To capture Twitch's essence, I have developed a generative system of creatures, each seeded by users in Twitch chat, and each controlled through messages and emotes.

 

 

The concept began life as coloured squiggles, but as the project evolved these became more stylised; varying line thickness, the way they move, and adding body parts combined to develop the creatures with distinct personalities. 

 

  • gallery-image
  • gallery-image
  • gallery-image
 
How It Works

Each user (up to 25) seeds a new creature in the piece. The first eight letters of the username provide the basis of the creature's genes, if the username has less than eight characters (four is the minimum length on Twitch) then random values are generated. First, the character values are mapped 0-25 and passed into the setGenes function:

void setGenes(int DNA[8]) { 
    bodySize = DNA[0] + 10; 
    headSize = int(bodySize / 2); 
    thickness = ofMap(DNA[1], 0, 25, 2, 5); 
    color1 = setColor(DNA[2]); 
    color2 = setColor(DNA[3]); 
    bounceDirection = ofMap(DNA[4], 0, 25, 0, 3); 
    bounceAmount = ofMap(DNA[5], 0, 25, 0.8, 2.2); 
    bounceSpeed = ofMap(DNA[6], 0, 25, 1.5, 7.5); 
    limbCount = int(ofMap(DNA[7], 0, 25, 3, 5)); 
    limbs.resize(limbCount); 
    limbs[0] = ofPoint(0, ofRandom(-headSize)); 
    for(int i = 1; i < limbs.size(); i++) { 
        // First point reaches from the centre outward. 
        // This makes it easier to mirror later. 
        limbs[i] = ofPoint(ofRandom(0, bodySize), ofRandom(-bodySize, bodySize)); 
    } 
}; 

The genes array follows a consistent format because of the previous character mapping; this means arbitrary data inputs can be used to set the genes whilst providing predictable behaviour. Because of this, further mapping occurs within the function to turn the genes into usable values appropriate for each genes specific function. The indices in the gene array correspond to the creature as follows:

  • [0] = max limb length.
  • [1] = limb thickness.
  • [2] = primary colour.
  • [3] = secondary colour.
  • [4] = creature bounce direction (up/down, sideways, or rotating in place).
  • [5] = bounce height.
  • [6] = bounce speed.
  • [7] = initial limb count.

 

Users interact with their creatures through the messages they send in chat. It's important to note that this interaction is invisible; the program joins random popular streams instead of reading from its own chat. For the viewer (who cannot see the chat), the creatures seem to grow and change organically.

 

Messages manipulate the creatures by introducing mutations.  Some mutations are immediately apparent, affecting the creatures behaviour or growing/removing limbs, others, affect genes "under the hood" and control future generations. Additionally, messages that contain iconic Twitch emotes have specific behaviour.

  • PogChamp && CoolStoryBob = animate creature.
  • KappaPride = increase body and head size.
  • NotLikeThis = decrease body and head size.
  • Kreygasm = increase limb count.
  • Kappa && KappaRoss = regrow creature with new genes.

 

 
Connecting To Twitch Chat

Twitches connects to Twitch chat via Twitch's IRC server using a simple TCP client. I abstracted this functionality into a separate class for future use and release. receiveMessages() is a private method of the ofxTwitchClient class, and handles getting data from the TCP server and parsing that data into useful messages. Messages come in three main types (although the server is capable of sending more if the client subscribes to them):

  • PING requests.
  • Server messages.
  • Channel messages (weirdly sent as "PRIVMSG").

 

PING requests are sent every 5 minutes and must be responded with PONG, this ensures the server doesn't keep open any dead connections. Server messages are sent from "tmi.twitch.tv" so we can filter these messages by searching for that substring. Finally, channel messages contain the substring "PRIVMSG": searching for this allows us to determine that a channel message has been received, from there the message sender and content can be extracted.

void ofxTwitchClient::receiveMessages() {
    // Whenever working with a TCP client you must check to
    // see if a connection has already been established before
    // attempting to receive data.
    if(twitchIRC.isConnected()) {
        string message = twitchIRC.receive();
        
        while(message.length() > 0) {
            if(message.substr(0, 4) == "PING") {
                // Twitch sends a ping every five minutes to ensure dead
                // connections don't stay open.
                twitchIRC.sendRaw("PONG\r\n");
            } else if(message.substr(0, 14) == ":tmi.twitch.tv") {
                // Messages from the server are logged directly to the terminal.
                if(log) ofLogNotice("ofxTwitchClient") << message;
            } else if(size_t found = message.find("PRIVMSG") != string::npos) {
                // Twitch IRC messages are formatted in a particular and consistant manner.
                // Knowing this we can extract sender and content from the entire message
                // by grabbing substrings.
                string sender  = message.substr(message.find(':')+1,
                                                message.find('!')-1);
                string content = message.substr(message.find(channel) + channel.length() + 2);
                
                messageIn.push_back(sender + ":" + content);
                
                if(log) ofLogNotice("ofxTwitchClient") << messageIn.back();
            }
            // Essentially a recursive call. Makes sure we receive all messages
            // every time we check.
            message = twitchIRC.receive();
        }
    } else if(log) ofLogNotice("ofxTwitchClient") << "Not connected to IRC server.";
};

Messages are stored in a String vector and retrieved one-by-one with a public getter method. This pops the newest message off the back of the vector and returns it. A public available() method returns a boolean indicating whether or not a message is available (aka, the vector is not size 0).

// In ofxTwitchClient.cpp
bool ofxTwitchClient::available() {
    if(messageIn.size() > 0) return true;
    else return false;
};
//------------------------------------------------------------------//
string ofxTwitchClient::getMessage() {
    // Grab the newest message.
    string message = messageIn.back();
    messageIn.pop_back();
    
    return message;
};

// In ofApp.cpp
while(twitchChat.available()) {
    string twitchMsg = twitchChat.getMessage();
    // Do something with message...
}