Block Coding Simulator

Block Coding Simulator

Block Coding Simulator

Simulation Stage

Your browser does not support the canvas element.

Controls

Instructions

  • Go to the Data Configuration tab to build your program.
  • Add, remove, and reorder blocks.
  • Return here and press Run Program.
  • Press Reset Simulator to clear the stage.

Block Palette

Movement

Pen

Control

Program Workspace

Add blocks from the palette to start building your program.

`; } bcs_programBlocks.forEach((block, index) => { const blockDiv = document.createElement('div'); blockDiv.className = `bcs-block p-3 rounded-md flex items-center justify-between space-x-4 ${BLOCK_COLORS[block.type.split('_')[0]]}`; blockDiv.setAttribute('data-id', block.id); // Block Info (Name + Value Input) let valueInput = ''; if (block.hasOwnProperty('value')) { valueInput = ` ${block.type.includes('MOVE') ? 'steps' : (block.type.includes('TURN') ? 'degrees' : 'times')} `; } let indentClass = ''; if (block.type === 'LOOP_END') indentClass = 'ml-8'; if (block.type !== 'LOOP_START' && block.type !== 'LOOP_END') { // Check if inside a loop let indentLevel = 0; for (let i = 0; i < index; i++) { if (bcs_programBlocks[i].type === 'LOOP_START') indentLevel++; if (bcs_programBlocks[i].type === 'LOOP_END') indentLevel--; } if (indentLevel > 0) indentClass = `ml-${indentLevel * 8}`; } blockDiv.innerHTML = `
${block.name}
${valueInput}
`; bcs_programList.appendChild(blockDiv); }); } /** * Handles adding a new block from the palette */ function bcs_addBlock(type) { const newBlock = { id: bcs_blockIdCounter++, ...BLOCK_DEFAULTS[type] }; bcs_programBlocks.push(newBlock); bcs_renderProgramList(); } // --- SIMULATOR CANVAS FUNCTIONS --- /** * Resizes the canvas to fit its container */ function bcs_resizeCanvas() { if (!bcs_canvas) return; bcs_canvas.width = bcs_canvas.clientWidth; bcs_canvas.height = bcs_canvas.clientHeight; } /** * Resets the simulator to its initial state */ function bcs_resetSimulator() { if (!bcs_canvas || !bcs_ctx) return; bcs_ctx.clearRect(0, 0, bcs_canvas.width, bcs_canvas.height); bcs_sprite = { x: bcs_canvas.width / 2, y: bcs_canvas.height / 2, angle: -90, // Start facing "up" isPenDown: false }; bcs_path = [{ x: bcs_sprite.x, y: bcs_sprite.y }]; bcs_drawSprite(); } /** * Draws the sprite (a triangle) on the canvas */ function bcs_drawSprite() { if (!bcs_ctx) return; const { x, y, angle } = bcs_sprite; const angleRad = angle * Math.PI / 180; bcs_ctx.save(); bcs_ctx.translate(x, y); bcs_ctx.rotate(angleRad); bcs_ctx.beginPath(); bcs_ctx.moveTo(0, -10); // Tip bcs_ctx.lineTo(6, 7); // Bottom right bcs_ctx.lineTo(-6, 7); // Bottom left bcs_ctx.closePath(); bcs_ctx.fillStyle = bcs_isRunning ? '#e11d48' : '#0284c7'; // Red when running, blue when idle bcs_ctx.fill(); bcs_ctx.restore(); } /** * Draws the entire path stored in the bcs_path array */ function bcs_drawPath() { if (!bcs_ctx || bcs_path.length < 2) return; bcs_ctx.beginPath(); bcs_ctx.moveTo(bcs_path[0].x, bcs_path[0].y); for (let i = 1; i < bcs_path.length; i++) { bcs_ctx.lineTo(bcs_path[i].x, bcs_path[i].y); } bcs_ctx.strokeStyle = '#0f172a'; // Black bcs_ctx.lineWidth = 2; bcs_ctx.stroke(); } /** * Main program execution logic */ async function bcs_runProgram() { if (bcs_isRunning) return; bcs_isRunning = true; bcs_runButton.disabled = true; bcs_runButton.classList.add('opacity-50', 'cursor-not-allowed'); bcs_resetSimulator(); const loopContext = []; // Stack for loop info for (let i = 0; i < bcs_programBlocks.length; i++) { const block = bcs_programBlocks[i]; // --- Update Sprite State (Model) --- switch (block.type) { case 'MOVE_FORWARD': { const angleRad = bcs_sprite.angle * Math.PI / 180; const newX = bcs_sprite.x + block.value * Math.cos(angleRad); const newY = bcs_sprite.y + block.value * Math.sin(angleRad); bcs_sprite.x = newX; bcs_sprite.y = newY; if (bcs_sprite.isPenDown) { bcs_path.push({ x: newX, y: newY }); } else { if (bcs_path.length > 0) bcs_path = [{ x: newX, y: newY }]; } break; } case 'TURN_RIGHT': bcs_sprite.angle += block.value; break; case 'TURN_LEFT': bcs_sprite.angle -= block.value; break; case 'PEN_DOWN': bcs_sprite.isPenDown = true; bcs_path = [{ x: bcs_sprite.x, y: bcs_sprite.y }]; break; case 'PEN_UP': bcs_sprite.isPenDown = false; break; case 'LOOP_START': loopContext.push({ count: block.value, index: i, current: 0 }); break; case 'LOOP_END': { if (loopContext.length > 0) { const loop = loopContext[loopContext.length - 1]; loop.current++; if (loop.current < loop.count) { i = loop.index; // Jump back to loop start } else { loopContext.pop(); // Loop finished } } break; } } // --- Render State (View) --- bcs_ctx.clearRect(0, 0, bcs_canvas.width, bcs_canvas.height); bcs_drawPath(); bcs_drawSprite(); await bcs_sleep(100); } bcs_isRunning = false; bcs_runButton.disabled = false; bcs_runButton.classList.remove('opacity-50', 'cursor-not-allowed'); bcs_drawSprite(); // Draw in idle color } /** * Generates and downloads a PDF of the program and canvas */ async function bcs_downloadPDF() { if (typeof jspdf === 'undefined' || typeof html2canvas === 'undefined') { console.error("BCS Tool Error: jsPDF or html2canvas library not loaded."); alert("Error: PDF libraries failed to load. Please check console."); return; } const { jsPDF } = window.jspdf; const doc = new jsPDF({ orientation: 'p', unit: 'pt', format: 'a4' }); // --- Page 1: Program Code --- doc.setFontSize(18); doc.text("Block Coding Simulator Report", 40, 60); doc.setFontSize(14); doc.text("Program Code:", 40, 90); doc.setFontSize(10); doc.setFont("courier", "normal"); let y = 110; let indent = 0; bcs_programBlocks.forEach((block, index) => { if (y > 780) { // Page break doc.addPage(); y = 60; } if (block.type === 'LOOP_END') indent--; const indentStr = " ".repeat(Math.max(0, indent)); const valueStr = block.hasOwnProperty('value') ? `(${block.value})` : ''; doc.text(`${index + 1}. ${indentStr}${block.name} ${valueStr}`, 50, y); y += 15; if (block.type === 'LOOP_START') indent++; }); // --- Page 2: Canvas Image --- doc.addPage(); doc.setFont("helvetica", "normal"); doc.setFontSize(14); doc.text("Final Simulation Output:", 40, 60); try { const canvas = document.getElementById('bcs-canvas'); const canvasImg = await html2canvas(canvas); const imgData = canvasImg.toDataURL('image/png'); const pdfWidth = doc.internal.pageSize.getWidth(); const pdfHeight = doc.internal.pageSize.getHeight(); const margin = 40; const imgWidth = canvasImg.width; const imgHeight = canvasImg.height; const ratio = Math.min((pdfWidth - margin * 2) / imgWidth, (pdfHeight - 100) / imgHeight); const w = imgWidth * ratio; const h = imgHeight * ratio; const x = (pdfWidth - w) / 2; // Center horizontally doc.addImage(imgData, 'PNG', x, 80, w, h); } catch (error) { console.error("BCS Tool Error: PDF canvas capture failed.", error); doc.text("Error capturing canvas image.", 40, 80); } doc.save('Block_Coding_Report.pdf'); } // --- EVENT LISTENERS --- // Tab link clicks bcs_tabLinks.forEach((link, index) => { link.addEventListener('click', () => bcs_switchTab(index)); }); // Next/Prev button clicks if (bcs_prevButton) { bcs_prevButton.addEventListener('click', () => { if (bcs_currentTab > 0) bcs_switchTab(bcs_currentTab - 1); }); } if (bcs_nextButton) { bcs_nextButton.addEventListener('click', () => { if (bcs_currentTab < bcs_tabLinks.length - 1) bcs_switchTab(bcs_currentTab + 1); }); } // PDF download if (bcs_downloadPdfButton) { bcs_downloadPdfButton.addEventListener('click', bcs_downloadPDF); } // Simulator controls if (bcs_runButton) bcs_runButton.addEventListener('click', bcs_runProgram); if (bcs_resetButton) bcs_resetButton.addEventListener('click', bcs_resetSimulator); // Palette buttons bcs_addBlockButtons.forEach(btn => { btn.addEventListener('click', () => { bcs_addBlock(btn.getAttribute('data-type')); }); }); // Program list actions (Event Delegation) if (bcs_programList) { bcs_programList.addEventListener('click', (e) => { const button = e.target.closest('button'); if (!button) return; const blockDiv = button.closest('.bcs-block'); const id = parseInt(blockDiv.getAttribute('data-id')); const index = parseInt(button.getAttribute('data-index')); if (button.classList.contains('bcs-remove-btn')) { bcs_programBlocks = bcs_programBlocks.filter(b => b.id !== id); } if (button.classList.contains('bcs-move-up-btn') && index > 0) { [bcs_programBlocks[index], bcs_programBlocks[index - 1]] = [bcs_programBlocks[index - 1], bcs_programBlocks[index]]; } if (button.classList.contains('bcs-move-down-btn') && index < bcs_programBlocks.length - 1) { [bcs_programBlocks[index], bcs_programBlocks[index + 1]] = [bcs_programBlocks[index + 1], bcs_programBlocks[index]]; } bcs_renderProgramList(); }); // Handle value changes bcs_programList.addEventListener('change', (e) => { if (e.target.classList.contains('bcs-block-value')) { const id = parseInt(e.target.getAttribute('data-id')); const newValue = parseInt(e.target.value, 10); const block = bcs_programBlocks.find(b => b.id === id); if (block) { block.value = newValue; } } }); } // Window resize window.addEventListener('resize', () => { bcs_resizeCanvas(); bcs_resetSimulator(); }); // --- INITIALIZATION --- bcs_resizeCanvas(); bcs_loadSampleProgram(); bcs_renderProgramList(); bcs_resetSimulator(); // Set initial tab state bcs_tabPanes.forEach((pane, index) => { pane.classList.toggle('hidden', index !== 0); pane.classList.toggle('bcs-active', index === 0); }); });