added sensor objects and drawing, multiple extended types, no logic yet
parent
0676c077fd
commit
485aa4557d
13
game.js
13
game.js
|
|
@ -1,9 +1,12 @@
|
||||||
import { Robot } from "./robot.js";
|
import { Robot } from "./robot.js";
|
||||||
|
import { GameWorld } from "./gameworld.js";
|
||||||
|
|
||||||
const consoleElement = document.getElementById("console");
|
const consoleElement = document.getElementById("console");
|
||||||
const gameCanvas = document.getElementById("gameCanvas");
|
const gameCanvas = document.getElementById("gameCanvas");
|
||||||
const ctx = gameCanvas.getContext("2d");
|
const ctx = gameCanvas.getContext("2d");
|
||||||
|
|
||||||
|
const gameWorld = new GameWorld();
|
||||||
|
|
||||||
let pyodideWorker = startPyodideWorker();
|
let pyodideWorker = startPyodideWorker();
|
||||||
let robots = createInitialRobots();
|
let robots = createInitialRobots();
|
||||||
let paused = false;
|
let paused = false;
|
||||||
|
|
@ -41,9 +44,9 @@ function startPyodideWorker() {
|
||||||
// ✅ Function to create initial robots
|
// ✅ Function to create initial robots
|
||||||
function createInitialRobots() {
|
function createInitialRobots() {
|
||||||
return {
|
return {
|
||||||
"player": new Robot("player", 50, 50, "blue"),
|
"player": new Robot("player", 50, 50, "blue")
|
||||||
"enemy1": new Robot("enemy1", 200, 150, "red"),
|
//"enemy1": new Robot("enemy1", 200, 150, "red"),
|
||||||
"enemy2": new Robot("enemy2", 400, 250, "red")
|
//"enemy2": new Robot("enemy2", 400, 250, "red")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -96,8 +99,10 @@ function resetGame() {
|
||||||
function gameLoop() {
|
function gameLoop() {
|
||||||
if (!paused) {
|
if (!paused) {
|
||||||
ctx.clearRect(0, 0, gameCanvas.width, gameCanvas.height);
|
ctx.clearRect(0, 0, gameCanvas.width, gameCanvas.height);
|
||||||
|
gameWorld.draw(ctx);
|
||||||
|
|
||||||
for (let id in robots) {
|
for (let id in robots) {
|
||||||
robots[id].update(ctx);
|
robots[id].update(ctx, gameWorld);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
requestAnimationFrame(gameLoop);
|
requestAnimationFrame(gameLoop);
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -25,7 +25,7 @@
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<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>
|
<textarea id="python-code" rows="10" cols="50" placeholder="Enter your Python code here..."></textarea>
|
||||||
<br><br>
|
<br><br>
|
||||||
<button id="compile-button">Compile and Run</button>
|
<button id="compile-button">Compile and Run</button>
|
||||||
|
|
@ -37,7 +37,7 @@
|
||||||
<div id="console"></div>
|
<div id="console"></div>
|
||||||
|
|
||||||
<!-- Game canvas -->
|
<!-- Game canvas -->
|
||||||
<h2>2D Tank Simulation</h2>
|
<h2>Robot Simulation</h2>
|
||||||
<canvas id="gameCanvas" width="800" height="600"></canvas>
|
<canvas id="gameCanvas" width="800" height="600"></canvas>
|
||||||
|
|
||||||
<script type="module" src="game.js"></script>
|
<script type="module" src="game.js"></script>
|
||||||
|
|
|
||||||
47
robot.js
47
robot.js
|
|
@ -1,22 +1,41 @@
|
||||||
|
import { RaycastSensor } from "./sensor.js";
|
||||||
|
import { FloorColorSensor } from "./sensor.js";
|
||||||
|
|
||||||
export class Robot {
|
export class Robot {
|
||||||
constructor(id, x, y, color = "blue") {
|
constructor(id, x, y, color = "blue") {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
this.velocity = 0;
|
this.velocity = 0;
|
||||||
this.direction = 0; // Degrees
|
this.angle = 0; // Degrees
|
||||||
this.width = 20;
|
this.width = 20;
|
||||||
this.height = 20;
|
this.height = 20;
|
||||||
this.color = color;
|
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_position();
|
||||||
|
this.update_sensors(gameWorld);
|
||||||
this.draw(ctx);
|
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() {
|
update_position() {
|
||||||
const radians = (this.direction * Math.PI) / 180;
|
const radians = (this.angle * Math.PI) / 180;
|
||||||
this.x += Math.cos(radians) * this.velocity;
|
this.x += Math.cos(radians) * this.velocity;
|
||||||
this.y += Math.sin(radians) * this.velocity;
|
this.y += Math.sin(radians) * this.velocity;
|
||||||
}
|
}
|
||||||
|
|
@ -26,14 +45,14 @@ export class Robot {
|
||||||
}
|
}
|
||||||
|
|
||||||
turn(degrees) {
|
turn(degrees) {
|
||||||
this.direction += degrees;
|
this.angle += degrees;
|
||||||
}
|
}
|
||||||
|
|
||||||
draw(ctx) {
|
draw(ctx) {
|
||||||
ctx.fillStyle = this.color;
|
ctx.fillStyle = this.color;
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.translate(this.x, this.y);
|
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)
|
// Draw the rectangle (tank body)
|
||||||
ctx.fillRect(-this.width / 2, -this.height / 2, this.width, this.height);
|
ctx.fillRect(-this.width / 2, -this.height / 2, this.width, this.height);
|
||||||
|
|
@ -46,7 +65,25 @@ export class Robot {
|
||||||
ctx.closePath();
|
ctx.closePath();
|
||||||
|
|
||||||
ctx.fill(); // Fill both the rectangle and the triangle
|
ctx.fill(); // Fill both the rectangle and the triangle
|
||||||
|
|
||||||
|
|
||||||
ctx.restore();
|
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();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
6
todo.md
6
todo.md
|
|
@ -1,9 +1,11 @@
|
||||||
DO
|
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
|
IN PROGRESS
|
||||||
|
|
||||||
|
|
||||||
DONE
|
DONE
|
||||||
|
Add robot sensors that detect things in game world, color sense and distance sense for start
|
||||||
Add Pause Button
|
Add Pause Button
|
||||||
Add reset button.
|
Add reset button.
|
||||||
Loading…
Reference in New Issue