> show canvas only <


/* built with Studio Sketchpad: 
 *   https://sketchpad.cc
 * 
 * observe the evolution of this sketch: 
 *   https://studio.sketchpad.cc/sp/pad/view/ro.XyQl6NaXq5c/rev.981
 * 
 * authors: 
 *   Julien Blanchet

 * license (unless otherwise specified): 
 *   creative commons attribution-share alike 3.0 license.
 *   https://creativecommons.org/licenses/by-sa/3.0/ 
 */ 



color backgroundColor = 0;
int maxRunners = 10;
int frame_rate = 60;
int fade_rate = 1;

boolean paused = false;
boolean started = false;

ArrayList<RunnerController> runners;
ArrayList<RunnerController> runnersToRemove;

void setup() {
    size(600, 600);

  colorMode(RGB, 255, 255, 255, 100);
  background(backgroundColor);
  noStroke();
  frameRate(frame_rate);
  runners = new ArrayList<RunnerController>();
  runnersToRemove = new ArrayList<RunnerController>();
}

void draw() {
  
  if (paused)
    return;
  
  
  if (fade_rate != 0){
      // Fade the background
      noStroke();
      color black = color(0, fade_rate);
      fill(black);
      rect(0,0,width,height);
  }
  
  for (RunnerController r: runners) {
    r.step();
    r.draw();
    if (r.finished())
      runnersToRemove.add(r);
  }
  for (RunnerController r: runnersToRemove)
    runners.remove(r);

  int addedThisFrame = 0;
  while (runners.size () < maxRunners && addedThisFrame < 1) {
    runners.add(new RunnerController());
    addedThisFrame += 1;
  }
  
    if (!started){
      stroke(128);
      strokeWeight(10);
      fill(255);
      rect(130,130,width-260, height-260);
      fill(0);
      textAlign(CENTER, CENTER);
      textSize(20);
      
      float t_y = height*.33;
      text("Welcome to...", width/2, t_y);
      t_y += textAscent() + textDescent();
      
      textSize(48);
      t_y += textAscent();
      text("RUNNERS", width/2, t_y);
      t_y += textAscent() + textDescent();
      
      textSize(15);
      text("A creative sketch by Julien Blanchet", width/2, t_y); 
      t_y += 6 * (textAscent() + textDescent());
      
      text("Click to start\nSpacebar pauses/plays the simulation\nNumber Keys control fade rate", width/2, t_y);
  }
  strokeWeight(2);
}

void mouseClicked(){
    if (started == false)
        background(0);
    started = true;
    paused = false;
    maxRunners += 1;
}


void keyTyped(){
  if (key == ' '){
    if (started == false)
        background(0);
    paused = !paused;
    started = true;
  }
  else if (key == '0')
      fade_rate = 0;
  else if (key == '1')
        fade_rate = 1;
    else if (key == '2')
        fade_rate = 2;
    else if (key == '3')
        fade_rate = 3;
    else if (key == '4')
        fade_rate = 4;
    else if (key == '5')
        fade_rate = 5;
    else if (key == '6')
        fade_rate = 6;
    else if (key == '7')
        fade_rate = 7;
    else if (key == '8')
        fade_rate = 8;
    else if (key == '9')
        fade_rate = 9;
}

interface Runner{
  void initXYA(V2D xy, float ang);
  void draw();
  void step();
  boolean finished();
  V2D getXY();
  float getAng();
  
}
class RunnerCircle implements Runner{
  V2D xy;
  float ang, rad;
  color col;
  boolean drawn;
  
  RunnerCircle(){
    xy = new V2D(mouseX, mouseY);
    ang = random(0, TWO_PI);
    rad = random(6, 15);
    col = posColor();
    drawn = false;
  }
  
  void initXYA(V2D xy, float ang){
    this.xy = xy;
    this.ang = ang;
  }
  void draw(){
    fill(col);
    noStroke();
    ellipse(xy.x, xy.y, rad, rad);
    drawn = true;
  }
  boolean finished(){return drawn;}
  void step(){}
  float getAng(){return ang;}
  V2D getXY(){return xy;}
  
}
static float RUNNER_LINE_THICKNESS = 1;


class RunnerLines implements Runner {
  float x, y, w, ang, spd, d_ang, d_spd, d_w, draw_angle;
  int curType;
  int stagesLeft;
  
  float framesLeft;
  float framesInLife;
  color col;

  RunnerLines() {
    this.x = mouseX;
    this.y = mouseY;
    this.w = random(2, 10);
    this.ang = random(0, TWO_PI);
    this.spd = random(2, 5);
    this.col = posColor();
    this.draw_angle = random(0, TWO_PI);
    float prob = random(0, 100);

    if (prob < 80)
      this.framesLeft = random(7, 25);
    else
      this.framesLeft = random(25, 105);
    this.framesInLife = framesLeft;

    prob = random(0, 100);
    float angleChange = random(QUARTER_PI / 3, QUARTER_PI);
   this. d_ang = angleChange / framesInLife;
    if (prob < 50) 
      this.d_ang = - d_ang;
      
    this.d_w = random(- (w / framesInLife), (w / framesInLife));

    prob = random(0, 100);
    if (prob < 80)
      this.d_spd = random(- spd / framesInLife, spd / framesInLife);
    else
      this.d_spd = random(spd / framesInLife, 2* spd / framesInLife);
  }
  
  void initXYA(V2D xy, float ang){
    this.x = xy.x;
    this.y = xy.y;
    this.ang = ang;
  }

  void draw() {
    if (finished())
      return;


    float dx = x - w * sin(draw_angle) / 2;
    float dy = y + w * cos(draw_angle);
    float dx2 = x + w * sin(draw_angle) / 2;
    float dy2 = y - w * cos(draw_angle);
    stroke(this.col);
    line(dx, dy, dx2, dy2);
  }
  
  V2D getXY(){
   return new V2D(x,y);
  }
  float getAng(){
    return ang;
  }

  void step() {
    this.x += cos(ang) * spd;
    this.y += sin(ang) * spd;
    this.ang += d_ang;
    this.w += d_w;
    this.spd += d_spd;
    framesLeft -= 1.0;
  }

  boolean finished() {
    return framesLeft < 0;
  }
}


static final int STRAIGHT_THICKNESS = 2;

class RunnerStraight implements Runner {
  V2D xy;
  float ang, len;
  color col;
  boolean drawn;

  RunnerStraight() {
    xy = new V2D(mouseX, mouseY);
    ang = random(0, TWO_PI);
    len = random(10, 30);
    col = posColor();
    drawn = false;
  }

  void initXYA(V2D xy, float ang) {
    this.xy = xy;
    this.ang = ang;

    float modAngle = random(HALF_PI / 4, HALF_PI / 2);
    if (random(0, 100) < 50)
      ang -= modAngle;
    else
      ang += modAngle;
  }
  void draw() {
    strokeWeight(STRAIGHT_THICKNESS);
    stroke(col);
    line(xy.x, xy.y, xy.x + cos(ang) * len, xy.y + sin(ang) * len);
    drawn = true;
  }
  boolean finished() {
    return drawn;
  }
  void step() {
  }
  float getAng() {
    return ang;
  }
  V2D getXY() {
    return xy;
  }
}

class V2D {
  float x, y;

  V2D(float x, float y) {
    this.x = x;
    this.y = y;
  } 
  V2D() {
    this.x = 0;
    this.y = 0;
  }
}

static final int RUNNER_TYPE_LINES = 1;

static final int RUNNER_TYPE_CIRCLE = 2;
static final int RUNNER_TYPE_STRAIGHT = 3;
static int min_run_type = 1;
static int max_run_type = 2;

class RunnerController {
  Runner myRunner;
  int stagesLeft;
  V2D xy;
  float ang;

  RunnerController() {
    stagesLeft = int(random(1, 5));
    xy = new V2D(mouseX, mouseY);
    ang = random(0, TWO_PI);
    initMyRunner();
  }
  void initMyRunner() {
    int runType = int(random(min_run_type, max_run_type + 1));
    switch(runType) {
    case RUNNER_TYPE_LINES:
      myRunner = new RunnerLines();
      break;

    case RUNNER_TYPE_CIRCLE:
      myRunner = new RunnerCircle();
      break;

    case RUNNER_TYPE_STRAIGHT:
      myRunner = new RunnerStraight();
      break;

    default:
      myRunner = new InvalidRunner();
      println("invalid runner type: reverting to runner liens");
      break;
    }
    myRunner.initXYA(xy, ang);
    //    stagesLeft -= 1;
  }

  void draw() {
    myRunner.draw();
  }
  void step() {
    myRunner.step();
    if (myRunner.finished()) {
      xy = myRunner.getXY();
      ang = myRunner.getAng();

      // if runner has strayed, point them back towards the screen
      if (xy.x < 0 || xy.x > width ||
        xy.y < 0 || xy.y > height) {
          
          V2D tarPoint = new V2D(random(10, width-10), random(10, height-10));
          ang = atan2(tarPoint.y - xy.y, tarPoint.x - xy.x) + random(-QUARTER_PI / 4, QUARTER_PI / 4);
      }
      initMyRunner();
    }
  } 
  boolean finished() {
    return myRunner.finished() && stagesLeft <= 0;
  }
}

color[] possibleColors = {
  #F93149, 
  #FFA033, 
  #3097C9, 
  #55E82E
};
color posColor() {
  return possibleColors[int(random(0, possibleColors.length))];
}

class InvalidRunner implements Runner {
  float getAng() {
    return 0;
  }
  V2D getXY() {
    return new V2D(0, 0);
  }
  void step() {
  }
  void initXYA(V2D xy, float ang) {
  }
  boolean finished() {
    return true;
  }
  void draw() {
  }
}