<div id="road">
    <hr />
</div>
<div id="lane0stats"></div>
<div id="lane1stats"></div>
#road {
  width: 100%;
  height: 50px;
  background-color: #CCC;
  position: relative;
}

#road hr {
  width: 100%;
  border: 2px dashed yellow;
  margin: -1px 0;
  position: absolute;
  top: 50%;
  transform: translateY(-30%);
}

.lane0 {
  top: 5%;
}

.lane1 {
  top: calc(55% + 2px);
}

.car, .truck {
  position: absolute;
  height: calc(40% - 2px);
  text-align: center;
  transition: all .2s linear;
}

.car {
  width: 50px;
  left: -50px;
  background-color: red;
}

.truck {
  width: 75px;
  left: -75px;
  background-color: white;
}
var vehicleNumber = 0;
var numVehicles = 0;
var maxVehicles = 10;
var lanePref = 1;
var vehicles = [];
var oneChangeLane = false;

function Vehicle(type, lane) {
    this.type = type ? 1 : 0;
    this.name = type ? "car" : "truck";
    this.lane = type ? lane : 1;
    this.lanePref = Math.floor(Math.random() * lanePref);
    this.lanePref = 1;
    this.width = type ? 50 : 75;
    this.location = - this.width;
    this.speed = (5 + Math.floor(Math.random() * 8) + (5 * type))/2;
    this.caution = this.speed * 1.5;
    this.isMatching = false;
    this.matched = {};
    this.id = this.name + vehicleNumber;
  
    numVehicles += 1;
    vehicleNumber += 1;
    //console.log("Creating vehicle " + numVehicles + "/" + maxVehicles);
  
    addToLane(lane, this);
}

Vehicle.prototype.drive = function() {
  var carAhead = carAheadInCurrentLane(this);
  //console.log(carAhead);
  var speed = this.speed;
  if (carAhead) {
     if (canSwitchLanes(this)) {
       switchLane(this);
     } else {
       //console.log("Matching speed");
       speed = matchSpeed(this, carAhead);
     }
  } else {
    // Reset matched
    $("#" + this.id).text(this.id);
    this.isMatching = false;
    this.matched = {};
    
    if (this.lane != this.lanePref && this.lanePref != 2) {
      //console.log("not in preferred lane...");
      if(canSwitchLanes(this)) {
        switchLane(this);
      }
    }
  }

  this.location += speed ? speed : this.speed;
  $("#" + this.id).css("left", this.location + "px"); // move it

  var object = this;
  if (this.location > $(window).width()) {
    remove(this);
    numVehicles -= 1;
  }
}

function getMatched(car) {
  if (car.isMatching) {
    return getMatched(car.matched);
  } else {
    return car;
  }
}

function matchSpeed(car, carAhead) {
  var carToMatch = getMatched(carAhead);
  
  car.matched = carToMatch;
  car.isMatching = true;
  
  //$("#" + car.id).text(car.id + " matching " + carToMatch.id);
  
  return carToMatch.speed;
}


function canSwitchLanes(object) {
  var canSwitch = true;
  var oBack = object.location;
  var oFront = object.location + object.width;
  
  for (let item of vehicles) {
    if ( object.id != item.id && item.lane != object.lane) {
      var iBack = item.location;
      var iFront = item.location + item.width;
      
      // console.log("Checking if can switch");
      // If back of item is within car + caution buffer
      if (iBack > oBack - object.caution && iBack < oFront + object.caution) {
        canSwitch = false;
        // break;
      }
      // If front of item is within car + caution buffer
      if (iFront > oBack - object.caution && iFront < oFront + object.caution) {
        canSwitch = false;
        // break;
      }
      // If back of car is within item + caution buffer
      if (oBack - object.caution > iBack && oBack - object.caution < iFront) {
        canSwitch = false;
        // break;
      }
      // If front of car is within item + caution buffer
      if (oFront + object.caution > iBack && oFront + object.caution < iFront) {
        canSwitch = false;
        // break;
      }
    }
  }
  
  return canSwitch;
}

function carAheadInCurrentLane(object) {
  var carAhead = 0;
    vehicles.forEach( function(item, index) {
      if (object.id != item.id && item.lane == object.lane) {
        if ((object.location + object.width + object.caution) > (item.location) && (object.location + object.width) < item.location) {
           carAhead = item;
         }
      }
    })
  
  return carAhead;
}

function showCurrentVehiclesInLanes() {
   // Enumerate cars in each lane
  $("#lane0stats").text("");
  $("#lane1stats").text("");
  vehicles.forEach(function(item){
    if (item.lane == 1) {
      $("#lane1stats").append(item.id + ", ");
    } else {
      $("#lane0stats").append(item.id + ", ");
    }
  })
}

function switchLane(object) {
  //console.log("Switching lanes");
  oneChangeLane = true;
  vehicles.forEach(function(item){
    if (item.id == object.id) {
      item.lane = item.lane ? 0 : 1;
    }
  });
  
  object.isMatching = false;
  object.matched = {};
  
  $("#" + object.id).toggleClass("lane0 lane1");
  
  showCurrentVehiclesInLanes();
}

function addToLane(lane, object) {
  //console.log("Adding vehicle to lane " + lane);
  object.lane = lane;
  vehicles.push(object);
  
    $("#road")
   .append('<div id="' + object.id + '" class="' + object.name + " lane" + object.lane + '"></div>');    
  
  showCurrentVehiclesInLanes();
}

function remove(object) {
  $("#" + object.id).remove();
  
  vehicles = jQuery.grep(vehicles, function(value) {
    return value.id != object.id;
  });
  
  unlinkVehicles(object);

  showCurrentVehiclesInLanes();
}

// Unlink all vehicles matching speed
function unlinkVehicles(object) {
  vehicles.forEach(function(item, index){
    if (item.matched == object) {
      item.matched = {};
      item.isMatching = false;
    }
  })
}
 
function laneOpen(lane) {
  var laneOpen = true;
  vehicles.forEach(function(item, index){
    if (item.lane == lane && item.location < item.caution) {
      laneOpen = false;
    }
  });
  
  //console.log(laneOpen);
  return laneOpen;
}

function createVehicle() {
  if (numVehicles < maxVehicles) {
    var lane =  Math.floor(Math.random() * 2);
    //var lane = 1;
    var type =  Math.floor(Math.random() * 8);
    
    //console.log("Is lane " + lane + " open?");
    if (laneOpen(lane)) {
      var vehicle = new Vehicle(type, lane);
      setInterval(function(){vehicle.drive();}, 200);
    }
  }
  var delay = 500 + Math.floor(Math.random() * 2000);
  setTimeout(function() {createVehicle();}, delay );
}

function start() {
  
  createVehicle();
}

window.addEventListener('load', function () {
    start();
});
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js