From 485aa4557d81bd2191467f40daa846c7240b661c Mon Sep 17 00:00:00 2001 From: Jake Date: Thu, 27 Mar 2025 14:38:23 +0800 Subject: [PATCH] added sensor objects and drawing, multiple extended types, no logic yet --- game.js | 13 +++++-- gameworld.js | 33 ++++++++++++++++ index.html | 4 +- robot.js | 47 ++++++++++++++++++++--- sensor.js | 106 +++++++++++++++++++++++++++++++++++++++++++++++++++ todo.md | 6 ++- 6 files changed, 196 insertions(+), 13 deletions(-) create mode 100644 gameworld.js create mode 100644 sensor.js diff --git a/game.js b/game.js index c3f61f5..fde345b 100644 --- a/game.js +++ b/game.js @@ -1,9 +1,12 @@ import { Robot } from "./robot.js"; +import { GameWorld } from "./gameworld.js"; const consoleElement = document.getElementById("console"); const gameCanvas = document.getElementById("gameCanvas"); const ctx = gameCanvas.getContext("2d"); +const gameWorld = new GameWorld(); + let pyodideWorker = startPyodideWorker(); let robots = createInitialRobots(); let paused = false; @@ -41,9 +44,9 @@ function startPyodideWorker() { // ✅ Function to create initial robots function createInitialRobots() { return { - "player": new Robot("player", 50, 50, "blue"), - "enemy1": new Robot("enemy1", 200, 150, "red"), - "enemy2": new Robot("enemy2", 400, 250, "red") + "player": new Robot("player", 50, 50, "blue") + //"enemy1": new Robot("enemy1", 200, 150, "red"), + //"enemy2": new Robot("enemy2", 400, 250, "red") }; } @@ -96,8 +99,10 @@ function resetGame() { function gameLoop() { if (!paused) { ctx.clearRect(0, 0, gameCanvas.width, gameCanvas.height); + gameWorld.draw(ctx); + for (let id in robots) { - robots[id].update(ctx); + robots[id].update(ctx, gameWorld); } } requestAnimationFrame(gameLoop); diff --git a/gameworld.js b/gameworld.js new file mode 100644 index 0000000..8c06ccf --- /dev/null +++ b/gameworld.js @@ -0,0 +1,33 @@ +export class GameWorld { + constructor() { + this.obstacles = [ + { x: 300, y: 300, width: 50, height: 50 }, // Example obstacle + { x: 500, y: 400, width: 70, height: 70 } // 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 the floor color based on (x, y) coordinates + getFloorColor(x, y) { + return (x + y) % 50 < 25 ? "black" : "white"; // Example pattern + } + + // 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); + }); + + // 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 + } +} diff --git a/index.html b/index.html index 70ac71e..6ae8923 100644 --- a/index.html +++ b/index.html @@ -25,7 +25,7 @@ -

Real-Time Python Execution with Pyodide

+

Real-Time Python Execution



@@ -37,7 +37,7 @@
-

2D Tank Simulation

+

Robot Simulation

diff --git a/robot.js b/robot.js index 28c3d20..9b27850 100644 --- a/robot.js +++ b/robot.js @@ -1,22 +1,41 @@ +import { RaycastSensor } from "./sensor.js"; +import { FloorColorSensor } from "./sensor.js"; + export class Robot { constructor(id, x, y, color = "blue") { this.id = id; this.x = x; this.y = y; this.velocity = 0; - this.direction = 0; // Degrees + this.angle = 0; // Degrees this.width = 20; this.height = 20; this.color = color; + this.sensors = []; + + this.addSensor(new RaycastSensor(this, -40, 13, -45, 60)); + this.addSensor(new RaycastSensor(this, 40, 13, 45, 60)); + this.addSensor(new FloorColorSensor(this, 0, 0)); } - update(ctx){ + update(ctx, gameWorld){ this.update_position(); + this.update_sensors(gameWorld); this.draw(ctx); } + update_sensors(gameWorld) { + this.sensors.forEach(sensor => sensor.read(this, gameWorld)); + } + + addSensor(sensor) { + if (this.sensors.length < 8) { + this.sensors.push(sensor); + } + } + update_position() { - const radians = (this.direction * Math.PI) / 180; + const radians = (this.angle * Math.PI) / 180; this.x += Math.cos(radians) * this.velocity; this.y += Math.sin(radians) * this.velocity; } @@ -26,14 +45,14 @@ export class Robot { } turn(degrees) { - this.direction += degrees; + this.angle += degrees; } draw(ctx) { ctx.fillStyle = this.color; ctx.save(); ctx.translate(this.x, this.y); - ctx.rotate((this.direction * Math.PI) / 180); + ctx.rotate((this.angle * Math.PI) / 180); // Draw the rectangle (tank body) ctx.fillRect(-this.width / 2, -this.height / 2, this.width, this.height); @@ -46,7 +65,25 @@ export class Robot { ctx.closePath(); ctx.fill(); // Fill both the rectangle and the triangle + + ctx.restore(); + + this.sensors.forEach(sensor => sensor.draw(ctx)); + } + + draw_sensors(ctx) { + ctx.fillStyle = "yellow"; + + this.sensors.forEach(sensor => { + const radians = ((this.angle + sensor.angleOffset) * Math.PI) / 180; + const sensorX = this.x + Math.cos(radians) * 15; + const sensorY = this.y + Math.sin(radians) * 15; + + ctx.beginPath(); + ctx.arc(sensorX, sensorY, 3, 0, 2 * Math.PI); + ctx.fill(); + }); } diff --git a/sensor.js b/sensor.js new file mode 100644 index 0000000..5a5a4c0 --- /dev/null +++ b/sensor.js @@ -0,0 +1,106 @@ +export class Sensor { + constructor(robot, offsetAngle, offsetDistance) { + this.robot = robot + this.value = null; // Last recorded value + this.offsetAngle = offsetAngle; + this.offsetDistance = offsetDistance; + + + } + + updatePosition() { + const robotAngle = this.robot.angle * (Math.PI / 180); + const offsetAngleRad = this.offsetAngle * (Math.PI / 180); + this.startX = this.robot.x + this.offsetDistance * Math.cos(robotAngle + offsetAngleRad); + this.startY = this.robot.y + this.offsetDistance * Math.sin(robotAngle + offsetAngleRad); + } + + read(robot, gameWorld) { + throw new Error("read() must be implemented in subclasses"); + } + + draw(ctx) { + // Default empty draw, can be overridden + } +} + +export class RaycastSensor extends Sensor { + constructor(robot, offsetAngle, offsetDistance, angle, distance) { + super(robot, offsetAngle, offsetDistance); // Call the parent class constructor + this.angle = angle; + this.distance = distance; + this.range = 100; + } + + updatePosition() { + const robotAngle = this.robot.angle * (Math.PI / 180); + const offsetAngleRad = this.offsetAngle * (Math.PI / 180); + this.startX = this.robot.x + this.offsetDistance * Math.cos(robotAngle + offsetAngleRad); + this.startY = this.robot.y + this.offsetDistance * Math.sin(robotAngle + offsetAngleRad); + const sensorAngle = robotAngle + (this.angle * (Math.PI / 180)); + this.endX = this.startX + Math.cos(sensorAngle) * this.range; + this.endY = this.startY + Math.sin(sensorAngle) * this.range; + } + + read(robot, gameWorld) { + //console.log(robot); + //console.log(gameWorld); + 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!"); + } else { + //console.log("Clear path!"); + } + } + + draw(ctx) { + this.updatePosition(); + + ctx.strokeStyle = "lime"; + ctx.fillStyle = "green" + ctx.beginPath(); + ctx.arc(this.startX, this.startY, 2, 0, 2 * Math.PI); + ctx.fillStyle = "red"; + ctx.fill(); + ctx.stroke(); + + + ctx.strokeStyle = "yellow"; + ctx.beginPath(); + ctx.lineWidth = 2; + ctx.moveTo(this.startX, this.startY); + ctx.lineTo(this.endX, this.endY); + ctx.stroke(); + + } +} + + +export class FloorColorSensor extends Sensor { + constructor(robot, offsetAngle, offsetDistance) { + super(robot, offsetAngle, offsetDistance); // No angle offset, directly below robot + } + + read(robot, gameWorld) { + this.value = gameWorld.getFloorColor(robot.x, robot.y); + return this.value; + } + + draw(ctx) { + this.updatePosition(); + + ctx.strokeStyle = "purple"; + ctx.fillStyle = "green" + ctx.beginPath(); + ctx.arc(this.startX, this.startY, 2, 0, 2 * Math.PI); + ctx.fillStyle = "red"; + ctx.fill(); + ctx.stroke(); + + + } +} diff --git a/todo.md b/todo.md index cce3a75..f9565cf 100644 --- a/todo.md +++ b/todo.md @@ -1,9 +1,11 @@ DO - +Add robot.angle and other state variables +Add robot sensor data connections to detect and change parameters +Add pan and zoom to canvas IN PROGRESS - DONE +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