linefollower_instruction_site/LineFollowerSim.js

180 lines
4.6 KiB
JavaScript

export class LineFollowerSim {
constructor(canvas, updateBoxFn) {
this.canvas = canvas;
this.ctx = canvas.getContext("2d");
this.box = {
x: 250,
y: 150,
width: 40,
height: 20,
speed: 0.4,
dir: 0
};
this.lineX = 250;
this.lineTargetX = 250;
this.lineWidth = 12;
const bbThresholdSlider = document.getElementById("bbThresholdSlider");
const bbTurnSpeedSlider = document.getElementById("bbTurnSpeedSlider");
const bbThresholdValue = document.getElementById("bbThresholdValue");
const bbTurnSpeedValue = document.getElementById("bbTurnSpeedValue");
this.threshold = parseFloat(bbThresholdSlider.value);
const resetBtn = document.getElementById("resetBangBangBtn");
resetBtn.addEventListener("click", () => {
// Put the box instantly on the line
this.box.x = this.lineX;
});
this.lastUpdateTime = 0;
this.updateInterval = 50; // ms
this.updateBox = updateBoxFn || this.defaultUpdateBox.bind(this);
this.loop = this.loop.bind(this);
}
updateCodeBlock() {
this.code = `
if left_color < ${this.threshold}:
motor(-${this.turnAmount}, ${this.turnAmount}) # Turn left
elif right_color < ${this.threshold}:
motor(${this.turnAmount}, -${this.turnAmount}) # Turn right
else:
motor(50, 50) # Go straight
`
document.getElementById('code-block').textContent = this.code;
}
updateLine() {
if (Math.random() < 0.02) {
this.lineTargetX = 250 + (Math.random() - 0.5) * 200;
}
this.lineX += (this.lineTargetX - this.lineX) * 0.0025;
}
lineProximity(x, lineX) {
const distance = Math.abs(x - lineX);
const maxDistance = 20;
const maxValue = 1024;
if (distance > maxDistance) return 0;
// Reverse the falloff: closer = lower, farther = higher
const value = 100 + (distance / maxDistance) * (maxValue - 100);
return Math.round(value);
}
defaultUpdateBox() {
const now = performance.now();
if (now - this.lastUpdateTime < this.updateInterval) return;
this.lastUpdateTime = now;
const bbLeftColorValue = document.getElementById("leftColor");
const bbRightColorValue = document.getElementById("rightColor");
const bbThresh = parseFloat(bbThresholdSlider.value);
const bbTurnSpeed = parseFloat(bbTurnSpeedSlider.value);
bbThresholdValue.textContent = bbThresh.toFixed(0);
bbTurnSpeedValue.textContent = bbTurnSpeed.toFixed(2);
this.threshold = bbThresh.toFixed(0);
this.turnAmount = bbTurnSpeed.toFixed(0);
const leftSensor = this.box.x - this.box.width / 2;
const rightSensor = this.box.x + this.box.width / 2;
this.updateCodeBlock();
const onLine = x => x >= this.lineX - this.lineWidth / 2 && x <= this.lineX + this.lineWidth / 2;
let leftColor = this.lineProximity(leftSensor, this.lineX);
let rightColor = this.lineProximity(rightSensor, this.lineX);
if (leftColor == 0) {
leftColor = 1024; // No line detected
}
if (rightColor == 0) {
rightColor = 1024; // No line detected
}
bbLeftColorValue.textContent = leftColor;
bbRightColorValue.textContent = rightColor;
const leftOnLine = onLine(leftSensor);
const rightOnLine = onLine(rightSensor);
if (leftColor < bbThresh) {
this.box.dir -= (bbTurnSpeed * 0.01);
if (this.box.dir < -5) {
this.box.dir = -5; // Limit the turn speed
}
} else if (rightColor < bbThresh) {
this.box.dir += bbTurnSpeed * 0.01;
if (this.box.dir > 5) {
this.box.dir = 5; // Limit the turn speed
}
} else {
if (this.box.dir > 1) {
this.box.dir -= 0.1;
} else if (this.box.dir < -1) {
this.box.dir += 0.1;
} else {
this.box.dir = 0;
}
}
console.log(this.box.dir);
this.box.x += this.box.dir * this.box.speed;
}
draw() {
const ctx = this.ctx;
const { x, y, width, height } = this.box;
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// Line
ctx.beginPath();
ctx.moveTo(this.lineX, 0);
ctx.lineTo(this.lineX, this.canvas.height);
ctx.strokeStyle = "blue";
ctx.lineWidth = this.lineWidth;
ctx.stroke();
// Box
ctx.fillStyle = "red";
ctx.fillRect(x - width / 2, y - height / 2, width, height);
// Sensors
ctx.fillStyle = "black";
ctx.beginPath();
ctx.arc(x - width / 2, y - height / 2, 3, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(x + width / 2, y - height / 2, 3, 0, Math.PI * 2);
ctx.fill();
}
loop() {
this.updateLine();
this.updateBox();
this.draw();
requestAnimationFrame(this.loop);
}
start() {
this.loop();
}
}