robot sensor values now available in simulation, line sensors return distance, await is automatically inserted before sleep commands

master
Jake 2025-03-30 21:31:04 +08:00
parent 39cac21741
commit 6051be2f61
7 changed files with 140 additions and 47 deletions

11
game.js
View File

@ -21,7 +21,7 @@ let startX, startY; // Mouse start positions
// ✅ Function to create the Pyodide Worker
function startPyodideWorker() {
const worker = new Worker("pyodide-worker.js");
const worker = new Worker("pyodide-worker.js?v=" + Date.now());
// ✅ Reattach the event listener when a new worker is created
worker.onmessage = (event) => {
@ -60,7 +60,7 @@ function createInitialRobots() {
// ✅ Function to log messages to console
function logToConsole(text) {
console.log(text.replace("<b>", "").replace("</b>", ""));
//console.log(text.replace("<b>", "").replace("</b>", ""));
consoleElement.innerHTML += text.replace(/\n/g, "<br>") + "<br>";
consoleElement.scrollTop = consoleElement.scrollHeight;
}
@ -155,13 +155,16 @@ function updateSensorData() {
//logToConsole(`📡 Sensor Update - Distance: ${distance.toFixed(2)}, Speed: ${speed.toFixed(2)}`);
}
setInterval(updateSensorData, 2000); // Call every 2 seconds
//setInterval(updateSensorData, 2000); // Call every 2 seconds
// ✅ Button Event Listeners
document.getElementById("compile-button").addEventListener("click", () => {
if (paused) return;
const code = document.getElementById("python-code").value;
let code = document.getElementById("python-code").value;
console.log(code);
code = code.replace(/time\.sleep\(/g, "await time.sleep(");
console.log(code);
consoleElement.innerHTML = "";
pyodideWorker.postMessage({
type: "execute",

View File

@ -40,7 +40,7 @@ export class GameWorld {
{ x: 420, y: 380 }, // Vertex 2
{ x: 350, y: 550 }, // Vertex 3
{ x: 280, y: 420 } // Vertex 4
], { x: 400, y: 0 });
], { x: 400, y: -50 });
}
@ -123,27 +123,31 @@ export class GameWorld {
}
rayCast(startX, startY, endX, endY) {
rayCast(startX, startY, endX, endY, ignoreBodies = []) {
let closestIntersection = null;
let startPoint = { x: startX, y: startY };
let endPoint = { x: endX, y: endY };
// // 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);
// }
// Get all bodies in the world
let allBodies = Matter.Composite.allBodies(this.engine.world);
let hit = this.getRayHitPoint(startPoint, endPoint, allBodies);
// ✅ Filter out ignored bodies instead of removing them
let filteredBodies = allBodies.filter(body => !ignoreBodies.includes(body));
// Debugging
//console.log("Ignoring bodies: ", ignoreBodies.map(b => b.id));
//console.log("Filtered bodies: ", filteredBodies.map(b => b.id));
// Perform raycast only on the filtered bodies
let hit = this.getRayHitPoint(startPoint, endPoint, filteredBodies);
if (hit) {
closestIntersection = hit.point;
}
return closestIntersection;
}
getRayHitPoint(start, end, bodies) {
let closestHit = null;
let minDistance = Infinity;

View File

@ -1,6 +1,7 @@
importScripts("https://cdn.jsdelivr.net/pyodide/v0.23.4/full/pyodide.js");
let sensorData = {}; // ✅ Store sensor values
let gameWorld = null;
async function initializePyodide() {
self.pyodide = await loadPyodide({
@ -11,9 +12,44 @@ async function initializePyodide() {
self.postMessage({ type: event, data: data });
});
// ✅ Expose sensor data to Python
self.pyodide.globals.set("get_sensor_data", (name) => {
return sensorData[name] ?? null;
if (gameWorld == null){
return null;
}
//console.log(gameWorld.robots);
let robot = gameWorld.robots[0];
//console.log(robot);
let sensorArray = robot.sensors || []; // Assuming `sensors` is an array inside the robot object
sensorData = sensorArray.map(sensor => ({
"type": sensor.type, // Individual sensor's type
"angle": sensor.angle, // Individual sensor's angle
"distance": sensor.distance, // Individual sensor's distance
"hitpoint": sensor.hitpoint // Whatever other attributes you need
}));
//console.log(sensorData["x"]);
sensorData = JSON.stringify(sensorData);
return sensorData;
});
self.pyodide.globals.set("get_robot_data", () => {
if (gameWorld == null){
return null;
}
let robot = gameWorld.robots[0];
let robotData = {
"position": robot.body.position,
"angle": robot.body.angle,
"velocity": robot.body.velocity
};
robotData = JSON.stringify(robotData);
return robotData;
});
// ✅ Run Python initialization
@ -22,10 +58,44 @@ import sys
import pyodide
import asyncio
import time
import json
import math
class RobotModule:
def get_sensor(self, name):
return get_sensor_data(name)
sensor_data = json.loads(get_sensor_data(name))
return sensor_data
def get_sensors(self):
return json.loads(get_sensor_data("sensors")) # Returns list of sensor dicts
def get_pos(self):
robot_data = json.loads(get_robot_data())
position = robot_data["position"]
return position
def get_x(self):
robot_data = json.loads(get_robot_data())
position = robot_data["position"]["x"]
return position
def get_y(self):
robot_data = json.loads(get_robot_data())
position = robot_data["position"]["y"]
return position
def get_velocity(self):
robot_data = json.loads(get_robot_data())
velocity = robot_data["velocity"]
return velocity
def get_velocity_magnitude(self):
velXY = self.get_velocity()
velX = velXY["x"]
velY = velXY["y"]
magnitude = math.sqrt(velX**2 + velY**2)
return magnitude
def move(self, speed):
send_to_main("move", speed)
@ -65,7 +135,6 @@ time.sleep = async_sleep # ✅ Monkey-patch time.sleep()
}
initializePyodide();
self.postMessage({ type: "console", message: ":-(" });
self.onmessage = async (event) => {
if (!self.pyodide) {
@ -77,7 +146,8 @@ self.onmessage = async (event) => {
// ✅ Update sensor data
Object.assign(sensorData, event.data.data);
} else if (event.data.type === "game_state") {
console.log("Game state updated in");
gameWorld = event.data.state;
//console.log(event.data);
} else if (event.data.type === "execute") {
try {

View File

@ -1,8 +1,12 @@
import robot
import time
robot.move(0.5)
await time.sleep(1)
robot.move(0.00006)
while True:
robot.turn(5)
if robot.get_sensors()[0]["hitpoint"]["x"] is not None:
robot.turn(0.001)
elif robot.get_sensors()[1]["hitpoint"]["x"] is not None:
robot.turn(-0.001)
else:
robot.turn(0)
await time.sleep(0.1)

View File

@ -22,8 +22,8 @@ export class Robot {
{ x: -this.width / 2, y: this.height / 2 } // Vertex 4
];
this.addSensor(new RaycastSensor(this, -40, 13, -45, 60));
this.addSensor(new RaycastSensor(this, 40, 13, 45, 60));
this.addSensor(new RaycastSensor(this, -40, 12, -45, 60));
this.addSensor(new RaycastSensor(this, 40, 12, 45, 60));
this.addSensor(new FloorColorSensor(this, 0, 0));
@ -72,7 +72,7 @@ export class Robot {
}
turn(degrees) {
this.angularThrust += degrees;
this.angularThrust = degrees;
}
draw(ctx) {

View File

@ -1,5 +1,6 @@
export class Sensor {
constructor(robot, offsetAngle, offsetDistance) {
this.type = "base";
this.robot = robot
this.value = null; // Last recorded value
this.offsetAngle = offsetAngle;
@ -7,6 +8,8 @@ export class Sensor {
this.hitX = null;
this.hitY = null;
this.hitObject = null;
this.angle = 0;
this.hitpoint = {x: null, y: null};
}
@ -28,11 +31,12 @@ export class Sensor {
}
export class RaycastSensor extends Sensor {
constructor(robot, offsetAngle, offsetDistance, angle, distance) {
constructor(robot, offsetAngle, offsetDistance, angle, range) {
super(robot, offsetAngle, offsetDistance); // Call the parent class constructor
this.type = "line";
this.angle = angle;
this.distance = distance;
this.range = 100;
this.distance = null;
this.range = range;
}
updatePosition() {
@ -51,19 +55,24 @@ export class RaycastSensor extends Sensor {
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;
const x = this.robot.x + Math.cos(angleInRadians) * this.range;
const y = this.robot.y + Math.sin(angleInRadians) * this.range;
// Ensure gameWorld is available and properly passed to the sensor
let hitPos = gameWorld.rayCast(this.startX, this.startY, this.endX, this.endY);
let hitPos = gameWorld.rayCast(this.startX, this.startY, this.endX, this.endY, [this.robot.body]);
if (hitPos != null) {
this.hitX = hitPos.x;
this.hitY = hitPos.y;
this.endX = this.hitX;
this.endY = this.hitY;
this.hitpoint = {x: this.hitX, y: this.hitY};
this.distance = Math.sqrt(Math.pow(this.hitX - this.startX, 2) + Math.pow(this.hitY - this.startY, 2));
} else {
this.hitX = null;
this.hitY = null;
this.hitpoint = {x: this.hitX, y: this.hitY};
this.distance = Math.sqrt(Math.pow(this.endX - this.startX, 2) + Math.pow(this.endY - this.startY, 2));
}
// console.log("Obstacle detected!");
// } else {
@ -106,6 +115,7 @@ export class RaycastSensor extends Sensor {
export class FloorColorSensor extends Sensor {
constructor(robot, offsetAngle, offsetDistance) {
super(robot, offsetAngle, offsetDistance); // No angle offset, directly below robot
this.type = "color";
}
read(robot, gameWorld) {

10
todo.md
View File

@ -1,12 +1,14 @@
DO
Create test level of a track with obstacles all around
Improve the text editor to be larger, and maybe have some colour coding and allow tabs
IN PROGRESS
DONE
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
Collision between robots and objects
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