diff --git a/data/lessons.js b/data/lessons.js
new file mode 100644
index 0000000..819c24f
--- /dev/null
+++ b/data/lessons.js
@@ -0,0 +1,27 @@
+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.
+
+ - In the code editor below, type
print("Hello World")
+ - Click the "Run" button to execute your code
+ - You should see "Hello World" printed in the output area
+
+ `,
+ doneCondition: (consoleText) => consoleText.includes("Hello World")
+ },
+ {
+ id: 'lesson2',
+ title: 'Using Interrupts',
+ difficulty: 'medium',
+ content: `
+ Interrupts allow your code to react to pin changes asynchronously.
+ More content here...
+ `
+ }
+];
diff --git a/data/lessons.json b/data/lessons.json
new file mode 100644
index 0000000..876afa6
--- /dev/null
+++ b/data/lessons.json
@@ -0,0 +1,10 @@
+[
+ {
+ "title": "Lesson 1",
+ "content": "Welcome to Python. We are going to learn how to use python to control robots, but we need some basics first.Let's start with the basics of Python programming. Python is a versatile language that is easy to learn and widely used in robotics.
"
+ },
+ {
+ "title": "Lesson 2",
+ "content": "Now let's learn about interrupts..."
+ }
+]
\ No newline at end of file
diff --git a/game.js b/game.js
index 9394bbe..6d02ef2 100644
--- a/game.js
+++ b/game.js
@@ -1,5 +1,86 @@
import { Robot } from "./robot.js";
import { GameWorld } from "./gameworld.js";
+import { lessons } from './data/lessons.js';
+
+
+let currentLesson = 0;
+
+async function loadLessons() {
+ const response = await fetch('data/lessons.json');
+ lessons = await response.json();
+ showLesson(0);
+}
+
+function showLesson(index) {
+ if (index < 0 || index >= lessons.length) return;
+ currentLesson = index;
+
+ const lesson = lessons[index];
+ document.getElementById('lesson-title').textContent = lesson.title;
+ document.getElementById('lesson-content').innerHTML = lesson.content;
+
+ document.getElementById('prev-lesson').disabled = index === 0;
+ document.getElementById('next-lesson').disabled = index === lessons.length - 1;
+
+ console.log(isLessonDone(lesson.id));
+ updateLessonStatus();
+}
+
+document.getElementById('prev-lesson').addEventListener('click', () => {
+ showLesson(currentLesson - 1);
+});
+document.getElementById('next-lesson').addEventListener('click', () => {
+ showLesson(currentLesson + 1);
+});
+
+function checkLessonDone(outputText) {
+
+ const lesson = lessons[currentLesson];
+ if (lesson.doneCondition && lesson.doneCondition(outputText)) {
+ markLessonDone(lesson.id);
+ }
+}
+
+function markLessonDone(lessonId) {
+ // Your logic here: e.g., store in localStorage or update UI
+ console.log(`Lesson ${lessonId} marked as done!`);
+ // For example:
+ localStorage.setItem(`lessonDone_${lessonId}`, 'true');
+ logToConsole("✅ Task Completed! ✅");
+ updateLessonStatus();
+}
+
+function updateLessonStatus() {
+ const statusEl = document.getElementById('lesson-status');
+ const lesson = lessons[currentLesson];
+ const done = isLessonDone(lesson.id); // your persistent check
+
+ if (done) {
+ statusEl.textContent = '✅';
+ } else {
+ statusEl.textContent = '';
+ }
+ document.getElementById('next-lesson').disabled = !done || currentLesson === lessons.length - 1;
+}
+
+
+function isLessonDone(lessonId) {
+ return localStorage.getItem(`lessonDone_${lessonId}`) === 'true';
+}
+
+function clearLessonProgress() {
+ Object.keys(localStorage).forEach(key => {
+ if (key.startsWith('lessonDone_')) {
+ localStorage.removeItem(key);
+ }
+ });
+ // Optionally update the UI after clearing
+ updateLessonStatus();
+}
+
+
+//clearLessonProgress(); // Clear progress on load for testing
+showLesson(0);
const consoleElement = document.getElementById("console");
const gameCanvas = document.getElementById("gameCanvas");
@@ -65,30 +146,32 @@ const maxLines = 64;
const logLines = [];
function logToConsole(text) {
- if (text.includes("Pyodide not initialized yet.")) return;
+ if (text.includes("Pyodide not initialized yet.")) return;
- const newLines = text.split('\n').map(line => line.trim()).filter(line => line !== "");
+ checkLessonDone(text);
- // Add new lines to the array
- logLines.push(...newLines);
+ const newLines = text.split('\n').map(line => line.trim()).filter(line => line !== "");
- // Keep only the last maxLines entries
- if (logLines.length > maxLines) {
- logLines.splice(0, logLines.length - maxLines);
- }
+ // Add new lines to the array
+ logLines.push(...newLines);
- // Clear console
- consoleElement.innerHTML = "";
+ // Keep only the last maxLines entries
+ if (logLines.length > maxLines) {
+ logLines.splice(0, logLines.length - maxLines);
+ }
- // Create and append separate divs for each line
- logLines.forEach(lineText => {
- const lineDiv = document.createElement('div');
- lineDiv.textContent = lineText;
- consoleElement.appendChild(lineDiv);
- });
+ // Clear console
+ consoleElement.innerHTML = "";
- // Scroll to bottom
- consoleElement.scrollTop = consoleElement.scrollHeight;
+ // Create and append separate divs for each line
+ logLines.forEach(lineText => {
+ const lineDiv = document.createElement('div');
+ lineDiv.textContent = lineText;
+ consoleElement.appendChild(lineDiv);
+ });
+
+ // Scroll to bottom
+ consoleElement.scrollTop = consoleElement.scrollHeight;
}
@@ -161,8 +244,8 @@ function gameLoop(timestamp) {
gameWorld.update();
gameWorld.draw(ctx);
- if (gameWorld.checkPlayerCompletedTask()){
- logToConsole("🏁 Task Completed! 🏁");
+ if (gameWorld.checkPlayerCompletedTask()) {
+ logToConsole("✅ Task Completed! ✅");
togglePause();
gameWorld.currentLevel++;
}
@@ -196,7 +279,7 @@ function updateSensorData() {
document.getElementById("compile-button").addEventListener("click", () => {
if (paused) return;
-
+
// Use the Monaco Editor instance to get the code
let code = monacoEditor.getValue(); // Get text from the editor
console.log(code);
diff --git a/index.html b/index.html
index 7043d91..7dc403a 100644
--- a/index.html
+++ b/index.html
@@ -10,185 +10,8 @@
-
+
@@ -199,20 +22,43 @@
Learn CircuitPython
Write code, simulate physics, and see instant output
-
-
-
-
-
+
+
+
+
Lesson Title
+
Lesson Content
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/style.css b/style.css
index 3984262..7ff06e7 100644
--- a/style.css
+++ b/style.css
@@ -1,46 +1,273 @@
-/* Use a grid layout for the entire page */
- body {
- margin: 0;
- display: grid;
- grid-template-columns: 1fr 1fr; /* Two equal columns */
- grid-template-rows: auto 2fr 1fr; /* Header, main content, and console sections */
- height: 100vh; /* Full viewport height */
- font-family: Arial, sans-serif;
- }
+/* Reset & base */
+* {
+ box-sizing: border-box;
+}
- h1 {
- grid-column: span 2; /* Header spans both columns */
- text-align: center;
- margin: 10px 0;
- }
+body,
+html {
+ margin: 0;
+ padding: 0;
+ height: 100%;
+ font-family: Arial, sans-serif;
+ background-color: #fff;
- #monaco-editor {
- grid-column: 1; /* Left column */
- grid-row: 2 / span 2; /* Takes full left side */
- border: 1px solid #ccc;
- height: 100%; /* Fill available space */
- }
+ display: flex;
+ flex-direction: column;
+}
- #gameCanvas {
- grid-column: 2; /* Right column */
- grid-row: 2; /* Top two-thirds */
- width: 100%;
- height: 100%; /* Canvas stretches to fill the grid cell */
- border: 1px solid black;
- }
- #console {
- grid-column: 2; /* Right column */
- grid-row: 3; /* Bottom one-third */
- background-color: black;
- color: white;
- font-family: monospace;
- padding: 10px;
- overflow-y: auto;
- border: 1px solid #ccc;
- height: 100%;
- }
+/* ===== Header (top bar) ===== */
+header {
+ background-color: #f3f4f6;
+ /* light gray */
+ padding: 16px 0;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
- button {
- margin: 10px;
- }
\ No newline at end of file
+.header-inner {
+ max-width: 960px;
+ margin: 0 auto;
+ padding: 0 16px;
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: space-between;
+ gap: 16px;
+}
+
+header h1 {
+ font-size: 1.5rem;
+ font-weight: bold;
+ margin: 0 0 4px 0;
+ color: #1f2937;
+ /* gray-800 */
+}
+
+header p {
+ margin: 0;
+ font-size: 0.875rem;
+ color: #4b5563;
+ /* gray-600 */
+}
+
+.button-group {
+ display: flex;
+ gap: 12px;
+ flex-wrap: wrap;
+}
+
+button {
+ cursor: pointer;
+ border: none;
+ border-radius: 6px;
+ padding: 8px 16px;
+ font-weight: 600;
+ color: white;
+ box-shadow: 0 2px 4px rgb(0 0 0 / 0.1);
+ transition: background-color 0.3s ease;
+}
+
+#compile-button {
+ background-color: #2563eb;
+ /* blue-600 */
+}
+
+#compile-button:hover {
+ background-color: #1d4ed8;
+ /* blue-700 */
+}
+
+#pause-button {
+ background-color: #eab308;
+ /* yellow-500 */
+ color: #000;
+}
+
+#pause-button:hover {
+ background-color: #ca8a04;
+ /* yellow-600 */
+ color: #000;
+}
+
+#reset-button {
+ background-color: #dc2626;
+ /* red-600 */
+}
+
+#reset-button:hover {
+ background-color: #b91c1c;
+ /* red-700 */
+}
+
+/* ===== Main Content ===== */
+main {
+ display: flex;
+ justify-content: center;
+ padding: 16px;
+ background: #f9fafb;
+}
+
+.container {
+ width: 75vw;
+ /* about 12.5% margin on each side */
+ max-width: 1200px;
+ height: 800px;
+ display: flex;
+ gap: 20px;
+ background: white;
+}
+
+.content-width {
+ width: 75vw;
+ max-width: 1200px;
+ margin: 0 auto;
+}
+.lesson-nav {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-top: 12px;
+}
+
+#lesson-status {
+ font-weight: 600;
+ color: green;
+ min-width: 120px;
+ text-align: center;
+ user-select: none;
+}
+
+.lesson-nav button {
+ padding: 6px 12px;
+ font-size: 14px;
+ font-weight: 600;
+ border: none;
+ border-radius: 6px;
+ cursor: pointer;
+ background-color: #2563eb; /* blue */
+ color: white;
+ transition: background-color 0.2s ease;
+}
+
+.lesson-nav button:hover {
+ background-color: #1e40af; /* darker blue */
+}
+.lesson-nav button:disabled {
+ background-color: #ccc;
+ color: #666;
+ cursor: not-allowed;
+ box-shadow: none;
+ opacity: 0.6;
+}
+
+
+
+/* Monaco editor - left half */
+#monaco-editor {
+ flex: 1;
+ border: 1px solid #d1d5db;
+ /* gray-300 */
+ height: 100%;
+}
+
+/* Right side: canvas + console */
+.right-side {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ height: 100%;
+}
+
+/* Canvas - top 2/3 */
+#gameCanvas {
+ flex: 2;
+ border: 1px solid black;
+ width: 100%;
+ background: white;
+}
+
+#button-bar {
+ display: flex;
+ justify-content: space-between;
+ padding: 8px;
+ background: #f5f5f5;
+}
+
+#button-bar .left-buttons,
+#button-bar .right-buttons {
+ display: flex;
+ gap: 8px;
+}
+
+#button-bar button {
+ padding: 6px 12px;
+ font-size: 14px;
+}
+
+
+#console {
+ flex: 1;
+ background: black;
+ color: white;
+ font-family: monospace, monospace;
+ padding: 8px;
+ overflow-y: auto;
+ border: 1px solid #d1d5db;
+ width: 100%;
+}
+
+/* alternate line colors */
+#console>div:nth-child(odd) {
+ background-color: #222;
+}
+
+#console>div:nth-child(even) {
+ background-color: #111;
+}
+
+/* Responsive for smaller screens */
+@media (max-width: 900px) {
+ .container {
+ flex-direction: column;
+ width: 95vw;
+ height: auto;
+ }
+
+ #monaco-editor,
+ .right-side {
+ height: 400px;
+ }
+}
+
+#lesson-box {
+ background: #f9f9f9;
+ padding: 8px 16px;
+ border-bottom: 1px solid #ddd;
+ font-family: sans-serif;
+ width: 100%;
+ box-sizing: border-box;
+ flex-shrink: 0; /* Prevent flex containers from stretching it */
+ flex-grow: 0;
+ overflow: hidden;
+}
+
+
+.lesson-inner {
+ max-width: 960px;
+ margin: 0 auto;
+ color: #333;
+}
+
+#lesson-box p {
+ margin: 0;
+ line-height: 1.4;
+}
+
+
+#lesson-box code {
+ background: #eee;
+ padding: 2px 4px;
+ border-radius: 4px;
+ font-family: monospace;
+}
\ No newline at end of file