Created
July 15, 2022 11:39
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Car { | |
constructor(outerDi, innerDi, screenHt, screenWd, carDi, obsts) { | |
this.x = | |
0.5 * (screenWd - (innerDi + outerDi) / 2) + round(random() * 80) - 40; // Slight variation in starting x | |
this.y = screenHt / 2 - 5; // Right above the starting line | |
this.angle = -PI / 2; // Facing upward | |
//Dimensions for later usage | |
this.carDi = carDi; | |
this.outerDi = outerDi; | |
this.innerDi = innerDi; | |
this.screenHt = screenHt; | |
this.obsts = obsts; | |
this.screenWd = screenWd; | |
// The location of the 'eye' of the car which shows the direction it is facing | |
this.eyeX = this.x + (cos(this.angle) * this.carDi) / 2; | |
this.eyeY = this.y + (sin(this.angle) * this.carDi) / 2; | |
this.v = 0; // Velocity of the car | |
this.fitness = 0; | |
this.currQuad = 1 // Current quadrant of the car(Used to detect if it moves backwards) | |
this.dead = false; | |
this.compLaps = 0 // Completed Laps | |
this.setupModel() | |
} | |
copyWeights() { | |
// Turn weights from Tensorformat to array format | |
const weights = this.model.getWeights(); // Pull weights | |
const weightCopies = []; | |
weights.forEach((wLayer) => { | |
let values = wLayer.dataSync(); // turn that layer from Tensor into array | |
weightCopies.push(values); | |
}); | |
return weightCopies; | |
} | |
setupModel() { | |
// Create a sequential neural network | |
this.model = tf.sequential(); | |
let hidden1 = tf.layers.dense({ | |
units: 8, | |
activation: "relu", | |
inputDim: 5, | |
}); | |
let outputLayer = tf.layers.dense({ | |
units: 4, | |
activation: "sigmoid", | |
}); | |
this.model.add(hidden1); | |
this.model.add(outputLayer); | |
//Compile | |
this.model.compile({ | |
optimizer: "adam", | |
loss: "binaryCrossentropy" | |
}); | |
} | |
updatePos() { // Called at every loop | |
// Move based on velocity | |
this.x += cos(this.angle) * this.v; | |
this.y += sin(this.angle) * this.v; | |
// Update location of eye based on angle | |
this.eyeX = this.x + (cos(this.angle) * this.carDi) / 3; | |
this.eyeY = this.y + (sin(this.angle) * this.carDi) / 3; | |
this.v *= 0.98; // Deccelerate from Friction | |
this.fitness = this.updateFitness(); | |
// Don't let the car move backwards | |
if (this.v < 0) { | |
this.v = 0; | |
} | |
} | |
updateFitness() { | |
// Geometry to calculate how far along in the track the car is(Using arctangent) | |
// Relative cartesian location to racetrack | |
let oX = this.x - this.screenWd / 2; | |
let oY = this.y - this.screenHt / 2; | |
let arctan = atan(oY / oX); | |
// Determine quadrant and lap progress from arctangent and relative location | |
let lapProg = 0; | |
let quad = 0; | |
if (oX < 0) { | |
if (oY < 0) { | |
quad = 1; | |
lapProg = round((50 * arctan) / PI); | |
} else { | |
quad = 4; | |
lapProg = round(100 + (50 * arctan) / PI); | |
} | |
} else { | |
lapProg = 50 + round((50 * arctan) / PI); | |
if (oY < 0) { | |
quad = 2; | |
} else { | |
quad = 3; | |
} | |
} | |
if (this.currQuad == 1 && quad == 4) { // If it moves backwards past the starting line | |
this.dead = true; | |
return this.fitness; | |
} else if (this.currQuad == 4 && quad == 1) { // If it passes the finish line | |
this.compLaps += 1; | |
} | |
this.currQuad = quad; | |
return this.compLaps * 100 + lapProg; | |
} | |
getCollisDist() { | |
// Calculate how close an obstacle is in each of the 5 directions | |
let detDist = [20, 20, 20, 20, 20]; | |
for (let i = 0; i < 20; i++) { | |
//Front | |
let xPos = this.x + (i * 5 + this.carDi / 2) * cos(this.angle); | |
let yPos = this.y + (i * 5 + this.carDi / 2) * sin(this.angle); | |
if (this.ptInObst(xPos, yPos) && detDist[0] > i) { | |
detDist[0] = i; | |
} | |
//Peripheral Right | |
xPos = this.x + (i * 5 + this.carDi / 2) * cos(this.angle + PI / 4); | |
yPos = this.y + (i * 5 + this.carDi / 2) * sin(this.angle + PI / 4); | |
if (this.ptInObst(xPos, yPos) && detDist[1] > i) { | |
detDist[1] = i; | |
} | |
//Peripheral Left | |
xPos = this.x + (i * 5 + this.carDi / 2) * cos(this.angle - PI / 4); | |
yPos = this.y + (i * 5 + this.carDi / 2) * sin(this.angle - PI / 4); | |
if (this.ptInObst(xPos, yPos) && detDist[2] > i) { | |
detDist[2] = i; | |
} | |
//Side Right | |
xPos = this.x + (i * 5 + this.carDi / 2) * cos(this.angle + PI / 2); | |
yPos = this.y + (i * 5 + this.carDi / 2) * sin(this.angle + PI / 2); | |
if (this.ptInObst(xPos, yPos) && detDist[3] > i) { | |
detDist[3] = i; | |
} | |
//Side Left | |
xPos = this.x + (i * 5 + this.carDi / 2) * cos(this.angle - PI / 2); | |
yPos = this.y + (i * 5 + this.carDi / 2) * sin(this.angle - PI / 2); | |
if (this.ptInObst(xPos, yPos) && detDist[4] > i) { | |
detDist[4] = i; | |
} | |
} | |
return detDist; | |
} | |
ptInObst(x, y) { | |
// Check if a point is in an obstacle. Used for both the car and obstacle sensing | |
let outerXRad = this.outerDi / 2 - this.carDi / 2; | |
let outerYRad = this.outerDi / 3 - this.carDi / 2; | |
let innerXRad = this.innerDi / 2 + this.carDi / 2; | |
let innerYRad = this.innerDi / 4 + this.carDi / 2; | |
let xOffset = (x - this.screenWd / 2) ** 2; | |
let yOffset = (y - this.screenHt / 2) ** 2; | |
if (xOffset / outerXRad ** 2 + yOffset / outerYRad ** 2 > 1) { | |
return true; | |
} | |
if (xOffset / innerXRad ** 2 + yOffset / innerYRad ** 2 < 1) { | |
return true; | |
} | |
let hitObst = false; | |
//Obstacles | |
this.obsts.forEach((obst) => { | |
let xObstOffset = (x - obst.x) ** 2; | |
let yObstOffset = (y - obst.y) ** 2; | |
let obstRad = obst.di / 2 + this.carDi / 2; | |
if (xObstOffset + yObstOffset < obstRad ** 2) { | |
hitObst = true; | |
} | |
}); | |
return hitObst; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment