added second robot lesson and changing levels. Need to check win conditions on all checkpoints reached, current error leaves level incomplete if checkpoint is reached after code finishes

master
Jake 2025-07-04 18:21:15 +08:00
parent 8a0219500d
commit 3551ec747d
5 changed files with 224 additions and 81 deletions

View File

@ -848,77 +848,6 @@ print("Done!")
})()
},
{
id: 'robot1',
title: '1. Moving the Robot',
tabtitle: 'Importing Modules',
level: 'robot',
content: `
<p>This robot simulation is a simplified version of a real robot.</p>
<p>It has a single library which you can use to access all its controls and sensors.</p>
<p>In a real robot you would have many different libraries for different parts, sensors, and even microcontroller functions.</p>
</br>
<p>We'll start by importing the robot library, and using it to move.</p>
<pre><code>
import robot # Import the robot library
import time # Import the time module
robot.move(1) # Move forward at max speed
time.sleep(2) # Wait for 2 seconds
robot.move(-1) # Move backward at max speed
time.sleep(2) # Wait for 2 seconds
robot.move(0) # Stop the robot
</code></pre>
`,
objectives: [
"Reach the first checkpoint",
"Reach the second checkpoint",
"Code should complete without errors"
],
doneCondition: (() => {
return ({ code, consoleText, codeRanGood, gameWorld }) => {
const progress = {
firstCheckpoint: gameWorld.waypointsReached[0],
secondCheckpoint: gameWorld.waypointsReached[1],
codeRanGood: codeRanGood,
};
if (!codeRanGood) {
return { done: false, hint: "" };
}
// 5. Build hint
const missing = [];
if (!progress.firstCheckpoint) missing.push("reach the first checkpoint");
if (!progress.secondCheckpoint) missing.push("reach the second checkpoint");
let hint = "";
if (missing.length === 1) {
hint = `I still need you to ${missing[0]}`;
} else if (missing.length > 1) {
hint = `I still need you to ${missing.slice(0, -1).join(", ")} and ${missing.at(-1)}`;
}
return {
done:
progress.firstCheckpoint &&
progress.secondCheckpoint &&
progress.codeRanGood,
progressArray: Object.values(progress),
hint,
};
};
})()
},
{
id: 'lesson10',
@ -1022,5 +951,147 @@ print("Done!")
},
{
id: 'robot1',
title: '1. Moving the Robot',
tabtitle: 'Importing Modules',
level: 'robot',
map: 'Level 1',
content: `
<p>This robot simulation is a simplified version of a real robot.</p>
<p>It has a single library which you can use to access all its controls and sensors.</p>
<p>In a real robot you would have many different libraries for different parts, sensors, and even microcontroller functions.</p>
</br>
<p>We'll start by importing the robot library, and using it to move.</p>
<pre><code>
import robot # Import the robot library
import time # Import the time module
robot.move(1) # Move forward at max speed
time.sleep(2) # Wait for 2 seconds
robot.move(-1) # Move backward at max speed
time.sleep(2) # Wait for 2 seconds
robot.move(0) # Stop the robot
</code></pre>
`,
objectives: [
"Reach the first checkpoint",
"Reach the second checkpoint",
"Code should complete without errors"
],
doneCondition: (() => {
return ({ code, consoleText, codeRanGood, gameWorld }) => {
const progress = {
firstCheckpoint: gameWorld.waypointsReached[0],
secondCheckpoint: gameWorld.waypointsReached[1],
codeRanGood: codeRanGood,
};
if (!codeRanGood) {
return { done: false, hint: "" };
}
// 5. Build hint
const missing = [];
if (!progress.firstCheckpoint) missing.push("reach the first checkpoint");
if (!progress.secondCheckpoint) missing.push("reach the second checkpoint");
let hint = "";
if (missing.length === 1) {
hint = `I still need you to ${missing[0]}`;
} else if (missing.length > 1) {
hint = `I still need you to ${missing.slice(0, -1).join(", ")} and ${missing.at(-1)}`;
}
return {
done:
progress.firstCheckpoint &&
progress.secondCheckpoint &&
progress.codeRanGood,
progressArray: Object.values(progress),
hint,
};
};
})()
},
{
id: 'robot1',
title: '2. Steering the Robot',
tabtitle: 'Importing Modules',
level: 'robot',
map: 'Level 2',
content: `
<p>Turning is very similar to moving, we use the <code>robot.turn(amount)</code> function.</p>
<p>The <code>amount</code> parameter is a number between -1 and 1, where -1 is full left, 0 is no turn, and 1 is full right.</p>
<pre><code>
import robot
import time
robot.turn(1)
time.sleep(2)
robot.turn(0)
</code></pre>
</br>
<p>This code causes the robot to turn right at max speed for 2 seconds, then stop.</p>
<p>You'll need to combine moving, turning, and waiting to reach all the checkpoints.</p>
<p><strong>Note:</strong> The values for move, turn, and sleep can all be decimal numbers (floats). ie <code>time.sleep(0.5)</code> or <code>robot.move(0.8)</code></p>
`,
objectives: [
"Reach the first checkpoint",
"Reach the second checkpoint",
"Code should complete without errors"
],
doneCondition: (() => {
return ({ code, consoleText, codeRanGood, gameWorld }) => {
const progress = {
firstCheckpoint: gameWorld.waypointsReached[0],
secondCheckpoint: gameWorld.waypointsReached[1],
codeRanGood: codeRanGood,
};
if (!codeRanGood) {
return { done: false, hint: "" };
}
// 5. Build hint
const missing = [];
if (!progress.firstCheckpoint) missing.push("reach the first checkpoint");
if (!progress.secondCheckpoint) missing.push("reach the second checkpoint");
let hint = "";
if (missing.length === 1) {
hint = `I still need you to ${missing[0]}`;
} else if (missing.length > 1) {
hint = `I still need you to ${missing.slice(0, -1).join(", ")} and ${missing.at(-1)}`;
}
return {
done:
progress.firstCheckpoint &&
progress.secondCheckpoint &&
progress.codeRanGood,
progressArray: Object.values(progress),
hint,
};
};
})()
},
];

View File

@ -209,7 +209,33 @@
"waypoints": [
{
"position": {
"x": 220,
"x": 420,
"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": 420,
"y": 500
},
"vertices": [
@ -230,7 +256,7 @@
"y": 50
}
],
"strokeColor": "#000099",
"strokeColor": "#0000FF",
"fillColor": "#0000CC"
}
],
@ -255,8 +281,8 @@
}
],
"position": {
"x": 200,
"y": 400
"x": 800,
"y": 300
},
"strokeColor": "#999999",
"fillColor": "#CCCCCC"

26
game.js
View File

@ -23,6 +23,21 @@ function showLesson(index) {
loadLessonContent(lesson);
updateTabs(lessons, index);
if (lesson.map) {
console.log("Loading map for lesson:", lesson.map);
document.getElementById('gameCanvas').style.display = 'block';
for (let i = 0; i < gameWorld.levelData.length; i++) {
if (gameWorld.levelData[i].name === lesson.map) {
gameWorld.currentLevel = i;
console.log("Setting current level to:", i);
break;
}
}
resetGameWorld();
} else {
document.getElementById('gameCanvas').style.display = 'none';
}
document.getElementById('prev-lesson').disabled = index === 0;
document.getElementById('next-lesson').disabled = index === lessons.length - 1;
@ -95,6 +110,7 @@ function checkLessonDone() {
codeRanGood: codeRanGood,
gameWorld: gameWorld
});
console.log(gameWorld.waypointsReached);
if (result.done) {
markLessonDone(lesson.id);
}
@ -221,7 +237,7 @@ function toggleObjective(index, completed = true) {
}
//clearLessonProgress(); // Clear progress on load for testing
showLesson(9);
const consoleElement = document.getElementById("console");
const gameCanvas = document.getElementById("gameCanvas");
@ -385,6 +401,8 @@ function resetGame() {
document.getElementById("pause-button").innerText = "Pause";
logToConsole("Welcome to the game! Type your Python code in the editor and click 'Compile' to execute it.");
document.getElementById('compile-button').disabled = false;
}
const targetFPS = 30;
@ -408,7 +426,7 @@ function gameLoop(timestamp) {
gameWorld.update();
gameWorld.draw(ctx);
console.log(gameWorld.waypointsReached);
// if (gameWorld.checkPlayerCompletedTask()) {
// logToConsole("✅ Task Completed! ✅");
// togglePause();
@ -557,16 +575,18 @@ function setupCanvas() {
}
// Call this function when the page loads
fetch('/data/levels.json')
.then(response => response.json())
.then(data => {
gameWorld.levelData = data;
setupCanvas();
resetGame(); // Initialize the game and robots
// Start game loop
gameLoop();
showLesson(11);
});

View File

@ -31,7 +31,7 @@ async function initializePyodide() {
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
"distance": Math.round(sensor.distance * 100) / 100, // Individual sensor's distance
"hitpoint": sensor.hitpoint // Whatever other attributes you need
}));
//console.log(sensorData["x"]);
@ -69,6 +69,12 @@ class RobotModule:
sensor_data = json.loads(get_sensor_data(name))
return sensor_data
def get_distance_left(self):
return self.get_sensors()[0]["distance"]
def get_distance_right(self):
return self.get_sensors()[1]["distance"]
def get_sensors(self):
return json.loads(get_sensor_data("sensors")) # Returns list of sensor dicts

View File

@ -30,3 +30,23 @@ while True:
else:
robot.turn(0)
time.sleep(0.1)
import time
import robot
robot.move(1)
time.sleep(1.6)
robot.move(0)
robot.turn(1)
time.sleep(2.2)
robot.turn(0)
robot.move(1)