From 97e02d28c152f7664fec866fad852741320cc574 Mon Sep 17 00:00:00 2001 From: Jake Date: Tue, 24 Jun 2025 15:05:06 +0800 Subject: [PATCH] lesson interface added --- data/lessons.js | 27 ++++ data/lessons.json | 10 ++ game.js | 125 +++++++++++++++---- index.html | 212 +++++-------------------------- style.css | 309 ++++++++++++++++++++++++++++++++++++++++------ 5 files changed, 438 insertions(+), 245 deletions(-) create mode 100644 data/lessons.js create mode 100644 data/lessons.json 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.

+
    +
  1. In the code editor below, type print("Hello World")
  2. +
  3. Click the "Run" button to execute your code
  4. +
  5. You should see "Hello World" printed in the output area
  6. +
+ `, + 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