onlinecodesimulator/data/lessons.js

1027 lines
34 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

export const lessons = [
{
id: 'lesson1',
title: '1. Introduction to Python',
tabtitle: 'Hello World',
level: 'basics',
content: `
<p>Let's learn some Python..</p>
<p>We'll start with what's called a "Hello World" program to make sure everythings working.</p>
<p>In Python, this is done with the <code>print</code> function.</p>
<ol>
<li>In the code editor below, type <code>print("Hello World")</code></li>
<li>Click the "Run" button to execute your code</li>
<li>You should see "Hello World" printed in the output area</li>
</ol>
`,
objectives: [
"Print \"Hello World\" to the console"
],
doneCondition: (() => {
return ({ lesson, code, consoleText, codeRanGood }) => {
const progress = {
stringDone: false
};
if (codeRanGood && !progress.stringDone && consoleText.includes("Hello World")) {
progress.stringDone = true;
}
const missing = [];
if (!progress.stringDone) missing.push("Hello World");
let hint = "I still need you to print ";
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];
}
return {
done: progress.stringDone,
progressArray: Object.values(progress),
hint
};
};
})()
},
{
id: 'lesson2',
title: '2. Data Types',
tabtitle: 'Data Types',
level: 'basics',
content: `
<p>Did you try typeing <code>print(Hello World)</code>, without the quotation marks?</p>
<p>If you didn't, give it a try now.</p>
</br>
<p>Thats what we call a <strong>Syntax Error</strong>.</p>
<p>Python doesn't know what the term (Hello World) means, the "" we added before tell Python that this is a "string", a series of characters that it doesn't need to try and understand, just to repeat.</p>
</br>
String is just one of the many <strong>Data Types</strong> that Python has.</p>
To continue, I want you to print the following all at once, each on a new line:
<ol>
<li>String: <code>print("Hello World")</code></li>
<li>Integer: <code>print(42)</code></li>
<li>Float: <code>print(3.14)</code></li>
<li>Boolean: <code>print(True)</code></li>
</ol>
<p>Click the "Run" button to execute your code</p>
`,
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: (() => {
// Matches print("something") or print('something')
const stringPrintRegex = /print\s*\(\s*(['"]).*?\1\s*\)/;
// Matches print of float literal like 3.14, .5, -2.0, 1e-3
const floatPrintRegex = /print\s*\(\s*[-+]?(?:\d+\.\d*|\.\d+|\d+[eE][-+]?\d+)\s*\)/;
// Matches print of int literal like 3, -42 (excluding floats)
const intPrintRegex = /print\s*\(\s*[-+]?\d+\s*\)/;
// Matches print(True) or print(False), case-insensitive
const boolPrintRegex = /print\s*\(\s*(True|False)\s*\)/i;
return ({ code, codeRanGood }) => {
if (!codeRanGood) {
return {
done: false,
hint: "Your code had an error — try fixing it and run again."
};
}
const progress = {
stringDone: stringPrintRegex.test(code),
intDone: intPrintRegex.test(code),
floatDone: floatPrintRegex.test(code),
boolDone: boolPrintRegex.test(code),
};
// Fix false positives where float also matches int
if (progress.floatDone && progress.intDone) {
// Check if the float number has a dot or exponent; if not, it was a false int match
const matches = code.match(floatPrintRegex);
if (matches) {
const numbers = matches.map(m => m.match(/[-+]?(?:\d+\.\d*|\.\d+|\d+[eE][-+]?\d+)/)?.[0]);
for (const num of numbers) {
if (num && !num.includes('.') && !num.toLowerCase().includes('e')) {
// It's not a float, remove the float flag
progress.floatDone = false;
}
}
}
}
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");
let hint = "";
if (missing.length > 0) {
hint = "I still need you to use print() with a ";
hint += missing.length === 1
? missing[0]
: missing.slice(0, -1).join(", ") + " and " + missing[missing.length - 1];
}
const done = progress.stringDone && progress.intDone && progress.floatDone && progress.boolDone;
return {
done,
progressArray: Object.values(progress),
hint
};
};
})()
},
{
id: 'lesson3',
title: '3. Arithmetic Operations',
tabtitle: 'Arithmetic',
level: 'basics',
content: `
<p>Let's get python to do our math homework for us.</p>
<p>Python can handle basic arithmetic operations like addition, subtraction, multiplication, and division.</p>
<p>Try the following operations in the code editor:</p>
<ol>
<li>Addition: <code>print(5 + 3)</code></li>
<li>Subtraction: <code>print(10 - 2)</code></li>
<li>Multiplication: <code>print(4 * 7)</code></li>
<li>Division: <code>print(20 / 5)</code></li>
</ol>
`,
objectives: [
"Do some addition",
"Do some subtraction",
"Do some multiplication",
"Do some division",
],
doneCondition: (() => {
// Persistent tracking object inside closure
return ({ code, consoleText, codeRanGood }) => {
const progress = {
addDone: false,
subDone: false,
mulDone: false,
divDone: false,
};
if (!codeRanGood) {
return {
done: false,
hint: ""
};
}
// if (!progress.syntaxErrorDone && syntaxErrorRegex.test(text)) {
// progress.syntaxErrorDone = true;
// }
if (!progress.addDone && code.includes("+")) {
progress.addDone = true;
}
if (!progress.subDone && code.includes("-")) {
progress.subDone = true;
}
if (!progress.mulDone && code.includes("*")) {
progress.mulDone = true;
}
if (!progress.divDone && code.includes("/")) {
progress.divDone = true;
}
let missing = [];
//if (!progress.syntaxErrorDone) missing.push("syntax error");
//if (!progress.stringDone) missing.push("string");
if (!progress.addDone) missing.push("addition");
if (!progress.subDone) missing.push("subtraction");
if (!progress.mulDone) missing.push("multiplication");
if (!progress.divDone) missing.push("division");
let hint = "I still need you to do some ";
if (missing.length === 0) {
hint = "";
} else if (missing.length === 1) {
hint += missing[0];
} else {
// Join all but last with comma, then add 'and' + last
hint += missing.slice(0, -1).join(", ") + " and " + missing[missing.length - 1];
}
const done = progress.addDone && progress.subDone && progress.mulDone && progress.divDone;
return {
done,
progressArray: Object.values(progress),
hint
};
};
})()
},
{
id: 'lesson4',
title: '4. Variables',
tabtitle: 'Variables',
level: 'basics',
content: `
<p>It's common for us to need to keep some data or objects over multiple lines, for that we use <strong>variables</strong>.</p>
<p>Think of a variable as a container that holds an object for us to use later.</p>
<p>To make one, we only need to give it a name, and assign it a value.</p>
<p><code>bob = 32</code></p>
<p>In this case, we created a variable called <code>bob</code> and assigned it the value <code>32</code>.</p>
<p>As far as Python is concerned, <code>bob</code> is just a name for the number <code>32</code>.</p>
<p>Lets test it</p>
<ol>
<li>Initialize bob with the value 32: <code>bob = 32</code></li>
<li>Print the value of bob: <code>print(bob)</code></
</ol>
`,
objectives: [
"Initialize a variable with a value",
"Print the variable using print()"
],
doneCondition: (() => {
return ({ code, consoleText, codeRanGood }) => {
const progress = {
varCreated: false,
varPrinted: false,
varName: null,
};
if (!codeRanGood) {
return {
done: false,
hint: ""
};
}
const assignMatch = code.match(/^\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*=/m);
if (assignMatch) {
progress.varName = assignMatch[1];
progress.varCreated = true;
} else {
progress.varCreated = false;
progress.varName = null;
progress.varPrinted = false;
}
if (progress.varCreated && progress.varName) {
const printRegex = new RegExp(`print\\s*\\(\\s*${progress.varName}\\s*\\)`);
if (printRegex.test(code)) {
progress.varPrinted = true;
} else {
progress.varPrinted = false;
}
}
console.log('varCreated:', progress.varCreated, 'varPrinted:', progress.varPrinted);
let missing = [];
if (!progress.varCreated) missing.push("create a variable");
if (!progress.varPrinted) missing.push("print the variable");
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 done = progress.varCreated && progress.varPrinted;
return {
done,
progressArray: Object.values(progress),
hint
};
};
})()
},
{
id: 'lesson5',
title: '5. More on Variables',
tabtitle: 'Changing Variables',
level: 'basics',
content: `
<p>A variable will act like any other object of its data type.</p>
<p>For example, if we have a variable called <code>bob</code> with the value <code>32</code>, we can do math with it:</p>
<p><code>print(bob + 10)</code> will print <code>42</code>.</p>
</br>
<p>We can also change the value of a variable:</p>
<pre><code>
bob = 32
print(bob)
bob = bob + 10
print(bob)
</code></pre>
<p>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.</p>
<p>Give it a try.</p>
`,
objectives: [
"Initialize a variable with a value",
"Print the variable using print()",
"Alter the value of the variable by adding, subtracting, multiplying, or dividing by itself",
"Print the variable again using print()",
],
doneCondition: (() => {
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 = /^(\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 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',
title: '6. Conditionals',
tabtitle: 'if',
level: 'basics',
content: `
<p>Sometimes we want don't want part of our code to run, or we want it to run differently in different situations. That's when we use <strong>conditionals</strong>.</p>
<pre><code>
bob = 5
if bob > 10:
print("Bob is greater than 10")
</code></pre>
<p>In this example, the print function will only run if <code>bob</code> is 11 or higher.</p>
</br>
<p>One thing to note here is the formatting. Note that after the <code>if bob > 10:</code> there is a colon (:)<p>
<p>This tells Python that the next line is part of a new block of code.</p>
<p>In Python, we use indentation to define blocks of code, so the next line must be indented.</p>
<p>After the <code>else:</code> we also have a colon, and the next line is indented again.</p>
<p>Try it for yourself:</p>
<ol>
<li>Initialize a variable with a value</li>
<li>Use an <code>if</code> statement to check if the variable is greater or less than a value</li>
<li>Print a message based on the condition</li>
</ol>
<p>To test if two values are equal, you can use the <code>==</code> operator.</p>
<p>eg. <pre><code>
num = 10
if num == 10:
print("num is equal to 10")
</code></pre>
`,
objectives: [
"Initialize a variable with a value",
"Create an if statement to check a condition", ,
"Print a message based on the condition",
],
doneCondition: (() => {
return ({ code, consoleText, codeRanGood }) => {
const progress = {
varCreated: false,
ifStatement: false,
printedSomething: false,
};
if (!codeRanGood) {
return {
done: false,
hint: ""
};
}
// Check for variable assignment
const assignRegex = /\b(\w+)\s*=\s*[\d'"]/;
progress.varCreated = assignRegex.test(code);
// Check for if statement
progress.ifStatement = /(^|\n)\s*if\s+.*:/.test(code);
// Check for print() anywhere
progress.printedSomething = /print\s*\(.*\)/.test(code);
// Build hint
const missing = [];
if (!progress.varCreated) missing.push("create a variable");
if (!progress.ifStatement) missing.push("use an if statement");
if (!progress.printedSomething) missing.push("print something");
let hint = "";
if (missing.length > 0) {
hint = "I still need you to " + (missing.length === 1
? missing[0]
: missing.slice(0, -1).join(", ") + " and " + missing[missing.length - 1]);
}
const done = progress.varCreated && progress.ifStatement && progress.printedSomething;
return {
done,
progressArray: Object.values(progress),
hint
};
};
})()
},
{
id: 'lesson7',
title: '7. More Conditionals',
tabtitle: 'if/elif/else',
level: 'basics',
content: `
<p>Sometimes we'll want to run one thing OR another, rather than just running something or not. To do this we chain our if statements together with <code>elif</code> and <code>else</code>.</p>
<pre><code>
bob = 10
if bob == 10:
print("bob is equal to 10")
elif bob < 10:
print("bob is less than 10")
else:
print("bob is greater than 10")
</code></pre>
<p>In this example, only one of the print statements can ever run, <code>elif</code> is short for "else if", and is a second if statement which is only tested it the <code>if</code> above it resolves to False.</p>
<p>The <code>else</code> statement is run if all previous conditions are False.</p>
<p>You can chain as many <code>elif</code> statements as you like, but only one <code>else</code> at the end.</p>
<p>Try it for yourself:</p>
`,
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: (() => {
return ({ code, consoleText, codeRanGood }) => {
const progress = {
varCreated: false,
ifStatement: false,
elifStatement: false,
elseStatement: false,
threeDistinctPrints: false,
};
if (!codeRanGood) {
return { done: false, hint: "" };
}
// 1. Variable assignment
const assignRegex = /\b(\w+)\s*=\s*[\d'"]/;
progress.varCreated = assignRegex.test(code);
// 2. Line-by-line block tracking
const lines = code.split('\n');
let currentBlock = null;
const printContents = {
if: null,
elif: null,
else: null,
};
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
if (/^if\s+.*:/.test(line)) {
progress.ifStatement = true;
currentBlock = "if";
continue;
}
if (/^elif\s+.*:/.test(line)) {
progress.elifStatement = true;
currentBlock = "elif";
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;
}
}
const printedValues = Object.values(printContents).filter(Boolean);
const uniqueValues = new Set(printedValues);
progress.threeDistinctPrints = uniqueValues.size === 3;
// Hint generation
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");
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 = progress.varCreated && progress.ifStatement && progress.elifStatement &&
progress.elseStatement && progress.threeDistinctPrints;
return {
done,
progressArray: Object.values(progress),
hint
};
};
})()
},
{
id: 'lesson8',
title: '8. While Loops',
tabtitle: 'While Loops',
level: 'basics',
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>Python has two main types of loops: <code>for</code> loops and <code>while</code> loops.</p>
<p>The <code>while</code> loop will continue to run as long as a condition is True.</p>
<pre><code>
count = 0
while count < 5:
print(count)
count = count + 1
print("Done!")
</code></pre>
<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,
hasWhileLoop: false,
whileConditionChecksVar: false,
printsVarInLoop: false,
updatesVarInLoop: false,
codeTerminates: codeRanGood
};
if (!codeRanGood) {
return { done: false, hint: "" };
}
// Step 1: Variable assignment (e.g., x = 0)
const assignRegex = /\b(\w+)\s*=\s*[\d'"]/;
const match = code.match(assignRegex);
const varName = match ? match[1] : null;
progress.varCreated = !!varName;
const lines = code.split('\n');
let inWhile = false;
for (let line of lines) {
const trimmed = line.trim();
// 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;
}
// Step 34: Check inside loop
if (inWhile) {
// Stop tracking if the line is dedented (exits the loop)
if (/^\S/.test(line)) {
inWhile = false;
continue;
}
// Step 3: print(varName)
const printMatch = trimmed.match(/^print\(([^)]+)\)$/);
if (printMatch) {
const printedValue = printMatch[1].trim();
if (printedValue === varName) {
progress.printsVarInLoop = true;
}
}
// 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.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
? 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: 'lesson9',
title: '8. For Loops',
tabtitle: 'For Loops',
level: 'basics',
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: 'robot1',
title: '1. Moving the Robot',
tabtitle: 'Importing Modules',
level: 'robot',
content: `
<p>This robot simulation is a simplified version of a real robot.</p>
<p>It has a single library which you can use to access all its controls and sensors.</p>
<p>In a real robot you would have many different libraries for different parts, sensors, and even microcontroller functions.</p>
</br>
<p>We'll start by importing the robot library, and using it to move.</p>
<pre><code>
import robot # Import the robot library
import time # Import the time module
robot.move(1) # Move forward at max speed
time.sleep(2) # Wait for 2 seconds
robot.move(-1) # Move backward at max speed
time.sleep(2) # Wait for 2 seconds
robot.move(0) # Stop the robot
</code></pre>
`,
objectives: [
"Reach the first checkpoint",
"Reach the second checkpoint",
"Code should complete without errors"
],
doneCondition: (() => {
return ({ code, consoleText, codeRanGood, gameWorld }) => {
const progress = {
firstCheckpoint: gameWorld.waypointsReached[0],
secondCheckpoint: gameWorld.waypointsReached[1],
codeRanGood: codeRanGood,
};
if (!codeRanGood) {
return { done: false, hint: "" };
}
// 5. Build hint
const missing = [];
if (!progress.firstCheckpoint) missing.push("reach the first checkpoint");
if (!progress.secondCheckpoint) missing.push("reach the second checkpoint");
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.firstCheckpoint &&
progress.secondCheckpoint &&
progress.codeRanGood,
progressArray: Object.values(progress),
hint,
};
};
})()
},
{
id: 'lesson10',
title: '9. Libraries & Modules (time)',
tabtitle: 'Importing Modules',
level: 'basics',
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,
};
};
})()
},
];