Interactive Simulation
Design Challenge
Create a sketch that would simulate an existing natural system - look at physics, biology, and other natural sciences for good examples. Start with the environment - where is the system situated? What are the forces the environment might exert on the system? 
Examine the agents in the system, their relationships to each other and the environment they are in. Then look at how this system would develop over time. What are the rules that you are going to come up with? What are the parameters you are going to feed into them and what effect will the changes have on the development of the system?
Design tools used: p5.js javascript
Ideation and concepts
In approaching this design challenge, I started to think about natural systems and how some forces attract and repel each other. One of the first ideas I had was a school of fish avoiding a large shark. The idea works fine, but practically, I didn't really have a clue for how it could be coded
This sketch illustrates how the smaller objects would scatter away from the predator, but if was in it's way, it would be consumed. 
I moved on to another idea of attraction and repulsion, traffic. Cars must avoid each other and other dangers on the road in order to survive to their destination. Similarly to the shark idea, the largest mechanical predator is a train. Avoid it or get destroyed. So I started thinking about how traffic flows and some basic movements. The train would cut across the traffic and it would be controlled by the user. 
Designing this concept and creating javascript code for proved to be very challenging. So I simplified the movements. 
The train now travels from north to south, while traffic move east to west on the screen. Here is a sketch of that concept. The idea here is that traffic is free flowing and cars move at different speeds, avoiding each other without colliding. Once the train enters the screen, the cars would detect it and stop traffic so there is no collision. 
The interaction of this concept is the control of the train. By pressing the spacebar, it signals the train to start moving south until it reaches the other side to its destination. Another press would reverse course. 

If a car would be in its path as it is moving, it would be consumed by train, simulation a tragic event. 
Once there is not train on the screen, the traffic would resume its natural course of flow. 
The JS Sketch
These randomly colorful rectangles represent the cars in traffic. The top 3 lanes are going west, and the bottom 3 lanes are going east. 
The initial collision detection code did make the cars stop but right next to the train as it moved from north to south. The cars were able to make each other stop without crashing into each other, as intended. 
However, there are a few bugs that catches the cars as they are moving to their destination. So i increased the radius around the train. And that seemed to work. 
Now, any car that is caught in the path of the train still gets consumed and removed from the array, resetting a new car in its place. 
Final Thoughts
This code simulates how traffic moves in the world. However, a few tweaks could make this even better. I would like to learn how get cars to see ahead of time and anticipate that a train is coming. Currently, the cars can only detect the train if it is right in front of it. Ideally, each car would be able to recognize that a train is about to cross their path. Also, additional details such as traffic signals and the roads could make this simulation a bit more easier to understand.

You may notice that there is a bug that I have not been able to figure out. The cars stop in front of the train, but they stack on top of each other before resuming their direction of travel. 
The P5.js Code
let traffics = [];
let train;
let trainMoving = true; // Train will start moving immediately
const lanes = 6; // Number of lanes
const laneHeight = 50; // Height of each lane
const laneYPositions = []; // Array to store the y-coordinate of each lane
const minSpacing = 2; // Minimum spacing between cars (buffer)
const bufferZone = 20; // Buffer zone for stopping cars
const trainBuffer = 50; // Buffer space around the train for stopping
const maxCarsPerLane = 6; // Maximum number of cars per lane
const carWidth = 50; // Width of the car
function setup() {
  createCanvas(800, lanes * laneHeight);
  // Calculate y-coordinates for each lane
  for (let i = 0; i < lanes; i++) {
    laneYPositions.push(i * laneHeight + laneHeight / 2 - 10); // Centering cars in each lane
  }
  // Create multiple traffic objects for each lane
  for (let i = 0; i < lanes; i++) {
    traffics.push(new Traffic(laneYPositions[i], i < 3 ? -1 : 1)); // Top 3 lanes go right-to-left, bottom 3 go left-to-right
  }
  // Create a single train object with an initial direction
  train = new Train(width / 2 - 50, -300, 1);
}
function draw() {
  background(220);
  // Update and display each traffic group
  for (let traffic of traffics) {
    traffic.update();
    traffic.display();
  }
  // Update and display train only if it's moving
  if (trainMoving) {
    train.move();
    if (train.isOffScreen()) {
      // Resume all cars' original speed when train exits screen
      for (let traffic of traffics) {
        traffic.resume();
      }
      trainMoving = false; // Stop train movement until re-triggered
    }
  }
  train.display();
}
// Traffic class to manage a group of cars in a lane
class Traffic {
  constructor(y, direction) {
    this.y = y;
    this.direction = direction; // 1 for left-to-right, -1 for right-to-left
    this.cars = [];
    this.createCars(); // Initialize cars
  }
  createCars() {
    let numberOfCars = Math.floor(random(1, maxCarsPerLane + 1)); // Random number of cars between 1 and 6
    let positions = []; // To store x positions of cars
    // Create cars ensuring spacing
    for (let i = 0; i < numberOfCars; i++) {
      let speed = random(2, 5);
      let startX;
      // Check for spacing with existing cars
      let carSpacing = false;
      let attempts = 0; // Limit the number of attempts to find a position
      do {
        startX = this.direction === 1 ? -random(200) : width + random(200);
        carSpacing = true;
        // Check if the new car's position conflicts with existing cars
        for (let pos of positions) {
          if (this.direction === 1) {
            // Check right-to-left direction
            if (startX + carWidth + minSpacing > pos && startX < pos + carWidth + minSpacing) {
              carSpacing = false; // Collision detected
              break;
            }
          } else {
            // Check left-to-right direction
            if (startX < pos + carWidth + minSpacing && startX + carWidth + minSpacing > pos) {
              carSpacing = false; // Collision detected
              break;
            }
          }
        }
        attempts++;
      } while (!carSpacing && attempts < 20); // Try up to 20 times to find a valid position
      if (carSpacing) {
        positions.push(startX); // Add position if no collision
        this.cars.push(new Car(startX, this.y, speed, this.direction));
      }
    }
  }
  update() {
    for (let i = this.cars.length - 1; i >= 0; i--) { // Loop backwards to safely remove cars
      let car = this.cars[i];
      // Check if the car is in the buffer zone
      if (car.isInBufferZone(train)) {
        car.stop(); // Stop the car if it's in the buffer zone
        // Check if the car is in the train's path
        if (car.isInTrainPath(train)) {
          car.reset(); // Reset car if it collides with the train
          this.cars.splice(i, 1); // Remove the car from the array
          addNewCar(this.cars, car.y, this.direction); // Add a new car in the same lane
        }
      } else {
        car.resume(); // Resume car's original speed
        car.move();
      }
      car.display();
    }
  }
  display() {
    for (let car of this.cars) {
      car.display();
    }
  }
  // Resume all cars in this traffic group
  resume() {
    for (let car of this.cars) {
      car.resume();
    }
  }
}
// Car class
class Car {
  constructor(x, y, speed, direction) {
    this.x = x;
    this.y = y;
    this.baseSpeed = speed;
    this.speed = speed;
    this.direction = direction; // 1 for left-to-right, -1 for right-to-left
    this.stopped = false;
    this.color = color(random(255), random(255), random(255)); // Random color for each car
  }
  move() {
    if (!this.stopped) {
      this.x += this.speed * this.direction;
      // Reset car position and randomize speed when it goes off-screen
      if (this.direction === 1 && this.x > width) {
        this.x = -50;
        this.randomizeSpeed();
      } else if (this.direction === -1 && this.x < -50) {
        this.x = width;
        this.randomizeSpeed();
      }
    }
  }
  // Randomize speed within a specified range
  randomizeSpeed() {
    this.speed = random(2, 5);
    this.baseSpeed = this.speed; // Update baseSpeed to maintain consistency
  }
  stop() {
    this.stopped = true;
    this.speed = 0; // Set speed to zero when stopped
  }
  resume() {
    this.stopped = false;
    this.speed = this.baseSpeed; // Restore the car’s original speed
  }
  display() {
    fill(this.color); // Use the car's random color
    rect(this.x, this.y, carWidth, 20);
  }
  // Reset car to a starting position when it collides with the train
  reset() {
    this.x = this.direction === 1 ? -random(200) : width + random(200);
    this.randomizeSpeed(); // Set a new random speed
  }
  // Check if the car is in the buffer zone (20 pixels) around the train
  isInBufferZone(train) {
    return (
      this.y > train.y - trainBuffer && this.y < train.y + 350 + trainBuffer && // Buffer around train vertically
      this.x + carWidth > train.x - 50 - bufferZone && this.x < train.x + 90 + bufferZone // Buffer around train horizontally
    );
  }
  // Check if the car is in the train's path (as a single unit)
  isInTrainPath(train) {
    return (
      this.y > train.y - trainBuffer && this.y < train.y + 350 + trainBuffer && // Buffer around train vertically
      this.x + carWidth > train.x - 50 - 1 && this.x < train.x + 90 + 1 // Check 1 pixel away from train
    );
  }
}
// Train class (now a composite of 3 vertical rectangles with a 5-pixel gap)
class Train {
  constructor(x, y, speed) {
    this.x = x;
    this.y = y;
    this.speed = speed;
    this.direction = 1; // 1 for moving down
  }
  move() {
    this.y += this.speed * this.direction;
  }
  display() {
    fill(0, 0, 255);
    for (let i = 0; i < 3; i++) {
      rect(this.x, this.y + i * 105, 40, 100);
    }
  }
  // Check if the train is completely off the screen
  isOffScreen() {
    return (this.y > height && this.direction === 1) || (this.y < -320 && this.direction === -1);
  }
  // Change the direction of the train
  toggleDirection() {
    this.direction *= -1; // Reverse direction
  }
}
// Add a new car in the same lane after a collision
function addNewCar(carsArray, y, direction) {
  let speed = random(2, 5);
  let startX = direction === 1 ? -random(200) : width + random(200);
  carsArray.push(new Car(startX, y, speed, direction));
}
// Handle spacebar input
function keyPressed() {
  if (key === ' ') {
    trainMoving = true;
    train.toggleDirection(); // Toggle train direction on spacebar press
  }
}


Back to Top