added more done conditions

master
Jake 2025-06-24 17:28:28 +08:00
parent 97e02d28c1
commit af9cd8177e
3 changed files with 332 additions and 39 deletions

View File

@ -17,11 +17,236 @@ export const lessons = [
},
{
id: 'lesson2',
title: 'Using Interrupts',
difficulty: 'medium',
title: 'Data Types and Variables',
difficulty: 'easy',
content: `
<p>Interrupts allow your code to react to pin changes asynchronously.</p>
<p>More content here...</p>
`
<p>Did you try typeing <code>print(Hello World)</code>, without the quotation marks?</p>
<p>If you didn't, give it a try now.</p>
</br>
<p>Thats what we call a <strong>Syntax Error</strong>.</p>
<p>Python doesn't know what the term (Hello World) means, the "" we added before tell Python that this is a "string", a series of characters that it doesn't need to try and understand, just to repeat.</p>
</br>
String is just one of the many <strong>Data Types</strong> that Python has.</p>
To continue, I want you to print the following all at once, each on a new line:
<ol>
<li>String: <code>print("Hello World")</code></li>
<li>Integer: <code>print(42)</code></li>
<li>Float: <code>print(3.14)</code></li>
<li>Boolean: <code>print(True)</code></li>
</ol>
<p>Click the "Run" button to execute your code</p>
`,
doneCondition: (consoleText => {
// Persistent tracking object inside closure
const progress = {
stringDone: false,
intDone: false,
floatDone: false,
boolDone: false,
syntaxErrorDone: false,
};
//const syntaxErrorRegex = /SyntaxError/i;
const stringRegex = /(["'])(?:(?=(\\?))\2.)*?\1/; // optional improvement based on context
const intRegex = /(?<![\d.])[-+]?\d+(?![\d.])/;
const floatRegex = /[-+]?(?:\d+\.\d*|\.\d+)(?:[eE][-+]?\d+)?/;
const boolRegex = /\b(True|False|true|false)\b/;
return (text) => {
// if (!progress.syntaxErrorDone && syntaxErrorRegex.test(text)) {
// progress.syntaxErrorDone = true;
// }
if (!progress.stringDone && stringRegex.test(text)) {
progress.stringDone = true;
}
if (!progress.floatDone && floatRegex.test(text)) {
progress.floatDone = true;
}
if (!progress.intDone && intRegex.test(text)) {
progress.intDone = true;
}
if (!progress.boolDone && boolRegex.test(text)) {
progress.boolDone = true;
}
let missing = [];
//if (!progress.syntaxErrorDone) missing.push("syntax error");
//if (!progress.stringDone) missing.push("string");
if (!progress.floatDone) missing.push("float");
if (!progress.intDone) missing.push("int");
if (!progress.boolDone) missing.push("boolean");
let hint = "I still need you to print a ";
if (missing.length === 0) {
hint = "";
} else if (missing.length === 1) {
hint += missing[0];
} else {
// Join all but last with comma, then add 'and' + last
hint += missing.slice(0, -1).join(", ") + " and " + missing[missing.length - 1];
}
const done = progress.intDone && progress.floatDone && progress.boolDone;
return { done, hint };
};
})()
},
{
id: 'lesson3',
title: '3. Arithmetic Operations',
difficulty: 'easy',
content: `
<p>Let's get python to do our math homework for us.</p>
<p>Python can handle basic arithmetic operations like addition, subtraction, multiplication, and division.</p>
<p>Try the following operations in the code editor:</p>
<ol>
<li>Addition: <code>print(5 + 3)</code></li>
<li>Subtraction: <code>print(10 - 2)</code></li>
<li>Multiplication: <code>print(4 * 7)</code></li>
<li>Division: <code>print(20 / 5)</code></li>
</ol>
`,
doneCondition: (consoleText => {
// Persistent tracking object inside closure
const progress = {
addDone: false,
subDone: false,
mulDone: false,
divDone: false,
};
return (text) => {
// if (!progress.syntaxErrorDone && syntaxErrorRegex.test(text)) {
// progress.syntaxErrorDone = true;
// }
if (!progress.addDone && text.includes("+")) {
progress.addDone = true;
}
if (!progress.subDone && text.includes("-")) {
progress.subDone = true;
}
if (!progress.mulDone && text.includes("*")) {
progress.mulDone = true;
}
if (!progress.divDone && text.includes("/")) {
progress.divDone = true;
}
let missing = [];
//if (!progress.syntaxErrorDone) missing.push("syntax error");
//if (!progress.stringDone) missing.push("string");
if (!progress.addDone) missing.push("addition");
if (!progress.subDone) missing.push("subtraction");
if (!progress.mulDone) missing.push("multiplication");
if (!progress.divDone) missing.push("division");
let hint = "I still need you to do some ";
if (missing.length === 0) {
hint = "";
} else if (missing.length === 1) {
hint += missing[0];
} else {
// Join all but last with comma, then add 'and' + last
hint += missing.slice(0, -1).join(", ") + " and " + missing[missing.length - 1];
}
const done = progress.addDone && progress.subDone && progress.mulDone && progress.divDone;
return { done, hint };
};
})()
},
{
id: 'lesson4',
title: '4. Variables',
difficulty: 'easy',
content: `
<p>It's common for us to need to keep some data or objects over multiple lines, for that we use <strong>variables</strong>.</p>
<p>Think of a variable as a container that holds an object for us to use later.</p>
<p>To make one, we only need to give it a name, and assign it a value.</p>
<p><code>bob = 32</code></p>
<p>In this case, we created a variable called <code>bob</code> and assigned it the value <code>32</code>.</p>
<p>As far as Python is concerned, <code>bob</code> is just a name for the number <code>32</code>.</p>
<p>Lets test it</p>
<ol>
<li>Initialize bob with the value 32: <code>bob = 32</code></li>
<li>Print the value of bob: <code>print(bob)</code></
</ol>
`,
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: (code => {
const progress = {
varCreated: false,
varPrinted: false,
varName: null,
};
return (text) => {
// Extract variable name from current text each time
const assignMatch = text.match(/^\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*=/m);
if (assignMatch) {
progress.varName = assignMatch[1];
progress.varCreated = true;
} else {
// No variable assignment found this run
progress.varCreated = false;
progress.varName = null;
progress.varPrinted = false; // reset printed too because no var
}
// Check if variable is printed in this run (only if varName exists)
if (progress.varCreated && progress.varName) {
const printRegex = new RegExp(`print\\s*\\(\\s*${progress.varName}\\s*\\)`);
if (printRegex.test(text)) {
progress.varPrinted = true;
} else {
progress.varPrinted = false; // reset if print no longer found
}
}
// Build hint
let missing = [];
if (!progress.varCreated) missing.push("create a variable");
if (progress.varCreated && !progress.varPrinted) missing.push("print the variable");
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.varPrinted;
return { done, hint };
};
})()
},
];

68
game.js
View File

@ -4,6 +4,7 @@ import { lessons } from './data/lessons.js';
let currentLesson = 0;
let lessonComplete = false;
async function loadLessons() {
const response = await fetch('data/lessons.json');
@ -13,11 +14,11 @@ async function loadLessons() {
function showLesson(index) {
if (index < 0 || index >= lessons.length) return;
lessonComplete = false;
currentLesson = index;
const lesson = lessons[index];
document.getElementById('lesson-title').textContent = lesson.title;
document.getElementById('lesson-content').innerHTML = lesson.content;
loadLessonContent(lesson);
document.getElementById('prev-lesson').disabled = index === 0;
document.getElementById('next-lesson').disabled = index === lessons.length - 1;
@ -26,6 +27,11 @@ function showLesson(index) {
updateLessonStatus();
}
function loadLessonContent(lesson){
document.getElementById('lesson-title').textContent = lesson.title;
document.getElementById('lesson-content').innerHTML = lesson.content;
}
document.getElementById('prev-lesson').addEventListener('click', () => {
showLesson(currentLesson - 1);
});
@ -34,14 +40,22 @@ document.getElementById('next-lesson').addEventListener('click', () => {
});
function checkLessonDone(outputText) {
checkCurrentStep(outputText);
const lesson = lessons[currentLesson];
if (lesson.doneCondition && lesson.doneCondition(outputText)) {
const result = lesson.doneCondition(outputText);
if (result.done) {
markLessonDone(lesson.id);
}
if (result.hint) {
logToConsole("Hint: " + result.hint, false);
//console.log("Hint:", result.hint); // Or show it in your console UI
}
}
function markLessonDone(lessonId) {
if (lessonComplete) return; // Prevent marking multiple times
lessonComplete = true; // Set flag to prevent further marking
// Your logic here: e.g., store in localStorage or update UI
console.log(`Lesson ${lessonId} marked as done!`);
// For example:
@ -68,6 +82,31 @@ function isLessonDone(lessonId) {
return localStorage.getItem(`lessonDone_${lessonId}`) === 'true';
}
let currentStepIndex = 0;
function checkCurrentStep(text) {
const lesson = lessons[currentLesson];
const step = lesson.steps[currentStepIndex];
const result = step.doneCondition(text);
if (result) {
currentStepIndex++;
renderSteps(); // Re-render to show the new step
}
}
function renderSteps() {
const lesson = lessons[currentLesson];
const container = document.getElementById("lesson-content");
container.innerHTML = "";
loadLessonContent(lesson); // Load the lesson content first
for (let i = 0; i <= currentStepIndex && i < lesson.steps.length; i++) {
const stepEl = document.createElement("div");
stepEl.innerHTML = lesson.steps[i].content;
container.appendChild(stepEl);
}
}
function clearLessonProgress() {
Object.keys(localStorage).forEach(key => {
if (key.startsWith('lessonDone_')) {
@ -80,7 +119,7 @@ function clearLessonProgress() {
//clearLessonProgress(); // Clear progress on load for testing
showLesson(0);
showLesson(3);
const consoleElement = document.getElementById("console");
const gameCanvas = document.getElementById("gameCanvas");
@ -145,10 +184,10 @@ function createInitialRobots() {
const maxLines = 64;
const logLines = [];
function logToConsole(text) {
function logToConsole(text, checkLesson = true) {
if (text.includes("Pyodide not initialized yet.")) return;
checkLessonDone(text);
const newLines = text.split('\n').map(line => line.trim()).filter(line => line !== "");
@ -166,13 +205,23 @@ function logToConsole(text) {
// Create and append separate divs for each line
logLines.forEach(lineText => {
const lineDiv = document.createElement('div');
lineDiv.textContent = lineText;
lineDiv.innerHTML = lineText; // <-- render HTML tags here
lineDiv.style.maxWidth = "100%";
lineDiv.style.whiteSpace = "pre-wrap";
lineDiv.style.overflowWrap = "break-word";
lineDiv.style.wordWrap = "break-word";
consoleElement.appendChild(lineDiv);
});
// Scroll to bottom
consoleElement.scrollTop = consoleElement.scrollHeight;
if (checkLesson && !text.includes("Welcome")) { // Don't check lesson completion for welcome message
checkLessonDone(text);
}
}
function clearConsole() {
@ -291,6 +340,9 @@ document.getElementById("compile-button").addEventListener("click", () => {
type: "execute",
code: code
});
checkLessonDone(code);
});

View File

@ -205,16 +205,32 @@ main {
font-size: 14px;
}
.container,
.right-side,
#console,
#console > div {
min-width: 0;
}
#console {
flex: 1;
overflow-y: auto;
white-space: pre-wrap;
overflow-wrap: break-word;
word-wrap: break-word;
border: 1px solid #d1d5db;
background: black;
color: white;
font-family: monospace, monospace;
font-family: monospace;
padding: 8px;
overflow-y: auto;
border: 1px solid #d1d5db;
width: 100%;
box-sizing: border-box;
}
#console > div {
max-width: 100%;
box-sizing: border-box;
overflow-wrap: break-word;
word-wrap: break-word;
white-space: pre-wrap;
}
/* alternate line colors */