diff --git a/data/lessons.js b/data/lessons.js index 9838526..a9d77fb 100644 --- a/data/lessons.js +++ b/data/lessons.js @@ -13,12 +13,16 @@ export const lessons = [
  • You should see "Hello World" printed in the output area
  • `, + objectives: [ + "Print \"Hello World\" to the console" + ], doneCondition: (() => { - const progress = { - stringDone: false - }; - return ({ code, consoleText, codeRanGood }) => { + + return ({ lesson, code, consoleText, codeRanGood }) => { + const progress = { + stringDone: false + }; if (codeRanGood && !progress.stringDone && consoleText.includes("Hello World")) { progress.stringDone = true; } @@ -37,6 +41,7 @@ export const lessons = [ return { done: progress.stringDone, + progressArray: Object.values(progress), hint }; }; @@ -63,21 +68,28 @@ export const lessons = [

    Click the "Run" button to execute your code

    `, + objectives: [ + "Print a string to the console", + "Print an int to the console", + "Print a float to the console", + "Print a boolean (True/False) to the console" + ], doneCondition: (() => { - const progress = { - stringDone: false, - intDone: false, - floatDone: false, - boolDone: false, - // syntaxErrorDone: false, // optional - }; - const stringRegex = /(["'])(?:(?=(\\?))\2.)*?\1/; + + const stringRegex = /print\s*\(\s*(['"]).*?\1\s*\)/; const intRegex = /(? { + const progress = { + stringDone: false, + intDone: false, + floatDone: false, + boolDone: false, + // syntaxErrorDone: false, // optional + }; if (!codeRanGood) { return { done: false, @@ -85,7 +97,7 @@ export const lessons = [ }; } - if (!progress.stringDone && stringRegex.test(consoleText)) { + if (!progress.stringDone && stringRegex.test(code)) { progress.stringDone = true; } if (!progress.floatDone && floatRegex.test(consoleText)) { @@ -99,6 +111,7 @@ export const lessons = [ } const missing = []; + if (!progress.stringDone) missing.push("string"); if (!progress.floatDone) missing.push("float"); if (!progress.intDone) missing.push("int"); if (!progress.boolDone) missing.push("boolean"); @@ -112,8 +125,12 @@ export const lessons = [ hint += missing.slice(0, -1).join(", ") + " and " + missing[missing.length - 1]; } - const done = progress.intDone && progress.floatDone && progress.boolDone; - return { done, hint }; + const done = progress.stringDone && progress.intDone && progress.floatDone && progress.boolDone; + return { + done, + progressArray: Object.values(progress), + hint + }; }; })() @@ -133,19 +150,25 @@ export const lessons = [
  • Division: print(20 / 5)
  • `, + objectives: [ + "Do some addition", + "Do some subtraction", + "Do some multiplication", + "Do some division", + ], doneCondition: (() => { // Persistent tracking object inside closure - const progress = { - addDone: false, - subDone: false, - mulDone: false, - divDone: false, - - }; - return ({ code, consoleText, codeRanGood }) => { + const progress = { + addDone: false, + subDone: false, + mulDone: false, + divDone: false, + + }; + if (!codeRanGood) { return { done: false, @@ -190,7 +213,11 @@ export const lessons = [ const done = progress.addDone && progress.subDone && progress.mulDone && progress.divDone; - return { done, hint }; + return { + done, + progressArray: Object.values(progress), + hint + }; }; })() }, @@ -211,28 +238,19 @@ export const lessons = [
  • 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"), - } + objectives: [ + "Initialize a variable with a value", + "Print the variable using print()" ], doneCondition: (() => { - const progress = { - varCreated: false, - varPrinted: false, - varName: null, - }; + return ({ code, consoleText, codeRanGood }) => { + const progress = { + varCreated: false, + varPrinted: false, + varName: null, + }; if (!codeRanGood) { return { done: false, @@ -274,7 +292,11 @@ export const lessons = [ } const done = progress.varCreated && progress.varPrinted; - return { done, hint }; + return { + done, + progressArray: Object.values(progress), + hint + }; }; })() }, @@ -297,72 +319,111 @@ print(bob)

    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.

    Give it a try.

    `, - 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"), - } + objectives: [ + "Initialize a variable with a value", + "Print the variable using print()", + "Alter the value of the variable", + "Print the variable again using print()", ], doneCondition: (() => { - const progress = { - varCreated: false, - varPrinted: false, - varArithmeticDone: false, - varPrintedTwice: false, - }; + return ({ code, consoleText, codeRanGood }) => { + const progress = { + varCreated: false, + varPrinted: false, + varArithmeticDone: false, + varPrintedTwice: false, + }; if (!codeRanGood) { return { done: false, + progressArray: Object.values(progress), 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 assignRegex = /^(\w+)\s*=\s*.+$/m; + const assignMatch = assignRegex.exec(code); + if (!assignMatch) { + return { + done: false, + progressArray: Object.values(progress), + hint: "I still need you to create a variable" + }; } - const done = progress.varCreated && progress.dynamicPrint1 && progress.varArithmeticDone && progress.varPrintedTwice; - return { done, hint }; + const varName = assignMatch[1]; + + const printRegex = new RegExp(`print\\s*\\(\\s*${varName}\\s*\\)`, "g"); + const assignAllRegex = new RegExp(`^${varName}\\s*=.+$`, "gm"); + const arithmeticRegex = new RegExp( + `^${varName}\\s*=\\s*${varName}\\s*[+\\-*/]\\s*.+$|^${varName}\\s*[+\\-*/]=\\s*.+$`, + "gm" + ); + + progress.varCreated = true; + + const printMatches = [...code.matchAll(printRegex)]; + progress.varPrinted = printMatches.length > 0; + if (!progress.varPrinted) { + return { + done: false, + progressArray: Object.values(progress), + hint: "I still need you to print the variable" + }; + } + + progress.varArithmeticDone = arithmeticRegex.test(code); + if (!progress.varArithmeticDone) { + return { + done: false, + progressArray: Object.values(progress), + + hint: "I still need you to alter the variable (e.g. with arithmetic)" + }; + } + + const assignMatches = [...code.matchAll(assignAllRegex)]; + if (assignMatches.length < 2) { + return { + done: false, + progressArray: Object.values(progress), + hint: "I still need you to alter the variable before printing it again" + }; + } + + // Debug logs + console.log("Variable:", varName); + console.log("Assignments found:", assignMatches.length); + assignMatches.forEach((m, i) => + console.log(`Assign #${i}: index=${m.index}, text=${m[0].trim()}`) + ); + console.log("Prints found:", printMatches.length); + printMatches.forEach((m, i) => + console.log(`Print #${i}: index=${m.index}, text=${code.slice(m.index, m.index + 20)}`) + ); + console.log("Arithmetic detected:", progress.varArithmeticDone); + + const secondAssignIndex = assignMatches[1].index; + progress.varPrintedTwice = printMatches.some((m) => m.index > secondAssignIndex); + + if (!progress.varPrintedTwice) { + return { + done: false, + progressArray: Object.values(progress), + hint: "I still need you to print the variable a second time after altering it", + }; + } + + return { + done: true, + progressArray: Object.values(progress), + hint: "", + }; }; })() + }, { id: 'lesson6', @@ -394,15 +455,20 @@ if num == 10: print("num is equal to 10") `, + objectives: [ + "Initialize a variable with a value", + "Create an if statement to check a condition", , + "Print a message based on the condition", + ], doneCondition: (() => { - const progress = { - varCreated: false, - ifStatement: false, - printedSomething: false, - }; return ({ code, consoleText, codeRanGood }) => { + const progress = { + varCreated: false, + ifStatement: false, + printedSomething: false, + }; if (!codeRanGood) { return { done: false, @@ -434,7 +500,11 @@ if num == 10: } const done = progress.varCreated && progress.ifStatement && progress.printedSomething; - return { done, hint }; + return { + done, + progressArray: Object.values(progress), + hint + }; }; })() @@ -459,17 +529,24 @@ else:

    You can chain as many elif statements as you like, but only one else at the end.

    Try it for yourself:

    `, - + objectives: [ + "Initialize a variable with a value", + "Create an if statement to check a condition", , + "Create an elif statement to check another condition", + "Create an else statement to catch all other conditions", + "Print a message based on each of the conditions", + ], doneCondition: (() => { - const progress = { - varCreated: false, - ifStatement: false, - elifStatement: false, - elseStatement: false, - threeDistinctPrints: false, - }; return ({ code, consoleText, codeRanGood }) => { + + const progress = { + varCreated: false, + ifStatement: false, + elifStatement: false, + elseStatement: false, + threeDistinctPrints: false, + }; if (!codeRanGood) { return { done: false, hint: "" }; } @@ -536,7 +613,11 @@ else: const done = progress.varCreated && progress.ifStatement && progress.elifStatement && progress.elseStatement && progress.threeDistinctPrints; - return { done, hint }; + return { + done, + progressArray: Object.values(progress), + hint + }; }; })() }, diff --git a/game.js b/game.js index 2046943..40c967b 100644 --- a/game.js +++ b/game.js @@ -27,6 +27,7 @@ function showLesson(index) { //console.log(isLessonDone(lesson.id)); updateLessonStatus(); + PopulateObjectives(lesson.objectives); // Populate objectives for the lesson } function loadLessonContent(lesson) { @@ -60,6 +61,17 @@ function checkLessonDone() { if (result.done) { markLessonDone(lesson.id); } + if (result.progressArray){ + console.log("Progress: ", result.progressArray); + for (let i = 0; i < result.progressArray.length; i++) { + const objective = result.progressArray[i]; + if (objective) { + toggleObjective(i, true); // Mark as completed + } else { + toggleObjective(i, false); // Mark as not completed + } + } + } if (result.hint) { logToConsole("Hint: " + result.hint, false); //console.log("Hint:", result.hint); // Or show it in your console UI @@ -132,8 +144,47 @@ function clearLessonProgress() { } +let objectiveElements = []; // store checkbox + item div + +const listContainer = document.getElementById("objectives-list"); + +function PopulateObjectives(objectives) { + + listContainer.innerHTML = ""; // Clear the DOM + objectiveElements.length = 0; // Clear the stored references + objectives.forEach((text, index) => { + const item = document.createElement("div"); + item.className = "objective"; + + const checkbox = document.createElement("input"); + checkbox.type = "checkbox"; + checkbox.id = "obj-" + index; + checkbox.disabled = true; // make it non-interactive + + const label = document.createElement("label"); + label.htmlFor = checkbox.id; + label.textContent = text; + + item.appendChild(checkbox); + item.appendChild(label); + listContainer.appendChild(item); + + objectiveElements.push({ checkbox, item }); // Store reference + }); +} + + + +function toggleObjective(index, completed = true) { + const obj = objectiveElements[index]; + if (obj) { + obj.checkbox.checked = completed; + obj.item.classList.toggle("completed", completed); + } +} + clearLessonProgress(); // Clear progress on load for testing -showLesson(7); +showLesson(6); const consoleElement = document.getElementById("console"); const gameCanvas = document.getElementById("gameCanvas"); @@ -166,7 +217,7 @@ function startPyodideWorker() { switch (event.data.type) { case "console": logToConsole(event.data.data); - + break; case "error": logToConsole(`${event.data.message}`); diff --git a/index.html b/index.html index 7dc403a..4eca3c0 100644 --- a/index.html +++ b/index.html @@ -27,22 +27,34 @@
    -
    -

    Lesson Title

    -

    Lesson Content

    +
    -
    - -
    - + +
    +

    Lesson Title

    +

    Lesson Content

    + +
    + +
    + +
    +
    -
    + + + +
    -
    diff --git a/style.css b/style.css index 05248e5..b2c03b1 100644 --- a/style.css +++ b/style.css @@ -16,6 +16,28 @@ html { } + + .objective { + margin: 10px 0; + display: flex; + align-items: center; + } + + .objective label { + margin-left: 8px; + transition: color 0.3s; + } + + .completed label { + color: green; + font-weight: bold; + text-decoration: line-through; + } + + input[type="checkbox"] { + pointer-events: none; /* make it non-clickable */ + } + /* ===== Header (top bar) ===== */ header { background-color: #f3f4f6;