import { Robot } from "./robot.js"; import { GameWorld } from "./gameworld.js"; const consoleElement = document.getElementById("console"); const gameCanvas = document.getElementById("gameCanvas"); const ctx = gameCanvas.getContext("2d"); const gameWorld = new GameWorld(); let monacoEditor; let pyodideWorker = startPyodideWorker(); let robots = null;//createInitialRobots(); //gameWorld.addRobot(robots["player"]); let paused = false; let scale = 1; // Zoom level let offsetX = 0; // Pan X let offsetY = 0; // Pan Y let isPanning = false; let startX, startY; // Mouse start positions // ✅ Function to create the Pyodide Worker function startPyodideWorker() { const worker = new Worker("pyodide-worker.js?v=" + Date.now()); // ✅ Reattach the event listener when a new worker is created worker.onmessage = (event) => { if (paused) return; switch (event.data.type) { case "console": logToConsole(event.data.data); break; case "error": logToConsole(`${event.data.message}`); break; case "fire": fire(); break; case "turn": turn(event.data.data); break; case "move": move(event.data.data); break; } }; return worker; } // ✅ Function to create initial robots function createInitialRobots() { return { "player": new Robot("player", 50, 50, "blue") //"enemy1": new Robot("enemy1", 200, 150, "red"), //"enemy2": new Robot("enemy2", 400, 250, "red") }; } // ✅ Function to log messages to console const maxLines = 64; const logLines = []; function logToConsole(text) { 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 function fire() { logToConsole("🔥 Gun Fired! 🔥"); } function turn(deg) { robots["player"].turn(deg); } function move(distance) { robots["player"].move(distance); } // ✅ Pause/Resume Function function togglePause() { paused = !paused; document.getElementById("pause-button").innerText = paused ? "Resume" : "Pause"; } // ✅ Reset Function (Fixed) function resetGame() { // Terminate the worker pyodideWorker.terminate(); clearConsole(); // Restart the worker and rebind event listener pyodideWorker = startPyodideWorker(); // Reset the robots to their initial state robots = createInitialRobots(); gameWorld.reset(robots["player"]); //gameWorld.addRobot(robots["player"]); // Clear the console consoleElement.innerHTML = ""; // 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; const targetInterval = 1000 / targetFPS; // Time in milliseconds per frame let lastFrameTime = 0; // ✅ Game Loop function gameLoop(timestamp) { const deltaTime = timestamp - lastFrameTime; // If enough time has passed since the last frame, update and draw if (deltaTime >= targetInterval) { lastFrameTime = timestamp; if (!paused) { ctx.resetTransform(); // Fill the entire visible canvas to remove artifacts ctx.fillStyle = "#DDD"; ctx.fillRect(0, 0, gameCanvas.width, gameCanvas.height); ctx.translate(offsetX, offsetY); // Apply panning ctx.scale(scale, scale); // Apply zooming gameWorld.update(); gameWorld.draw(ctx); if (gameWorld.checkPlayerCompletedTask()){ logToConsole("🏁 Task Completed! 🏁"); togglePause(); gameWorld.currentLevel++; } pyodideWorker.postMessage({ type: "game_state", state: gameWorld }); } } requestAnimationFrame(gameLoop); } // ✅ Update "distance" and "speed" every 2 seconds with random values function updateSensorData() { const distance = Math.random() * 100; // Random distance (0-100) const speed = Math.random() * 10; // Random speed (0-10) console.log(`Distance: ${distance.toFixed(2)}, Speed: ${speed.toFixed(2)}`); pyodideWorker.postMessage({ type: "sensor_update", data: { distance, speed } }); //logToConsole(`📡 Sensor Update - Distance: ${distance.toFixed(2)}, Speed: ${speed.toFixed(2)}`); } //setInterval(updateSensorData, 2000); // Call every 2 seconds 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); code = code.replace(/time\.sleep\(/g, "await time.sleep("); console.log(code); consoleElement.innerHTML = ""; pyodideWorker.postMessage({ type: "execute", code: code }); }); document.getElementById("pause-button").addEventListener("click", togglePause); document.getElementById("reset-button").addEventListener("click", resetGame); gameCanvas.addEventListener("wheel", (event) => { event.preventDefault(); const scaleFactor = 1.1; const mouseX = event.offsetX; const mouseY = event.offsetY; // Convert mouse coordinates to world coordinates (before zoom) const worldX = (mouseX - offsetX) / scale; const worldY = (mouseY - offsetY) / scale; // Apply zoom if (event.deltaY < 0) { scale *= scaleFactor; // Zoom in } else { scale /= scaleFactor; // Zoom out } // Keep zoom within limits scale = Math.max(0.5, Math.min(3, scale)); // Adjust offset so zooming is centered at mouse position offsetX = mouseX - worldX * scale; offsetY = mouseY - worldY * scale; }); gameCanvas.addEventListener("mousedown", (event) => { isPanning = true; startX = event.clientX - offsetX; startY = event.clientY - offsetY; }); gameCanvas.addEventListener("mousemove", (event) => { if (!isPanning) return; offsetX = event.clientX - startX; offsetY = event.clientY - startY; }); gameCanvas.addEventListener("mouseup", () => { isPanning = false; }); const textarea = document.getElementById('python-code'); // textarea.addEventListener('keydown', function (event) { // if (event.key === 'Tab') { // event.preventDefault(); // const start = this.selectionStart; // const end = this.selectionEnd; // this.value = this.value.substring(0, start) + ' ' + this.value.substring(end); // this.selectionStart = this.selectionEnd = start + 1; // } // }); // game.js export function initializeMonaco() { require.config({ paths: { vs: "https://cdn.jsdelivr.net/npm/monaco-editor@0.39.0/min/vs" } }); require(["vs/editor/editor.main"], function () { // Store the editor instance in the global variable monacoEditor = monaco.editor.create(document.getElementById("monaco-editor"), { value: "# Type your Python code here\n", language: "python", theme: "vs-dark", automaticLayout: true }); }); } function setupCanvas() { const canvas = document.getElementById("gameCanvas"); const context = canvas.getContext("2d"); // Get the device pixel ratio const dpr = window.devicePixelRatio || 1; // Adjust the canvas size for high-DPI displays const rect = canvas.getBoundingClientRect(); canvas.width = rect.width * dpr; canvas.height = rect.height * dpr; // Scale the drawing context context.scale(dpr, dpr); // Optional: Clear the canvas for better visuals context.clearRect(0, 0, canvas.width, canvas.height); } // Call this function when the page loads fetch('/data/levels.json') .then(response => response.json()) .then(data => { gameWorld.levelData = data; setupCanvas(); resetGame(); // Initialize the game and robots // Start game loop gameLoop(); });