added objectives to first robot lesson, locked compile button while executing, game world resets on compile & run
parent
9b702b6ff1
commit
8a0219500d
|
|
@ -872,59 +872,31 @@ robot.move(0) # Stop the robot
|
|||
</code></pre>
|
||||
`,
|
||||
objectives: [
|
||||
"Import the time module",
|
||||
"Print something",
|
||||
"Use time.sleep() to pause for an amount of time",
|
||||
"Print something else after the pause"
|
||||
"Reach the first checkpoint",
|
||||
"Reach the second checkpoint",
|
||||
|
||||
"Code should complete without errors"
|
||||
],
|
||||
|
||||
doneCondition: (() => {
|
||||
return ({ code, consoleText, codeRanGood }) => {
|
||||
return ({ code, consoleText, codeRanGood, gameWorld }) => {
|
||||
const progress = {
|
||||
importedTime: false,
|
||||
printedBefore: false,
|
||||
usedSleep: false,
|
||||
printedAfter: false,
|
||||
firstCheckpoint: gameWorld.waypointsReached[0],
|
||||
secondCheckpoint: gameWorld.waypointsReached[1],
|
||||
codeRanGood: codeRanGood,
|
||||
|
||||
};
|
||||
|
||||
if (!codeRanGood) {
|
||||
return { done: false, hint: "" };
|
||||
}
|
||||
|
||||
// 1. Check for "import time"
|
||||
const importRegex = /^\s*import\s+time\b/m;
|
||||
progress.importedTime = importRegex.test(code);
|
||||
|
||||
// 2. Match all print(...) calls
|
||||
const printRegex = /print\s*\(.*?\)/g;
|
||||
const printMatches = [...code.matchAll(printRegex)];
|
||||
|
||||
// 3. Match time.sleep(...)
|
||||
const sleepRegex = /time\.sleep\s*\(\s*[\d.]+\s*\)/;
|
||||
const sleepMatch = sleepRegex.exec(code);
|
||||
progress.usedSleep = !!sleepMatch;
|
||||
|
||||
// 4. Handle print position logic
|
||||
if (printMatches.length > 0) {
|
||||
// If there's no sleep, we just say "they printed something" — early lesson support
|
||||
if (!sleepMatch) {
|
||||
progress.printedBefore = true; // consider *any* print valid before sleep
|
||||
} else {
|
||||
const sleepIndex = sleepMatch.index;
|
||||
for (const m of printMatches) {
|
||||
if (m.index < sleepIndex) progress.printedBefore = true;
|
||||
if (m.index > sleepIndex) progress.printedAfter = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Build hint
|
||||
const missing = [];
|
||||
if (!progress.importedTime) missing.push("import the time module");
|
||||
if (!progress.printedBefore) missing.push("print something before sleeping");
|
||||
if (!progress.usedSleep) missing.push("use time.sleep()");
|
||||
if (!progress.printedAfter && progress.usedSleep)
|
||||
missing.push("print something after sleeping");
|
||||
if (!progress.firstCheckpoint) missing.push("reach the first checkpoint");
|
||||
if (!progress.secondCheckpoint) missing.push("reach the second checkpoint");
|
||||
|
||||
let hint = "";
|
||||
if (missing.length === 1) {
|
||||
|
|
@ -935,10 +907,9 @@ robot.move(0) # Stop the robot
|
|||
|
||||
return {
|
||||
done:
|
||||
progress.importedTime &&
|
||||
progress.printedBefore &&
|
||||
progress.usedSleep &&
|
||||
progress.printedAfter,
|
||||
progress.firstCheckpoint &&
|
||||
progress.secondCheckpoint &&
|
||||
progress.codeRanGood,
|
||||
progressArray: Object.values(progress),
|
||||
hint,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,7 +13,33 @@
|
|||
{
|
||||
"position": {
|
||||
"x": 420,
|
||||
"y": 600
|
||||
"y": 200
|
||||
},
|
||||
"vertices": [
|
||||
{
|
||||
"x": -50,
|
||||
"y": -50
|
||||
},
|
||||
{
|
||||
"x": 50,
|
||||
"y": -50
|
||||
},
|
||||
{
|
||||
"x": 50,
|
||||
"y": 50
|
||||
},
|
||||
{
|
||||
"x": -50,
|
||||
"y": 50
|
||||
}
|
||||
],
|
||||
"strokeColor": "#0000FF",
|
||||
"fillColor": "#0000CC"
|
||||
},
|
||||
{
|
||||
"position": {
|
||||
"x": 50,
|
||||
"y": 200
|
||||
},
|
||||
"vertices": [
|
||||
{
|
||||
|
|
|
|||
26
game.js
26
game.js
|
|
@ -92,7 +92,8 @@ function checkLessonDone() {
|
|||
const result = lesson.doneCondition({
|
||||
code: mostRecentCode,
|
||||
consoleText: consoleText,
|
||||
codeRanGood: codeRanGood
|
||||
codeRanGood: codeRanGood,
|
||||
gameWorld: gameWorld
|
||||
});
|
||||
if (result.done) {
|
||||
markLessonDone(lesson.id);
|
||||
|
|
@ -220,7 +221,7 @@ function toggleObjective(index, completed = true) {
|
|||
}
|
||||
|
||||
//clearLessonProgress(); // Clear progress on load for testing
|
||||
showLesson(1);
|
||||
showLesson(9);
|
||||
|
||||
const consoleElement = document.getElementById("console");
|
||||
const gameCanvas = document.getElementById("gameCanvas");
|
||||
|
|
@ -269,6 +270,7 @@ function startPyodideWorker() {
|
|||
break;
|
||||
case "execution_done":
|
||||
console.log("Execution done");
|
||||
document.getElementById('compile-button').disabled = false;
|
||||
checkLessonDone();
|
||||
break;
|
||||
}
|
||||
|
|
@ -355,6 +357,12 @@ function togglePause() {
|
|||
document.getElementById("pause-button").innerText = paused ? "Resume" : "Pause";
|
||||
}
|
||||
|
||||
function resetGameWorld() {
|
||||
robots = createInitialRobots();
|
||||
gameWorld.reset(robots["player"]);
|
||||
pyodideWorker.postMessage({ type: "interrupt" });
|
||||
}
|
||||
|
||||
// ✅ Reset Function (Fixed)
|
||||
function resetGame() {
|
||||
// Terminate the worker
|
||||
|
|
@ -400,11 +408,12 @@ function gameLoop(timestamp) {
|
|||
|
||||
gameWorld.update();
|
||||
gameWorld.draw(ctx);
|
||||
if (gameWorld.checkPlayerCompletedTask()) {
|
||||
logToConsole("✅ Task Completed! ✅");
|
||||
togglePause();
|
||||
gameWorld.currentLevel++;
|
||||
}
|
||||
|
||||
// if (gameWorld.checkPlayerCompletedTask()) {
|
||||
// logToConsole("✅ Task Completed! ✅");
|
||||
// togglePause();
|
||||
// gameWorld.currentLevel++;
|
||||
// }
|
||||
|
||||
pyodideWorker.postMessage({
|
||||
type: "game_state",
|
||||
|
|
@ -435,7 +444,8 @@ function updateSensorData() {
|
|||
|
||||
document.getElementById("compile-button").addEventListener("click", () => {
|
||||
if (paused) return;
|
||||
|
||||
document.getElementById('compile-button').disabled = true;
|
||||
resetGameWorld();
|
||||
// Use the Monaco Editor instance to get the code
|
||||
let code = monacoEditor.getValue(); // Get text from the editor
|
||||
//console.log(code);
|
||||
|
|
|
|||
55
gameworld.js
55
gameworld.js
|
|
@ -14,6 +14,7 @@ export class GameWorld {
|
|||
this.currentLevel = 0;
|
||||
|
||||
this.waypoints = [];
|
||||
this.waypointsReached = [];
|
||||
this.obstacles = [];
|
||||
this.robots = [];
|
||||
|
||||
|
|
@ -28,6 +29,7 @@ export class GameWorld {
|
|||
this.robots = []
|
||||
this.obstacles = []
|
||||
this.waypoints = [];
|
||||
this.waypointsReached = [];
|
||||
Matter.World.clear(this.world); // Clear the world without resetting the engine
|
||||
|
||||
let level = this.levelData[this.currentLevel];
|
||||
|
|
@ -51,7 +53,9 @@ export class GameWorld {
|
|||
let obstacle = level.waypoints[i];
|
||||
console.log("Adding waypoint:", obstacle);
|
||||
this.addWaypoint(obstacle.vertices, obstacle.position, obstacle.strokeColor, obstacle.fillColor);
|
||||
this.waypointsReached.push(false); // Initialize as not reached
|
||||
}
|
||||
console.log(level.waypointsReached)
|
||||
|
||||
// this.addObstacle([
|
||||
// { x: -100, y: -100 }, // Vertex 1
|
||||
|
|
@ -77,7 +81,19 @@ export class GameWorld {
|
|||
this.robots[id].update(this);
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.waypoints.length; i++) {
|
||||
if (this.waypointsReached[i]) {
|
||||
|
||||
} else {
|
||||
let waypoint = this.waypoints[i];
|
||||
let playerPos = this.robots[0].body.position;
|
||||
let waypointBounds = waypoint.bounds;
|
||||
if (Matter.Bounds.contains(waypointBounds, playerPos)) {
|
||||
console.log("Player is inside the waypoint's bounding box.");
|
||||
this.waypointsReached[i] = true; // Mark this waypoint as reached
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//this.checkPlayerCompletedTask();
|
||||
|
||||
|
|
@ -86,16 +102,20 @@ export class GameWorld {
|
|||
|
||||
}
|
||||
|
||||
waypointsReached() {
|
||||
return this.waypointsReached;
|
||||
}
|
||||
|
||||
// Needs to be updated to handle different win conditions
|
||||
checkPlayerCompletedTask() {
|
||||
let playerPos = this.robots[0].body.position;
|
||||
let waypointBounds = this.waypoints[0].bounds;
|
||||
if (Matter.Bounds.contains(waypointBounds, playerPos)) {
|
||||
console.log("Player is inside the waypoint's bounding box.");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// checkPlayerCompletedTask() {
|
||||
// let playerPos = this.robots[0].body.position;
|
||||
// let waypointBounds = this.waypoints[0].bounds;
|
||||
// if (Matter.Bounds.contains(waypointBounds, playerPos)) {
|
||||
// console.log("Player is inside the waypoint's bounding box.");
|
||||
// return true;
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
|
||||
addWaypoint(vertices, position = { x: 0, y: 0 }, strokeColor = "yellow", fillColor = "yellow") {
|
||||
// Convert the polygon points into a Matter.js body
|
||||
|
|
@ -167,22 +187,29 @@ export class GameWorld {
|
|||
});
|
||||
|
||||
// Draw Waypoints
|
||||
this.waypoints.forEach(body => {
|
||||
for (let w = 0; w < this.waypoints.length; w++) {
|
||||
let body = this.waypoints[w];
|
||||
ctx.beginPath();
|
||||
let vertices = body.vertices;
|
||||
ctx.moveTo(vertices[0].x, vertices[0].y);
|
||||
for (let i = 1; i < vertices.length; i++) {
|
||||
ctx.lineTo(vertices[i].x, vertices[i].y);
|
||||
}
|
||||
ctx.fillStyle = body.fillColor;;
|
||||
|
||||
ctx.strokeStyle = body.strokeColor;
|
||||
ctx.closePath();
|
||||
if (this.waypointsReached[w]) {
|
||||
ctx.globalAlpha = 0.05; // Applies to all drawing
|
||||
ctx.fillStyle = "green"; // Color for reached waypoints
|
||||
ctx.strokeStyle = "rgba(0, 128, 0, 0.2)"; // green with 80% opacity
|
||||
} else {
|
||||
ctx.globalAlpha = 0.2; // Applies to all drawing
|
||||
ctx.fillStyle = body.fillColor; // Default color for waypoints
|
||||
ctx.strokeStyle = body.strokeColor;
|
||||
}
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.globalAlpha = 1.0; // Reset after if needed
|
||||
ctx.stroke();
|
||||
});
|
||||
}
|
||||
|
||||
this.robots.forEach(robot => {
|
||||
let body = robot.body; // Get the Matter.js body from the robot object
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
importScripts("https://cdn.jsdelivr.net/pyodide/v0.23.4/full/pyodide.js");
|
||||
importScripts("https://cdn.jsdelivr.net/pyodide/v0.27.7/full/pyodide.js");
|
||||
|
||||
let sensorData = {}; // ✅ Store sensor values
|
||||
let gameWorld = null;
|
||||
|
||||
async function initializePyodide() {
|
||||
self.pyodide = await loadPyodide({
|
||||
indexURL: "https://cdn.jsdelivr.net/pyodide/v0.23.4/full/"
|
||||
indexURL: "https://cdn.jsdelivr.net/pyodide/v0.27.7/full/"
|
||||
});
|
||||
console.log("Pyodide loaded:", self.pyodide.version);
|
||||
|
||||
|
||||
|
||||
self.pyodide.globals.set("send_to_main", (event, data) => {
|
||||
self.postMessage({ type: event, data: data });
|
||||
|
|
@ -139,6 +142,8 @@ async def async_sleep(seconds):
|
|||
|
||||
|
||||
time.sleep = async_sleep # ✅ Monkey-patch time.sleep()
|
||||
|
||||
|
||||
`);
|
||||
|
||||
self.postMessage({ type: "ready" }); // ✅ Notify main thread that Pyodide is ready
|
||||
|
|
@ -146,6 +151,11 @@ time.sleep = async_sleep # ✅ Monkey-patch time.sleep()
|
|||
|
||||
initializePyodide();
|
||||
|
||||
// Add this function to trigger interrupts
|
||||
function interruptExecution() {
|
||||
interruptBuffer[0] = 2; // 2 is the magic number for KeyboardInterrupt
|
||||
}
|
||||
|
||||
self.onmessage = async (event) => {
|
||||
if (!self.pyodide) {
|
||||
self.postMessage({ type: "error", message: "Pyodide not initialized yet." });
|
||||
|
|
|
|||
Loading…
Reference in New Issue