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: +
    +
  1. String: print("Hello World")
  2. +
  3. Integer: print(42)
  4. +
  5. Float: print(3.14)
  6. +
  7. Boolean: print(True)
  8. +
+

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:

+
    +
  1. Addition: print(5 + 3)
  2. +
  3. Subtraction: print(10 - 2)
  4. +
  5. Multiplication: print(4 * 7)
  6. +
  7. Division: print(20 / 5)
  8. +
+ `, + 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

+
    +
  1. Initialize bob with the value 32: bob = 32
  2. +
  3. 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 */