// images
PImage startImg, bgImg, readyImg, goImg, earthImg, saturnImg, winImg, loseImg, tieImg;
PFont kontra;

// obj initialization
int numBalls = 2;
float radius = 6;
Ball[] balls = new Ball[numBalls];

int numPaddles = 2;
Paddle[] paddles = new Paddle[numPaddles];
Position[] origins = new Position[numPaddles];
float paddleW = HALF_PI/3, r1 = 100, r2 = 80;

int maxExplosions = 1000;
int explosionCount = 0;
Explosion[] explosions = new Explosion[maxExplosions];
PImage[] explosionFrames = new PImage[9];

// control vars
boolean start = true;
boolean play = false;
boolean pause = true;
boolean done = false;
boolean win = false;
int level = 0;
int time, timer, wait = 2500;
int gameLength = 10000;
int timerBarWidth = 5;

void setup()
{
  size(600,300);
  smooth();
  //framerate(60);
  
  kontra = loadFont("Kontrapunkt-Light-48.vlw");
  textFont(kontra, 16);
  
  startImg = loadImage("start.jpg");
  bgImg = loadImage("bg.jpg");
  readyImg = loadImage("ready.gif");
  goImg = loadImage("go.gif");
  earthImg = loadImage("earth.gif");
  saturnImg = loadImage("saturn.gif");
  winImg = loadImage("youwin.gif");
  loseImg = loadImage("youlose.gif");
  tieImg = loadImage("tiegame.gif");
  
  for (int i = 0; i < 9; i++) {
    explosionFrames[i] = loadImage("explode_0"+i+".gif");
  }
    
  origins[0] = new Position(width/5, height/2);
  origins[1] = new Position(4*width/5, height/2);

  paddles[0] = new Paddle(origins[0].x, origins[0].y, paddleW, r1, r2);
  paddles[1] = new Paddle(origins[1].x, origins[1].y, paddleW, r1, r2);
  paddles[1].computer_controlled(true);
  
  for (int i = 0; i < numBalls; i++ )
    balls[i] = new Ball(radius, i);
  
}

void draw()
{
  if (start) {
    image(startImg, 0, 0);
  } else if (play) {
    event_game();
  } else if (done) {
    show_winner();
  }
}

void event_game()
{
  draw_bg();
  draw_planets();
  draw_explosions();
  
  for (int i = 0; i < numPaddles; i++) {
    paddles[i].draw();
    paddles[i].test();
  }
  
  if (pause) {
  
    timer = (timer == 0) ? millis() : timer+1;
    time = millis();
    if (time - timer >= wait) {
      pause = false;
      timer = 0;
    }
    if (time - timer <= wait-500) {
      image(readyImg, width/2-readyImg.width/2, height/2-readyImg.height/2);
    } else {
      image(goImg, width/2-goImg.width/2, height/2-goImg.height/2);
    }      
    
    for (int i = 0; i < numBalls; i++)
      balls[i].draw_ball();
    
  } else {
  
    draw_timer();
    draw_score();
    for (int i = 0; i < numBalls; i++) {
      balls[i].draw();
    }
  }
}

void draw_bg()
{
  background(bgImg);
}

void draw_planets()
{
  noFill();
  stroke(0,255,0,127);
  
  for (int i = 1; i < 12; i++) {
    stroke(0,255,0,255-lerp(0,255,(float)i/12));
    ellipse(origins[0].x, origins[0].y, sq(i)+2*i, sq(i)+2*i);
    ellipse(origins[1].x, origins[1].y, sq(i)+2*i, sq(i)+2*i);
  }
  image(earthImg, origins[0].x-earthImg.width/2, origins[0].y-earthImg.height/2);
  image(saturnImg, origins[1].x-saturnImg.width/2, origins[1].y-saturnImg.height/2);
}

void draw_explosions()
{
  for (int i = 0; i < explosionCount; i++) {
    explosions[i].draw();
  }
}

void draw_timer()
{
  fill(0,255,0);
  stroke(0,255,0);
  timer = (timer == 0) ? millis() : timer+1;
  time = millis();
  float diff = time - timer;
  float totalBars = (width/2)/(timerBarWidth+1);
  float currentBars = int(diff*totalBars/gameLength);
  for (int i = 0; i < currentBars; i++) {
    rect(i*(timerBarWidth+1), height-20, 3, 18);
    rect(width-timerBarWidth-i*(timerBarWidth+1), height-20, 3, 18);
  }
  if (diff >= gameLength) {
    play = false;
    done = true;
  }
}

void draw_score()
{
  textMode(ALIGN_LEFT);
  text("YOU: "+paddles[1].score, 10, 18);
  textMode(ALIGN_RIGHT);
  text("THEM: "+paddles[0].score, width-10, 18);
}

void show_winner()
{
  draw_bg();
  push(); translate(width/2, height/2);
  if (paddles[0].score == paddles[1].score) {
    win = false;
    image(tieImg, -tieImg.width/2, -tieImg.height/2);
  } else if (paddles[0].score > paddles[1].score) {
    win = false;
    image(loseImg, -loseImg.width/2, -loseImg.height/2);
  } else {
    win = true;
    image(winImg, -winImg.width/2, -winImg.height/2);
  }
  pop();
  textMode(ALIGN_CENTER);
  text("YOU: "+paddles[1].score+"   THEM: "+paddles[0].score, width/2, 200);
  text("Click to play again", width/2, 220);
}

void mousePressed()
{
  if (start) {
    start = false;
    play = true;
  } else if (done) {
    done = false;
    play = true;
    reset();
  }
}

void reset()
{
  pause = true;
  timer = 0;
  for (int i = 0; i < numPaddles; i++)
    paddles[i].score = 0;
  for (int i = 0; i < numBalls; i++)
    balls[i].reset();
  explosionCount = 0;
  if (win) level++;
  
}


/***********************************************/
/*** PADDLE CLASS
/***********************************************/
class Paddle
{
  Position anchor;
  float a, w, r1, r2;
  Position[] p = new Position[6];
  float speed = HALF_PI/10;
  boolean user = true;
  int score = 0;
  float friction = .06;
  
  Paddle(float $x, float $y, float $w, float $r1, float $r2)
  {
    w = $w; r1 = $r1; r2 = $r2;
    a = 0;
    anchor = new Position($x,$y);
    for (int i = 0; i < p.length; i++)
      p[i] = new Position();
  }
  
  void computer_controlled(boolean t)
  {
    user = !t;
    friction -= level*.01;
  }
  
  void draw()
  {
    if (user) {
      rotate();
    } else {
      autorotate();
    }
    
    draw_shape();
  }
  
  void draw_shape()
  {
    fill(0,255,0,127);
    stroke(0,255,0);
    p[0].set(anchor.x + cos(a-w/2)*r1, anchor.y + sin(a-w/2)*r1);
    p[1].set(anchor.x + cos(a)*r1, anchor.y + sin(a)*r1);
    p[2].set(anchor.x + cos(a+w/2)*r1, anchor.y + sin(a+w/2)*r1);
    p[3].set(anchor.x + cos(a+w/2)*r2, anchor.y + sin(a+w/2)*r2);
    p[4].set(anchor.x + cos(a)*r2, anchor.y + sin(a)*r2);
    p[5].set(anchor.x + cos(a-w/2)*r2, anchor.y + sin(a-w/2)*r2);
    
    beginShape(POLYGON);
      vertex(p[0].x, p[0].y);
      vertex(p[1].x, p[1].y);
      vertex(p[2].x, p[2].y);
      vertex(p[3].x, p[3].y);
      vertex(p[4].x, p[4].y);
      vertex(p[5].x, p[5].y);
    endShape();
  }
  
  void autorotate()
  {
    float na, d = 100000, nd, ny = 0, nx = 0;
    for (int i = 0; i < numBalls; i++) {
      nd = dist(balls[i].p.x, balls[i].p.y, anchor.x, anchor.y);
      if (nd < d) {
        d = nd;
        nx = balls[i].p.x;
        ny = balls[i].p.y;
      }
    }
    na = atan2(anchor.y - ny, anchor.x - nx)+PI;
    a = friction(a, na, friction);
  }
  
  void rotate()
  {
    a = atan2(anchor.y - mouseY, anchor.x - mouseX)+PI;
  }
  
  float friction(float o, float n, float f)
  {
    if (o > 3*HALF_PI && o < TWO_PI && n < HALF_PI && n > 0) {
      n += TWO_PI;
    }
    return o+(n-o)*f;
  }
  
  boolean hit(Position oldp, Position newp, float rad)
  {
    float d = dist(anchor, newp);
    boolean inside = (d <= r1 && d >= r2);
    float th = atan2(anchor.y - newp.y, anchor.x - newp.x)+PI;
    boolean btw = (th >= a - w/2 && th <= a + w/2);
    
    return (btw && inside) ? true : false;
  }
  
  void monitor()
  {
    fill(0,255,0);
    textMode(ALIGN_LEFT);
    float th = atan2(anchor.y - balls[0].p.y, anchor.x - balls[0].p.x)+PI;
    boolean btw = (th > a - w/2 && th < a + w/2);
    float d = dist(anchor, balls[0].p);
    text(str(btw), mouseX,mouseY+20);
    boolean inside = (d <= r1 && d >= r2);
    text(str(inside), mouseX,mouseY);
  }
  
  void test()
  {
    for (int i = 0; i < numBalls; i++) {
      if (balls[i].p.closeTo(anchor, 7)) {
        explosions[explosionCount] = new Explosion(anchor.x, anchor.y, explosionFrames);
        explosionCount++;
        score++;
      }
    }
  }
}


/***********************************************/
/*** BALL CLASS
/***********************************************/
class Ball
{
  Position p,op;
  Position[] h = new Position[numPaddles];
  Vector v;
  Vector[] g = new Vector[numPaddles];
  float r, id, M = 5000;
  
  Ball(float $r, float $id)
  {
    r = $r; id = $id;
    
    p = new Position(width/2+random(-5,5),(id+1)*height/(numBalls+1));
    op = new Position(p.x, p.y);
    
    for (int i = 0; i < numPaddles; i++)
      h[i] = new Position(origins[i].x, origins[i].y);
      
    for (int i = 0; i < numPaddles; i++)
      g[i] = new Vector();
      
    v = new Vector(0,0);
  }
  
  void drop(float $x, float $y)
  {
    p.set($x,$y);
    v.set(0,0);  
  }
  
  void draw()
  {    
    float m,a;
    for (int i = 0; i < origins.length; i++) {
      m = dist(p,h[i]);
      m = M/(sq(m) + 4*m + 2);
      a = atan2(p,h[i]);
      g[i].set(m,a);
      v.add(g[i]);
    }
    v.m = constrain(v.m, 0, 100);
    
    op.set(p.x, p.y);
    p.frictionAdd(v, .15);

    bounce();
    
    draw_ball();
  }
  
  void draw_ball()
  {
    fill(0,255,0);
    stroke(0,255,0);
    ellipse(p.x, p.y, r*2, r*2);
  }
  
  void bounce()
  {    
    float th;
    float add;
    
    for (int i = 0; i < numBalls; i++) {
      if (i != id && balls[i].hit(p,r)) {
        th = atan2(balls[i].p.y - p.y, balls[i].p.x - p.x);
        reflectOver(th);
        ballBoundaries(balls[i]);
        //v.scale(.8);
      }
    }    
    for (int i = 0; i < numPaddles; i++) {
      if (paddles[i].hit(op,p,r)) {
        th = atan2(paddles[i].anchor.y - p.y, paddles[i].anchor.x - p.x)+PI;
        th = (th > HALF_PI && th <= 3*HALF_PI) ? th+PI : th;
        reflectOver(th);
        v.scale(1.3);
        paddleBoundaries(paddles[i]);
      }
    }
    if (hit_horiz_wall()) {
      reflectY();
      v.scale(.8);
      boundaries();
    } else if (hit_vert_wall()) {
      reflectX();
      v.scale(.8);
      boundaries();
    }
  }
  
  boolean hit(Position b, float br)
  {
    return (dist(p.x, p.y, b.x, b.y) <= r+br) ? true : false;
  }
  
  boolean hit_vert_wall()
  {
    return (p.x-r <= 0 || p.x+r >= width) ? true : false;
  }
  boolean hit_horiz_wall()
  {
    return (p.y-r <= 0 || p.y+r >= height) ? true: false;
  }
  void reflectX()
  {
    float x = cos(v.a)*v.m;
    float y = sin(v.a)*v.m;
    v.a = atan2(y,-x);
  }
  void reflectY()
  {
    float x = cos(v.a)*v.m;
    float y = sin(v.a)*v.m;
    v.a = atan2(-y, x);
  }
  void reflectOver(float a)
  {
    float x = cos(v.a)*v.m;
    float y = sin(v.a)*v.m;
    v.a = atan2(y, -x) + a;
  }
  
  void boundaries()
  {
    p.x = (p.x-r <= 0) ? 0+r : p.x;
    p.x = (p.x+r >= width) ? width-r : p.x;
    p.y = (p.y-r <= 0) ? 0+r : p.y;
    p.y = (p.y+r >= height) ? height-r : p.y;
  }
  void paddleBoundaries(Paddle paddle)
  {
    float th = atan2(paddle.anchor.y - p.y, paddle.anchor.x - p.x)+PI;
    float d1 = dist(op.x, op.y, paddle.anchor.x, paddle.anchor.y);

    if (d1-r >= paddle.r1) {
      p.x = cos(th)*paddle.r1 + paddle.anchor.x + r+0.1;
      p.y = sin(th)*paddle.r1 + paddle.anchor.y + r+.1;
    } else if (d1+r <= paddle.r2) {
      p.x = cos(th)*paddle.r2 + paddle.anchor.x + r+.1;
      p.y = sin(th)*paddle.r2 + paddle.anchor.y + r+.1;
    }      
  }
  void ballBoundaries(Ball ball)
  {
    float th = atan2(ball.p.y - p.y, ball.p.x - p.x)+PI;
    float d1 = dist(p.x+r, p.y+r, ball.p.x, ball.p.y);

    p.x += cos(th)*ball.r + cos(th)*r;
    p.y += sin(th)*ball.r + sin(th)*r;   
  }
  
  void test()
  {
    for (int i = 0; i < origins.length; i++) {
      if (p.closeTo(h[i], r)) {
        done = true;
      }
    }
  }
  
  void reset() {
    v.set(0,0);
    p.set(width/2+random(-5,5), (id+1)*height/(numBalls+1));
  }
}

/***********************************************/
/*** EXPLOSION CLASS
/***********************************************/
class Explosion
{
  float x,y;
  PImage[] f;
  int c = 0, delay = 5;
  
  Explosion(float $x, float $y, PImage[] $frames)
  {
    x = $x; y = $y; f = $frames;
  }
  
  void draw()
  {
    if (c < f.length*delay) {
      int i = int(c/delay);
      alpha(f[i], f[i]);
      image(f[i], x-f[i].width/2, y-f[i].height/2);
    }
    c++;
  }
}

/***********************************************/
/*** POSITION CLASS
/***********************************************/
class Position
{
  float x,y;
  
  Position() {}
  Position(float $x, float $y)
  {
    x = $x; y = $y;
  } 
  Position(Position p)
  {
    x = p.x; y = p.y;
  }
  
  void set(float $x, float $y)
  {
    x = $x; y = $y;
  }
  
  void add(Vector v)
  {
    x += cos(v.a)*v.m;
    y += sin(v.a)*v.m;
  }
  
  boolean eq(Position p)
  {
    return (x == p.x && y == p.y) ? true : false;
  }
  boolean closeTo(Position p, float d)
  {
    return (dist(x,y,p.x,p.y) <= d) ? true : false;
  }
  
  void friction(Position n, float f)
  {
    x += (n.x-x)*f;
    y += (n.y-y)*f;
  }
  void frictionAdd(Vector v, float f)
  {
    float nx = x + cos(v.a)*v.m;
    float ny = y + sin(v.a)*v.m;
    x += (nx-x)*f;
    y += (ny-y)*f;
  }
}


/***********************************************/
/*** VECTOR CLASS
/***********************************************/
class Vector
{
  float m,a;
  
  Vector() {}
  
  Vector (float $m, float $a)
  {
    m = $m; a = $a;
  }
  
  void set(float $m, float $a)
  {
    m = $m; a = $a;
  }
  
  void add(Vector v)
  {
    float x = cos(a)*m + cos(v.a)*v.m;
    float y = sin(a)*m + sin(v.a)*v.m;
    m = dist(0,0,x,y);
    a = atan2(y,x);
  }
  
  void negate()
  {
    a += HALF_PI;
  }
  
  void scale(float s)
  {
    m *= s;
  }
}


// MISC FUNCTIONS
float dist(Position $1, Position $2)
{
  return dist($1.x, $1.y, $2.x, $2.y);
}

float atan2(Position $1, Position $2)
{
  return atan2($2.y - $1.y, $2.x - $1.x);
}
Project 3: Escape
The individuals presented in Turkle's text all used games as a way to escape and/or alter their self-perception. Develop an event/game which lasts 10 seconds and explores your personal fantasy for escape.