<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();
});
This Pen doesn't use any external CSS resources.