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; this.updateBox = updateBoxFn || this.defaultUpdateBox.bind(this); this.loop = this.loop.bind(this); } updateLine() { if (Math.random() < 0.02) { this.lineTargetX = 250 + (Math.random() - 0.5) * 200; } this.lineX += (this.lineTargetX - this.lineX) * 0.005; } defaultUpdateBox() { const leftSensor = this.box.x - this.box.width / 2; const rightSensor = this.box.x + this.box.width / 2; const onLine = x => x >= this.lineX - this.lineWidth / 2 && x <= this.lineX + this.lineWidth / 2; const leftOnLine = onLine(leftSensor); const rightOnLine = onLine(rightSensor); if (leftOnLine && !rightOnLine) { this.box.dir = -1; } else if (rightOnLine && !leftOnLine) { this.box.dir = 1; } else { //this.box.dir = 0; } 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(); } }