Introduction: Simon the Octopus

Everyone loves the Simon game ! So we decided to take it to another level. 
This project was done for our Things That Think class where we had to make a PlushBot (a plush toy that thinks! "with computation") thus, kids were our target.

We decided on an octopus that would have all eight arms flashing, however, due to the limited number of pins on the Lilypad Arduino micro-controller, the number of functioning arms were decreased to four.

This project has been done for the Things That Think class at CU Boulder.
 


Tools used: 
- Sewing machine.
- Laser cutter (possibly replaced by precise manual work)
- Lilypad Arduino
- Fennel fabric 
- Conductive thread
- Octopus pattern
- 10k Resistors
-
Alligator clips (for testing)

Step 1: Design

- Body : For the octopus body and head, we used a preset pattern (as shown below) and scaled it up to the size we needed, and used the laser cutter to have it cleanly cut.

- The pieces were sewn on the inside using the sewing machine, flipped and then stuffed.

- Arms Wiring:
For each leg there will be 4 wires/threads coming out, 2 for the button and 2 for the light.  
For each button one end will go to positive and the other will go to an input pin labeled in the code.
For the lights the negatives will all be pulled to negative and the positive will be pulled to an output pin labeled in the code.

- To avoid the conductive threads crossing (which would cause circuit shorts) we had layers of fabric on the bottom of the Lilypad to separate them.

Step 2: Code

- We used the buzzer, LEDs, and buttons that were included with the lillypad.

In order to get the buzzer to work there are two calls that you need: tone() and noTone().
As a quick note the tone() function interferes with the PWM output on pins 3 and 11 except on the Mega.
The function definition of tone is:
tone(pin, frequency) or tone(pin, frequency, duration)
If the duration is not specified the tone will continue to play until the noTone() function is called
applied to pin. The function definition of noTone() is simply noTone(pin).
You can only play one tone on the pin at a time, this wasn't an issue for us because
we only used one tone per leg.

Our original idea was to use each leg for a different note in the key of C and have
an entire octive. Unfortunately, we did not use all 8 legs for this but with the
right hardware you would just need to make a few mall changes to the code below
to have that working.

There is a nice header file that is included in the arduino package that defines
all of the frequencies to the corresponding note name. Once this header file is
included all you need to do is call NOTE_C4 to have the buzzer play middle C or
if you need a sharp you just insert 'S' between the note name and number i.e.
NOTE_GS4.



In our code below you will notice that we are using an analog pin for one of the
leg buttons. We had to do this based on the lillypad layout as well as the total
number of pins we were using. We needed to do a few little hacks to get this to
work as the digital pins you can just check to see if the pin is high or low while
the analog outputs its voltage.

For each leg there is a button and a light. When the button is pressed the assigned
note gets activated through the sound pin and plays through the buzzer as well
as the LED in the leg lighting up.

When you win the first part of the Beatles Octopus's Garden plays followed by
the letter V. The song was done by placing the notes in an array and looping over
that array when the time is right.

The game is relatively simple. At the start of each cycle we check all of the buttons
to see if any of them are pressed. We did this with calls to digitalRead(). We then 'loop'
through the buttons and if there is a button that is active we set a variable that
stores the most recent pin. We do just check one after another so if you had pins
1 and 4 pressed at the same time it would set the variable to pin 4.

We then take that information and pass it into a switch statement which activates
the light and sound associated with the button pressed. Because of the way we
set everything up it will not light multiple legs or play multiple sounds. That
would be a nice improvement to this project. If you added 3 buzzers you could
play basic major and minor chords by pressing different legs. This would add a
real time music making aspect to the octopus. Also with 8 legs you have all of
the notes you would need to play basic songs in the key of C.

For the game aspect of the code after each iteration through the main loop we check
that we are in simon mode and that the button state has changed (a button had been
pressed). It checks the button value and compares it to the value saved in an array
that describes the game sequence so far. If the values match it adds a new value
to the end of the sequence and plays the entire updated sequence from the start.
If the values do not match it enters into the 'You Lose' loop which plays a loser
light sequence and resets the game.


#include "pitches.h"

int mode = 0; //0-Simon ... 1-sounds/lights

const int leg0B = A4;
const int leg0L = 13;
const int leg1B = 2;
const int leg1L = 3;
const int leg2B = 12;
const int leg2L = 11;
const int leg3B = 6;
const int leg3L = 7;
const int soundPin = 9;

int buttonState0 = 0;
int buttonState1 = 0;
int buttonState2 = 0;
int buttonState3 = 0;

int pauseBetweenNotes = 300;
int playTime = 500; //when playing the simon sequence

int pinsUsed= 4;
int lastPin = 0;
int currentPin = -1;

int lastLight = 0;

const int maxTurns = 50;
int game[maxTurns];
int turn = 0;
int arrayCounter = 0;

const int turnsToWin = 4;

unsigned long loseTime;
unsigned long currentTime;
unsigned long delayAllowed = 10000; //press a button within 4 seconds or you lose





// notes in the melody:
//Sharp F G C D
int melody[] = {
  NOTE_B4, NOTE_B4, NOTE_CS4, NOTE_B4, NOTE_B0,
  NOTE_GS4, NOTE_GS4, NOTE_A4, NOTE_GS4,
  NOTE_GS4, NOTE_FS4, NOTE_E4, NOTE_E4, NOTE_E4,
  NOTE_CS4, NOTE_B4, NOTE_GS4, NOTE_E4, NOTE_GS4,
  NOTE_FS4
};

// note durations: 4 = quarter note, 8 = eighth note, etc.:
int noteDurations[] = {
  4, 4, 4, 1, 4,
  4, 4, 4, 1,
  8, 8, 4, 4, 4,
  4, 4, 2, 8, 8, 1 };



void setup() {
/*
for (int thisNote = 0; thisNote < 20; thisNote++) {

    // to calculate the note duration, take one second
    // divided by the note type.
    //e.g. quarter note = 1000 / 4, eighth note = 1000/8, etc.
    int noteDuration = 1000/noteDurations[thisNote];
    tone(soundPin, melody[thisNote],noteDuration);

    // to distinguish the notes, set a minimum time between them.
    // the note's duration + 30% seems to work well:
    int pauseBetweenNotes = noteDuration * 1.30;
    delay(pauseBetweenNotes);
    // stop the tone playing:
    noTone(soundPin);

  }
  */


Serial.begin(9600);
millis();  // initialize the legxB pins as a input:
  pinMode(leg0B, INPUT);
  pinMode(leg1B, INPUT);
  pinMode(leg2B, INPUT);
  pinMode(leg3B, INPUT);
//  pinMode(leg4B, INPUT);
//  pinMode(leg5B, INPUT);
//  pinMode(leg6B, INPUT);
//  pinMode(leg7B, INPUT);
  // initialize the LEDs as an output:
  pinMode(leg0L, OUTPUT);
  pinMode(leg1L, OUTPUT);
  pinMode(leg2L, OUTPUT);
  pinMode(leg3L, OUTPUT);
//  pinMode(leg4L, OUTPUT);
//  pinMode(leg5L, OUTPUT);
//  pinMode(leg6L, OUTPUT);
//  pinMode(leg7L, OUTPUT);
  // initialize the sound pin as output:
  pinMode(soundPin, OUTPUT);
  // to distinguish the notes, set a minimum time between them.
  // the note's duration + 30% seems to work well:
//  int pauseBetweenNotes = noteDuration * 1.30;

  game[0] = -1;
}

void loop() {

  // detect the button pressed
  buttonState0 = analogRead(leg0B);
  Serial.println(buttonState0);
  buttonState1 = digitalRead(leg1B);
  buttonState2 = digitalRead(leg2B);
  buttonState3 = digitalRead(leg3B);
/*
  buttonState4 = digitalRead(leg4B);
  buttonState5 = digitalRead(leg5B);
  buttonState6 = digitalRead(leg6B);
  buttonState7 = digitalRead(leg7B);
*/
  // set var based on the button pressed
  currentPin = -1; //-1 if no buttons
//  if(buttonState0 == HIGH)
  if(buttonState0 > 200)
    currentPin = 0;
  if(buttonState1 == HIGH)
    currentPin = 1;
  if(buttonState2 == HIGH)
    currentPin = 2;
  if(buttonState3 == HIGH)
    currentPin = 3;
Serial.println(currentPin);
/*
  if(buttonState4 == HIGH)
    currentPin = 4;
  if(buttonState5 == HIGH)
    currentPin = 5;
  if(buttonState6 == HIGH)
    currentPin = 6;
  if(buttonState7 == HIGH)
    currentPin = 7;
*/
currentTime = millis();
//loseTime = millis() + delayAllowed;

if(mode == 0 && currentTime > loseTime)
{
  gameLost(0);
}

if(mode == 0 && game[0] == -1)
{
  Serial.println("GAME START");
  turn = 0;
  addRound();
}

  if(currentPin != lastPin)
  {
    digitalWrite(lastLight, LOW);
    if(mode == 0 && currentPin != -1)//simon mode
    {
      if(game[arrayCounter] != currentPin)
      {
        gameLost(1);
        currentPin = -1;
      }
      else
      {
        arrayCounter++;
        currentTime = millis();
        loseTime = millis() + delayAllowed;
      
      }
    }

    switch (currentPin) {
      case -1:
        noTone(soundPin);
        Serial.println("case -1");
        break;
      case 0:
        Serial.println("case 0");
        //play sound in leg 0 as well as light up the leg
        digitalWrite(leg0L, HIGH);
        lastLight = leg0L;
        tone(soundPin, NOTE_C4);
        //delay(pauseBetweenNotes);
        //noTone(soundPin);
        //digitalWrite(leg0L, LOW);
        break;
      case 1:
      Serial.println("case 1");
        //play sound in leg 1 as well as light up the leg
        digitalWrite(leg1L, HIGH);
        lastLight = leg1L;
        tone(soundPin, NOTE_D4);
        //delay(pauseBetweenNotes);
        //noTone(soundPin);
        //digitalWrite(leg1L, LOW);
        break;
      case 2:
      Serial.println("case 2");
        //play sound in leg 2 as well as light up the leg
        digitalWrite(leg2L, HIGH);
        lastLight = leg2L;
        tone(soundPin, NOTE_E4);
        //delay(pauseBetweenNotes);
        //noTone(soundPin);
        //digitalWrite(leg2L, LOW);
        break;
      case 3:
      Serial.println("case 3");
        //play sound in leg 3 as well as light up the leg
        digitalWrite(leg3L, HIGH);
        lastLight = leg3L;
        tone(soundPin, NOTE_F4);
        //delay(pauseBetweenNotes);
        //noTone(soundPin);
        //digitalWrite(leg3L, LOW);
        break;
/*
      case 4:
        //play sound in leg 4 as well as light up the leg
        digitalWrite(leg4L, HIGH);
        lastLight = leg4L;
        tone(soundPin, NOTE_G4);
        //delay(pauseBetweenNotes);
        //noTone(soundPin);
        //digitalWrite(leg4L, LOW);
        break;
      case 5:
        //play sound in leg 5 as well as light up the leg
        digitalWrite(leg5L, HIGH);
        lastLight = leg5L;
        tone(soundPin, NOTE_A4);
        //delay(pauseBetweenNotes);
        //noTone(soundPin);
        //digitalWrite(leg5L, LOW);
        break;
      case 6:
        //play sound in leg 6 as well as light up the leg
        digitalWrite(leg6L, HIGH);
        lastLight = leg6L;
        tone(soundPin, NOTE_B4);
        //delay(pauseBetweenNotes);
        //noTone(soundPin);
        //digitalWrite(leg6L, LOW);
        break;
      case 7:
        //play sound in leg 7 as well as light up the leg
        digitalWrite(leg7L, HIGH);
        lastLight = leg7L;
        tone(soundPin, NOTE_C5);
        //delay(pauseBetweenNotes);
        //noTone(soundPin);
        //digitalWrite(leg7L, LOW);
        break;
*/
      //default:
        // if nothing else matches, do the default
        // default is optional
    }
    lastPin = currentPin;
  
    if(arrayCounter >= turn)
    {
      delay(500);
      digitalWrite(lastLight, LOW);
      noTone(soundPin);
      delay(1000);
      addRound();
    }
  }
delay(100);
}

void playSequence()
{
  Serial.println("playSequence");
  int seqCounter = 0;
  int value = game[seqCounter];
  int noteLength = 1000-(100*turn);
  if(noteLength < 500)
  {
    noteLength = 500;
  }
  while(value != -1)
  {
    switch (value) {
    case 0:
      //play sound in leg 0 as well as light up the leg
      digitalWrite(leg0L, HIGH);
      tone(soundPin, NOTE_C4);
      delay(noteLength);
      noTone(soundPin);
      digitalWrite(leg0L, LOW);
      break;
    case 1:
      //play sound in leg 1 as well as light up the leg
      digitalWrite(leg1L, HIGH);
      tone(soundPin, NOTE_D4);
      delay(noteLength);
      noTone(soundPin);
      digitalWrite(leg1L, LOW);
      break;
    case 2:
      //play sound in leg 2 as well as light up the leg
      digitalWrite(leg2L, HIGH);
      tone(soundPin, NOTE_E4);
      delay(noteLength);
      noTone(soundPin);
      digitalWrite(leg2L, LOW);
      break;
    case 3:
      //play sound in leg 3 as well as light up the leg
      digitalWrite(leg3L, HIGH);
      tone(soundPin, NOTE_F4);
      delay(noteLength);
      noTone(soundPin);
      digitalWrite(leg3L, LOW);
      break;
/*
    case 4:
      //play sound in leg 4 as well as light up the leg
      digitalWrite(leg4L, HIGH);
      tone(soundPin, NOTE_G4);
      delay(noteLength);
      noTone(soundPin);
      digitalWrite(leg4L, LOW);
      break;
    case 5:
      //play sound in leg 5 as well as light up the leg
      digitalWrite(leg5L, HIGH);
      tone(soundPin, NOTE_A4);
      delay(noteLength);
      noTone(soundPin);
      digitalWrite(leg5L, LOW);
      break;
    case 6:
      //play sound in leg 6 as well as light up the leg
      digitalWrite(leg6L, HIGH);
      tone(soundPin, NOTE_B4);
      delay(noteLength);
      noTone(soundPin);
      digitalWrite(leg6L, LOW);
      break;
    case 7:
      //play sound in leg 7 as well as light up the leg
      digitalWrite(leg7L, HIGH);
      tone(soundPin, NOTE_C5);
      delay(noteLength);
      noTone(soundPin);
      digitalWrite(leg7L, LOW);
      break;*/
    }
    seqCounter++;
    value = game[seqCounter];
    delay(pauseBetweenNotes);
  }
}

void addRound()
{
  Serial.print("ADDING TURN ");
  Serial.println(turn);
  long next = random(pinsUsed);
  if(turn > turnsToWin)
  {
    gameWon();
    currentPin = -1;
    lastPin = -1;
  }
  else
  {
    game[turn] = next;
    game[turn+1] = -1;
    turn++;
    playSequence();
    arrayCounter = 0;
    currentTime = millis();
    loseTime = currentTime + delayAllowed;
    Serial.print("Lose time and such - ");
    Serial.println(loseTime);
    Serial.print("current time - ");
    Serial.println(currentTime);
  }
}

void gameWon()
{


  
for (int thisNote = 0; thisNote < 20; thisNote++) {

    // to calculate the note duration, take one second
    // divided by the note type.
    //e.g. quarter note = 1000 / 4, eighth note = 1000/8, etc.
    int noteDuration = 1000/noteDurations[thisNote];
    tone(soundPin, melody[thisNote],noteDuration);

    // to distinguish the notes, set a minimum time between them.
    // the note's duration + 30% seems to work well:
    int pauseBetweenNotes = noteDuration * 1.30;
    delay(pauseBetweenNotes);
    // stop the tone playing:
    noTone(soundPin);

  }
  delay(10);




  digitalWrite(leg0L, HIGH);
digitalWrite(leg1L, HIGH);
digitalWrite(leg2L, HIGH);
digitalWrite(leg3L, HIGH);
tone(soundPin, NOTE_C4);
delay(pauseBetweenNotes);
noTone(soundPin);
digitalWrite(leg0L, LOW);
digitalWrite(leg1L, LOW);
digitalWrite(leg2L, LOW);
digitalWrite(leg3L, LOW);
delay(pauseBetweenNotes);

digitalWrite(leg0L, HIGH);
digitalWrite(leg1L, HIGH);
digitalWrite(leg2L, HIGH);
digitalWrite(leg3L, HIGH);
tone(soundPin, NOTE_C4);
delay(pauseBetweenNotes);
noTone(soundPin);
digitalWrite(leg0L, LOW);
digitalWrite(leg1L, LOW);
digitalWrite(leg2L, LOW);
digitalWrite(leg3L, LOW);
delay(pauseBetweenNotes);

digitalWrite(leg0L, HIGH);
digitalWrite(leg1L, HIGH);
digitalWrite(leg2L, HIGH);
digitalWrite(leg3L, HIGH);
tone(soundPin, NOTE_C4);
delay(pauseBetweenNotes);
noTone(soundPin);
digitalWrite(leg0L, LOW);
digitalWrite(leg1L, LOW);
digitalWrite(leg2L, LOW);
digitalWrite(leg3L, LOW);
delay(pauseBetweenNotes);

digitalWrite(leg0L, HIGH);
digitalWrite(leg1L, HIGH);
digitalWrite(leg2L, HIGH);
digitalWrite(leg3L, HIGH);
tone(soundPin, NOTE_C4);
delay(pauseBetweenNotes);
delay(pauseBetweenNotes);
delay(pauseBetweenNotes);
noTone(soundPin);
digitalWrite(leg0L, LOW);
digitalWrite(leg1L, LOW);
digitalWrite(leg2L, LOW);
digitalWrite(leg3L, LOW);
delay(pauseBetweenNotes);
delay(3000);
gameReset();

}

void gameLost(int i)
{
//S
digitalWrite(leg0L, HIGH);
digitalWrite(leg1L, HIGH);
digitalWrite(leg2L, HIGH);
digitalWrite(leg3L, HIGH);
tone(soundPin, NOTE_C4);
delay(pauseBetweenNotes);
noTone(soundPin);
digitalWrite(leg0L, LOW);
digitalWrite(leg1L, LOW);
digitalWrite(leg2L, LOW);
digitalWrite(leg3L, LOW);
delay(pauseBetweenNotes);

digitalWrite(leg0L, HIGH);
digitalWrite(leg1L, HIGH);
digitalWrite(leg2L, HIGH);
digitalWrite(leg3L, HIGH);
tone(soundPin, NOTE_C4);
delay(pauseBetweenNotes);
noTone(soundPin);
digitalWrite(leg0L, LOW);
digitalWrite(leg1L, LOW);
digitalWrite(leg2L, LOW);
digitalWrite(leg3L, LOW);
delay(pauseBetweenNotes);

digitalWrite(leg0L, HIGH);
digitalWrite(leg1L, HIGH);
digitalWrite(leg2L, HIGH);
digitalWrite(leg3L, HIGH);
tone(soundPin, NOTE_C4);
delay(pauseBetweenNotes);
noTone(soundPin);
digitalWrite(leg0L, LOW);
digitalWrite(leg1L, LOW);
digitalWrite(leg2L, LOW);
digitalWrite(leg3L, LOW);
delay(pauseBetweenNotes);
delay(pauseBetweenNotes);
delay(pauseBetweenNotes);

//O
digitalWrite(leg0L, HIGH);
digitalWrite(leg1L, HIGH);
digitalWrite(leg2L, HIGH);
digitalWrite(leg3L, HIGH);
tone(soundPin, NOTE_C4);
delay(pauseBetweenNotes);
delay(pauseBetweenNotes);
delay(pauseBetweenNotes);
noTone(soundPin);
digitalWrite(leg0L, LOW);
digitalWrite(leg1L, LOW);
digitalWrite(leg2L, LOW);
digitalWrite(leg3L, LOW);
delay(pauseBetweenNotes);

digitalWrite(leg0L, HIGH);
digitalWrite(leg1L, HIGH);
digitalWrite(leg2L, HIGH);
digitalWrite(leg3L, HIGH);
tone(soundPin, NOTE_C4);
delay(pauseBetweenNotes);
delay(pauseBetweenNotes);
delay(pauseBetweenNotes);
noTone(soundPin);
digitalWrite(leg0L, LOW);
digitalWrite(leg1L, LOW);
digitalWrite(leg2L, LOW);
digitalWrite(leg3L, LOW);
delay(pauseBetweenNotes);

digitalWrite(leg0L, HIGH);
digitalWrite(leg1L, HIGH);
digitalWrite(leg2L, HIGH);
digitalWrite(leg3L, HIGH);
tone(soundPin, NOTE_C4);
delay(pauseBetweenNotes);
delay(pauseBetweenNotes);
delay(pauseBetweenNotes);
noTone(soundPin);
digitalWrite(leg0L, LOW);
digitalWrite(leg1L, LOW);
digitalWrite(leg2L, LOW);
digitalWrite(leg3L, LOW);
delay(pauseBetweenNotes);
delay(pauseBetweenNotes);
delay(pauseBetweenNotes);

//S
digitalWrite(leg0L, HIGH);
digitalWrite(leg1L, HIGH);
digitalWrite(leg2L, HIGH);
digitalWrite(leg3L, HIGH);
tone(soundPin, NOTE_C4);
delay(pauseBetweenNotes);
noTone(soundPin);
digitalWrite(leg0L, LOW);
digitalWrite(leg1L, LOW);
digitalWrite(leg2L, LOW);
digitalWrite(leg3L, LOW);
delay(pauseBetweenNotes);

digitalWrite(leg0L, HIGH);
digitalWrite(leg1L, HIGH);
digitalWrite(leg2L, HIGH);
digitalWrite(leg3L, HIGH);
tone(soundPin, NOTE_C4);
delay(pauseBetweenNotes);
noTone(soundPin);
digitalWrite(leg0L, LOW);
digitalWrite(leg1L, LOW);
digitalWrite(leg2L, LOW);
digitalWrite(leg3L, LOW);
delay(pauseBetweenNotes);

digitalWrite(leg0L, HIGH);
digitalWrite(leg1L, HIGH);
digitalWrite(leg2L, HIGH);
digitalWrite(leg3L, HIGH);
tone(soundPin, NOTE_C4);
delay(pauseBetweenNotes);
noTone(soundPin);
digitalWrite(leg0L, LOW);
digitalWrite(leg1L, LOW);
digitalWrite(leg2L, LOW);
digitalWrite(leg3L, LOW);
delay(pauseBetweenNotes);
delay(pauseBetweenNotes);
delay(pauseBetweenNotes);
  delay(3000);
  gameReset();

}

Step 3: Testing

For testing this project we made sure the buttons and lights were working using
gator clips attached to the Lillypad first. We made sure each leg was working then
we tested they all worked when connected. Once we were sure we had the code working
well enough we started using the conductive thread and attached a leg at a time.
In the code the serial monitor is activated and will print out the pin value that
has been selected. This allowed us to figure out which leg was shorting when a problem
did arise.

We did run into several issues along the way with shorts here and there.




Step 4: Faced Issues


- Connectivity: With textile, in order to get the circuit running we needed to use conductive thread, which caused some connectivity issues with large number of threads used. 
As a solution, we had layers of patches to minimize the contact with the each other. Noting that there is a special website (http://www.plushbot.com) made by "Yingdan Huang"  that helps you design your fabric beforehand in order to minimize such issues.

- Power:
The power of the computer was good to have the lights and sounds bright enough to see through the fabric (as shown in the test video.) However, when the lilypad (( 3.V )) battery was added, the sound was very low and lights were barely flashing.
Another (( 3.V )) battery was added but the result still wasn't as we wished for.

- Button Debouncing:
One of the issues we faced was when we would activate a button it would remain
'pressed' for longer than it actually was. To solve this we needed to add a
resistor to the circuit. This caused some problems. For the original testing we
had just wrapped them above the lillypad on one end, the other was attached to
each of the pins around the board. This worked fine but was basically a bunch of
wires protruding from the octopus. We then tried to 'sew' the resistors into a layer
of fabric underneath so everything was again flat. This proved to be very difficult to work
with as well as producing shorts every so often.