added more done conditions
parent
97e02d28c1
commit
af9cd8177e
235
data/lessons.js
235
data/lessons.js
|
|
@ -17,11 +17,236 @@ export const lessons = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'lesson2',
|
id: 'lesson2',
|
||||||
title: 'Using Interrupts',
|
title: 'Data Types and Variables',
|
||||||
difficulty: 'medium',
|
difficulty: 'easy',
|
||||||
content: `
|
content: `
|
||||||
<p>Interrupts allow your code to react to pin changes asynchronously.</p>
|
<p>Did you try typeing <code>print(Hello World)</code>, without the quotation marks?</p>
|
||||||
<p>More content here...</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>
|
||||||
|
`,
|
||||||
|
doneCondition: (consoleText => {
|
||||||
|
// Persistent tracking object inside closure
|
||||||
|
const progress = {
|
||||||
|
stringDone: false,
|
||||||
|
intDone: false,
|
||||||
|
floatDone: false,
|
||||||
|
boolDone: false,
|
||||||
|
syntaxErrorDone: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
//const syntaxErrorRegex = /SyntaxError/i;
|
||||||
|
const stringRegex = /(["'])(?:(?=(\\?))\2.)*?\1/; // optional improvement based on context
|
||||||
|
const intRegex = /(?<![\d.])[-+]?\d+(?![\d.])/;
|
||||||
|
const floatRegex = /[-+]?(?:\d+\.\d*|\.\d+)(?:[eE][-+]?\d+)?/;
|
||||||
|
const boolRegex = /\b(True|False|true|false)\b/;
|
||||||
|
|
||||||
|
return (text) => {
|
||||||
|
|
||||||
|
|
||||||
|
// if (!progress.syntaxErrorDone && syntaxErrorRegex.test(text)) {
|
||||||
|
// progress.syntaxErrorDone = true;
|
||||||
|
// }
|
||||||
|
if (!progress.stringDone && stringRegex.test(text)) {
|
||||||
|
progress.stringDone = true;
|
||||||
}
|
}
|
||||||
|
if (!progress.floatDone && floatRegex.test(text)) {
|
||||||
|
progress.floatDone = true;
|
||||||
|
}
|
||||||
|
if (!progress.intDone && intRegex.test(text)) {
|
||||||
|
progress.intDone = true;
|
||||||
|
}
|
||||||
|
if (!progress.boolDone && boolRegex.test(text)) {
|
||||||
|
progress.boolDone = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let missing = [];
|
||||||
|
|
||||||
|
//if (!progress.syntaxErrorDone) missing.push("syntax error");
|
||||||
|
//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 {
|
||||||
|
// Join all but last with comma, then add 'and' + last
|
||||||
|
hint += missing.slice(0, -1).join(", ") + " and " + missing[missing.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const done = progress.intDone && progress.floatDone && progress.boolDone;
|
||||||
|
|
||||||
|
return { done, hint };
|
||||||
|
};
|
||||||
|
})()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'lesson3',
|
||||||
|
title: '3. Arithmetic Operations',
|
||||||
|
difficulty: 'easy',
|
||||||
|
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>
|
||||||
|
`,
|
||||||
|
doneCondition: (consoleText => {
|
||||||
|
// Persistent tracking object inside closure
|
||||||
|
const progress = {
|
||||||
|
addDone: false,
|
||||||
|
subDone: false,
|
||||||
|
mulDone: false,
|
||||||
|
divDone: false,
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (text) => {
|
||||||
|
|
||||||
|
|
||||||
|
// if (!progress.syntaxErrorDone && syntaxErrorRegex.test(text)) {
|
||||||
|
// progress.syntaxErrorDone = true;
|
||||||
|
// }
|
||||||
|
if (!progress.addDone && text.includes("+")) {
|
||||||
|
progress.addDone = true;
|
||||||
|
}
|
||||||
|
if (!progress.subDone && text.includes("-")) {
|
||||||
|
progress.subDone = true;
|
||||||
|
}
|
||||||
|
if (!progress.mulDone && text.includes("*")) {
|
||||||
|
progress.mulDone = true;
|
||||||
|
}
|
||||||
|
if (!progress.divDone && text.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, hint };
|
||||||
|
};
|
||||||
|
})()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'lesson4',
|
||||||
|
title: '4. Variables',
|
||||||
|
difficulty: 'easy',
|
||||||
|
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>
|
||||||
|
`,
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
content: `<p>First, try printing an addition: <code>print(2 + 3)</code></p>`,
|
||||||
|
doneCondition: (text) => text.includes("5"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: `<p>Nice! Now try subtraction: <code>print(5 - 2)</code></p>`,
|
||||||
|
doneCondition: (text) => text.includes("3"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: `<p>Now try multiplication: <code>print(4 * 2)</code></p>`,
|
||||||
|
doneCondition: (text) => text.includes("8"),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
doneCondition: (code => {
|
||||||
|
const progress = {
|
||||||
|
varCreated: false,
|
||||||
|
varPrinted: false,
|
||||||
|
varName: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (text) => {
|
||||||
|
// Extract variable name from current text each time
|
||||||
|
const assignMatch = text.match(/^\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*=/m);
|
||||||
|
if (assignMatch) {
|
||||||
|
progress.varName = assignMatch[1];
|
||||||
|
progress.varCreated = true;
|
||||||
|
} else {
|
||||||
|
// No variable assignment found this run
|
||||||
|
progress.varCreated = false;
|
||||||
|
progress.varName = null;
|
||||||
|
progress.varPrinted = false; // reset printed too because no var
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if variable is printed in this run (only if varName exists)
|
||||||
|
if (progress.varCreated && progress.varName) {
|
||||||
|
const printRegex = new RegExp(`print\\s*\\(\\s*${progress.varName}\\s*\\)`);
|
||||||
|
if (printRegex.test(text)) {
|
||||||
|
progress.varPrinted = true;
|
||||||
|
} else {
|
||||||
|
progress.varPrinted = false; // reset if print no longer found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build hint
|
||||||
|
let missing = [];
|
||||||
|
if (!progress.varCreated) missing.push("create a variable");
|
||||||
|
if (progress.varCreated && !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, hint };
|
||||||
|
};
|
||||||
|
})()
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
68
game.js
68
game.js
|
|
@ -4,6 +4,7 @@ import { lessons } from './data/lessons.js';
|
||||||
|
|
||||||
|
|
||||||
let currentLesson = 0;
|
let currentLesson = 0;
|
||||||
|
let lessonComplete = false;
|
||||||
|
|
||||||
async function loadLessons() {
|
async function loadLessons() {
|
||||||
const response = await fetch('data/lessons.json');
|
const response = await fetch('data/lessons.json');
|
||||||
|
|
@ -13,11 +14,11 @@ async function loadLessons() {
|
||||||
|
|
||||||
function showLesson(index) {
|
function showLesson(index) {
|
||||||
if (index < 0 || index >= lessons.length) return;
|
if (index < 0 || index >= lessons.length) return;
|
||||||
|
lessonComplete = false;
|
||||||
currentLesson = index;
|
currentLesson = index;
|
||||||
|
|
||||||
const lesson = lessons[index];
|
const lesson = lessons[index];
|
||||||
document.getElementById('lesson-title').textContent = lesson.title;
|
loadLessonContent(lesson);
|
||||||
document.getElementById('lesson-content').innerHTML = lesson.content;
|
|
||||||
|
|
||||||
document.getElementById('prev-lesson').disabled = index === 0;
|
document.getElementById('prev-lesson').disabled = index === 0;
|
||||||
document.getElementById('next-lesson').disabled = index === lessons.length - 1;
|
document.getElementById('next-lesson').disabled = index === lessons.length - 1;
|
||||||
|
|
@ -26,6 +27,11 @@ function showLesson(index) {
|
||||||
updateLessonStatus();
|
updateLessonStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadLessonContent(lesson){
|
||||||
|
document.getElementById('lesson-title').textContent = lesson.title;
|
||||||
|
document.getElementById('lesson-content').innerHTML = lesson.content;
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('prev-lesson').addEventListener('click', () => {
|
document.getElementById('prev-lesson').addEventListener('click', () => {
|
||||||
showLesson(currentLesson - 1);
|
showLesson(currentLesson - 1);
|
||||||
});
|
});
|
||||||
|
|
@ -34,14 +40,22 @@ document.getElementById('next-lesson').addEventListener('click', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
function checkLessonDone(outputText) {
|
function checkLessonDone(outputText) {
|
||||||
|
checkCurrentStep(outputText);
|
||||||
const lesson = lessons[currentLesson];
|
const lesson = lessons[currentLesson];
|
||||||
if (lesson.doneCondition && lesson.doneCondition(outputText)) {
|
const result = lesson.doneCondition(outputText);
|
||||||
|
if (result.done) {
|
||||||
markLessonDone(lesson.id);
|
markLessonDone(lesson.id);
|
||||||
}
|
}
|
||||||
|
if (result.hint) {
|
||||||
|
logToConsole("Hint: " + result.hint, false);
|
||||||
|
//console.log("Hint:", result.hint); // Or show it in your console UI
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function markLessonDone(lessonId) {
|
function markLessonDone(lessonId) {
|
||||||
|
if (lessonComplete) return; // Prevent marking multiple times
|
||||||
|
lessonComplete = true; // Set flag to prevent further marking
|
||||||
// Your logic here: e.g., store in localStorage or update UI
|
// Your logic here: e.g., store in localStorage or update UI
|
||||||
console.log(`Lesson ${lessonId} marked as done!`);
|
console.log(`Lesson ${lessonId} marked as done!`);
|
||||||
// For example:
|
// For example:
|
||||||
|
|
@ -68,6 +82,31 @@ function isLessonDone(lessonId) {
|
||||||
return localStorage.getItem(`lessonDone_${lessonId}`) === 'true';
|
return localStorage.getItem(`lessonDone_${lessonId}`) === 'true';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let currentStepIndex = 0;
|
||||||
|
|
||||||
|
function checkCurrentStep(text) {
|
||||||
|
const lesson = lessons[currentLesson];
|
||||||
|
const step = lesson.steps[currentStepIndex];
|
||||||
|
const result = step.doneCondition(text);
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
currentStepIndex++;
|
||||||
|
renderSteps(); // Re-render to show the new step
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSteps() {
|
||||||
|
const lesson = lessons[currentLesson];
|
||||||
|
const container = document.getElementById("lesson-content");
|
||||||
|
container.innerHTML = "";
|
||||||
|
loadLessonContent(lesson); // Load the lesson content first
|
||||||
|
for (let i = 0; i <= currentStepIndex && i < lesson.steps.length; i++) {
|
||||||
|
const stepEl = document.createElement("div");
|
||||||
|
stepEl.innerHTML = lesson.steps[i].content;
|
||||||
|
container.appendChild(stepEl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function clearLessonProgress() {
|
function clearLessonProgress() {
|
||||||
Object.keys(localStorage).forEach(key => {
|
Object.keys(localStorage).forEach(key => {
|
||||||
if (key.startsWith('lessonDone_')) {
|
if (key.startsWith('lessonDone_')) {
|
||||||
|
|
@ -80,7 +119,7 @@ function clearLessonProgress() {
|
||||||
|
|
||||||
|
|
||||||
//clearLessonProgress(); // Clear progress on load for testing
|
//clearLessonProgress(); // Clear progress on load for testing
|
||||||
showLesson(0);
|
showLesson(3);
|
||||||
|
|
||||||
const consoleElement = document.getElementById("console");
|
const consoleElement = document.getElementById("console");
|
||||||
const gameCanvas = document.getElementById("gameCanvas");
|
const gameCanvas = document.getElementById("gameCanvas");
|
||||||
|
|
@ -145,10 +184,10 @@ function createInitialRobots() {
|
||||||
const maxLines = 64;
|
const maxLines = 64;
|
||||||
const logLines = [];
|
const logLines = [];
|
||||||
|
|
||||||
function logToConsole(text) {
|
function logToConsole(text, checkLesson = true) {
|
||||||
if (text.includes("Pyodide not initialized yet.")) return;
|
if (text.includes("Pyodide not initialized yet.")) return;
|
||||||
|
|
||||||
checkLessonDone(text);
|
|
||||||
|
|
||||||
const newLines = text.split('\n').map(line => line.trim()).filter(line => line !== "");
|
const newLines = text.split('\n').map(line => line.trim()).filter(line => line !== "");
|
||||||
|
|
||||||
|
|
@ -166,15 +205,25 @@ function logToConsole(text) {
|
||||||
// Create and append separate divs for each line
|
// Create and append separate divs for each line
|
||||||
logLines.forEach(lineText => {
|
logLines.forEach(lineText => {
|
||||||
const lineDiv = document.createElement('div');
|
const lineDiv = document.createElement('div');
|
||||||
lineDiv.textContent = lineText;
|
lineDiv.innerHTML = lineText; // <-- render HTML tags here
|
||||||
|
lineDiv.style.maxWidth = "100%";
|
||||||
|
lineDiv.style.whiteSpace = "pre-wrap";
|
||||||
|
lineDiv.style.overflowWrap = "break-word";
|
||||||
|
lineDiv.style.wordWrap = "break-word";
|
||||||
|
|
||||||
consoleElement.appendChild(lineDiv);
|
consoleElement.appendChild(lineDiv);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Scroll to bottom
|
// Scroll to bottom
|
||||||
consoleElement.scrollTop = consoleElement.scrollHeight;
|
consoleElement.scrollTop = consoleElement.scrollHeight;
|
||||||
|
|
||||||
|
if (checkLesson && !text.includes("Welcome")) { // Don't check lesson completion for welcome message
|
||||||
|
checkLessonDone(text);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function clearConsole() {
|
function clearConsole() {
|
||||||
logLines.length = 0; // Empty the logLines array
|
logLines.length = 0; // Empty the logLines array
|
||||||
consoleElement.innerHTML = ""; // Clear the console DOM element
|
consoleElement.innerHTML = ""; // Clear the console DOM element
|
||||||
|
|
@ -291,6 +340,9 @@ document.getElementById("compile-button").addEventListener("click", () => {
|
||||||
type: "execute",
|
type: "execute",
|
||||||
code: code
|
code: code
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
checkLessonDone(code);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
26
style.css
26
style.css
|
|
@ -205,16 +205,32 @@ main {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.container,
|
||||||
|
.right-side,
|
||||||
|
#console,
|
||||||
|
#console > div {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
#console {
|
#console {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
word-wrap: break-word;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
background: black;
|
background: black;
|
||||||
color: white;
|
color: white;
|
||||||
font-family: monospace, monospace;
|
font-family: monospace;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
overflow-y: auto;
|
box-sizing: border-box;
|
||||||
border: 1px solid #d1d5db;
|
}
|
||||||
width: 100%;
|
|
||||||
|
#console > div {
|
||||||
|
max-width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
word-wrap: break-word;
|
||||||
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* alternate line colors */
|
/* alternate line colors */
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue