added up to lesson 8:loops. checkLessonDone() now done on code execution compelte only

master
Jake 2025-06-24 23:34:05 +08:00
parent af9cd8177e
commit 9bbc8592e0
5 changed files with 515 additions and 104 deletions

View File

@ -13,7 +13,34 @@ export const lessons = [
<li>You should see "Hello World" printed in the output area</li> <li>You should see "Hello World" printed in the output area</li>
</ol> </ol>
`, `,
doneCondition: (consoleText) => consoleText.includes("Hello World") doneCondition: (() => {
const progress = {
stringDone: false
};
return ({ code, consoleText, codeRanGood }) => {
if (codeRanGood && !progress.stringDone && consoleText.includes("Hello World")) {
progress.stringDone = true;
}
const missing = [];
if (!progress.stringDone) missing.push("Hello World");
let hint = "I still need you to print ";
if (missing.length === 0) {
hint = "";
} else if (missing.length === 1) {
hint += missing[0];
} else {
hint += missing.slice(0, -1).join(", ") + " and " + missing[missing.length - 1];
}
return {
done: progress.stringDone,
hint
};
};
})()
}, },
{ {
id: 'lesson2', id: 'lesson2',
@ -36,65 +63,60 @@ export const lessons = [
</ol> </ol>
<p>Click the "Run" button to execute your code</p> <p>Click the "Run" button to execute your code</p>
`, `,
doneCondition: (consoleText => { doneCondition: (() => {
// Persistent tracking object inside closure
const progress = { const progress = {
stringDone: false, stringDone: false,
intDone: false, intDone: false,
floatDone: false, floatDone: false,
boolDone: false, boolDone: false,
syntaxErrorDone: false, // syntaxErrorDone: false, // optional
}; };
//const syntaxErrorRegex = /SyntaxError/i; const stringRegex = /(["'])(?:(?=(\\?))\2.)*?\1/;
const stringRegex = /(["'])(?:(?=(\\?))\2.)*?\1/; // optional improvement based on context
const intRegex = /(?<![\d.])[-+]?\d+(?![\d.])/; const intRegex = /(?<![\d.])[-+]?\d+(?![\d.])/;
const floatRegex = /[-+]?(?:\d+\.\d*|\.\d+)(?:[eE][-+]?\d+)?/; const floatRegex = /[-+]?(?:\d+\.\d*|\.\d+)(?:[eE][-+]?\d+)?/;
const boolRegex = /\b(True|False|true|false)\b/; const boolRegex = /\b(True|False|true|false)\b/;
return (text) => { return ({ code, consoleText, codeRanGood }) => {
if (!codeRanGood) {
return {
done: false,
hint: "Your code had an error — try fixing it and run again."
};
}
if (!progress.stringDone && stringRegex.test(consoleText)) {
// if (!progress.syntaxErrorDone && syntaxErrorRegex.test(text)) {
// progress.syntaxErrorDone = true;
// }
if (!progress.stringDone && stringRegex.test(text)) {
progress.stringDone = true; progress.stringDone = true;
} }
if (!progress.floatDone && floatRegex.test(text)) { if (!progress.floatDone && floatRegex.test(consoleText)) {
progress.floatDone = true; progress.floatDone = true;
} }
if (!progress.intDone && intRegex.test(text)) { if (!progress.intDone && intRegex.test(consoleText)) {
progress.intDone = true; progress.intDone = true;
} }
if (!progress.boolDone && boolRegex.test(text)) { if (!progress.boolDone && boolRegex.test(consoleText)) {
progress.boolDone = true; progress.boolDone = true;
} }
let missing = []; const missing = [];
//if (!progress.syntaxErrorDone) missing.push("syntax error");
//if (!progress.stringDone) missing.push("string");
if (!progress.floatDone) missing.push("float"); if (!progress.floatDone) missing.push("float");
if (!progress.intDone) missing.push("int"); if (!progress.intDone) missing.push("int");
if (!progress.boolDone) missing.push("boolean"); if (!progress.boolDone) missing.push("boolean");
let hint = "I still need you to print a ";
let hint = "I still need you to print a ";
if (missing.length === 0) { if (missing.length === 0) {
hint = ""; hint = "";
} else if (missing.length === 1) { } else if (missing.length === 1) {
hint += missing[0]; hint += missing[0];
} else { } else {
// Join all but last with comma, then add 'and' + last
hint += missing.slice(0, -1).join(", ") + " and " + missing[missing.length - 1]; hint += missing.slice(0, -1).join(", ") + " and " + missing[missing.length - 1];
} }
const done = progress.intDone && progress.floatDone && progress.boolDone; const done = progress.intDone && progress.floatDone && progress.boolDone;
return { done, hint }; return { done, hint };
}; };
})() })()
}, },
{ {
id: 'lesson3', id: 'lesson3',
@ -111,7 +133,7 @@ export const lessons = [
<li>Division: <code>print(20 / 5)</code></li> <li>Division: <code>print(20 / 5)</code></li>
</ol> </ol>
`, `,
doneCondition: (consoleText => { doneCondition: (() => {
// Persistent tracking object inside closure // Persistent tracking object inside closure
const progress = { const progress = {
addDone: false, addDone: false,
@ -123,22 +145,27 @@ export const lessons = [
return (text) => { return ({ code, consoleText, codeRanGood }) => {
if (!codeRanGood) {
return {
done: false,
hint: ""
};
}
// if (!progress.syntaxErrorDone && syntaxErrorRegex.test(text)) { // if (!progress.syntaxErrorDone && syntaxErrorRegex.test(text)) {
// progress.syntaxErrorDone = true; // progress.syntaxErrorDone = true;
// } // }
if (!progress.addDone && text.includes("+")) { if (!progress.addDone && code.includes("+")) {
progress.addDone = true; progress.addDone = true;
} }
if (!progress.subDone && text.includes("-")) { if (!progress.subDone && code.includes("-")) {
progress.subDone = true; progress.subDone = true;
} }
if (!progress.mulDone && text.includes("*")) { if (!progress.mulDone && code.includes("*")) {
progress.mulDone = true; progress.mulDone = true;
} }
if (!progress.divDone && text.includes("/")) { if (!progress.divDone && code.includes("/")) {
progress.divDone = true; progress.divDone = true;
} }
@ -198,40 +225,44 @@ export const lessons = [
doneCondition: (text) => text.includes("8"), doneCondition: (text) => text.includes("8"),
} }
], ],
doneCondition: (code => { doneCondition: (() => {
const progress = { const progress = {
varCreated: false, varCreated: false,
varPrinted: false, varPrinted: false,
varName: null, varName: null,
}; };
return (text) => { return ({ code, consoleText, codeRanGood }) => {
// Extract variable name from current text each time if (!codeRanGood) {
const assignMatch = text.match(/^\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*=/m); return {
done: false,
hint: ""
};
}
const assignMatch = code.match(/^\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*=/m);
if (assignMatch) { if (assignMatch) {
progress.varName = assignMatch[1]; progress.varName = assignMatch[1];
progress.varCreated = true; progress.varCreated = true;
} else { } else {
// No variable assignment found this run
progress.varCreated = false; progress.varCreated = false;
progress.varName = null; progress.varName = null;
progress.varPrinted = false; // reset printed too because no var progress.varPrinted = false;
} }
// Check if variable is printed in this run (only if varName exists)
if (progress.varCreated && progress.varName) { if (progress.varCreated && progress.varName) {
const printRegex = new RegExp(`print\\s*\\(\\s*${progress.varName}\\s*\\)`); const printRegex = new RegExp(`print\\s*\\(\\s*${progress.varName}\\s*\\)`);
if (printRegex.test(text)) { if (printRegex.test(code)) {
progress.varPrinted = true; progress.varPrinted = true;
} else { } else {
progress.varPrinted = false; // reset if print no longer found progress.varPrinted = false;
} }
} }
// Build hint console.log('varCreated:', progress.varCreated, 'varPrinted:', progress.varPrinted);
let missing = []; let missing = [];
if (!progress.varCreated) missing.push("create a variable"); if (!progress.varCreated) missing.push("create a variable");
if (progress.varCreated && !progress.varPrinted) missing.push("print the variable"); if (!progress.varPrinted) missing.push("print the variable");
let hint = "I still need you to "; let hint = "I still need you to ";
if (missing.length === 0) { if (missing.length === 0) {
@ -245,8 +276,367 @@ export const lessons = [
const done = progress.varCreated && progress.varPrinted; const done = progress.varCreated && progress.varPrinted;
return { done, hint }; return { done, hint };
}; };
})() })()
},
{
id: 'lesson5',
title: '5. More on Variables',
difficulty: 'easy',
content: `
<p>A variable will act like any other object of its data type.</p>
<p>For example, if we have a variable called <code>bob</code> with the value <code>32</code>, we can do math with it:</p>
<p><code>print(bob + 10)</code> will print <code>42</code>.</p>
</br>
<p>We can also change the value of a variable:</p>
<pre><code>
bob = 32
print(bob)
bob = bob + 10
print(bob)
</code></pre>
<p>In this example, the first time you print bob it will give his initial value of 32, but the second time we have added 10 to bob.</p>
<p>Give it a try.</p>
`,
steps: [
{
content: `<p>First, try printing an addition: <code>print(2 + 3)</code></p>`,
doneCondition: (text) => text.includes("5"),
},
{
content: `<p>Nice! Now try subtraction: <code>print(5 - 2)</code></p>`,
doneCondition: (text) => text.includes("3"),
},
{
content: `<p>Now try multiplication: <code>print(4 * 2)</code></p>`,
doneCondition: (text) => text.includes("8"),
}
],
doneCondition: (() => {
const progress = {
varCreated: false,
varPrinted: false,
varArithmeticDone: false,
varPrintedTwice: false,
};
return ({ code, consoleText, codeRanGood }) => {
if (!codeRanGood) {
return {
done: false,
hint: ""
};
}
const assignRegex = /\b(\w+)\s*=\s*[\d'"]/; // variable assignment
const printRegex1 = /print\(\s*\1\s*\)/; // first print of the variable
const mathRegex = /\1\s*=\s*\1\s*[\+\-\*/]\s*[\d'"]/; // arithmetic using itself
const printRegex2 = /print\(\s*\1\s*\)/; // second print of the variable
const match = code.match(assignRegex);
if (!match) return false;
const varName = match[1];
const dynamicPrint1 = new RegExp(`print\\(\\s*${varName}\\s*\\)`);
const dynamicMath = new RegExp(`${varName}\\s*=\\s*${varName}\\s*[+\\-*/]\\s*[\\d'"]`);
const dynamicPrint2 = new RegExp(`print\\(\\s*${varName}\\s*\\)`, 'g');
progress.varCreated = match;
progress.dynamicPrint1 = dynamicPrint1.test(code);
progress.varArithmeticDone = dynamicMath.test(code);
progress.varPrintedTwice = (code.match(dynamicPrint2) || []).length >= 2
let missing = [];
if (!progress.varCreated) missing.push("create a variable");
if (!progress.dynamicPrint1) missing.push("print the variable");
if (!progress.varArithmeticDone) missing.push("alter the variable");
if (!progress.varPrintedTwice) missing.push("print the variable a second time");
let hint = "I still need you to ";
if (missing.length === 0) {
hint = "";
} else if (missing.length === 1) {
hint += missing[0];
} else {
hint += missing.slice(0, -1).join(", ") + " and " + missing[missing.length - 1];
}
const done = progress.varCreated && progress.dynamicPrint1 && progress.varArithmeticDone && progress.varPrintedTwice;
return { done, hint };
};
})()
},
{
id: 'lesson6',
title: '6. Conditionals',
difficulty: 'easy',
content: `
<p>Sometimes we want don't want part of our code to run, or we want it to run differently in different situations. That's when we use <strong>conditionals</strong>.</p>
<pre><code>
bob = 5
if bob > 10:
print("Bob is greater than 10")
</code></pre>
<p>In this example, the print function will only run if <code>bob</code> is 11 or higher.</p>
</br>
<p>One thing to note here is the formatting. Note that after the <code>if bob > 10:</code> there is a colon (:)<p>
<p>This tells Python that the next line is part of a new block of code.</p>
<p>In Python, we use indentation to define blocks of code, so the next line must be indented.</p>
<p>After the <code>else:</code> we also have a colon, and the next line is indented again.</p>
<p>Try it for yourself:</p>
<ol>
<li>Initialize a variable with a value</li>
<li>Use an <code>if</code> statement to check if the variable is greater or less than a value</li>
<li>Print a message based on the condition</li>
</ol>
<p>To test if two values are equal, you can use the <code>==</code> operator.</p>
<p>eg. <pre><code>
num = 10
if num == 10:
print("num is equal to 10")
</code></pre>
`,
doneCondition: (() => {
const progress = {
varCreated: false,
ifStatement: false,
printedSomething: false,
};
return ({ code, consoleText, codeRanGood }) => {
if (!codeRanGood) {
return {
done: false,
hint: ""
};
}
// Check for variable assignment
const assignRegex = /\b(\w+)\s*=\s*[\d'"]/;
progress.varCreated = assignRegex.test(code);
// Check for if statement
progress.ifStatement = /(^|\n)\s*if\s+.*:/.test(code);
// Check for print() anywhere
progress.printedSomething = /print\s*\(.*\)/.test(code);
// Build hint
const missing = [];
if (!progress.varCreated) missing.push("create a variable");
if (!progress.ifStatement) missing.push("use an if statement");
if (!progress.printedSomething) missing.push("print something");
let hint = "";
if (missing.length > 0) {
hint = "I still need you to " + (missing.length === 1
? missing[0]
: missing.slice(0, -1).join(", ") + " and " + missing[missing.length - 1]);
}
const done = progress.varCreated && progress.ifStatement && progress.printedSomething;
return { done, hint };
};
})()
}, },
{
id: 'lesson7',
title: '7. More Conditionals',
difficulty: 'easy',
content: `
<p>Sometimes we'll want to run one thing OR another, rather than just running something or not. To do this we chain our if statements together with <code>elif</code> and <code>else</code>.</p>
<pre><code>
bob = 10
if bob == 10:
print("bob is equal to 10")
elif bob < 10:
print("bob is less than 10")
else:
print("bob is greater than 10")
</code></pre>
<p>In this example, only one of the print statements can ever run, <code>elif</code> is short for "else if", and is a second if statement which is only tested it the <code>if</code> above it resolves to False.</p>
<p>The <code>else</code> statement is run if all previous conditions are False.</p>
<p>You can chain as many <code>elif</code> statements as you like, but only one <code>else</code> at the end.</p>
<p>Try it for yourself:</p>
`,
doneCondition: (() => {
const progress = {
varCreated: false,
ifStatement: false,
elifStatement: false,
elseStatement: false,
threeDistinctPrints: false,
};
return ({ code, consoleText, codeRanGood }) => {
if (!codeRanGood) {
return { done: false, hint: "" };
}
// 1. Variable assignment
const assignRegex = /\b(\w+)\s*=\s*[\d'"]/;
progress.varCreated = assignRegex.test(code);
// 2. Line-by-line block tracking
const lines = code.split('\n');
let currentBlock = null;
const printContents = {
if: null,
elif: null,
else: null,
};
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
if (/^if\s+.*:/.test(line)) {
progress.ifStatement = true;
currentBlock = "if";
continue;
}
if (/^elif\s+.*:/.test(line)) {
progress.elifStatement = true;
currentBlock = "elif";
continue;
}
if (/^else\s*:/.test(line)) {
progress.elseStatement = true;
currentBlock = "else";
continue;
}
const printMatch = line.match(/print\s*\((.*?)\)/);
if (printMatch && currentBlock && !printContents[currentBlock]) {
const cleaned = printMatch[1].replace(/\s+/g, '').toLowerCase();
printContents[currentBlock] = cleaned;
}
}
const printedValues = Object.values(printContents).filter(Boolean);
const uniqueValues = new Set(printedValues);
progress.threeDistinctPrints = uniqueValues.size === 3;
// Hint generation
const missing = [];
if (!progress.varCreated) missing.push("create a variable");
if (!progress.ifStatement) missing.push("use an if statement");
if (!progress.elifStatement) missing.push("use an elif statement");
if (!progress.elseStatement) missing.push("use an else statement");
if (!progress.threeDistinctPrints) missing.push("print different things in the if, elif, and else blocks");
const hint = missing.length > 0
? "I still need you to " + (missing.length === 1
? missing[0]
: missing.slice(0, -1).join(", ") + " and " + missing[missing.length - 1])
: "";
const done = progress.varCreated && progress.ifStatement && progress.elifStatement &&
progress.elseStatement && progress.threeDistinctPrints;
return { done, hint };
};
})()
},
{
id: 'lesson8',
title: '8. Loops',
difficulty: 'easy',
content: `
<p>Most of the time we'll want our code to run multiple times, or even endlessly, in that case we need <strong>loops</strong>.</p>
<p>Python has two main types of loops: <code>for</code> loops and <code>while</code> loops.</p>
<p>The <code>while</code> loop will continue to run as long as a condition is True.</p>
<pre><code>
count = 0
while count < 5:
print("Count is:", count)
count = count + 1
print("Done!")
</code></pre>
<p>You have to be careful with <code>while</code> loops, if the condition never becomes False, the loop will run forever!</p>
`,
doneCondition: (() => {
const progress = {
varCreated: false,
ifStatement: false,
elifStatement: false,
elseStatement: false,
threeDistinctPrints: false,
};
return ({ code, consoleText, codeRanGood }) => {
if (!codeRanGood) {
return { done: false, hint: "" };
}
// 1. Variable assignment
const assignRegex = /\b(\w+)\s*=\s*[\d'"]/;
progress.varCreated = assignRegex.test(code);
// 2. Line-by-line block tracking
const lines = code.split('\n');
let currentBlock = null;
const printContents = {
if: null,
elif: null,
else: null,
};
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
if (/^if\s+.*:/.test(line)) {
progress.ifStatement = true;
currentBlock = "if";
continue;
}
if (/^elif\s+.*:/.test(line)) {
progress.elifStatement = true;
currentBlock = "elif";
continue;
}
if (/^else\s*:/.test(line)) {
progress.elseStatement = true;
currentBlock = "else";
continue;
}
const printMatch = line.match(/print\s*\((.*?)\)/);
if (printMatch && currentBlock && !printContents[currentBlock]) {
const cleaned = printMatch[1].replace(/\s+/g, '').toLowerCase();
printContents[currentBlock] = cleaned;
}
}
const printedValues = Object.values(printContents).filter(Boolean);
const uniqueValues = new Set(printedValues);
progress.threeDistinctPrints = uniqueValues.size === 3;
// Hint generation
const missing = [];
if (!progress.varCreated) missing.push("create a variable");
if (!progress.ifStatement) missing.push("use an if statement");
if (!progress.elifStatement) missing.push("use an elif statement");
if (!progress.elseStatement) missing.push("use an else statement");
if (!progress.threeDistinctPrints) missing.push("print different things in the if, elif, and else blocks");
const hint = missing.length > 0
? "I still need you to " + (missing.length === 1
? missing[0]
: missing.slice(0, -1).join(", ") + " and " + missing[missing.length - 1])
: "";
const done = progress.varCreated && progress.ifStatement && progress.elifStatement &&
progress.elseStatement && progress.threeDistinctPrints;
return { done, hint };
};
})()
},
]; ];

View File

@ -1,10 +0,0 @@
[
{
"title": "Lesson 1",
"content": "Welcome to Python. We are going to learn how to use python to control robots, but we need some basics first.</br>Let's start with the basics of Python programming. Python is a versatile language that is easy to learn and widely used in robotics.<br>"
},
{
"title": "Lesson 2",
"content": "Now let's learn about interrupts..."
}
]

48
game.js
View File

@ -2,7 +2,9 @@ import { Robot } from "./robot.js";
import { GameWorld } from "./gameworld.js"; import { GameWorld } from "./gameworld.js";
import { lessons } from './data/lessons.js'; import { lessons } from './data/lessons.js';
let mostRecentCode = ""; // The users code
let codeRanGood = false; // Your own function that returns true/false
let consoleText = ""; // What was printed
let currentLesson = 0; let currentLesson = 0;
let lessonComplete = false; let lessonComplete = false;
@ -23,11 +25,11 @@ function showLesson(index) {
document.getElementById('prev-lesson').disabled = index === 0; document.getElementById('prev-lesson').disabled = index === 0;
document.getElementById('next-lesson').disabled = index === lessons.length - 1; document.getElementById('next-lesson').disabled = index === lessons.length - 1;
console.log(isLessonDone(lesson.id)); //console.log(isLessonDone(lesson.id));
updateLessonStatus(); updateLessonStatus();
} }
function loadLessonContent(lesson){ function loadLessonContent(lesson) {
document.getElementById('lesson-title').textContent = lesson.title; document.getElementById('lesson-title').textContent = lesson.title;
document.getElementById('lesson-content').innerHTML = lesson.content; document.getElementById('lesson-content').innerHTML = lesson.content;
} }
@ -39,10 +41,22 @@ document.getElementById('next-lesson').addEventListener('click', () => {
showLesson(currentLesson + 1); showLesson(currentLesson + 1);
}); });
function checkLessonDone(outputText) { function checkLessonDone() {
checkCurrentStep(outputText); //consoleText = outputText; // Update console text
if (consoleText.includes("Error") || consoleText.includes("Exception")) {
codeRanGood = false;
} else {
codeRanGood = true;
}
console.log("codeRanGood ", codeRanGood);
//checkCurrentStep(outputText);
const lesson = lessons[currentLesson]; const lesson = lessons[currentLesson];
const result = lesson.doneCondition(outputText); const result = lesson.doneCondition({
code: mostRecentCode,
consoleText: consoleText,
codeRanGood: codeRanGood
});
if (result.done) { if (result.done) {
markLessonDone(lesson.id); markLessonDone(lesson.id);
} }
@ -60,7 +74,7 @@ function markLessonDone(lessonId) {
console.log(`Lesson ${lessonId} marked as done!`); console.log(`Lesson ${lessonId} marked as done!`);
// For example: // For example:
localStorage.setItem(`lessonDone_${lessonId}`, 'true'); localStorage.setItem(`lessonDone_${lessonId}`, 'true');
logToConsole("✅ Task Completed! ✅"); logToConsole("✅ Task Completed! ✅", false);
updateLessonStatus(); updateLessonStatus();
} }
@ -118,8 +132,8 @@ function clearLessonProgress() {
} }
//clearLessonProgress(); // Clear progress on load for testing clearLessonProgress(); // Clear progress on load for testing
showLesson(3); showLesson(7);
const consoleElement = document.getElementById("console"); const consoleElement = document.getElementById("console");
const gameCanvas = document.getElementById("gameCanvas"); const gameCanvas = document.getElementById("gameCanvas");
@ -152,6 +166,7 @@ function startPyodideWorker() {
switch (event.data.type) { switch (event.data.type) {
case "console": case "console":
logToConsole(event.data.data); logToConsole(event.data.data);
break; break;
case "error": case "error":
logToConsole(`<span style="color:red;">${event.data.message}</span>`); logToConsole(`<span style="color:red;">${event.data.message}</span>`);
@ -165,6 +180,10 @@ function startPyodideWorker() {
case "move": case "move":
move(event.data.data); move(event.data.data);
break; break;
case "execution_done":
console.log("Execution done");
checkLessonDone();
break;
} }
}; };
@ -218,7 +237,8 @@ function logToConsole(text, checkLesson = true) {
consoleElement.scrollTop = consoleElement.scrollHeight; consoleElement.scrollTop = consoleElement.scrollHeight;
if (checkLesson && !text.includes("Welcome")) { // Don't check lesson completion for welcome message if (checkLesson && !text.includes("Welcome")) { // Don't check lesson completion for welcome message
checkLessonDone(text); consoleText = text; // Update console text
//checkLessonDone(text);
} }
} }
@ -331,18 +351,18 @@ document.getElementById("compile-button").addEventListener("click", () => {
// Use the Monaco Editor instance to get the code // Use the Monaco Editor instance to get the code
let code = monacoEditor.getValue(); // Get text from the editor let code = monacoEditor.getValue(); // Get text from the editor
console.log(code); //console.log(code);
code = code.replace(/time\.sleep\(/g, "await time.sleep("); code = code.replace(/time\.sleep\(/g, "await time.sleep(");
console.log(code); //console.log(code);
consoleElement.innerHTML = ""; consoleElement.innerHTML = "";
pyodideWorker.postMessage({ pyodideWorker.postMessage({
type: "execute", type: "execute",
code: code code: code
}); });
mostRecentCode = code;
checkLessonDone(code); logToConsole("Compiling your code...", false);
}); });

View File

@ -16,7 +16,7 @@ async function initializePyodide() {
// ✅ Expose sensor data to Python // ✅ Expose sensor data to Python
self.pyodide.globals.set("get_sensor_data", (name) => { self.pyodide.globals.set("get_sensor_data", (name) => {
if (gameWorld == null){ if (gameWorld == null) {
return null; return null;
} }
@ -38,7 +38,7 @@ async function initializePyodide() {
self.pyodide.globals.set("get_robot_data", () => { self.pyodide.globals.set("get_robot_data", () => {
if (gameWorld == null){ if (gameWorld == null) {
return null; return null;
} }
let robot = gameWorld.robots[0]; let robot = gameWorld.robots[0];
@ -158,5 +158,6 @@ self.onmessage = async (event) => {
} catch (error) { } catch (error) {
self.postMessage({ type: "error", message: error.toString() }); self.postMessage({ type: "error", message: error.toString() });
} }
self.postMessage({ type: "execution_done" });
} }
}; };

View File

@ -50,6 +50,16 @@ header p {
/* gray-600 */ /* gray-600 */
} }
code, pre {
font-family: Consolas, Menlo, Monaco, "Courier New", monospace;
background-color: inherit;
color: blue;
border: none;
padding: 0;
margin: 0;
}
.button-group { .button-group {
display: flex; display: flex;
gap: 12px; gap: 12px;
@ -281,9 +291,9 @@ main {
} }
#lesson-box code { /* #lesson-box code {
background: #eee; background: #eee;
padding: 2px 4px; padding: 2px 4px;
border-radius: 4px; border-radius: 4px;
font-family: monospace; font-family: monospace;
} } */