From 92b2b12f92ebb4215a316d5e85bf70a2577039b1 Mon Sep 17 00:00:00 2001 From: Jake Date: Tue, 24 Jun 2025 13:21:04 +0800 Subject: [PATCH] new, minimalist layout --- data/levels.json | 197 ++++++++++++++++++++++++++++++++--- game.js | 46 ++++++++- gameworld.js | 72 ++++++++++++- index.html | 260 ++++++++++++++++++++++++++++++++++++----------- readme.md | 20 ++++ robot.js | 2 +- sensor.js | 2 +- style.css | 46 +++++++++ 8 files changed, 563 insertions(+), 82 deletions(-) create mode 100644 style.css diff --git a/data/levels.json b/data/levels.json index 446178d..b267d13 100644 --- a/data/levels.json +++ b/data/levels.json @@ -1,7 +1,7 @@ [ { "name": "Level 1", - "robots":{ + "robots": { "player": { "position": { "x": 200, @@ -9,6 +9,34 @@ } } }, + "waypoints": [ + { + "position": { + "x": 420, + "y": 200 + }, + "vertices": [ + { + "x": -50, + "y": -50 + }, + { + "x": 50, + "y": -50 + }, + { + "x": 50, + "y": 50 + }, + { + "x": -50, + "y": 50 + } + ], + "strokeColor": "#0000FF", + "fillColor": "#0000CC" + } + ], "obstacles": [ { "vertices": [ @@ -30,7 +58,7 @@ } ], "position": { - "x": 400, + "x": 800, "y": 300 }, "strokeColor": "#999999", @@ -39,25 +67,170 @@ { "vertices": [ { - "x": 300, - "y": 380 + "x": -10, + "y": -10 }, { - "x": 420, - "y": 380 + "x": 1010, + "y": -10 }, { - "x": 350, - "y": 550 + "x": 1010, + "y": 10 }, { - "x": 280, - "y": 420 + "x": -10, + "y": 10 } ], "position": { - "x": 400, - "y": -50 + "x": 500, + "y": 0 + }, + "strokeColor": "#999999", + "fillColor": "#CCCCCC" + }, + { + "vertices": [ + { + "x": -10, + "y": -10 + }, + { + "x": 1010, + "y": -10 + }, + { + "x": 1010, + "y": 10 + }, + { + "x": -10, + "y": 10 + } + ], + "position": { + "x": 500, + "y": 620 + }, + "strokeColor": "#999999", + "fillColor": "#CCCCCC" + }, + { + "vertices": [ + { + "x": -10, + "y": 10 + }, + { + "x": 10, + "y": 10 + }, + { + "x": 10, + "y": 610 + }, + { + "x": -10, + "y": 610 + } + ], + "position": { + "x": 0, + "y": 310 + }, + "strokeColor": "#999999", + "fillColor": "#CCCCCC" + }, + { + "vertices": [ + { + "x": -10, + "y": 10 + }, + { + "x": 10, + "y": 10 + }, + { + "x": 10, + "y": 610 + }, + { + "x": -10, + "y": 610 + } + ], + "position": { + "x": 1000, + "y": 310 + }, + "strokeColor": "#999999", + "fillColor": "#CCCCCC" + } + ] + }, + { + "name": "Level 2", + "robots": { + "player": { + "position": { + "x": 200, + "y": 200 + } + } + }, + "waypoints": [ + { + "position": { + "x": 220, + "y": 500 + }, + "vertices": [ + { + "x": -50, + "y": -50 + }, + { + "x": 50, + "y": -50 + }, + { + "x": 50, + "y": 50 + }, + { + "x": -50, + "y": 50 + } + ], + "strokeColor": "#000099", + "fillColor": "#0000CC" + } + ], + "obstacles": [ + { + "vertices": [ + { + "x": -100, + "y": -100 + }, + { + "x": 100, + "y": -100 + }, + { + "x": 100, + "y": 100 + }, + { + "x": -100, + "y": 100 + } + ], + "position": { + "x": 200, + "y": 300 }, "strokeColor": "#999999", "fillColor": "#CCCCCC" diff --git a/game.js b/game.js index b12efcf..9394bbe 100644 --- a/game.js +++ b/game.js @@ -61,10 +61,40 @@ function createInitialRobots() { } // ✅ Function to log messages to console +const maxLines = 64; +const logLines = []; + function logToConsole(text) { - //console.log(text.replace("", "").replace("", "")); - consoleElement.innerHTML += text.replace(/\n/g, "
") + "
"; - consoleElement.scrollTop = consoleElement.scrollHeight; + if (text.includes("Pyodide not initialized yet.")) return; + + const newLines = text.split('\n').map(line => line.trim()).filter(line => line !== ""); + + // Add new lines to the array + logLines.push(...newLines); + + // Keep only the last maxLines entries + if (logLines.length > maxLines) { + logLines.splice(0, logLines.length - maxLines); + } + + // Clear console + consoleElement.innerHTML = ""; + + // 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; +} + + +function clearConsole() { + logLines.length = 0; // Empty the logLines array + consoleElement.innerHTML = ""; // Clear the console DOM element } // ✅ Game Control Functions @@ -90,6 +120,7 @@ function togglePause() { function resetGame() { // Terminate the worker pyodideWorker.terminate(); + clearConsole(); // Restart the worker and rebind event listener pyodideWorker = startPyodideWorker(); @@ -105,6 +136,8 @@ function resetGame() { // Unpause the game if it was paused paused = false; document.getElementById("pause-button").innerText = "Pause"; + + logToConsole("Welcome to the game! Type your Python code in the editor and click 'Compile' to execute it."); } const targetFPS = 30; @@ -128,6 +161,11 @@ function gameLoop(timestamp) { gameWorld.update(); gameWorld.draw(ctx); + if (gameWorld.checkPlayerCompletedTask()){ + logToConsole("🏁 Task Completed! 🏁"); + togglePause(); + gameWorld.currentLevel++; + } pyodideWorker.postMessage({ type: "game_state", @@ -158,7 +196,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/gameworld.js b/gameworld.js index 193ecaf..3084451 100644 --- a/gameworld.js +++ b/gameworld.js @@ -11,7 +11,9 @@ export class GameWorld { let ground = Matter.Bodies.rectangle(400, 600, 800, 50, { isStatic: true }); Matter.World.add(this.world, ground); this.levelData = null; + this.currentLevel = 0; + this.waypoints = []; this.obstacles = []; this.robots = []; @@ -25,9 +27,10 @@ export class GameWorld { reset(player = null) { this.robots = [] this.obstacles = [] + this.waypoints = []; Matter.World.clear(this.world); // Clear the world without resetting the engine - let level = this.levelData[0]; + let level = this.levelData[this.currentLevel]; if (player) { console.log("Resetting player position to:", level.robots["player"]["position"]); @@ -41,7 +44,13 @@ export class GameWorld { for (let i = 0; i < level.obstacles.length; i++) { let obstacle = level.obstacles[i]; - this.addObstacle(obstacle.vertices, obstacle.position); + this.addObstacle(obstacle.vertices, obstacle.position, obstacle.strokeColor, obstacle.fillColor); + } + + for (let i = 0; i < level.waypoints.length; i++) { + let obstacle = level.waypoints[i]; + console.log("Adding waypoint:", obstacle); + this.addWaypoint(obstacle.vertices, obstacle.position, obstacle.strokeColor, obstacle.fillColor); } // this.addObstacle([ @@ -68,17 +77,48 @@ export class GameWorld { this.robots[id].update(this); } + + + //this.checkPlayerCompletedTask(); + Matter.Engine.update(this.engine); } - addObstacle(vertices, position = { x: 0, y: 0 }) { + // Needs to be updated to handle different win conditions + checkPlayerCompletedTask() { + let playerPos = this.robots[0].body.position; + let waypointBounds = this.waypoints[0].bounds; + if (Matter.Bounds.contains(waypointBounds, playerPos)) { + console.log("Player is inside the waypoint's bounding box."); + return true; + } + return false; + } + + addWaypoint(vertices, position = { x: 0, y: 0 }, strokeColor = "yellow", fillColor = "yellow") { + // Convert the polygon points into a Matter.js body + let body = Matter.Bodies.fromVertices(position.x, position.y, [vertices], { + isSensor: true, + isStatic: true, + label: "zone" + }); + body.strokeColor = strokeColor; + body.fillColor = fillColor; + // Add body to world and store it + Matter.World.add(this.world, body); + this.waypoints.push(body); + + } + + addObstacle(vertices, position = { x: 0, y: 0 }, strokeColor = "black", fillColor = "gray") { // Convert the polygon points into a Matter.js body let body = Matter.Bodies.fromVertices(position.x, position.y, [vertices], { isStatic: true, // Obstacles shouldn't move }); - + body.strokeColor = strokeColor; + body.fillColor = fillColor; // Add body to world and store it Matter.World.add(this.world, body); this.obstacles.push(body); @@ -118,7 +158,29 @@ export class GameWorld { for (let i = 1; i < vertices.length; i++) { ctx.lineTo(vertices[i].x, vertices[i].y); } + ctx.fillStyle = body.fillColor;; + + ctx.strokeStyle = body.strokeColor; ctx.closePath(); + ctx.fill(); + ctx.stroke(); + }); + + // Draw Waypoints + this.waypoints.forEach(body => { + ctx.beginPath(); + let vertices = body.vertices; + ctx.moveTo(vertices[0].x, vertices[0].y); + for (let i = 1; i < vertices.length; i++) { + ctx.lineTo(vertices[i].x, vertices[i].y); + } + ctx.fillStyle = body.fillColor;; + + ctx.strokeStyle = body.strokeColor; + ctx.closePath(); + ctx.globalAlpha = 0.2; // Applies to all drawing + ctx.fill(); + ctx.globalAlpha = 1.0; // Reset after if needed ctx.stroke(); }); @@ -145,6 +207,8 @@ export class GameWorld { let startPoint = { x: startX, y: startY }; let endPoint = { x: endX, y: endY }; + ignoreBodies.push(...this.waypoints); // Add all obstacles to the ignore list + // Get all bodies in the world let allBodies = Matter.Composite.allBodies(this.engine.world); diff --git a/index.html b/index.html index a47d571..7043d91 100644 --- a/index.html +++ b/index.html @@ -2,81 +2,221 @@ - - - Pyodide Real-Time Python Execution with Console + + + Learn CircuitPython + -

Real-Time Python Execution

+ +
+
+
+

Learn CircuitPython

+

Write code, simulate physics, and see instant output

+
+
+ + + +
+
+
- -
- - - - - -
- - -
- - - -
+ +
+
+
+
+ +
+
+
+
- + \ No newline at end of file diff --git a/readme.md b/readme.md index 65c81b7..e4c0417 100644 --- a/readme.md +++ b/readme.md @@ -9,4 +9,24 @@ while True: robot.turn(-0.001) else: robot.turn(0) + time.sleep(0.1) + + + + + +import robot +import time + +robot.move(0.00006) +while True: + left = robot.get_sensors()[0]["distance"] + right = robot.get_sensors()[1]["distance"] + print(str(int(left)) + "\t" + str(int(right))) + if left < 55: + robot.turn(0.01) + elif right < 55: + robot.turn(-0.01) + else: + robot.turn(0) time.sleep(0.1) \ No newline at end of file diff --git a/robot.js b/robot.js index bf19a8f..b31d35a 100644 --- a/robot.js +++ b/robot.js @@ -132,7 +132,7 @@ export class Robot { } draw_sensors(ctx) { - ctx.fillStyle = "yellow"; + ctx.fillStyle = "green"; this.sensors.forEach(sensor => { const radians = ((this.angle + sensor.angleOffset) * Math.PI) / 180; const sensorX = this.body.position.x + Math.cos(radians) * 15; diff --git a/sensor.js b/sensor.js index fd13bfd..abce3a6 100644 --- a/sensor.js +++ b/sensor.js @@ -102,7 +102,7 @@ export class RaycastSensor extends Sensor { } - ctx.strokeStyle = "yellow"; + ctx.strokeStyle = "green"; ctx.beginPath(); ctx.lineWidth = 2; ctx.moveTo(this.startX, this.startY); diff --git a/style.css b/style.css new file mode 100644 index 0000000..3984262 --- /dev/null +++ b/style.css @@ -0,0 +1,46 @@ +/* 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; + } + + h1 { + grid-column: span 2; /* Header spans both columns */ + text-align: center; + margin: 10px 0; + } + + #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 */ + } + + #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%; + } + + button { + margin: 10px; + } \ No newline at end of file