added loops and libraries/modules
parent
05dd8cb6a3
commit
4c970c8faf
299
data/lessons.js
299
data/lessons.js
|
|
@ -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: (() => {
|
||||||
|
return ({ code, consoleText, codeRanGood }) => {
|
||||||
const progress = {
|
const progress = {
|
||||||
varCreated: false,
|
varCreated: false,
|
||||||
ifStatement: false,
|
hasWhileLoop: false,
|
||||||
elifStatement: false,
|
whileConditionChecksVar: false,
|
||||||
elseStatement: false,
|
printsVarInLoop: false,
|
||||||
threeDistinctPrints: false,
|
updatesVarInLoop: false,
|
||||||
|
codeTerminates: codeRanGood
|
||||||
};
|
};
|
||||||
|
|
||||||
return ({ code, consoleText, 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 3–4: Check inside loop
|
||||||
progress.elifStatement = true;
|
if (inWhile) {
|
||||||
currentBlock = "elif";
|
// Stop tracking if the line is dedented (exits the loop)
|
||||||
|
if (/^\S/.test(line)) {
|
||||||
|
inWhile = false;
|
||||||
continue;
|
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]) {
|
|
||||||
const cleaned = printMatch[1].replace(/\s+/g, '').toLowerCase();
|
|
||||||
printContents[currentBlock] = cleaned;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = [];
|
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,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
2
game.js
2
game.js
|
|
@ -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");
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue