All posts by Alice Cai

Minim & Leap

Screen Shot 2014-05-07 at 1.05.36 AM

 

This is super late (sorry Dan!) but here’s a stupid pet trick. Although this doesn’t involve the Arduino, it does involve the LeapMotion. I had originally wanted to include the Leap in my final project, but didn’t have the time to actually play around with it and figure out the environment.

This is a mashup/side project with my other minim post — I think that the Leap would be fun to use with music-based applications. It only draws when your fingers can be detected – if they’re not over the Leap, then it stops drawing. It draws either an ellipse (if your touch mode is ‘touching’) or a rectangle (if your touch mode is ‘hovering’). Modes are also printed in console so you know what’s happening. (It’s much harder to get ellipses drawn…maybe I just have very shaky hands.)

My iMovie isn’t working at the moment, so this video doesn’t have audio…but the song that is playing is “Salty Sweet” by Ms Mr.

Until I get that fixed, here’s a temporary, but very short Instagram video of it (with sound).

//importing leapmotion library
import de.voidplus.leapmotion.*;

//importing minim and serial libraries
import ddf.minim.*;
import processing.serial.*;

//for music
Minim minim; 
AudioPlayer player; 

//for the leapmotion
LeapMotion leap;

float y = 0; 
float x = 0; 

void setup() {
  size(800, 500, P3D);
  colorMode(RGB);
  smooth();

  leap = new LeapMotion(this);

  //start minim with this
  minim = new Minim (this);

  player = minim.loadFile ("SaltySweet.mp3");
  player.play ( );
}

void draw() {
  x+=0.02;
  y+=0.005;
  float n = noise(x);
  float t = noise(y);

  for (Hand hand : leap.getHands()) {
    for (Finger finger : hand.getFingers()) {
      // finger.draw();

      int finger_id = finger.getId();
      // Touch Emulation
      int     touch_zone        = finger.getTouchZone();
      float   touch_distance    = finger.getTouchDistance();

      switch(touch_zone) {
      case -1: // None
        break;

      case 0: // Hovering
        println("Hovering (#"+finger_id+"): "+touch_distance);

        pushMatrix();
        stroke(30, 15, 77, 20);
        fill (60, 170, 205, t*4);
        rect(t*(width)-50, n*height+10, 50*n, height/2*n);
        popMatrix();
        break;

      case 1: // Touching
        println("Touching (#"+finger_id+")");

        pushMatrix();
        stroke(70, 3, 11, 20);
        fill (60, 170, 205, t*4);
        ellipse(n*height+20, t*(width)-100, height/4*n, 10*n);
        popMatrix();
        break;
      }
    }
  }
}

 

Pusheen – Too Busy Eating Edition

Pusheen - 3

Pusheen has to save the princess!

But oh no, why is all this food here? As one of our favorite internet cats, pusheen’s pretty notorious for eating everything…and avoiding adventures.

This is a tribute to not only Pusheen but also: a much-loved unicorn game, the classic Mario “The princess is in another castle!” level endings, and of course, food.

Things to update:

– Copy on the level/title screens so it’s clear what the objective of the game is. Add a video before the game, maybe?

– Level 4 is supposed to be a boss battle, with the flame in the middle and the skulls floating around. Since “eating” the skulls shouldn’t really be an option, I would add a collision function, so that every time Pusheen collides with the skulls, you have to start over from level 1.

– A timer class exists, but isn’t used: add that in levels 1-3 where the player has to eat everything in under x amount of time.

– Like Nicole and Dan mentioned, there is a great chance to incorporate inheritance for the food items.

Notes:

Although the game isn’t quite finished, and I solved the problem with the levels not appearing whilst in class, I’m happy the game (which would be the first I’ve made) works. I’m very glad I got to use and understand arraylists for the food objects eaten in the levels, and states for the actual levels. Many, many thanks to Pauline for helping me understand arraylists and for screaming with me when we got the boxes to disappear for the first time. (And also to everyone who pours their heart and code into processing tutorials.)

Download the game & all of the related files.

Code below:

FOR THE GAME:

//Pusheen objects:
Pusheen pusheen;

//Timer:
Timer timer;

//Box Arrays:
ArrayList<Box> box_list; //level 1
ArrayList<Box> box_list2; //level 2
ArrayList<Box> box_list3; //level 3
ArrayList<Box> box_list4; //level 4

//importing minim and serial libraries
import ddf.minim.*;
import processing.serial.*;

//for to play music
Minim minim; 
AudioPlayer player; 

//Background images:
PImage bg1;
PImage bg2;
PImage bg3;
PImage bg4;
PImage bg5;
PImage bg6;
//PImage bg7;
int y;

//Play button:
int playButtonWidth, playButtonHeight;
int playButtonX, playButtonY;

//Levels: 
final int START = 1;
final int LEVEL1 = 2;
final int LEVEL2 = 3;
final int LEVEL3 = 4; 
final int LEVEL4 = 5;
final int WIN = 6;
final int GAME_OVER = 7; 
int state = START; //initial value 

void setup () {
  size(880, 680);
  //load background images:
  bg1 = loadImage("level1bg.png");
  bg2 = loadImage("level2bg.png");
  bg3 = loadImage("level3bg.png");
  bg4 = loadImage("level4bg.png");
  bg5 = loadImage("castlebg.png");
  bg6 = loadImage("startbg.png");
  // bg7 = loadImage("bg7.png");

  minim = new Minim (this);
  player = minim.loadFile ("Always.mp3");
  //Because this is obviously THAT unicorn song.
  //player = minim.loadFile ("Outro.mp3");
  //This is a different song
  player.play ( );

  //array for box, change number of i for levels 
  //from box constructor

  //array for level 1 - img = muffin
  box_list = new ArrayList<Box>();
  for (int i=0; i<31; i++) {
    box_list.add (new Box (random (0, width), random (0, height), random(1, 4), random(1, 4), "muffin.png"));
  }

  //array for level 2 - img = hamburger
  box_list2 = new ArrayList<Box>();
  for (int i=0; i<41; i++) {
    box_list2.add (new Box (random (0, width), random (0, height), random(1, 4), random(1, 4), "hamburger.png"));
  }

  //array for level 3 - img = donut/candy/hotodg ??
  box_list3 = new ArrayList<Box>();
  for (int i=0; i<51; i++) {
    box_list3.add (new Box (random (0, width), random (0, height), random(1, 4), random(1, 4), "donut.png"));
  }

  //array for level 4 - img = skulls (boss-ish level)
  box_list4 = new ArrayList<Box>();
  for (int i=0; i<10; i++) {
    box_list4.add (new Box (random (0, width), random (0, height), random(1, 4), random(1, 4), "skull.png"));
  }

  //things from Pusheen constructor
  pusheen = new Pusheen(400, 200, "unipic.png");

  //play button:
  playButtonWidth = width / 5;
  playButtonHeight = height / 8;
  playButtonX = width / 2;
  playButtonY = 4 * height / 5;
}

void draw () {
  //background(255);
  noCursor();

  //Magic pusheen adventure time! 
  pusheen.unicorn_Draw();
  pusheen.unicorn_Move();

  switch (state)
  {
  case START: 
    showWelcomeScreen();
    pusheen.unicorn_Draw();
    pusheen.unicorn_Move();
    break;
  case LEVEL1:
    background(bg1);
    pusheen.unicorn_Draw();
    pusheen.unicorn_Move();
    for (int i=0; i<box_list.size()-1; i++) {
      box_list.get(i).drawBox();
      box_list.get(i).moveBox();

      if (box_list.get(i).checkBoundaries()); 
      {
        box_list.get(i).bounce_box();
        box_list.get(i).drawBox();
        box_list.get(i).moveBox();
      }

      if (pusheen.check_Collision(box_list.get(i)))
      {
        box_list.remove(i);
      }

      /* else if (box_list.size()==1);
       {
       //println("WHAT HAVE YOU DONE WITH THE COOKIES");
       }
       */
    }//end of FOR (in Level1)
    break;

  case LEVEL2:
    background(bg2);
    pusheen.unicorn_Draw();
    pusheen.unicorn_Move();
    for (int i=0; i<box_list2.size()-1; i++) {
      box_list2.get(i).drawBox();
      box_list2.get(i).moveBox();

      if (box_list2.get(i).checkBoundaries()); 
      {
        box_list2.get(i).bounce_box();
        box_list2.get(i).drawBox();
        box_list2.get(i).moveBox();
      }

      if (pusheen.check_Collision(box_list2.get(i)))
      {
        box_list2.remove(i);
      }
    }//end of FOR (in Level2)
    break;

  case LEVEL3:
    background(bg3);
    pusheen.unicorn_Draw();
    pusheen.unicorn_Move();
    for (int i=0; i<box_list3.size()-1; i++) {
      box_list3.get(i).drawBox();
      box_list3.get(i).moveBox();

      if (box_list3.get(i).checkBoundaries()); 
      {
        box_list3.get(i).bounce_box();
        box_list3.get(i).drawBox();
        box_list3.get(i).moveBox();
      }

      if (pusheen.check_Collision(box_list3.get(i)))
      {
        box_list3.remove(i);
      }
    }//end of FOR (in Level3)
    break;

  case LEVEL4:
    background(bg4);
    pusheen.unicorn_Draw();
    pusheen.unicorn_Move();
    for (int i=0; i<box_list4.size()-1; i++) {
      box_list4.get(i).drawBox();
      box_list4.get(i).moveBox();

      if (box_list4.get(i).checkBoundaries()); 
      {
        box_list4.get(i).bounce_box();
        box_list4.get(i).drawBox();
        box_list4.get(i).moveBox();
      }

      if (pusheen.check_Collision(box_list4.get(i)))
      {
        box_list4.remove(i);
      }
    }//end of FOR (in Level4)
    break;

  case GAME_OVER:
    showGameOverScreen();
    break;
  }//ends state

  //CHANGING LEVELS!!!!: 
  if (state == LEVEL1);
  {
    if (box_list.size() == 1) {
      println("Box array empty, going to level 2");
      println(box_list.size());
      state = LEVEL2;
      //return;
    }
  }

  if (state == LEVEL2);
  {
    if (box_list2.size() == 1) {
      println("MEOW");
      println(box_list2.size());
      state = LEVEL3;
      //return;
    }
  }

  if (state == LEVEL3);
  {
    if (box_list3.size() == 1) {
      println("level 3");
      println(box_list3.size());
      state = LEVEL4;
      // return;
    }
  }

  if (state == LEVEL4);
  {
    if (box_list4.size() == 1) {
      println("level 4");
      println(box_list4.size());
      state = GAME_OVER;
      return;
    }
  }

} //END OF DRAW  

void showGameOverScreen()
{
  background(bg5);
  pusheen.unicorn_Draw();
  pusheen.unicorn_Move();

  String s = "Look's like there's a castle...but you've been too busy eating all the food! The princess is gone...";
  fill(50);
  textSize(20);
  fill(#6C5B7B);
  text(s, width/2, 500, 500, 500);
}

void showWelcomeScreen()
{
  background(bg6);

  showTitle();

  // Play button
  fill(#79BD9A);
  stroke(#3B8686);
  rectMode(CENTER);
  rect(playButtonX, playButtonY, playButtonWidth, playButtonHeight);
  fill(#A8DBA8);
  textSize(32);
  text("PLAY", width /2, 4 * height/5);

  // Instructions
  textSize(24);
  fill(#0B486B);
  text("Will he ever get there???", width/2, height/2 + 50);
}

void showTitle()
{
  // Title
  textAlign(CENTER, CENTER);
  textSize(48);
  fill(#6C5B7B);
  text("Pusheen Saves the Princess!", width/2, height/2);
}

//Boolean & mousePressed to check for player interaction with play button. Starts game. 
boolean isMouseOverPlayButton()
{
  // Half-wdith
  int hw = playButtonWidth / 2;
  // Half-height
  int hh = playButtonHeight / 2;
  // Returns true if mouse position is within the bounds of the button
  return mouseX >= playButtonX - hw && mouseX <= playButtonX + hw &&
    mouseY >= playButtonY - hh && mouseY <= playButtonY + hh;
}

void mousePressed()
{
  // Check if we are at the stat screen, and if the click is within the button bounds
  if (state == START && isMouseOverPlayButton())
  {
    // Now, we can start!
    state = LEVEL1;
    return;
  }
}

BOX CLASS: (What I called the food items)

class Box {

  //box position:
  float x;
  float y;

  int w = 20;
  int h = 20;

  //CHANGING image of items (item, skull, etc)
  // Each item is 40x40 px
  PImage item;

  //Getter functions
  float get_x() {
    return x;
  } //getter function for x position 
  float get_y() {
    return y;
  } //get y position
  int get_w() {
    return w;
  } //get width (of box)
  int get_h() {
    return h;
  } //get height

  //box velocity/speed:
  float x_velocity;
  float y_velocity;  

  //Constructor: 
  Box(float _x, float _y, float _x_velocity, float _y_velocity, String filename) {
    x = _x;
    y = _y; 
    x_velocity = _x_velocity;
    y_velocity = _y_velocity;
    item = loadImage(filename);
  }

  void drawBox() {
    // rect(x, y, w, h);
    image(item, x, y);
  }

  void moveBox() {
    x += x_velocity; 
    y += y_velocity;

    //  this.drawBox();
  }

  //checks to see if boxes go off the screen
  boolean checkBoundaries() {
    boolean out_of_bounds = false;
    //Out of screen (horizontal)
    if (x>(width+(1.5* w))|| x < (0 - (1.5*w))) {
      out_of_bounds = true;
    }

    //Out of screen (vertical)
    if (y>(height+(1.5*h)) || y < (0 - (1.5*h))) {
      out_of_bounds = true;
    }
    return out_of_bounds;
  }

  void bounce_box() {
    //makes box array bounce

    //left side of screen
    if (x<=0) {
      x_velocity*=-1;
    }

    //right side of screen
    if (x+50>=width) {
      x_velocity*=-1;
    }

    //top side of screen
    if (y<=0) {
      y_velocity*=-1;
    }

    //right side of screen
    if (y+50>=height) {
      y_velocity*=-1;
    }
  }
  //End of Box Class
}

PUSHEEN CLASS:

class Pusheen {

  //Pusheen image:
  int x; 
  int y;

  int w = 162; //width
  int h = 148; //height

  PImage unicorn;

  //Constructor:
  Pusheen(int _x, int _y, String filename) {
    x = _x; 
    y = _y; 
    unicorn = loadImage(filename);
  }

  void unicorn_Draw() {
    image(unicorn, x, y);
  }

  void unicorn_Move() {
    x = mouseX-(w/2);
    y = mouseY-(h/2);
  }

  //collision function goes here, 
  boolean check_Collision(Box temporary) {
    boolean signal = false;
    float temp_x = temporary.get_x();
    float temp_y = temporary.get_y();
    int temp_box_height = temporary.get_h();
    int temp_box_width = temporary.get_w();

    if (x < (temp_x + temp_box_width) && (x+w) > (temp_x ))    
      if (y < (temp_y + temp_box_height) && (y+h) > (temp_y )) {
        signal = true;
      }
      else {
        signal = false;
      }

    return signal;
  }  

  //end of pusheen class
}

 

 

These Mediated Environments

When we imagine environments, what comes to mind are environments in which we live, work, and play. Think of one now. The scene you painted in your head may be filled with images of people, places, and things…but have you thought about these things, and how a proliferation of them are digital? So much of life is mediated by the screen that we scarcely live outside of it. TV shows (even if we no longer watch them on a TV), social media, songs — culture has become digital culture. We live inside of our media.

Anthropologist Edmund Carpenter, in his essay “Obsolescence,” writes that “Electronic media have turned the entire globe into a midden. Artists are now busy transforming all of our yesterdays into now. The whole world has become a happening.”

Recently, there has been an increased importance on social visibility: we take pictures at events to share them with people so that they know we were there, doing that thing, at that time. There is this notion of being present (at functions, at least seemingly to other people) while being being absent (consuming everyone else’s media instead of actually engaging in the function you are at).

The whole world has become a happening…but it has the potential to be a happening with each other. More than anything else, this class has taught me that media itself is an environment and that media is malleable. It is an environment we can change.

Barry Schwartz, during the 2014 Ted Talks, said that “We design human nature by defining the institutions within which people live and work.” As students in Intro to Computational Media (or Digital Encoding, et al.), we have the potential to define these environments and institutions, and shape our lives and culture. There is this pessimistic vision of the future where all communication happens virtually, and people are espoused to their devices rather than to each other. A more optimistic vision would be one where interactive media acts as a bridge between the environment and the public to creates new forms of communication and increase interactions between people. Rather than merely consuming, they will be engaging, participating, playing.

We Feel…Fine?

Screen Shot 2014-05-04 at 10.03.51 PM

How are you feeling?

Fine is a four-lettered placeholder.

This grabs feelings from the We Feel Fine API. We Feel Fine’s system searches blog entries for occurrences of the phrases “I feel” and “I am feeling.” The latest feeling (sentence) displays in the console and in the sketch – it refreshes every 20 seconds. Usually, it takes up to 40 seconds for a new feeling to surface.

Things to fix:

– Somehow ignore the <feelings></feelings> part of the String, so that only the sentence shows up

– Fit the whole sentence inside of a text box (attempted, but did not work).

Things to add:

– Color change every time the feeling ascribed to the sentence changes. Something that correlates with Goethe’s ideas about psychology in relation to color & emotion.

//most of the code is from the class example with the NYT headlines
String title = "Gathering feelings...";
int lastTimeChecked;
int xpos;

void setup() {
  size(800, 200);
  // textAlign(CENTER,CENTER);
}

void draw() {
  background(0);

  if (millis() - lastTimeChecked > 20000) { 
  //use a timer to check every 20 seconds, because it doesn't update that often anyways
    thread("loadFeelings");  //start a parallel thread to ask for feelings
    lastTimeChecked = millis();   
  }

   /*
   //Attempting to fit all the text within a text box...
   text(title, 200,200, xpos, height/2); //this animation stays smooth because you used a thread
   */

   text(title, xpos, height/2);
   xpos++
    if (xpos > width) xpos = 0;
  }

void loadFeelings() {
  XML root = loadXML("http://api.wefeelfine.org:8080/ShowFeelings?display=xml&returnfields=sentence&limit=1");
  title = root.toString();

  println("check" + title);
}

 

 

minim(al) visualization

minim(al) visual

So I was playing around with the minim library, recording voices and wondering If that’s how I really sound. My original plan was to save a recording, and play it back using the FSR, but I could only get it to work sometimes with the FilePlayer.

Instead, here’s a pretty/cool visualization of any song that plays. Personally, the visualization reminds me of Mega Man/Transformers.

To add your own song, just put the song file into the same Processing folder, and replace “Friend.mp3” with the name of the song file. To play the song, the force applied to the FSR has to be higher than 150.

//importing minim and serial libraries
import ddf.minim.*;
import processing.serial.*;

//for to play music
Minim minim; 
AudioPlayer player; 

//for the arduino connection
Serial myPort;

//force applied to fsr
int force;

float y = 0;
float x = 0;

void setup() {
  size(600, 600);
  smooth();
  colorMode(RGB);

  //code from class
  printArray(Serial.list());
  String portName = Serial.list()[5];
  myPort = new Serial(this, portName, 9600);
  myPort.readStringUntil('\n'); 

  //start minim with this
  minim = new Minim (this);

  if (force > 150) //force applied to pressure sensor has to be higher than 150 
  {
    player = minim.loadFile ("Friend.mp3"); //replace this .mp3 file with the name of your .mp3 file
    player.play ( );
  }
}

//draws rectangles using matrix
void draw() {
  x+=0.02;
  y+=0.005;
  float n = noise(x);
  float t = noise(y);

  pushMatrix();
  stroke(70, 3, 11, 20);
  translate(310, -140);
  rotate(.8);
  fill (60, 170, 205, t*4);
  rect(t*(width)-50, n*height+10, 50*n, height/2*n);
  rect( n*height+10, t*(width)-50, height/2*n, 50*n);
  popMatrix();
}

//code from class for serial communication
void serialEvent(Serial _port) { //this is a callback function
  if (myPort == null) return;  //this is a hack to cover a bug where the port does not get set up in time.  
  //this says if the port is not set up yet, bail (for now.) 
  String input = myPort.readStringUntil('\n');
  if (input != null) {  //if a '\n' character has in fact now arrived
    input = input.trim();  //Take off the '\n' character
    force = int(input);  //Turn it into number 
    println(force);
  }
}

 

Pacman Pt.2 – omnomnom

pacpt2 pacpt2-1

Got pacman to work, kind of – the dots have yet to disappear, although I have tried to use an if statement for it. The trails they’re leaving behind due to the stroke(255) was cool, so I left it there. Pacman trails!

I know that the code can be simplified further if I used for loops for the pellet locations.

Code below.

	Pacman pac1;
	Pacman pac2;
	Pellet pellet1;
	Pellet pellet2;
	Pellet pellet3;
	Pellet pellet4;
	Pellet pellet5;

	void setup() {
	  background(255);
	  stroke(255);

	  size(600, 400);
	  smooth();
	  noCursor();
	  pac1 = new Pacman(0, 50, 5);
	  pac2 = new Pacman(0, 100, 2);
	  pellet1 = new Pellet(color(100,0,400,34),400,50);
	  pellet2 = new Pellet(color(300,0,400,100),400,100);
	  pellet3 = new Pellet(color(100,0,400,34),400,150);
	  pellet4 = new Pellet(color(300,0,400,100),400,200);
	  pellet5 = new Pellet(color(100,0,400,34),400,250);
	}

	void draw() {
	  pac1.body();
	  pac2.body();
	  pac1.eat();
	  pac2.eat();

	  pellet1.place();
	  pellet2.place();
	  pellet3.place();
	  pellet4.place();
	  pellet5.place();

	 //line(400, 0, 400, height);
	}

	class Pellet {
	  color c;
	  float pelletX;
	  float pelletY; 

	  Pellet(color tempC, float tempxpos, float tempypos) {
	    c=tempC;
	    pelletX = tempxpos;
	    pelletY = tempypos;
	  }

	  void place() {
	    fill(c);
	    smooth();

	    //for (int i=0; i<10; i++){
	    ellipse(pelletX, pelletY+50, 15, 15);
	   // }

	 //Attempted to make the pellets disappear (turn white)
	 //   if (pacX == 400) {
	 //    c=0;
	 //   }
	  }
	}

	class Pacman {
	  float pacX;
	  float pacY;
	  float pacsize=20;
	  float xspeed;

	  Pacman(float temppacX, float temppacY, float tempxspeed) {
	    pacX = temppacX;
	    pacY = temppacY;
	    xspeed = tempxspeed;
	  }

	  void body() {
	    fill(250, 250, 40); //classic yellow color
	    arc(pacX, pacY, 50, 50, PI/4, 7*PI/4);
	  }

	  void eat() {
	    pacX= pacX + xspeed;
	    if (pacX > 600 || pacX < 0) {
	      xspeed = xspeed * -1.2;
	      pacY = pacY + 100;
	      // if (pacY > 500) {
	      //   xspeed = 0;
	    }
	  }
	}

 

Help Pacman!

pacman

Look at how lonely and hungry he is. Won’t you help? My plan was to make Pacman (which I did!) and have it gobble up bouncing balls (which I didn’t do).

Things I have trouble with: multiple classes/variables(?) in Processing

If any of you can help, add some wizardry/magic to it, that’d be very cool. Code below!

 

float pacX=mouseX;
    float pacY=350;
    float count=0;
    float pacsize=20;

    void setup() {
      size(600, 400);
      background(0);
      smooth();
      noCursor(); 
    }

    void draw() {
        pacman();

    }

    void pacman() // Hey look it's Pacman!
    {
      background(0);
      fill(250, 250, 40); //classic yellow color
      arc(pacX, pacY, 50, 50, PI/4, 7*PI/4);
      pacX=mouseX;
      pacY=350;
    }

 

 

 

The World Will End in Processing

Random Ellipses

Wait, this doesn’t look very sinister, you say.

….

Or is it?

Some predict that the end of the world involves an interaction between Earth and the black hole at the center of the galaxy, the next meteor crashing down, or a collision with a planet called Nibiru. Meanwhile, I predict that the world will end in an explosion of bokeh and unhappy computer fans

Although really, this is just a few randomly generated ellipses, so there shouldn’t be anything to worry about. Right?

void setup () {
       size(600,600);
       smooth();
       noCursor();
       stroke(0, 0, 0);
     }

     void draw (){
       background(0);

       drawEllipse(200, 200, 30, 50);
       drawEllipse(400, 200, 50, 50);
       drawEllipse(200, 400, 100, 50);
       drawEllipse(400, 400, 210, 50);

     }

     void drawEllipse(float x, float y, float r, float b) {

       fill(r, 0, b);
       ellipse(random(x), random(y), 20, 20);

     }

 

 

To Read

There are always more books to be read. But how would you know when you should clear some off your reading list, without a bright blue light to tell you?

This switch is pressure-sensitive. Wires are attached to a square piece of aluminum foil with an “O” shaped foam cutout, and this is in turn folded into the last page of two books. When the books lay on top of each other, they are not heavy enough to compress the foam and activate the switch. As you add more books on top of the original two, the added weight activates the switch.

See it in action here on Instagram.