diff --git a/game.js b/game.js index fde345b..bb92dcb 100644 --- a/game.js +++ b/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; +}); + diff --git a/gameworld.js b/gameworld.js index 8c06ccf..064cbf6 100644 --- a/gameworld.js +++ b/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 + } + } diff --git a/sensor.js b/sensor.js index 5a5a4c0..72008ae 100644 --- a/sensor.js +++ b/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(); diff --git a/todo.md b/todo.md index f9565cf..e5acbdc 100644 --- a/todo.md +++ b/todo.md @@ -6,6 +6,7 @@ Add pan and zoom to canvas IN PROGRESS DONE +Add line sensor object collisions Add robot sensors that detect things in game world, color sense and distance sense for start Add Pause Button Add reset button. \ No newline at end of file