From af9cd8177ed4adcd8dc23413ada750291d586ce3 Mon Sep 17 00:00:00 2001
From: Jake
Date: Tue, 24 Jun 2025 17:28:28 +0800
Subject: [PATCH] added more done conditions
---
data/lessons.js | 237 ++++++++++++++++++++++++++++++++++++++++++++++--
game.js | 100 +++++++++++++++-----
style.css | 34 +++++--
3 files changed, 332 insertions(+), 39 deletions(-)
diff --git a/data/lessons.js b/data/lessons.js
index 819c24f..8873738 100644
--- a/data/lessons.js
+++ b/data/lessons.js
@@ -17,11 +17,236 @@ export const lessons = [
},
{
id: 'lesson2',
- title: 'Using Interrupts',
- difficulty: 'medium',
+ title: 'Data Types and Variables',
+ difficulty: 'easy',
content: `
- Interrupts allow your code to react to pin changes asynchronously.
- More content here...
- `
- }
+ Did you try typeing print(Hello World), without the quotation marks?
+ If you didn't, give it a try now.
+
+ Thats what we call a Syntax Error.
+ 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.
+
+ String is just one of the many Data Types that Python has.
+ To continue, I want you to print the following all at once, each on a new line:
+
+ - String:
print("Hello World")
+ - Integer:
print(42)
+ - Float:
print(3.14)
+ - Boolean:
print(True)
+
+ Click the "Run" button to execute your code
+ `,
+ 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 = /(? {
+
+
+ // 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: `
+ Let's get python to do our math homework for us.
+ Python can handle basic arithmetic operations like addition, subtraction, multiplication, and division.
+ Try the following operations in the code editor:
+
+ - Addition:
print(5 + 3)
+ - Subtraction:
print(10 - 2)
+ - Multiplication:
print(4 * 7)
+ - Division:
print(20 / 5)
+
+ `,
+ 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: `
+ It's common for us to need to keep some data or objects over multiple lines, for that we use variables.
+ Think of a variable as a container that holds an object for us to use later.
+ To make one, we only need to give it a name, and assign it a value.
+ bob = 32
+ In this case, we created a variable called bob and assigned it the value 32.
+ As far as Python is concerned, bob is just a name for the number 32.
+ Lets test it
+
+ - Initialize bob with the value 32:
bob = 32
+ - Print the value of bob:
print(bob)
+
+ `,
+ steps: [
+ {
+ content: `First, try printing an addition: print(2 + 3)
`,
+ doneCondition: (text) => text.includes("5"),
+ },
+ {
+ content: `Nice! Now try subtraction: print(5 - 2)
`,
+ doneCondition: (text) => text.includes("3"),
+ },
+ {
+ content: `Now try multiplication: print(4 * 2)
`,
+ 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 };
+ };
+})()
+
+
+ },
];
diff --git a/game.js b/game.js
index 6d02ef2..7368e27 100644
--- a/game.js
+++ b/game.js
@@ -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:
@@ -51,16 +65,16 @@ function markLessonDone(lessonId) {
}
function updateLessonStatus() {
- const statusEl = document.getElementById('lesson-status');
- const lesson = lessons[currentLesson];
- const done = isLessonDone(lesson.id); // your persistent check
+ const statusEl = document.getElementById('lesson-status');
+ const lesson = lessons[currentLesson];
+ const done = isLessonDone(lesson.id); // your persistent check
- if (done) {
- statusEl.textContent = '✅';
- } else {
- statusEl.textContent = '';
- }
- document.getElementById('next-lesson').disabled = !done || currentLesson === lessons.length - 1;
+ if (done) {
+ statusEl.textContent = '✅';
+ } else {
+ statusEl.textContent = '';
+ }
+ document.getElementById('next-lesson').disabled = !done || currentLesson === lessons.length - 1;
}
@@ -68,19 +82,44 @@ function isLessonDone(lessonId) {
return localStorage.getItem(`lessonDone_${lessonId}`) === 'true';
}
-function clearLessonProgress() {
- Object.keys(localStorage).forEach(key => {
- if (key.startsWith('lessonDone_')) {
- localStorage.removeItem(key);
+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
}
- });
- // Optionally update the UI after clearing
- updateLessonStatus();
+}
+
+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_')) {
+ localStorage.removeItem(key);
+ }
+ });
+ // Optionally update the UI after clearing
+ updateLessonStatus();
}
//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,15 +205,25 @@ 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() {
logLines.length = 0; // Empty the logLines array
consoleElement.innerHTML = ""; // Clear the console DOM element
@@ -291,6 +340,9 @@ document.getElementById("compile-button").addEventListener("click", () => {
type: "execute",
code: code
});
+
+
+ checkLessonDone(code);
});
diff --git a/style.css b/style.css
index 7ff06e7..1be08f4 100644
--- a/style.css
+++ b/style.css
@@ -205,16 +205,32 @@ main {
font-size: 14px;
}
-
+.container,
+.right-side,
+#console,
+#console > div {
+ min-width: 0;
+}
#console {
- flex: 1;
- background: black;
- color: white;
- font-family: monospace, monospace;
- padding: 8px;
- overflow-y: auto;
- border: 1px solid #d1d5db;
- width: 100%;
+ 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;
+ padding: 8px;
+ 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 */