Compare commits
2 Commits
ba1adee770
...
2986d89fc5
| Author | SHA1 | Date |
|---|---|---|
|
|
2986d89fc5 | |
|
|
2c0c13bc9b |
11
game.js
11
game.js
|
|
@ -525,6 +525,12 @@ gameCanvas.addEventListener("wheel", (event) => {
|
|||
offsetY = mouseY - worldY * scale;
|
||||
});
|
||||
|
||||
const canvas = document.getElementById("gameCanvas");
|
||||
window.addEventListener("resize", () => {
|
||||
// console.log("RESIZE");
|
||||
// gameWorld.resizeCanvas(canvas)
|
||||
});
|
||||
|
||||
|
||||
gameCanvas.addEventListener("mousedown", (event) => {
|
||||
isPanning = true;
|
||||
|
|
@ -574,6 +580,7 @@ function setupCanvas() {
|
|||
const canvas = document.getElementById("gameCanvas");
|
||||
const context = canvas.getContext("2d");
|
||||
|
||||
|
||||
// Get the device pixel ratio
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
|
||||
|
|
@ -590,7 +597,7 @@ function setupCanvas() {
|
|||
}
|
||||
|
||||
// Call this function when the page loads
|
||||
fetch('/data/levels.json')
|
||||
fetch('./data/levels.json')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
gameWorld.levelData = data;
|
||||
|
|
@ -601,7 +608,7 @@ fetch('/data/levels.json')
|
|||
// Start game loop
|
||||
gameLoop();
|
||||
|
||||
showLesson(0);
|
||||
showLesson(10);
|
||||
});
|
||||
|
||||
|
||||
|
|
|
|||
100
gameworld.js
100
gameworld.js
|
|
@ -19,6 +19,21 @@ export class GameWorld {
|
|||
this.obstacles = [];
|
||||
this.robots = [];
|
||||
|
||||
this.lineColor = `rgba(0,0,0,1)`;
|
||||
this.floorLines = [
|
||||
{ x: 170, y: 75 },
|
||||
{ x: 300, y: 75 },
|
||||
{ x: 500, y: 300 },
|
||||
{ x: 500, y: 400 },
|
||||
{ x: 450, y: 450 },
|
||||
{ x: 400, y: 450 },
|
||||
{ x: 100, y: 350 },
|
||||
{ x: 50, y: 250 },
|
||||
{ x: 80, y: 150 },
|
||||
{ x: 170, y: 75 },
|
||||
// add more points as needed
|
||||
];
|
||||
this.lineWidth = 3;
|
||||
|
||||
|
||||
|
||||
|
|
@ -247,15 +262,24 @@ export class GameWorld {
|
|||
|
||||
// Return the floor color based on (x, y) coordinates
|
||||
getFloorColor(x, y) {
|
||||
return (x + y) % 50 < 25 ? "black" : "white"; // Example pattern
|
||||
const isUnderLine = this.isPointNearLine(x, y, this.floorLines, this.lineWidth);
|
||||
return isUnderLine;
|
||||
// if (isUnderLine) {
|
||||
// console.log("Robot is over a floor line");
|
||||
// }
|
||||
}
|
||||
|
||||
// Draw the game world (e.g., obstacles, background)
|
||||
draw(ctx) {
|
||||
//this.render(ctx);
|
||||
|
||||
|
||||
this.drawFloorLines(ctx);
|
||||
// Draw obstacles
|
||||
ctx.strokeStyle = "gray"; // Obstacle outline color
|
||||
ctx.lineWidth = 2; // Optional: to make the outline thicker
|
||||
|
||||
|
||||
// Draw obstacles
|
||||
this.obstacles.forEach(body => {
|
||||
ctx.beginPath();
|
||||
|
|
@ -313,8 +337,45 @@ export class GameWorld {
|
|||
robot.draw(ctx); // Draw the robot's hull and sensors
|
||||
});
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
drawFloorLines(ctx) {
|
||||
if (this.floorLines.length < 2) return;
|
||||
|
||||
ctx.strokeStyle = this.lineColor;
|
||||
ctx.lineWidth = this.lineWidth;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(this.floorLines[0].x, this.floorLines[0].y);
|
||||
for (let i = 1; i < this.floorLines.length; i++) {
|
||||
ctx.lineTo(this.floorLines[i].x, this.floorLines[i].y);
|
||||
}
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
|
||||
resizeCanvasToDisplaySize(ctx) {
|
||||
let canvas = ctx.canvas;
|
||||
const width = canvas.clientWidth;
|
||||
const height = canvas.clientHeight;
|
||||
|
||||
if (canvas.width !== width || canvas.height !== height) {
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
}
|
||||
}
|
||||
|
||||
render(ctx) {
|
||||
let canvas = ctx.canvas;
|
||||
this.resizeCanvasToDisplaySize(canvas); // 👈 ensures drawing resolution matches display size
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
game.draw(ctx);
|
||||
requestAnimationFrame(render);
|
||||
}
|
||||
|
||||
|
||||
|
||||
rayCast(startX, startY, endX, endY, ignoreBodies = []) {
|
||||
let closestIntersection = null;
|
||||
let startPoint = { x: startX, y: startY };
|
||||
|
|
@ -389,8 +450,45 @@ export class GameWorld {
|
|||
return null; // No valid intersection
|
||||
}
|
||||
|
||||
isPointNearLine(x, y, linePoints, width) {
|
||||
const threshold = width / 2;
|
||||
|
||||
for (let i = 0; i < linePoints.length - 1; i++) {
|
||||
const p1 = linePoints[i];
|
||||
const p2 = linePoints[i + 1];
|
||||
|
||||
const dist = this.pointToSegmentDistance(x, y, p1.x, p1.y, p2.x, p2.y);
|
||||
if (dist <= threshold) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
pointToSegmentDistance(px, py, x1, y1, x2, y2) {
|
||||
const dx = x2 - x1;
|
||||
const dy = y2 - y1;
|
||||
const lengthSquared = dx * dx + dy * dy;
|
||||
|
||||
if (lengthSquared === 0) {
|
||||
// p1 == p2
|
||||
const dxp = px - x1;
|
||||
const dyp = py - y1;
|
||||
return Math.sqrt(dxp * dxp + dyp * dyp);
|
||||
}
|
||||
|
||||
// Project point onto the segment
|
||||
let t = ((px - x1) * dx + (py - y1) * dy) / lengthSquared;
|
||||
t = Math.max(0, Math.min(1, t)); // clamp to segment
|
||||
|
||||
const projX = x1 + t * dx;
|
||||
const projY = y1 + t * dy;
|
||||
|
||||
const dxp = px - projX;
|
||||
const dyp = py - projY;
|
||||
|
||||
return Math.sqrt(dxp * dxp + dyp * dyp);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,12 +17,12 @@
|
|||
<body>
|
||||
<!-- Top Bar with Heading and Buttons -->
|
||||
<header>
|
||||
<div class="header-inner">
|
||||
<!-- <div class="header-inner">
|
||||
<div>
|
||||
<h1>Learn CircuitPython</h1>
|
||||
<p>Write code, simulate physics, and see instant output</p>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</header>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -32,8 +32,10 @@ async function initializePyodide() {
|
|||
"type": sensor.type, // Individual sensor's type
|
||||
"angle": sensor.angle, // Individual sensor's angle
|
||||
"distance": Math.round(sensor.distance * 100) / 100, // Individual sensor's distance
|
||||
"hitpoint": sensor.hitpoint // Whatever other attributes you need
|
||||
"hitpoint": sensor.hitpoint, // Whatever other attributes you need
|
||||
"canSeeLine": sensor.hasLine
|
||||
}));
|
||||
//console.log(robot.sensors);
|
||||
//console.log(sensorData["x"]);
|
||||
sensorData = JSON.stringify(sensorData);
|
||||
return sensorData;
|
||||
|
|
@ -75,6 +77,12 @@ class RobotModule:
|
|||
def get_distance_right(self):
|
||||
return self.get_sensors()[1]["distance"]
|
||||
|
||||
def get_line_left(self):
|
||||
return self.get_sensors()[2]["canSeeLine"]
|
||||
|
||||
def get_line_right(self):
|
||||
return self.get_sensors()[3]["canSeeLine"]
|
||||
|
||||
def get_sensors(self):
|
||||
return json.loads(get_sensor_data("sensors")) # Returns list of sensor dicts
|
||||
|
||||
|
|
|
|||
17
readme.md
17
readme.md
|
|
@ -80,3 +80,20 @@ while True:
|
|||
robot.move(1)
|
||||
robot.turn(0)
|
||||
time.sleep(0.01)
|
||||
|
||||
|
||||
|
||||
# FOLLOW LINE
|
||||
import robot
|
||||
import time
|
||||
|
||||
while True:
|
||||
robot.move(0.1)
|
||||
if robot.get_line_left():
|
||||
robot.turn(-1)
|
||||
elif robot.get_line_right():
|
||||
robot.turn(1)
|
||||
else:
|
||||
robot.turn(0)
|
||||
|
||||
time.sleep(0.05)
|
||||
7
robot.js
7
robot.js
|
|
@ -24,7 +24,8 @@ export class Robot {
|
|||
|
||||
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));
|
||||
this.addSensor(new FloorColorSensor(this, -20, 12));
|
||||
this.addSensor(new FloorColorSensor(this, 20, 12));
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -43,10 +44,6 @@ export class Robot {
|
|||
this.update_sensors(gameWorld);
|
||||
}
|
||||
|
||||
draw(ctx){
|
||||
this.draw(ctx);
|
||||
}
|
||||
|
||||
update_sensors(gameWorld) {
|
||||
this.sensors.forEach(sensor => sensor.read(this, gameWorld));
|
||||
}
|
||||
|
|
|
|||
18
sensor.js
18
sensor.js
|
|
@ -115,22 +115,34 @@ 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.hasLine = false;
|
||||
this.type = "color";
|
||||
this.fill = "red";
|
||||
}
|
||||
|
||||
read(robot, gameWorld) {
|
||||
this.value = gameWorld.getFloorColor(robot.x, robot.y);
|
||||
this.updatePosition();
|
||||
|
||||
this.value = gameWorld.getFloorColor(this.startX, this.startY);
|
||||
this.hasLine = this.value;
|
||||
if (this.value) {
|
||||
this.fill = "green";
|
||||
} else {
|
||||
this.fill = "red";
|
||||
}
|
||||
//console.log(this.value);
|
||||
return this.value;
|
||||
}
|
||||
|
||||
draw(ctx) {
|
||||
this.updatePosition();
|
||||
|
||||
ctx.strokeStyle = "purple";
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeStyle = this.fill;
|
||||
ctx.fillStyle = "green"
|
||||
ctx.beginPath();
|
||||
ctx.arc(this.startX, this.startY, 2, 0, 2 * Math.PI);
|
||||
ctx.fillStyle = "red";
|
||||
ctx.fillStyle = this.fill;
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
|
|
|
|||
|
|
@ -137,6 +137,7 @@ button {
|
|||
|
||||
/* ===== Main Content ===== */
|
||||
main {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
|
|
@ -246,6 +247,9 @@ main {
|
|||
border: 1px solid #d1d5db;
|
||||
/* gray-300 */
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
/* Right side: canvas + console */
|
||||
|
|
|
|||
7
todo.md
7
todo.md
|
|
@ -1,3 +1,10 @@
|
|||
BUGS?
|
||||
Allow different caps in "Hello World" in lesson1?
|
||||
Disallow copy/paste of objectives?
|
||||
Require printing of arithmetic in lesson2?
|
||||
Hide hints behind button? Maybe require a few fails first?
|
||||
Variables persist past reset
|
||||
|
||||
DO
|
||||
Create test level of a track with obstacles all around
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue