export const lessons = [ { id: 'lesson1', title: '1. Introduction to Python', difficulty: 'easy', 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.

  1. In the code editor below, type print("Hello World")
  2. Click the "Run" button to execute your code
  3. You should see "Hello World" printed in the output area
`, 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: 'Data Types and Variables', difficulty: 'easy', content: `

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:
  1. String: print("Hello World")
  2. Integer: print(42)
  3. Float: print(3.14)
  4. Boolean: 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: (() => { const stringRegex = /print\s*\(\s*(['"]).*?\1\s*\)/; const intRegex = /(? { const progress = { stringDone: false, intDone: false, floatDone: false, boolDone: false, // syntaxErrorDone: false, // optional }; if (!codeRanGood) { return { done: false, hint: "Your code had an error — try fixing it and run again." }; } if (!progress.stringDone && stringRegex.test(code)) { progress.stringDone = true; } if (!progress.floatDone && floatRegex.test(consoleText)) { progress.floatDone = true; } if (!progress.intDone && intRegex.test(consoleText)) { progress.intDone = true; } if (!progress.boolDone && boolRegex.test(consoleText)) { progress.boolDone = true; } 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 = "I still need you to print a "; 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.stringDone && progress.intDone && progress.floatDone && progress.boolDone; return { done, progressArray: Object.values(progress), hint }; }; })() }, { id: 'lesson3', title: '3. Arithmetic Operations', difficulty: 'easy', 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:

  1. Addition: print(5 + 3)
  2. Subtraction: print(10 - 2)
  3. Multiplication: print(4 * 7)
  4. Division: print(20 / 5)
`, 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', difficulty: 'easy', content: `

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

  1. Initialize bob with the value 32: bob = 32
  2. Print the value of bob: print(bob) `, 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', difficulty: 'easy', content: `

    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", "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', difficulty: 'easy', 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:

    1. Initialize a variable with a value
    2. Use an if statement to check if the variable is greater or less than a value
    3. Print a message based on the condition

    To 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', difficulty: 'easy', 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', difficulty: 'easy', 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 is:", 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!

    `, 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 3–4: 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', difficulty: 'easy', content: `

    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: 'lesson10', title: '9. Libraries & Modules (time)', difficulty: 'easy', 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.

    `, 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, }; }; })() }, ];