robot sensor values now available in simulation, line sensors return distance, await is automatically inserted before sleep commands
parent
39cac21741
commit
6051be2f61
11
game.js
11
game.js
|
|
@ -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",
|
||||
|
|
|
|||
22
gameworld.js
22
gameworld.js
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
10
readme.md
10
readme.md
|
|
@ -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)
|
||||
6
robot.js
6
robot.js
|
|
@ -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) {
|
||||
|
|
|
|||
22
sensor.js
22
sensor.js
|
|
@ -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
10
todo.md
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue