> show canvas only <


/* built with Studio Sketchpad: 
 *   https://sketchpad.cc
 * 
 * observe the evolution of this sketch: 
 *   https://studio.sketchpad.cc/sp/pad/view/ro.e82yjajYoj0/rev.223
 * 
 * authors: 
 *   Dave Vickery

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



// This sketch builds on a prior work, "Gammy Horse Race Simulator", created by Dave Vickery & [unnamed author]
// http://studio.sketchpad.cc/sp/pad/view/ro.9ImkoHfwqLGdq/rev.3058

// Walker objects move at random but are biased towards the target
// Click anywhere on the screen to set the target. First horse to reach the target is declared the winner.

final float MAXSTEPSIZE = 10;
final int TICKLENGTH = 10;
final int TRAILLENGTH = 30;
final int GOALSIZE = 30;
final int HORSECOUNT = 5;

Walker[] entities;
int prevMillis = 0;
PVector target;
int winner = -1;
boolean racing = false;
PImage bg;

void setup() {
    size(500, 300);
    background(255);
    target = new PVector(30, 30);
    bg = createImage(width, height, RGB);
    bg.loadPixels();
    for (int x = 0; x < width; x++) {
          for (int y = 0; y < height; y++) {
            float bright = map(noise((float)x/15, (float)y/15), 0, 1, 150, 200);
            bg.pixels[x+y*width] = color(0, bright, 0);
          }
        }
    bg.updatePixels();
    entities = new Walker[HORSECOUNT];
    for (int i = 0; i < entities.length; i++) {
        entities[i] = new Walker(""+(i+1), target);
        entities[i].setGoal(target);
    }
    //generator = new Random();
    noStroke();
    fill(0);
}
void draw() {
    if (millis() > prevMillis + TICKLENGTH) {
        image(bg, 0, 0);
        for (int i = 0; i < entities.length; i++) {
            entities[i].step();
            entities[i].display();
            if (winner == -1 && entities[i].reachedGoal()) {
                winner = i;
            }
        }
        prevMillis = millis();
    }
    noFill();
    stroke(255, 0, 0);
    strokeWeight(2);
    ellipse(target.x, target.y, GOALSIZE, GOALSIZE);
    strokeWeight(1);
    if (racing && winner > -1) {
        fill(0);
        text("Winner is number " + entities[winner].name + "!", 10, 20);
    }
}
class Walker {
    PVector[] points;
    PVector goal = new PVector(0, 0);
    String name;
    
    color col;
    
    Walker(String newName) {
        this(newName, new PVector((int)random(width), (int)random(height)), color(random(255), random(255), random(255)));
    }
    
    Walker(String newName, PVector start) {
        this(newName, start, color(random(255), random(255), random(255)));
    }
    
    Walker(String newName, PVector start, int startCol) {
        points = new PVector[TRAILLENGTH];
        for (int i = 0; i < points.length; i++) {
            points[i] = new PVector();
            points[i].set(start);
        }
        col = startCol;
        name = newName;
    }
    
    void display() {
        for (int i = 0; i < points.length; i++) {
            float alpha = map(i, 0, points.length, 255, 0);
            fill(col, alpha);
            stroke(col, alpha);
            if (i > 0) {
                line(points[i].x, points[i].y, points[i - 1].x, points[i - 1].y);
            }
            ellipse(points[i].x, points[i].y, 4, 4);
        }
    }
    
    void step() {
        PVector goalBias = new PVector(0, 0);
        if (goal.x < points[0].x) {
            goalBias.x = -0.5;
        } else {
            goalBias.x = 0.5;
        }
        if (goal.y < points[0].y) {
            goalBias.y = -0.5;
        } else {
            goalBias.y = 0.5;
        }
        for (int i = points.length - 1; i > 0; i--) {
            points[i].set(points[i - 1]);
        }
        float randX;
        do {
            randX = random(-1 + goalBias.x, 1 + goalBias.x);
        } while (random(1) < sq(randX));
        float randY;
        do {
            randY = random(-1 + goalBias.y, 1 + goalBias.y);
        } while (random(1) < sq(randY));
        
        points[0].x = points[1].x + (int)(randX * MAXSTEPSIZE);
        points[0].y = points[1].y + (int)(randY * MAXSTEPSIZE);
        fill(0);
        text(name, points[0].x, points[0].y);
    }
    
    void setGoal(PVector newGoal) {
        goal.set(newGoal);
    }
    
    boolean reachedGoal() {
        if (abs(points[0].x - goal.x) < (GOALSIZE / 2)
                && abs(points[0].y - goal.y) < (GOALSIZE / 2)) {
            return true;
        } else {
            return false;
        }
    }
}
void mousePressed() {
    target.x = mouseX;
    target.y = mouseY;
    for (int i = 0; i < entities.length; i++) {
        entities[i].setGoal(target);
    }
    winner = -1;
    racing = true;
}