export const lessons = [ { id: 'lesson1', title: '1. Introduction to Python', tabtitle: 'Hello World', level: 'basics', content: `
Let's learn some Python..
We'll start with what's called a "Hello World" program to make sure everythings working.
In Python, this is done with the print function.
print("Hello World")Did you try typeing print(Hello World), without the quotation marks?
If you didn't, give it a try now.
Thats what we call a Syntax Error.
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.
String is just one of the many Data Types that Python has. To continue, I want you to print the following all at once, each on a new line:print("Hello World")print(42)print(3.14)print(True)Click the "Run" button to execute your code
`, 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: `Let's get python to do our math homework for us.
Python can handle basic arithmetic operations like addition, subtraction, multiplication, and division.
Try the following operations in the code editor:
print(5 + 3)print(10 - 2)print(4 * 7)print(20 / 5)It's common for us to need to keep some data or objects over multiple lines, for that we use variables.
Think of a variable as a container that holds an object for us to use later.
To make one, we only need to give it a name, and assign it a value.
bob = 32
In this case, we created a variable called bob and assigned it the value 32.
As far as Python is concerned, bob is just a name for the number 32.
Lets test it
bob = 32print(bob)
A variable will act like any other object of its data type.
For example, if we have a variable called bob with the value 32, we can do math with it:
print(bob + 10) will print 42.
We can also change the value of a variable:
bob = 32
print(bob)
bob = bob + 10
print(bob)
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.
Give it a try.
`, 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: `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 conditionals.
bob = 5
if bob > 10:
print("Bob is greater than 10")
In this example, the print function will only run if bob is 11 or higher.
One thing to note here is the formatting. Note that after the if bob > 10: there is a colon (:)
This tells Python that the next line is part of a new block of code.
In Python, we use indentation to define blocks of code, so the next line must be indented.
After the else: we also have a colon, and the next line is indented again.
Try it for yourself:
if statement to check if the variable is greater or less than a valueTo test if two values are equal, you can use the == operator.
eg.
num = 10
if num == 10:
print("num is equal to 10")
`,
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: `
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 elif and else.
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")
In this example, only one of the print statements can ever run, elif is short for "else if", and is a second if statement which is only tested it the if above it resolves to False.
The else statement is run if all previous conditions are False.
You can chain as many elif statements as you like, but only one else at the end.
Try it for yourself:
`, 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: `Most of the time we'll want our code to run multiple times, or even endlessly, in that case we need loops.
Python has two main types of loops: for loops and while loops.
The while loop will continue to run as long as a condition is True.
count = 0
while count < 5:
print(count)
count = count + 1
print("Done!")
You have to be careful with while loops, if the condition never becomes False, the loop will run forever!
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: 'robot1',
title: '1. Moving the Robot',
tabtitle: 'Importing Modules',
level: 'robot',
content: `
This robot simulation is a simplified version of a real robot.
It has a single library which you can use to access all its controls and sensors.
In a real robot you would have many different libraries for different parts, sensors, and even microcontroller functions.
We'll start by importing the robot library, and using it to move.
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
`,
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,
};
};
})()
},
{
id: 'lesson10',
title: '9. Libraries & Modules (time)',
tabtitle: 'Importing Modules',
level: 'basics',
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.