Skip to content

Instantly share code, notes, and snippets.

@LiorB-D
Created July 15, 2022 11:38
Show Gist options
  • Save LiorB-D/37b8e894745ffd76dbd21b954ece288a to your computer and use it in GitHub Desktop.
Save LiorB-D/37b8e894745ffd76dbd21b954ece288a to your computer and use it in GitHub Desktop.
<head>
<!-- Load in Tensorflow, P5, and our Car Class-->
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@2.0.0/dist/tf.min.js"></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.min.js"
integrity="sha512-WIklPM6qPCIp6d3fSSr90j+1unQHUOoWDS4sdTiR8gxUTnyZ8S2Mr8e10sKKJ/bhJgpAa/qG068RDkg6fIlNFA=="
crossorigin="anonymous"
></script>
<script src="car.js"></script>
<script>
// Screen dimensions
const screenHt = 650;
const screenWd = 800;
const innerDi = 300; // Diameter of Inner Circle
const outerDi = 750; // Diameter of Outer Circle
const carDi = 20; // Diameter of each car
let frameCount = 0;
let currGen = 1;
let topFitnessScore = 0; // Highest fitness score of any generation
//Obstacles can be added/removed without any additional implementation
let obsts = [
{ x: screenWd / 2, y: screenHt / 4, di: 40 },
{ x: screenWd / 2, y: (3 * screenHt) / 4, di: 40 },
];
// UI elements
let nextGenBtn, mrateInput, mrateLbl;
let infoLbl;
let cars = [];
function setup() {
createCanvas(screenWd + 20 + 200, screenHt + 20);
frameRate(60); // Edit Frame Rate to change speed of simulation
// Setup UI elements with P5
nextGenBtn = createButton("Next Generation");
nextGenBtn.position(screenWd + 30, 110);
mrateInput = createInput("0.15");
mrateInput.position(screenWd + 30, 80);
mrateLbl = createElement("p", "Mutation Rate:");
mrateLbl.position(screenWd + 30, 45);
infoLbl = createElement(
"p",
"Gen: " + currGen + ", Top Score: " + topFitnessScore
);
infoLbl.position(screenWd + 30, 150);
// Add 20 new cars
for (let i = 0; i < 20; i++) {
cars.push(new Car(outerDi, innerDi, screenHt, screenWd, carDi, obsts));
}
}
function draw() {
// Looped based on framerate
//Draw Driving Map
strokeWeight(1);
fill(50);
stroke("black");
rect(0, 0, screenWd, screenHt);
//Outer Circle
fill(210);
ellipse(screenWd / 2, screenHt / 2, outerDi, outerDi / 1.5);
fill("green");
strokeWeight(3);
//Obstacles
obsts.forEach((obs) => {
ellipse(obs.x, obs.y, obs.di);
});
//Inner circle
ellipse(screenWd / 2, screenHt / 2, innerDi, innerDi / 2);
//Draw Finish Line
stroke("red");
strokeWeight(4);
line(
screenWd / 2 - outerDi / 2,
screenHt / 2,
screenWd / 2 - innerDi / 2,
screenHt / 2
);
cars.forEach((car) => {
//Draw car
//Draw Wheels
fill("black");
strokeWeight(0);
ellipse(
car.x + (carDi / 2) * cos(car.angle + PI / 4),
car.y + (carDi / 2) * sin(car.angle + PI / 4),
carDi / 2.5
);
ellipse(
car.x + (carDi / 2) * cos(car.angle - PI / 4),
car.y + (carDi / 2) * sin(car.angle - PI / 4),
carDi / 2.5
);
ellipse(
car.x + (carDi / 2) * cos(car.angle + 0.75 * PI),
car.y + (carDi / 2) * sin(car.angle + 0.75 * PI),
carDi / 2.5
);
ellipse(
car.x + (carDi / 2) * cos(car.angle - 0.75 * PI),
car.y + (carDi / 2) * sin(car.angle - 0.75 * PI),
carDi / 2.5
);
//Draw Car Base
fill("blue");
stroke("black");
strokeWeight(1);
ellipse(car.x, car.y, carDi);
//Draw Eye
fill("yellow");
ellipse(car.eyeX, car.eyeY, carDi / 3);
// Car Behavior
if (car.dead == false && car.ptInObst(car.x, car.y) == false) {
// Make sure car is alive
car.updatePos();
//Make Decision
//Get obstacle detection distances
let inputs = tf.tensor2d([car.getCollisDist()]);
// Predict the value
let decArr = car.model.predict(inputs).dataSync();
//Adjust angle and velocity based on outputs
car.v += 0.1 * decArr[0]; // Accelerate
car.v -= 0.2 * decArr[1]; // Brake
car.angle -= 0.1 * decArr[2]; // Turn Left
car.angle += 0.1 * decArr[3]; // Turn Right
} else {
car.dead = true; // Kill the car if it is in obstacle
}
});
frameCount += 1;
// If 200 frames has passed plus a scaled of the highestScore, then start next generation
if (frameCount > 200 + topFitnessScore * 3) {
startNewGen();
frameCount = 0;
} else if (frameCount > 80) { // Check for inactivity after 80 frames
let stillActive = false;
// Make sure atleast one car has made progress without dying
cars.forEach((car) => {
if (car.fitness > 3 && car.dead == false) {
stillActive = true;
}
});
if (stillActive == false) {
startNewGen();
frameCount = 0;
}
}
}
function endGen() {
let totalFitness = 0;
bestOfGen = 0;
// Calculate total fitness and find highest performing car
cars.forEach((car, ind) => {
car.dead = true;
totalFitness += car.fitness;
if (car.fitness > bestOfGen) {
bestOfGen = car.fitness;
}
});
//Update highest score if applicable
if (bestOfGen > topFitnessScore) {
topFitnessScore = bestOfGen;
}
return totalFitness;
}
function selectRandParent(tFit) {
let randVal = tFit * random(); // Random value between (0, tFit)
let count = 0; // Cumulitive Fitness
for (let i = 0; i < cars.length; i++) {
count += cars[i].fitness;
if (randVal <= count) {
return i;
}
}
}
function startNewGen() {
totalFitness = endGen();
let newCarArr = []; // The next generation
mutationRate = mrateInput.value() // Pull mutation rate
//Get parent weights
let parentWeights = [];
cars.forEach((c) => {
parentWeights.push(c.copyWeights());
});
//Create next generation
for (let i = 0; i < 10; i++) {
// Initialize Child
const child = new Car(
outerDi,
innerDi,
screenHt,
screenWd,
carDi,
obsts
);
let currWeights = child.model.getWeights();
let newWeights = [];
for (let i = 0; i < currWeights.length; i++) {
// Iterate through each layer
//Get Current Weight information
let currVals = currWeights[i].dataSync(); // Array format
let shape = currWeights[i].shape;
for (let j = 0; j < currVals.length; j++) {
// Iterate through each weight
// Select trait from random parent
currVals[j] = parentWeights[selectRandParent(totalFitness)][i][j];
// Assign random value to weight from Gaussian Distribution(Function from P5)
if(random() < mutationRate) {
currVals[j] += randomGaussian()
}
}
let newTens = tf.tensor(currVals, shape);
newWeights[i] = newTens;
}
child.model.setWeights(newWeights);
newCarArr.push(child);
}
cars = newCarArr;
// Increment Generation and Update Label
currGen += 1;
infoLbl.elt.innerText =
"Gen: " + currGen + ", Top Score: " + topFitnessScore;
}
</script>
<title>Learning to Drive w/ Genetic Algorithm</title>
</head>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment