added loops and libraries/modules

master
Jake 2025-06-27 00:38:55 +08:00
parent 05dd8cb6a3
commit 4c970c8faf
2 changed files with 261 additions and 50 deletions

View File

@ -623,7 +623,7 @@ else:
}, },
{ {
id: 'lesson8', id: 'lesson8',
title: '8. Loops', title: '8. While Loops',
difficulty: 'easy', difficulty: 'easy',
content: ` content: `
<p>Most of the time we'll want our code to run multiple times, or even endlessly, in that case we need <strong>loops</strong>.</p> <p>Most of the time we'll want our code to run multiple times, or even endlessly, in that case we need <strong>loops</strong>.</p>
@ -639,73 +639,88 @@ print("Done!")
<p>You have to be careful with <code>while</code> loops, if the condition never becomes False, the loop will run forever!</p> <p>You have to be careful with <code>while</code> loops, if the condition never becomes False, the loop will run forever!</p>
`, `,
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: (() => { doneCondition: (() => {
const progress = {
varCreated: false,
ifStatement: false,
elifStatement: false,
elseStatement: false,
threeDistinctPrints: false,
};
return ({ code, consoleText, codeRanGood }) => { return ({ code, consoleText, codeRanGood }) => {
const progress = {
varCreated: false,
hasWhileLoop: false,
whileConditionChecksVar: false,
printsVarInLoop: false,
updatesVarInLoop: false,
codeTerminates: codeRanGood
};
if (!codeRanGood) { if (!codeRanGood) {
return { done: false, hint: "" }; return { done: false, hint: "" };
} }
// 1. Variable assignment // Step 1: Variable assignment (e.g., x = 0)
const assignRegex = /\b(\w+)\s*=\s*[\d'"]/; 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'); const lines = code.split('\n');
let currentBlock = null; let inWhile = false;
const printContents = {
if: null,
elif: null,
else: null,
};
for (let i = 0; i < lines.length; i++) { for (let line of lines) {
const line = lines[i].trim(); const trimmed = line.trim();
if (/^if\s+.*:/.test(line)) { // Step 2: Detect while loop and condition
progress.ifStatement = true; if (/^while\s+(.*):/.test(trimmed)) {
currentBlock = "if"; progress.hasWhileLoop = true;
const condition = trimmed.match(/^while\s+(.*):/)[1];
if (varName && condition.includes(varName) && condition.includes('<')) {
progress.whileConditionChecksVar = true;
}
inWhile = true;
continue; continue;
} }
if (/^elif\s+.*:/.test(line)) { // Step 34: Check inside loop
progress.elifStatement = true; if (inWhile) {
currentBlock = "elif"; // Stop tracking if the line is dedented (exits the loop)
continue; if (/^\S/.test(line)) {
} inWhile = false;
continue;
}
if (/^else\s*:/.test(line)) { // Step 3: print(varName)
progress.elseStatement = true; const printMatch = trimmed.match(/^print\(([^)]+)\)$/);
currentBlock = "else"; if (printMatch) {
continue; const printedValue = printMatch[1].trim();
} if (printedValue === varName) {
progress.printsVarInLoop = true;
}
}
const printMatch = line.match(/print\s*\((.*?)\)/);
if (printMatch && currentBlock && !printContents[currentBlock]) { // Step 4: update variable (x += 1 or x = x + 1)
const cleaned = printMatch[1].replace(/\s+/g, '').toLowerCase(); const updateRegex1 = new RegExp(`\\b${varName}\\s*\\+=\\s*\\d+`);
printContents[currentBlock] = cleaned; 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); // ✅ Build hint
const uniqueValues = new Set(printedValues);
progress.threeDistinctPrints = uniqueValues.size === 3;
// Hint generation
const missing = []; const missing = [];
if (!progress.varCreated) missing.push("create a variable"); if (!progress.varCreated) missing.push("create a variable");
if (!progress.ifStatement) missing.push("use an if statement"); if (!progress.hasWhileLoop) missing.push("add a while loop");
if (!progress.elifStatement) missing.push("use an elif statement"); if (!progress.whileConditionChecksVar) missing.push("make the while loop check if the variable is less than something");
if (!progress.elseStatement) missing.push("use an else statement"); if (!progress.printsVarInLoop) missing.push("print the variable inside the loop");
if (!progress.threeDistinctPrints) missing.push("print different things in the if, elif, and else blocks"); 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 const hint = missing.length > 0
? "I still need you to " + (missing.length === 1 ? "I still need you to " + (missing.length === 1
@ -713,11 +728,207 @@ print("Done!")
: missing.slice(0, -1).join(", ") + " and " + missing[missing.length - 1]) : missing.slice(0, -1).join(", ") + " and " + missing[missing.length - 1])
: ""; : "";
const done = progress.varCreated && progress.ifStatement && progress.elifStatement && const done = Object.values(progress).every(Boolean);
progress.elseStatement && progress.threeDistinctPrints;
return { done, hint }; return {
done,
progressArray: Object.values(progress),
hint
};
}; };
})() })()
},
{
id: 'lesson9',
title: '8. For Loops',
difficulty: 'easy',
content: `
<p>A more common type of loop is the <strong>for</strong> loop.</p>
<p>A <code>for</code> loop includes the creation of a variable that will be used to count how many times it should run.</p>
<p>This makes them much safer in most cases, as it's much harder to accidentally create an infinite loop.</p>
<p>Here's an example:</p>
<pre><code>
for i in range(5):
print(i)
print("Done!")
</code></pre>
<p>The <code>range(n)</code> function actually creates a list of numbers, and each will be assigned to <code>i</code> in turn.</p>
<p>You can also add a second number, which will let you set a starting number as well as an ending one.</p>
<p></p>
<pre><code>
# Create a for loop that runs from 100 to 150
for i in range(100, 150):
print(i)
print("Done!")
</code></pre>
<p>You can add a third number, which will change how much your loop should iterate by, the default being 1</p>
<pre><code>
# Create a for loop that runs from 0 to 27, by 3s
for i in range(0, 27, 3):
print(i)
print("Done!")
</code></pre>
`,
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: `
<p>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.</p>
<p>For that we use <strong>libraries</strong> and <strong>modules</strong>.</p>
</br>
<p>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.</p>
<p>To import a module, we use the <code>import</code> statement:</p>
<p>eg. <code>import time</code></p>
</br>
<p>This effectively runs all the code in that module, and gives us access to all the functions and variables it defines.</p>
<p>Try this:</p>
<pre><code>
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!")
</code></pre>
</br>
<p><strong>Note:</strong> To count from actual 1-10 our <code>range(start, end)</code> starts at 1, and ends at 11, as the <code>end</code> number is exclusive.</p>
`,
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,
};
};
})()
}, },
]; ];

View File

@ -184,7 +184,7 @@ function toggleObjective(index, completed = true) {
} }
clearLessonProgress(); // Clear progress on load for testing clearLessonProgress(); // Clear progress on load for testing
showLesson(6); showLesson(9);
const consoleElement = document.getElementById("console"); const consoleElement = document.getElementById("console");
const gameCanvas = document.getElementById("gameCanvas"); const gameCanvas = document.getElementById("gameCanvas");