2025-06-24 07:05:06 +00:00
export const lessons = [
{
id : 'lesson1' ,
title : '1. Introduction to Python' ,
2025-07-03 15:25:13 +00:00
tabtitle : 'Hello World' ,
level : 'basics' ,
2025-06-24 07:05:06 +00:00
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 < / c o d e > f u n c t i o n . < / p >
< ol >
< li > In the code editor below , type < code > print ( "Hello World" ) < / c o d e > < / l i >
< li > Click the "Run" button to execute your code < / l i >
< li > You should see "Hello World" printed in the output area < / l i >
< / o l >
` ,
2025-06-26 09:53:24 +00:00
objectives : [
"Print \"Hello World\" to the console"
] ,
2025-06-24 15:34:05 +00:00
doneCondition : ( ( ) => {
2025-06-26 09:53:24 +00:00
return ( { lesson , code , consoleText , codeRanGood } ) => {
const progress = {
stringDone : false
} ;
2025-06-24 15:34:05 +00:00
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 ,
2025-06-26 09:53:24 +00:00
progressArray : Object . values ( progress ) ,
2025-06-24 15:34:05 +00:00
hint
} ;
} ;
} ) ( )
2025-06-24 07:05:06 +00:00
} ,
{
id : 'lesson2' ,
2025-07-03 15:25:13 +00:00
title : '2. Data Types' ,
tabtitle : 'Data Types' ,
level : 'basics' ,
2025-06-24 09:28:28 +00:00
content : `
< p > Did you try typeing < code > print ( Hello World ) < / c o d e > , w i t h o u t t h e q u o t a t i o n m a r k s ? < / p >
< p > If you didn ' t , give it a try now . < / p >
< / b r >
< p > Thats what we call a < strong > Syntax Error < / s t r o n g > . < / 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 >
< / b r >
String is just one of the many < strong > Data Types < / s t r o n g > t h a t P y t h o n h a s . < / 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" ) < / c o d e > < / l i >
< li > Integer : < code > print ( 42 ) < / c o d e > < / l i >
< li > Float : < code > print ( 3.14 ) < / c o d e > < / l i >
< li > Boolean : < code > print ( True ) < / c o d e > < / l i >
< / o l >
< p > Click the "Run" button to execute your code < / p >
` ,
2025-06-26 09:53:24 +00:00
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"
] ,
2025-06-24 15:34:05 +00:00
doneCondition : ( ( ) => {
2025-07-04 07:50:06 +00:00
// Matches print("something") or print('something')
const stringPrintRegex = /print\s*\(\s*(['"]).*?\1\s*\)/ ;
2025-06-24 09:28:28 +00:00
2025-07-04 07:50:06 +00:00
// 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*\)/ ;
2025-06-26 09:53:24 +00:00
2025-07-04 07:50:06 +00:00
// Matches print of int literal like 3, -42 (excluding floats)
const intPrintRegex = /print\s*\(\s*[-+]?\d+\s*\)/ ;
2025-06-24 09:28:28 +00:00
2025-07-04 07:50:06 +00:00
// Matches print(True) or print(False), case-insensitive
const boolPrintRegex = /print\s*\(\s*(True|False)\s*\)/i ;
2025-06-24 09:28:28 +00:00
2025-07-04 07:50:06 +00:00
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 ;
}
}
2025-07-03 15:25:13 +00:00
}
2025-06-24 09:28:28 +00:00
}
2025-07-03 15:25:13 +00:00
2025-07-04 07:50:06 +00:00
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" ) ;
2025-07-03 15:25:13 +00:00
2025-07-04 07:50:06 +00:00
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 ] ;
}
2025-07-03 15:25:13 +00:00
2025-07-04 07:50:06 +00:00
const done = progress . stringDone && progress . intDone && progress . floatDone && progress . boolDone ;
2025-07-03 15:25:13 +00:00
2025-07-04 07:50:06 +00:00
return {
done ,
progressArray : Object . values ( progress ) ,
hint
} ;
} ;
} ) ( )
2025-07-03 15:25:13 +00:00
2025-06-24 09:28:28 +00:00
2025-06-24 15:34:05 +00:00
2025-06-24 09:28:28 +00:00
} ,
{
id : 'lesson3' ,
title : '3. Arithmetic Operations' ,
2025-07-03 15:25:13 +00:00
tabtitle : 'Arithmetic' ,
level : 'basics' ,
2025-06-24 09:28:28 +00:00
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 ) < / c o d e > < / l i >
< li > Subtraction : < code > print ( 10 - 2 ) < / c o d e > < / l i >
< li > Multiplication : < code > print ( 4 * 7 ) < / c o d e > < / l i >
< li > Division : < code > print ( 20 / 5 ) < / c o d e > < / l i >
< / o l >
` ,
2025-06-26 09:53:24 +00:00
objectives : [
"Do some addition" ,
"Do some subtraction" ,
"Do some multiplication" ,
"Do some division" ,
] ,
2025-06-24 15:34:05 +00:00
doneCondition : ( ( ) => {
2025-06-24 09:28:28 +00:00
// Persistent tracking object inside closure
2025-06-26 09:53:24 +00:00
return ( { code , consoleText , codeRanGood } ) => {
const progress = {
addDone : false ,
subDone : false ,
mulDone : false ,
divDone : false ,
2025-06-24 09:28:28 +00:00
2025-06-26 09:53:24 +00:00
} ;
2025-06-24 09:28:28 +00:00
2025-06-24 15:34:05 +00:00
if ( ! codeRanGood ) {
return {
done : false ,
hint : ""
} ;
}
2025-06-24 09:28:28 +00:00
// if (!progress.syntaxErrorDone && syntaxErrorRegex.test(text)) {
// progress.syntaxErrorDone = true;
// }
2025-06-24 15:34:05 +00:00
if ( ! progress . addDone && code . includes ( "+" ) ) {
2025-06-24 09:28:28 +00:00
progress . addDone = true ;
}
2025-06-24 15:34:05 +00:00
if ( ! progress . subDone && code . includes ( "-" ) ) {
2025-06-24 09:28:28 +00:00
progress . subDone = true ;
}
2025-06-24 15:34:05 +00:00
if ( ! progress . mulDone && code . includes ( "*" ) ) {
2025-06-24 09:28:28 +00:00
progress . mulDone = true ;
}
2025-06-24 15:34:05 +00:00
if ( ! progress . divDone && code . includes ( "/" ) ) {
2025-06-24 09:28:28 +00:00
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 ;
2025-06-26 09:53:24 +00:00
return {
done ,
progressArray : Object . values ( progress ) ,
hint
} ;
2025-06-24 09:28:28 +00:00
} ;
} ) ( )
} ,
{
id : 'lesson4' ,
title : '4. Variables' ,
2025-07-03 15:25:13 +00:00
tabtitle : 'Variables' ,
level : 'basics' ,
2025-06-24 07:05:06 +00:00
content : `
2025-06-24 09:28:28 +00:00
< p > It ' s common for us to need to keep some data or objects over multiple lines , for that we use < strong > variables < / s t r o n g > . < / 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 < / c o d e > < / p >
< p > In this case , we created a variable called < code > bob < / c o d e > a n d a s s i g n e d i t t h e v a l u e < c o d e > 3 2 < / c o d e > . < / p >
< p > As far as Python is concerned , < code > bob < / c o d e > i s j u s t a n a m e f o r t h e n u m b e r < c o d e > 3 2 < / c o d e > . < / p >
< p > Lets test it < / p >
< ol >
< li > Initialize bob with the value 32 : < code > bob = 32 < / c o d e > < / l i >
< li > Print the value of bob : < code > print ( bob ) < /code></
< / o l >
` ,
2025-06-26 09:53:24 +00:00
objectives : [
"Initialize a variable with a value" ,
"Print the variable using print()"
2025-06-24 09:28:28 +00:00
] ,
2025-06-24 15:34:05 +00:00
doneCondition : ( ( ) => {
2025-06-26 09:53:24 +00:00
2025-06-24 15:34:05 +00:00
return ( { code , consoleText , codeRanGood } ) => {
2025-06-26 09:53:24 +00:00
const progress = {
varCreated : false ,
varPrinted : false ,
varName : null ,
} ;
2025-06-24 15:34:05 +00:00
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 ;
2025-06-26 09:53:24 +00:00
return {
done ,
progressArray : Object . values ( progress ) ,
hint
} ;
2025-06-24 15:34:05 +00:00
} ;
} ) ( )
} ,
{
id : 'lesson5' ,
title : '5. More on Variables' ,
2025-07-03 15:25:13 +00:00
tabtitle : 'Changing Variables' ,
level : 'basics' ,
2025-06-24 15:34:05 +00:00
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 < / c o d e > w i t h t h e v a l u e < c o d e > 3 2 < / c o d e > , w e c a n d o m a t h w i t h i t : < / p >
< p > < code > print ( bob + 10 ) < / c o d e > w i l l p r i n t < c o d e > 4 2 < / c o d e > . < / p >
< / b r >
< p > We can also change the value of a variable : < / p >
< pre > < code >
bob = 32
print ( bob )
bob = bob + 10
print ( bob )
< / c o d e > < / p r e >
< 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 >
` ,
2025-06-26 09:53:24 +00:00
objectives : [
"Initialize a variable with a value" ,
"Print the variable using print()" ,
2025-07-03 15:25:13 +00:00
"Alter the value of the variable by adding, subtracting, multiplying, or dividing by itself" ,
2025-06-26 09:53:24 +00:00
"Print the variable again using print()" ,
2025-06-24 15:34:05 +00:00
] ,
doneCondition : ( ( ) => {
2025-06-26 09:53:24 +00:00
2025-06-24 09:28:28 +00:00
2025-06-24 15:34:05 +00:00
return ( { code , consoleText , codeRanGood } ) => {
2025-06-26 09:53:24 +00:00
const progress = {
varCreated : false ,
varPrinted : false ,
varArithmeticDone : false ,
varPrintedTwice : false ,
} ;
2025-06-24 15:34:05 +00:00
if ( ! codeRanGood ) {
return {
done : false ,
2025-06-26 09:53:24 +00:00
progressArray : Object . values ( progress ) ,
2025-06-24 15:34:05 +00:00
hint : ""
} ;
}
2025-06-26 09:53:24 +00:00
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"
} ;
}
2025-06-24 09:28:28 +00:00
2025-06-26 09:53:24 +00:00
const varName = assignMatch [ 1 ] ;
2025-06-24 09:28:28 +00:00
2025-06-26 09:53:24 +00:00
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"
) ;
2025-06-24 09:28:28 +00:00
2025-06-26 09:53:24 +00:00
progress . varCreated = true ;
2025-06-24 09:28:28 +00:00
2025-06-26 09:53:24 +00:00
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"
} ;
2025-06-24 15:34:05 +00:00
}
2025-06-26 09:53:24 +00:00
// 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 : "" ,
} ;
2025-06-24 15:34:05 +00:00
} ;
} ) ( )
2025-06-26 09:53:24 +00:00
2025-06-24 15:34:05 +00:00
} ,
{
id : 'lesson6' ,
title : '6. Conditionals' ,
2025-07-03 15:25:13 +00:00
tabtitle : 'if' ,
level : 'basics' ,
2025-06-24 15:34:05 +00:00
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 < / s t r o n g > . < / p >
< pre > < code >
bob = 5
if bob > 10 :
print ( "Bob is greater than 10" )
< / c o d e > < / p r e >
< p > In this example , the print function will only run if < code > bob < / c o d e > i s 1 1 o r h i g h e r . < / p >
< / b r >
< p > One thing to note here is the formatting . Note that after the < code > if bob > 10 : < / c o d e > t h e r e i s a c o l o n ( : ) < 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 : < / c o d e > w e a l s o h a v e a c o l o n , a n d t h e n e x t l i n e i s i n d e n t e d a g a i n . < / p >
< p > Try it for yourself : < / p >
< ol >
< li > Initialize a variable with a value < / l i >
< li > Use an < code > if < / c o d e > s t a t e m e n t t o c h e c k i f t h e v a r i a b l e i s g r e a t e r o r l e s s t h a n a v a l u e < / l i >
< li > Print a message based on the condition < / l i >
< / o l >
< p > To test if two values are equal , you can use the < code >= = < / c o d e > o p e r a t o r . < / p >
< p > eg . < pre > < code >
num = 10
if num == 10 :
print ( "num is equal to 10" )
< / c o d e > < / p r e >
` ,
2025-06-26 09:53:24 +00:00
objectives : [
"Initialize a variable with a value" ,
"Create an if statement to check a condition" , ,
"Print a message based on the condition" ,
] ,
2025-06-24 15:34:05 +00:00
doneCondition : ( ( ) => {
return ( { code , consoleText , codeRanGood } ) => {
2025-06-26 09:53:24 +00:00
const progress = {
varCreated : false ,
ifStatement : false ,
printedSomething : false ,
} ;
2025-06-24 15:34:05 +00:00
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 ;
2025-06-26 09:53:24 +00:00
return {
done ,
progressArray : Object . values ( progress ) ,
hint
} ;
2025-06-24 15:34:05 +00:00
} ;
} ) ( )
} ,
{
id : 'lesson7' ,
title : '7. More Conditionals' ,
2025-07-03 15:25:13 +00:00
tabtitle : 'if/elif/else' ,
level : 'basics' ,
2025-06-24 15:34:05 +00:00
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 < / c o d e > a n d < c o d e > e l s e < / c o d e > . < / 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" )
< / c o d e > < / p r e >
< p > In this example , only one of the print statements can ever run , < code > elif < / c o d e > i s s h o r t f o r " e l s e i f " , a n d i s a s e c o n d i f s t a t e m e n t w h i c h i s o n l y t e s t e d i t t h e < c o d e > i f < / c o d e > a b o v e i t r e s o l v e s t o F a l s e . < / p >
< p > The < code > else < / c o d e > s t a t e m e n t i s r u n i f a l l p r e v i o u s c o n d i t i o n s a r e F a l s e . < / p >
< p > You can chain as many < code > elif < / c o d e > s t a t e m e n t s a s y o u l i k e , b u t o n l y o n e < c o d e > e l s e < / c o d e > a t t h e e n d . < / p >
< p > Try it for yourself : < / p >
` ,
2025-06-26 09:53:24 +00:00
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" ,
] ,
2025-06-24 15:34:05 +00:00
doneCondition : ( ( ) => {
return ( { code , consoleText , codeRanGood } ) => {
2025-06-26 09:53:24 +00:00
const progress = {
varCreated : false ,
ifStatement : false ,
elifStatement : false ,
elseStatement : false ,
threeDistinctPrints : false ,
} ;
2025-06-24 15:34:05 +00:00
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 ;
2025-06-26 09:53:24 +00:00
return {
done ,
progressArray : Object . values ( progress ) ,
hint
} ;
2025-06-24 15:34:05 +00:00
} ;
} ) ( )
} ,
{
id : 'lesson8' ,
2025-06-26 16:38:55 +00:00
title : '8. While Loops' ,
2025-07-03 15:25:13 +00:00
tabtitle : 'While Loops' ,
level : 'basics' ,
2025-06-24 15:34:05 +00:00
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 < / s t r o n g > . < / p >
< p > Python has two main types of loops : < code > for < / c o d e > l o o p s a n d < c o d e > w h i l e < / c o d e > l o o p s . < / p >
< p > The < code > while < / c o d e > l o o p w i l l c o n t i n u e t o r u n a s l o n g a s a c o n d i t i o n i s T r u e . < / p >
< pre > < code >
count = 0
while count < 5 :
2025-07-03 15:25:13 +00:00
print ( count )
2025-06-24 15:34:05 +00:00
count = count + 1
print ( "Done!" )
< / c o d e > < / p r e >
< p > You have to be careful with < code > while < / c o d e > l o o p s , i f t h e c o n d i t i o n n e v e r b e c o m e s F a l s e , t h e l o o p w i l l r u n f o r e v e r ! < / p >
` ,
2025-06-26 16:38:55 +00:00
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"
] ,
2025-06-24 15:34:05 +00:00
doneCondition : ( ( ) => {
return ( { code , consoleText , codeRanGood } ) => {
2025-06-26 16:38:55 +00:00
const progress = {
varCreated : false ,
hasWhileLoop : false ,
whileConditionChecksVar : false ,
printsVarInLoop : false ,
updatesVarInLoop : false ,
codeTerminates : codeRanGood
} ;
2025-06-24 15:34:05 +00:00
if ( ! codeRanGood ) {
return { done : false , hint : "" } ;
}
2025-06-26 16:38:55 +00:00
// Step 1: Variable assignment (e.g., x = 0)
2025-06-24 15:34:05 +00:00
const assignRegex = /\b(\w+)\s*=\s*[\d'"]/ ;
2025-06-26 16:38:55 +00:00
const match = code . match ( assignRegex ) ;
const varName = match ? match [ 1 ] : null ;
progress . varCreated = ! ! varName ;
2025-06-24 15:34:05 +00:00
const lines = code . split ( '\n' ) ;
2025-06-26 16:38:55 +00:00
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 ;
2025-06-24 15:34:05 +00:00
continue ;
}
2025-06-26 16:38:55 +00:00
// 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 ;
}
2025-06-24 15:34:05 +00:00
}
2025-06-26 16:38:55 +00:00
}
2025-06-24 15:34:05 +00:00
2025-06-26 16:38:55 +00:00
// ✅ 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" ) ;
2025-06-24 15:34:05 +00:00
2025-06-26 16:38:55 +00:00
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' ,
2025-07-03 15:25:13 +00:00
tabtitle : 'For Loops' ,
level : 'basics' ,
2025-06-26 16:38:55 +00:00
content : `
< p > A more common type of loop is the < strong > for < / s t r o n g > l o o p . < / p >
< p > A < code > for < / c o d e > l o o p i n c l u d e s t h e c r e a t i o n o f a v a r i a b l e t h a t w i l l b e u s e d t o c o u n t h o w m a n y t i m e s i t s h o u l d r u n . < / 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!" )
< / c o d e > < / p r e >
< p > The < code > range ( n ) < / c o d e > f u n c t i o n a c t u a l l y c r e a t e s a l i s t o f n u m b e r s , a n d e a c h w i l l b e a s s i g n e d t o < c o d e > i < / c o d e > i n t u r n . < / 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!" )
< / c o d e > < / p r e >
< 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 3 s
for i in range ( 0 , 27 , 3 ) :
print ( i )
print ( "Done!" )
< / c o d e > < / p r e >
` ,
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 : "" } ;
2025-06-24 15:34:05 +00:00
}
2025-06-26 16:38:55 +00:00
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
}
}
2025-06-24 15:34:05 +00:00
2025-06-26 16:38:55 +00:00
// Hints
2025-06-24 15:34:05 +00:00
const missing = [ ] ;
2025-06-26 16:38:55 +00:00
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" ) ;
2025-06-24 15:34:05 +00:00
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 ] )
: "" ;
2025-06-26 16:38:55 +00:00
const done = Object . values ( progress ) . every ( Boolean ) ;
return {
done ,
progressArray : Object . values ( progress ) ,
hint
} ;
} ;
} ) ( )
2025-07-03 15:25:13 +00:00
} ,
{
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 >
< / b r >
< 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
< / c o d e > < / p r e >
` ,
objectives : [
2025-07-04 07:50:06 +00:00
"Reach the first checkpoint" ,
"Reach the second checkpoint" ,
"Code should complete without errors"
2025-07-03 15:25:13 +00:00
] ,
doneCondition : ( ( ) => {
2025-07-04 07:50:06 +00:00
return ( { code , consoleText , codeRanGood , gameWorld } ) => {
2025-07-03 15:25:13 +00:00
const progress = {
2025-07-04 07:50:06 +00:00
firstCheckpoint : gameWorld . waypointsReached [ 0 ] ,
secondCheckpoint : gameWorld . waypointsReached [ 1 ] ,
codeRanGood : codeRanGood ,
2025-07-03 15:25:13 +00:00
} ;
if ( ! codeRanGood ) {
return { done : false , hint : "" } ;
}
// 5. Build hint
const missing = [ ] ;
2025-07-04 07:50:06 +00:00
if ( ! progress . firstCheckpoint ) missing . push ( "reach the first checkpoint" ) ;
if ( ! progress . secondCheckpoint ) missing . push ( "reach the second checkpoint" ) ;
2025-07-03 15:25:13 +00:00
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 :
2025-07-04 07:50:06 +00:00
progress . firstCheckpoint &&
progress . secondCheckpoint &&
progress . codeRanGood ,
2025-07-03 15:25:13 +00:00
progressArray : Object . values ( progress ) ,
hint ,
} ;
} ;
} ) ( )
2025-06-26 16:38:55 +00:00
} ,
{
id : 'lesson10' ,
title : '9. Libraries & Modules (time)' ,
2025-07-03 15:25:13 +00:00
tabtitle : 'Importing Modules' ,
level : 'basics' ,
2025-06-26 16:38:55 +00:00
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 < / s t r o n g > a n d < s t r o n g > m o d u l e s < / s t r o n g > . < / p >
< / b r >
< 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 < / c o d e > s t a t e m e n t : < / p >
< p > eg . < code > import time < / c o d e > < / p >
< / b r >
< 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!" )
< / c o d e > < / p r e >
< / b r >
< p > < strong > Note : < / s t r o n g > T o c o u n t f r o m a c t u a l 1 - 1 0 o u r < c o d e > r a n g e ( s t a r t , e n d ) < / c o d e > s t a r t s a t 1 , a n d e n d s a t 1 1 , a s t h e < c o d e > e n d < / c o d e > n u m b e r i s e x c l u s i v e . < / 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 ) } ` ;
}
2025-06-24 15:34:05 +00:00
2025-06-26 16:38:55 +00:00
return {
done :
progress . importedTime &&
progress . printedBefore &&
progress . usedSleep &&
progress . printedAfter ,
progressArray : Object . values ( progress ) ,
hint ,
} ;
2025-06-24 15:34:05 +00:00
} ;
} ) ( )
2025-06-26 16:38:55 +00:00
2025-06-24 09:28:28 +00:00
} ,
2025-06-24 07:05:06 +00:00
] ;