diff --git a/game.js b/game.js
index 845c8fe..5cfdd96 100644
--- a/game.js
+++ b/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("", "").replace("", ""));
+ //console.log(text.replace("", "").replace("", ""));
consoleElement.innerHTML += text.replace(/\n/g, "
") + "
";
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",
diff --git a/gameworld.js b/gameworld.js
index ff8f590..8a81ec8 100644
--- a/gameworld.js
+++ b/gameworld.js
@@ -16,13 +16,13 @@ export class GameWorld {
this.robots = [];
-
+
}
- reset(){
+ reset() {
this.robots = []
this.obstacles = []
Matter.World.clear(this.world); // Clear the world without resetting the engine
@@ -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,43 +123,47 @@ 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;
-
+
bodies.forEach(body => {
let vertices = body.vertices;
-
+
// Loop through each edge of the polygon body
for (let i = 0; i < vertices.length; i++) {
let v1 = vertices[i];
let v2 = vertices[(i + 1) % vertices.length]; // Wrap around to close the shape
-
+
let intersection = this.getLineIntersection(start, end, v1, v2);
if (intersection) {
let distance = Matter.Vector.magnitude(Matter.Vector.sub(intersection, start));
-
+
// Keep track of the closest intersection
if (distance < minDistance) {
minDistance = distance;
@@ -168,29 +172,29 @@ export class GameWorld {
}
}
});
-
+
return closestHit;
}
-
+
// Line intersection helper function
getLineIntersection(p1, p2, p3, p4) {
let denom = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);
-
+
if (denom === 0) return null; // Parallel lines, no intersection
-
+
let ua = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / denom;
let ub = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / denom;
-
+
if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) {
return {
x: p1.x + ua * (p2.x - p1.x),
y: p1.y + ua * (p2.y - p1.y)
};
}
-
+
return null; // No valid intersection
}
-
+
diff --git a/pyodide-worker.js b/pyodide-worker.js
index e937564..6581d2e 100644
--- a/pyodide-worker.js
+++ b/pyodide-worker.js
@@ -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 {
diff --git a/readme.md b/readme.md
index 2963302..f97d255 100644
--- a/readme.md
+++ b/readme.md
@@ -1,8 +1,12 @@
-import robot
+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)
\ No newline at end of file
diff --git a/robot.js b/robot.js
index 630f034..bf19a8f 100644
--- a/robot.js
+++ b/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) {
diff --git a/sensor.js b/sensor.js
index 083c9cc..fd13bfd 100644
--- a/sensor.js
+++ b/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) {
diff --git a/todo.md b/todo.md
index acaa6f1..3e27120 100644
--- a/todo.md
+++ b/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