diff --git a/data/lessons.js b/data/lessons.js
index a9d77fb..78c82da 100644
--- a/data/lessons.js
+++ b/data/lessons.js
@@ -623,7 +623,7 @@ else:
},
{
id: 'lesson8',
- title: '8. Loops',
+ title: '8. While Loops',
difficulty: 'easy',
content: `
Most of the time we'll want our code to run multiple times, or even endlessly, in that case we need loops.
@@ -639,73 +639,88 @@ print("Done!")
You have to be careful with while loops, if the condition never becomes False, the loop will run forever!
`,
+ objectives: [
+ "Create a variable",
+ "Create a while loop ",
+ "that runs while the value of that variable is less than a number",
+ "Print the value of the variable inside the loop",
+ "Add one or more to the variable inside the loop",
+ "Your loop should end on its own"
+ ],
doneCondition: (() => {
- const progress = {
- varCreated: false,
- ifStatement: false,
- elifStatement: false,
- elseStatement: false,
- threeDistinctPrints: false,
- };
-
return ({ code, consoleText, codeRanGood }) => {
+ const progress = {
+ varCreated: false,
+ hasWhileLoop: false,
+ whileConditionChecksVar: false,
+ printsVarInLoop: false,
+ updatesVarInLoop: false,
+ codeTerminates: codeRanGood
+ };
+
if (!codeRanGood) {
return { done: false, hint: "" };
}
- // 1. Variable assignment
+ // Step 1: Variable assignment (e.g., x = 0)
const assignRegex = /\b(\w+)\s*=\s*[\d'"]/;
- progress.varCreated = assignRegex.test(code);
+ const match = code.match(assignRegex);
+ const varName = match ? match[1] : null;
+ progress.varCreated = !!varName;
- // 2. Line-by-line block tracking
const lines = code.split('\n');
- let currentBlock = null;
- const printContents = {
- if: null,
- elif: null,
- else: null,
- };
+ let inWhile = false;
- for (let i = 0; i < lines.length; i++) {
- const line = lines[i].trim();
+ for (let line of lines) {
+ const trimmed = line.trim();
- if (/^if\s+.*:/.test(line)) {
- progress.ifStatement = true;
- currentBlock = "if";
+ // Step 2: Detect while loop and condition
+ if (/^while\s+(.*):/.test(trimmed)) {
+ progress.hasWhileLoop = true;
+ const condition = trimmed.match(/^while\s+(.*):/)[1];
+ if (varName && condition.includes(varName) && condition.includes('<')) {
+ progress.whileConditionChecksVar = true;
+ }
+ inWhile = true;
continue;
}
- if (/^elif\s+.*:/.test(line)) {
- progress.elifStatement = true;
- currentBlock = "elif";
- continue;
- }
+ // Step 3–4: Check inside loop
+ if (inWhile) {
+ // Stop tracking if the line is dedented (exits the loop)
+ if (/^\S/.test(line)) {
+ inWhile = false;
+ continue;
+ }
- if (/^else\s*:/.test(line)) {
- progress.elseStatement = true;
- currentBlock = "else";
- continue;
- }
+ // Step 3: print(varName)
+ const printMatch = trimmed.match(/^print\(([^)]+)\)$/);
+ if (printMatch) {
+ const printedValue = printMatch[1].trim();
+ if (printedValue === varName) {
+ progress.printsVarInLoop = true;
+ }
+ }
- const printMatch = line.match(/print\s*\((.*?)\)/);
- if (printMatch && currentBlock && !printContents[currentBlock]) {
- const cleaned = printMatch[1].replace(/\s+/g, '').toLowerCase();
- printContents[currentBlock] = cleaned;
+
+ // Step 4: update variable (x += 1 or x = x + 1)
+ const updateRegex1 = new RegExp(`\\b${varName}\\s*\\+=\\s*\\d+`);
+ const updateRegex2 = new RegExp(`\\b${varName}\\s*=\\s*${varName}\\s*\\+\\s*\\d+`);
+ if (updateRegex1.test(trimmed) || updateRegex2.test(trimmed)) {
+ progress.updatesVarInLoop = true;
+ }
}
}
- const printedValues = Object.values(printContents).filter(Boolean);
- const uniqueValues = new Set(printedValues);
- progress.threeDistinctPrints = uniqueValues.size === 3;
-
- // Hint generation
+ // ✅ Build hint
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");
+ if (!progress.hasWhileLoop) missing.push("add a while loop");
+ if (!progress.whileConditionChecksVar) missing.push("make the while loop check if the variable is less than something");
+ if (!progress.printsVarInLoop) missing.push("print the variable inside the loop");
+ if (!progress.updatesVarInLoop) missing.push("update the variable in the loop");
+ if (!progress.codeTerminates) missing.push("make sure the loop ends on its own");
const hint = missing.length > 0
? "I still need you to " + (missing.length === 1
@@ -713,11 +728,207 @@ print("Done!")
: missing.slice(0, -1).join(", ") + " and " + missing[missing.length - 1])
: "";
- const done = progress.varCreated && progress.ifStatement && progress.elifStatement &&
- progress.elseStatement && progress.threeDistinctPrints;
+ const done = Object.values(progress).every(Boolean);
- return { done, hint };
+ return {
+ done,
+ progressArray: Object.values(progress),
+ hint
+ };
};
})()
+
+ },
+ {
+ id: 'lesson9',
+ title: '8. For Loops',
+ difficulty: 'easy',
+ content: `
+ A more common type of loop is the for loop.
+ A for loop includes the creation of a variable that will be used to count how many times it should run.
+ This makes them much safer in most cases, as it's much harder to accidentally create an infinite loop.
+ Here's an example:
+
+for i in range(5):
+ print(i)
+print("Done!")
+
+ The range(n) function actually creates a list of numbers, and each will be assigned to i in turn.
+ You can also add a second number, which will let you set a starting number as well as an ending one.
+
+
+# Create a for loop that runs from 100 to 150
+for i in range(100, 150):
+ print(i)
+print("Done!")
+
+ You can add a third number, which will change how much your loop should iterate by, the default being 1
+
+# Create a for loop that runs from 0 to 27, by 3s
+for i in range(0, 27, 3):
+ print(i)
+print("Done!")
+
+ `,
+ objectives: [
+ "Create a loop",
+ "The loop should use range() and have all three arguments (start, end, step)",
+ "Your loop should end on its own (i.e. not run forever)",
+ ],
+
+ doneCondition: (() => {
+ return ({ code, consoleText, codeRanGood }) => {
+ const progress = {
+ forLoopFound: false,
+ rangeHas3Args: false,
+ codeTerminates: codeRanGood,
+ };
+
+ if (!codeRanGood) {
+ return { done: false, hint: "" };
+ }
+
+ const lines = code.split('\n');
+ for (let line of lines) {
+ const trimmed = line.trim();
+
+ // Look for a 'for' loop using range()
+ const forMatch = trimmed.match(/^for\s+\w+\s+in\s+range\((.*?)\):/);
+ if (forMatch) {
+ progress.forLoopFound = true;
+
+ const rangeArgs = forMatch[1].split(',').map(s => s.trim());
+ if (rangeArgs.length === 3) {
+ progress.rangeHas3Args = true;
+ }
+ break; // Found the loop, no need to keep scanning
+ }
+ }
+
+ // Hints
+ const missing = [];
+ if (!progress.forLoopFound) missing.push("write a for loop using range()");
+ else if (!progress.rangeHas3Args) missing.push("use start, end, and step in the range()");
+ if (!progress.codeTerminates) missing.push("make sure the loop ends on its own");
+
+ 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 = Object.values(progress).every(Boolean);
+
+ return {
+ done,
+ progressArray: Object.values(progress),
+ hint
+ };
+ };
+ })()
+
+
+ },
+ {
+ id: 'lesson10',
+ title: '9. Libraries & Modules (time)',
+ difficulty: 'easy',
+ content: `
+ A lot of the time we need more code than can fit in a single file, or we want to reuse our own, or someone elses code.
+ For that we use libraries and modules.
+
+ As an example, we'll import the "time" module, which lets do things like measure time, which we can use for all sorts of useful things.
+ To import a module, we use the import statement:
+ eg. import time
+
+ This effectively runs all the code in that module, and gives us access to all the functions and variables it defines.
+ Try this:
+
+import time # Import the time module
+for i in range(1, 10+1):
+ print(i)
+ time.sleep(1) # Wait for 1 second (0.5 for half a second)
+print("Done!")
+
+
+Note: To count from actual 1-10 our range(start, end) starts at 1, and ends at 11, as the end number is exclusive.
+ `,
+ objectives: [
+ "Import the time module",
+ "Print something",
+ "Use time.sleep() to pause for an amount of time",
+ "Print something else after the pause"
+ ],
+
+ doneCondition: (() => {
+ return ({ code, consoleText, codeRanGood }) => {
+ const progress = {
+ importedTime: false,
+ printedBefore: false,
+ usedSleep: false,
+ printedAfter: false,
+ };
+
+ if (!codeRanGood) {
+ return { done: false, hint: "" };
+ }
+
+ // 1. Check for "import time"
+ const importRegex = /^\s*import\s+time\b/m;
+ progress.importedTime = importRegex.test(code);
+
+ // 2. Match all print(...) calls
+ const printRegex = /print\s*\(.*?\)/g;
+ const printMatches = [...code.matchAll(printRegex)];
+
+ // 3. Match time.sleep(...)
+ const sleepRegex = /time\.sleep\s*\(\s*[\d.]+\s*\)/;
+ const sleepMatch = sleepRegex.exec(code);
+ progress.usedSleep = !!sleepMatch;
+
+ // 4. Handle print position logic
+ if (printMatches.length > 0) {
+ // If there's no sleep, we just say "they printed something" — early lesson support
+ if (!sleepMatch) {
+ progress.printedBefore = true; // consider *any* print valid before sleep
+ } else {
+ const sleepIndex = sleepMatch.index;
+ for (const m of printMatches) {
+ if (m.index < sleepIndex) progress.printedBefore = true;
+ if (m.index > sleepIndex) progress.printedAfter = true;
+ }
+ }
+ }
+
+ // 5. Build hint
+ const missing = [];
+ if (!progress.importedTime) missing.push("import the time module");
+ if (!progress.printedBefore) missing.push("print something before sleeping");
+ if (!progress.usedSleep) missing.push("use time.sleep()");
+ if (!progress.printedAfter && progress.usedSleep)
+ missing.push("print something after sleeping");
+
+ let hint = "";
+ if (missing.length === 1) {
+ hint = `I still need you to ${missing[0]}`;
+ } else if (missing.length > 1) {
+ hint = `I still need you to ${missing.slice(0, -1).join(", ")} and ${missing.at(-1)}`;
+ }
+
+ return {
+ done:
+ progress.importedTime &&
+ progress.printedBefore &&
+ progress.usedSleep &&
+ progress.printedAfter,
+ progressArray: Object.values(progress),
+ hint,
+ };
+ };
+ })()
+
+
+
+
},
];
diff --git a/game.js b/game.js
index 40c967b..c10f2c4 100644
--- a/game.js
+++ b/game.js
@@ -184,7 +184,7 @@ function toggleObjective(index, completed = true) {
}
clearLessonProgress(); // Clear progress on load for testing
-showLesson(6);
+showLesson(9);
const consoleElement = document.getElementById("console");
const gameCanvas = document.getElementById("gameCanvas");