added sensor objects and drawing, multiple extended types, no logic yet

master
Jake 2025-03-27 14:38:23 +08:00
parent 0676c077fd
commit 485aa4557d
6 changed files with 196 additions and 13 deletions

13
game.js
View File

@ -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);

33
gameworld.js Normal file
View File

@ -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
}
}

View File

@ -25,7 +25,7 @@
</head>
<body>
<h1>Real-Time Python Execution with Pyodide</h1>
<h1>Real-Time Python Execution</h1>
<textarea id="python-code" rows="10" cols="50" placeholder="Enter your Python code here..."></textarea>
<br><br>
<button id="compile-button">Compile and Run</button>
@ -37,7 +37,7 @@
<div id="console"></div>
<!-- Game canvas -->
<h2>2D Tank Simulation</h2>
<h2>Robot Simulation</h2>
<canvas id="gameCanvas" width="800" height="600"></canvas>
<script type="module" src="game.js"></script>

View File

@ -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();
});
}

106
sensor.js Normal file
View File

@ -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();
}
}

View File

@ -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.