added line sensor obstacle collisions
parent
485aa4557d
commit
0674e5ca2f
66
game.js
66
game.js
|
|
@ -11,10 +11,17 @@ let pyodideWorker = startPyodideWorker();
|
|||
let robots = createInitialRobots();
|
||||
let paused = false;
|
||||
|
||||
let scale = 1; // Zoom level
|
||||
let offsetX = 0; // Pan X
|
||||
let offsetY = 0; // Pan Y
|
||||
let isPanning = false;
|
||||
let startX, startY; // Mouse start positions
|
||||
|
||||
|
||||
// ✅ Function to create the Pyodide Worker
|
||||
function startPyodideWorker() {
|
||||
const worker = new Worker("pyodide-worker.js");
|
||||
|
||||
|
||||
// ✅ Reattach the event listener when a new worker is created
|
||||
worker.onmessage = (event) => {
|
||||
if (paused) return;
|
||||
|
|
@ -98,9 +105,17 @@ function resetGame() {
|
|||
// ✅ Game Loop
|
||||
function gameLoop() {
|
||||
if (!paused) {
|
||||
ctx.clearRect(0, 0, gameCanvas.width, gameCanvas.height);
|
||||
ctx.resetTransform();
|
||||
// Fill the entire visible canvas to remove artifacts
|
||||
ctx.fillStyle = "#DDD";
|
||||
ctx.fillRect(0, 0, gameCanvas.width, gameCanvas.height);
|
||||
|
||||
ctx.translate(offsetX, offsetY); // Apply panning
|
||||
ctx.scale(scale, scale); // Apply zooming
|
||||
|
||||
|
||||
gameWorld.draw(ctx);
|
||||
|
||||
|
||||
for (let id in robots) {
|
||||
robots[id].update(ctx, gameWorld);
|
||||
}
|
||||
|
|
@ -108,6 +123,7 @@ function gameLoop() {
|
|||
requestAnimationFrame(gameLoop);
|
||||
}
|
||||
|
||||
|
||||
// Start game loop
|
||||
gameLoop();
|
||||
|
||||
|
|
@ -124,3 +140,47 @@ document.getElementById("compile-button").addEventListener("click", () => {
|
|||
|
||||
document.getElementById("pause-button").addEventListener("click", togglePause);
|
||||
document.getElementById("reset-button").addEventListener("click", resetGame);
|
||||
|
||||
gameCanvas.addEventListener("wheel", (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
const scaleFactor = 1.1;
|
||||
const mouseX = event.offsetX;
|
||||
const mouseY = event.offsetY;
|
||||
|
||||
// Convert mouse coordinates to world coordinates (before zoom)
|
||||
const worldX = (mouseX - offsetX) / scale;
|
||||
const worldY = (mouseY - offsetY) / scale;
|
||||
|
||||
// Apply zoom
|
||||
if (event.deltaY < 0) {
|
||||
scale *= scaleFactor; // Zoom in
|
||||
} else {
|
||||
scale /= scaleFactor; // Zoom out
|
||||
}
|
||||
|
||||
// Keep zoom within limits
|
||||
scale = Math.max(0.5, Math.min(3, scale));
|
||||
|
||||
// Adjust offset so zooming is centered at mouse position
|
||||
offsetX = mouseX - worldX * scale;
|
||||
offsetY = mouseY - worldY * scale;
|
||||
});
|
||||
|
||||
|
||||
gameCanvas.addEventListener("mousedown", (event) => {
|
||||
isPanning = true;
|
||||
startX = event.clientX - offsetX;
|
||||
startY = event.clientY - offsetY;
|
||||
});
|
||||
|
||||
gameCanvas.addEventListener("mousemove", (event) => {
|
||||
if (!isPanning) return;
|
||||
offsetX = event.clientX - startX;
|
||||
offsetY = event.clientY - startY;
|
||||
});
|
||||
|
||||
gameCanvas.addEventListener("mouseup", () => {
|
||||
isPanning = false;
|
||||
});
|
||||
|
||||
|
|
|
|||
104
gameworld.js
104
gameworld.js
|
|
@ -1,16 +1,33 @@
|
|||
export class GameWorld {
|
||||
constructor() {
|
||||
|
||||
let polygon1 = [
|
||||
{ x: 80, y: 100 }, // Vertex 1
|
||||
{ x: 200, y: 100 }, // Vertex 2
|
||||
{ x: 200, y: 200 }, // Vertex 3
|
||||
{ x: 100, y: 200 } // Vertex 4
|
||||
];
|
||||
|
||||
let polygon2 = [
|
||||
{ x: 300, y: 400 }, // Vertex 1
|
||||
{ x: 320, y: 450 }, // Vertex 2
|
||||
{ x: 250, y: 550 }, // Vertex 3
|
||||
{ x: 280, y: 420 } // Vertex 4
|
||||
];
|
||||
|
||||
|
||||
this.obstacles = [
|
||||
{ x: 300, y: 300, width: 50, height: 50 }, // Example obstacle
|
||||
{ x: 500, y: 400, width: 70, height: 70 } // Another obstacle
|
||||
polygon1, // Example obstacle
|
||||
polygon2 // Another obstacle
|
||||
];
|
||||
}
|
||||
|
||||
// Check if a point (x, y) intersects with any obstacles
|
||||
isObstacle(x, y) {
|
||||
return this.obstacles.some(
|
||||
obj => x > obj.x && x < obj.x + obj.width && y > obj.y && y < obj.y + obj.height
|
||||
);
|
||||
// return this.obstacles.some(
|
||||
// obj => x > obj.x && x < obj.x + obj.width && y > obj.y && y < obj.y + obj.height
|
||||
// );
|
||||
return true;
|
||||
}
|
||||
|
||||
// Return the floor color based on (x, y) coordinates
|
||||
|
|
@ -21,13 +38,76 @@ export class GameWorld {
|
|||
// Draw the game world (e.g., obstacles, background)
|
||||
draw(ctx) {
|
||||
// Draw obstacles
|
||||
ctx.fillStyle = "gray"; // Obstacle color
|
||||
this.obstacles.forEach(obstacle => {
|
||||
ctx.fillRect(obstacle.x, obstacle.y, obstacle.width, obstacle.height);
|
||||
});
|
||||
ctx.strokeStyle = "gray"; // Obstacle outline color
|
||||
ctx.lineWidth = 2; // Optional: to make the outline thicker
|
||||
|
||||
// Optionally, draw the grid or other visual elements (e.g., floor color patterns)
|
||||
ctx.fillStyle = "lightgray"; // Example background
|
||||
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); // Draw background
|
||||
this.obstacles.forEach(obstacle => {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(obstacle[0].x, obstacle[0].y); // Start at the first vertex
|
||||
|
||||
// Loop through the rest of the vertices and draw lines
|
||||
for (let i = 1; i < obstacle.length; i++) {
|
||||
ctx.lineTo(obstacle[i].x, obstacle[i].y);
|
||||
}
|
||||
|
||||
// Close the path to form a closed polygon
|
||||
ctx.closePath();
|
||||
|
||||
ctx.stroke(); // Draw the outline
|
||||
});
|
||||
}
|
||||
|
||||
rayCast(startX, startY, endX, endY) {
|
||||
let closestIntersection = null;
|
||||
|
||||
// Loop through all obstacles
|
||||
for (let i = 0; i < this.obstacles.length; i++) {
|
||||
let obstacle = this.obstacles[i];
|
||||
|
||||
return this.lineIntersectsPolygon(startX, startY, endX, endY, obstacle);
|
||||
}
|
||||
|
||||
return closestIntersection;
|
||||
}
|
||||
|
||||
lineIntersection(line1Start, line1End, line2Start, line2End) {
|
||||
let denom = (line1Start.x - line1End.x) * (line2Start.y - line2End.y) -
|
||||
(line1Start.y - line1End.y) * (line2Start.x - line2End.x);
|
||||
|
||||
if (denom === 0) return null; // Lines are parallel
|
||||
|
||||
let t = ((line1Start.x - line2Start.x) * (line2Start.y - line2End.y) -
|
||||
(line1Start.y - line2Start.y) * (line2Start.x - line2End.x)) / denom;
|
||||
let u = ((line1Start.x - line2Start.x) * (line1Start.y - line1End.y) -
|
||||
(line1Start.y - line2Start.y) * (line1Start.x - line1End.x)) / denom;
|
||||
|
||||
if (t >= 0 && t <= 1 && u >= 0 && u <= 1) {
|
||||
let ix = line1Start.x + t * (line1End.x - line1Start.x);
|
||||
let iy = line1Start.y + t * (line1End.y - line1Start.y);
|
||||
return { x: ix, y: iy };
|
||||
}
|
||||
|
||||
return null; // No intersection
|
||||
}
|
||||
|
||||
lineIntersectsPolygon(startX, startY, endX, endY, polygon) {
|
||||
|
||||
|
||||
let lineStart = { x: startX, y: startY };
|
||||
let lineEnd = { x: endX, y: endY };
|
||||
|
||||
// Loop through all edges of the polygon
|
||||
for (let i = 0; i < polygon.length; i++) {
|
||||
let currentVertex = polygon[i];
|
||||
let nextVertex = polygon[(i + 1) % polygon.length]; // Loop back to the first vertex
|
||||
|
||||
let intersection = this.lineIntersection(lineStart, lineEnd, currentVertex, nextVertex);
|
||||
if (intersection) {
|
||||
return intersection; // Return the first intersection found
|
||||
}
|
||||
}
|
||||
|
||||
return null; // No intersection with any edge of the polygon
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
33
sensor.js
33
sensor.js
|
|
@ -4,7 +4,9 @@ export class Sensor {
|
|||
this.value = null; // Last recorded value
|
||||
this.offsetAngle = offsetAngle;
|
||||
this.offsetDistance = offsetDistance;
|
||||
|
||||
this.hitX = null;
|
||||
this.hitY = null;
|
||||
this.hitObject = null;
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -45,20 +47,31 @@ export class RaycastSensor extends Sensor {
|
|||
read(robot, gameWorld) {
|
||||
//console.log(robot);
|
||||
//console.log(gameWorld);
|
||||
this.updatePosition();
|
||||
|
||||
const angleInRadians = (this.robot.direction + this.angle) * Math.PI / 180;
|
||||
const x = this.robot.x + Math.cos(angleInRadians) * this.distance;
|
||||
const y = this.robot.y + Math.sin(angleInRadians) * this.distance;
|
||||
|
||||
// Ensure gameWorld is available and properly passed to the sensor
|
||||
if (gameWorld.isObstacle(x, y)) {
|
||||
//console.log("Obstacle detected!");
|
||||
let hitPos = gameWorld.rayCast(this.startX, this.startY, this.endX, this.endY);
|
||||
if (hitPos != null) {
|
||||
this.hitX = hitPos.x;
|
||||
this.hitY = hitPos.y;
|
||||
this.endX = this.hitX;
|
||||
this.endY = this.hitY;
|
||||
} else {
|
||||
//console.log("Clear path!");
|
||||
this.hitX = null;
|
||||
this.hitY = null;
|
||||
}
|
||||
// console.log("Obstacle detected!");
|
||||
// } else {
|
||||
// console.log("Clear path!");
|
||||
// }
|
||||
}
|
||||
|
||||
draw(ctx) {
|
||||
this.updatePosition();
|
||||
|
||||
|
||||
ctx.strokeStyle = "lime";
|
||||
ctx.fillStyle = "green"
|
||||
|
|
@ -68,6 +81,16 @@ export class RaycastSensor extends Sensor {
|
|||
ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
if (this.hitX != null) {
|
||||
ctx.strokeStyle = "red";
|
||||
ctx.fillStyle = "red"
|
||||
ctx.beginPath();
|
||||
ctx.arc(this.hitX, this.hitY, 2, 0, 2 * Math.PI);
|
||||
ctx.fillStyle = "red";
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
|
||||
ctx.strokeStyle = "yellow";
|
||||
ctx.beginPath();
|
||||
|
|
|
|||
Loading…
Reference in New Issue