added loops and libraries/modules
parent
05dd8cb6a3
commit
4c970c8faf
299
data/lessons.js
299
data/lessons.js
|
|
@ -623,7 +623,7 @@ else:
|
|||
},
|
||||
{
|
||||
id: 'lesson8',
|
||||
title: '8. Loops',
|
||||
title: '8. While Loops',
|
||||
difficulty: 'easy',
|
||||
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>
|
||||
|
|
@ -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>
|
||||
|
||||
`,
|
||||
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: (() => {
|
||||
return ({ code, consoleText, codeRanGood }) => {
|
||||
const progress = {
|
||||
varCreated: false,
|
||||
ifStatement: false,
|
||||
elifStatement: false,
|
||||
elseStatement: false,
|
||||
threeDistinctPrints: false,
|
||||
hasWhileLoop: false,
|
||||
whileConditionChecksVar: false,
|
||||
printsVarInLoop: false,
|
||||
updatesVarInLoop: false,
|
||||
codeTerminates: codeRanGood
|
||||
};
|
||||
|
||||
return ({ code, consoleText, 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";
|
||||
// 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;
|
||||
}
|
||||
|
||||
const printMatch = line.match(/print\s*\((.*?)\)/);
|
||||
if (printMatch && currentBlock && !printContents[currentBlock]) {
|
||||
const cleaned = printMatch[1].replace(/\s+/g, '').toLowerCase();
|
||||
printContents[currentBlock] = cleaned;
|
||||
// Step 3: print(varName)
|
||||
const printMatch = trimmed.match(/^print\(([^)]+)\)$/);
|
||||
if (printMatch) {
|
||||
const printedValue = printMatch[1].trim();
|
||||
if (printedValue === varName) {
|
||||
progress.printsVarInLoop = true;
|
||||
}
|
||||
}
|
||||
|
||||
const printedValues = Object.values(printContents).filter(Boolean);
|
||||
const uniqueValues = new Set(printedValues);
|
||||
progress.threeDistinctPrints = uniqueValues.size === 3;
|
||||
|
||||
// Hint generation
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 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: `
|
||||
<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,
|
||||
};
|
||||
};
|
||||
})()
|
||||
|
||||
|
||||
|
||||
|
||||
},
|
||||
];
|
||||
|
|
|
|||
2
game.js
2
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");
|
||||
|
|
|
|||
Loading…
Reference in New Issue