Spaces:
Running
Running
class KidsCodeApp { | |
constructor() { | |
this.codeBlocks = []; | |
this.character = document.getElementById('character'); | |
this.dropZone = document.getElementById('drop-zone'); | |
this.runButton = document.getElementById('run-button'); | |
this.clearButton = document.getElementById('clear-button'); | |
this.checkButton = document.getElementById('check-button'); | |
this.isRunning = false; | |
this.currentAssignment = 0; | |
this.characterColors = ['π±', 'π¦', 'π°', 'πΈ', 'πΌ']; | |
this.currentColorIndex = 0; | |
this.sounds = { | |
dance: this.createSound(440, 0.3, 'triangle'), | |
jump: this.createSound(660, 0.2, 'square'), | |
wave: this.createSound(330, 0.4, 'sine'), | |
sound: this.createSound(880, 0.3, 'sawtooth'), | |
spin: this.createSound(392, 0.4, 'sine'), | |
sleep: this.createSound(220, 0.8, 'triangle'), | |
grow: this.createSound(523, 0.3, 'square'), | |
shrink: this.createSound(294, 0.3, 'square'), | |
color: this.createSound(659, 0.2, 'sawtooth'), | |
success: this.createSound(523, 0.2, 'sine'), | |
complete: this.createSound([523, 659, 784], 0.5, 'sine') | |
}; | |
this.assignments = [ | |
{ | |
title: "First Steps!", | |
description: "Let's make Cody dance! π", | |
goal: "Use the Dance block", | |
solution: ["dance"], | |
hint: "Drag the π Dance block to your code area and press Play!" | |
}, | |
{ | |
title: "Jump for Joy!", | |
description: "Now make Cody jump up high! π¦", | |
goal: "Use the Jump block", | |
solution: ["jump"], | |
hint: "Find the π¦ Jump block and drag it over!" | |
}, | |
{ | |
title: "Dance Party!", | |
description: "Make Cody dance AND jump! π", | |
goal: "Use Dance then Jump blocks", | |
solution: ["dance", "jump"], | |
hint: "Put two blocks together - Dance first, then Jump!" | |
}, | |
{ | |
title: "Say Hello!", | |
description: "Make Cody wave hello to everyone! π", | |
goal: "Use the Wave block", | |
solution: ["wave"], | |
hint: "The π Wave block will make Cody friendly!" | |
}, | |
{ | |
title: "Make Some Noise!", | |
description: "Let Cody make a fun sound! π", | |
goal: "Use the Make Sound block", | |
solution: ["sound"], | |
hint: "The π Make Sound block is waiting for you!" | |
}, | |
{ | |
title: "Spin Around!", | |
description: "Make Cody spin like a tornado! πͺοΈ", | |
goal: "Use the Spin block", | |
solution: ["spin"], | |
hint: "Find the π Spin block to make Cody dizzy!" | |
}, | |
{ | |
title: "Growing Up!", | |
description: "Make Cody grow big and then small! π", | |
goal: "Use Grow Big then Get Small blocks", | |
solution: ["grow", "shrink"], | |
hint: "First π Grow Big, then π Get Small!" | |
}, | |
{ | |
title: "Colorful Cat!", | |
description: "Change Cody's look with colors! π¨", | |
goal: "Use the Change Color block", | |
solution: ["color"], | |
hint: "The π¨ Change Color block makes Cody look different!" | |
}, | |
{ | |
title: "Repeat Magic!", | |
description: "Make Cody dance twice using repeat! π", | |
goal: "Use Repeat 1 time then Dance", | |
solution: ["repeat", "dance"], | |
hint: "Put π Repeat 1 time first, then π Dance after it!" | |
}, | |
{ | |
title: "Grand Finale!", | |
description: "Create an amazing show! Use 4 different blocks! π", | |
goal: "Use any 4 different action blocks", | |
solution: ["dance", "jump", "spin", "sound"], | |
hint: "Mix and match your favorite blocks to create a show!", | |
flexible: true | |
} | |
]; | |
this.init(); | |
} | |
init() { | |
this.setupDragAndDrop(); | |
this.setupButtons(); | |
this.setupAssignmentPanel(); | |
this.hideDropHint(); | |
this.loadAssignment(0); | |
} | |
createSound(frequency, duration, type = 'sine') { | |
return () => { | |
try { | |
const audioContext = new (window.AudioContext || window.webkitAudioContext)(); | |
if (Array.isArray(frequency)) { | |
frequency.forEach((freq, index) => { | |
setTimeout(() => { | |
const oscillator = audioContext.createOscillator(); | |
const gainNode = audioContext.createGain(); | |
oscillator.connect(gainNode); | |
gainNode.connect(audioContext.destination); | |
oscillator.frequency.setValueAtTime(freq, audioContext.currentTime); | |
oscillator.type = type; | |
gainNode.gain.setValueAtTime(0.2, audioContext.currentTime); | |
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + duration / 3); | |
oscillator.start(audioContext.currentTime); | |
oscillator.stop(audioContext.currentTime + duration / 3); | |
}, index * (duration * 1000 / 3)); | |
}); | |
} else { | |
const oscillator = audioContext.createOscillator(); | |
const gainNode = audioContext.createGain(); | |
oscillator.connect(gainNode); | |
gainNode.connect(audioContext.destination); | |
oscillator.frequency.setValueAtTime(frequency, audioContext.currentTime); | |
oscillator.type = type; | |
gainNode.gain.setValueAtTime(0.3, audioContext.currentTime); | |
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + duration); | |
oscillator.start(audioContext.currentTime); | |
oscillator.stop(audioContext.currentTime + duration); | |
} | |
} catch (error) { | |
console.log('Audio not available'); | |
} | |
}; | |
} | |
setupDragAndDrop() { | |
const blocks = document.querySelectorAll('.block[draggable="true"]'); | |
blocks.forEach(block => { | |
block.addEventListener('dragstart', (e) => { | |
e.dataTransfer.setData('text/plain', JSON.stringify({ | |
type: block.dataset.blockType, | |
html: block.outerHTML | |
})); | |
}); | |
}); | |
this.dropZone.addEventListener('dragover', (e) => { | |
e.preventDefault(); | |
this.dropZone.classList.add('drag-over'); | |
}); | |
this.dropZone.addEventListener('dragleave', (e) => { | |
if (!this.dropZone.contains(e.relatedTarget)) { | |
this.dropZone.classList.remove('drag-over'); | |
} | |
}); | |
this.dropZone.addEventListener('drop', (e) => { | |
e.preventDefault(); | |
this.dropZone.classList.remove('drag-over'); | |
try { | |
const blockData = JSON.parse(e.dataTransfer.getData('text/plain')); | |
this.addBlock(blockData); | |
} catch (error) { | |
console.error('Error parsing block data:', error); | |
} | |
}); | |
} | |
addBlock(blockData) { | |
this.hideDropHint(); | |
const blockElement = document.createElement('div'); | |
blockElement.innerHTML = blockData.html; | |
const block = blockElement.firstChild; | |
block.classList.add('code-block'); | |
block.draggable = false; | |
const deleteBtn = document.createElement('button'); | |
deleteBtn.className = 'delete-btn'; | |
deleteBtn.innerHTML = 'β'; | |
deleteBtn.addEventListener('click', () => { | |
this.removeBlock(block); | |
}); | |
block.appendChild(deleteBtn); | |
this.dropZone.appendChild(block); | |
this.codeBlocks.push({ | |
element: block, | |
type: blockData.type | |
}); | |
this.addBlockConnector(); | |
} | |
addBlockConnector() { | |
if (this.codeBlocks.length > 1) { | |
const connector = document.createElement('div'); | |
connector.className = 'block-connector'; | |
connector.innerHTML = 'β¬οΈ'; | |
connector.style.cssText = ` | |
text-align: center; | |
font-size: 20px; | |
margin: 5px 0; | |
color: #a0aec0; | |
`; | |
const lastBlock = this.codeBlocks[this.codeBlocks.length - 2].element; | |
lastBlock.insertAdjacentElement('afterend', connector); | |
} | |
} | |
removeBlock(blockElement) { | |
const index = this.codeBlocks.findIndex(block => block.element === blockElement); | |
if (index !== -1) { | |
this.codeBlocks.splice(index, 1); | |
const nextElement = blockElement.nextElementSibling; | |
if (nextElement && nextElement.classList.contains('block-connector')) { | |
nextElement.remove(); | |
} | |
const prevElement = blockElement.previousElementSibling; | |
if (prevElement && prevElement.classList.contains('block-connector') && | |
(!blockElement.nextElementSibling || !blockElement.nextElementSibling.classList.contains('code-block'))) { | |
prevElement.remove(); | |
} | |
blockElement.remove(); | |
if (this.codeBlocks.length === 0) { | |
this.showDropHint(); | |
} | |
} | |
} | |
hideDropHint() { | |
const dropHint = this.dropZone.querySelector('.drop-hint'); | |
if (dropHint) { | |
dropHint.style.display = 'none'; | |
} | |
} | |
showDropHint() { | |
const dropHint = this.dropZone.querySelector('.drop-hint'); | |
if (dropHint) { | |
dropHint.style.display = 'block'; | |
} | |
} | |
setupButtons() { | |
this.runButton.addEventListener('click', () => { | |
if (!this.isRunning) { | |
this.runCode(); | |
} | |
}); | |
this.checkButton.addEventListener('click', () => { | |
if (!this.isRunning) { | |
this.checkAssignment(); | |
} | |
}); | |
this.clearButton.addEventListener('click', () => { | |
this.clearCode(); | |
}); | |
} | |
async runCode() { | |
if (this.codeBlocks.length === 0) { | |
this.showMessage('Add some blocks first! π§©'); | |
return; | |
} | |
this.isRunning = true; | |
this.runButton.disabled = true; | |
this.runButton.querySelector('.button-text').textContent = 'Running...'; | |
try { | |
await this.executeBlocks(); | |
this.sounds.success(); | |
this.showMessage('Great job! π'); | |
} catch (error) { | |
console.error('Error running code:', error); | |
this.showMessage('Oops! Something went wrong π '); | |
} finally { | |
this.isRunning = false; | |
this.runButton.disabled = false; | |
this.runButton.querySelector('.button-text').textContent = 'Play'; | |
} | |
} | |
async executeBlocks() { | |
for (let i = 0; i < this.codeBlocks.length; i++) { | |
const block = this.codeBlocks[i]; | |
block.element.style.outline = '3px solid #4facfe'; | |
block.element.style.boxShadow = '0 0 20px rgba(79, 172, 254, 0.5)'; | |
await this.executeBlock(block); | |
block.element.style.outline = ''; | |
block.element.style.boxShadow = ''; | |
await this.wait(200); | |
} | |
} | |
async executeBlock(block) { | |
switch (block.type) { | |
case 'dance': | |
await this.makeDance(); | |
break; | |
case 'jump': | |
await this.makeJump(); | |
break; | |
case 'wave': | |
await this.makeWave(); | |
break; | |
case 'sound': | |
await this.makeSound(); | |
break; | |
case 'spin': | |
await this.makeSpin(); | |
break; | |
case 'sleep': | |
await this.makeSleep(); | |
break; | |
case 'grow': | |
await this.makeGrow(); | |
break; | |
case 'shrink': | |
await this.makeShrink(); | |
break; | |
case 'color': | |
await this.changeColor(); | |
break; | |
case 'repeat': | |
await this.executeRepeat(block); | |
break; | |
case 'wait': | |
await this.wait(1000); | |
break; | |
} | |
} | |
async executeRepeat(repeatBlock) { | |
const repeatCount = 1; | |
const startIndex = this.codeBlocks.indexOf(repeatBlock); | |
const blocksToRepeat = []; | |
for (let i = startIndex + 1; i < this.codeBlocks.length; i++) { | |
const nextBlock = this.codeBlocks[i]; | |
if (nextBlock.type === 'repeat') break; | |
blocksToRepeat.push(nextBlock); | |
} | |
for (let rep = 0; rep < repeatCount; rep++) { | |
for (const blockToRepeat of blocksToRepeat) { | |
await this.executeBlock(blockToRepeat); | |
await this.wait(200); | |
} | |
} | |
} | |
async makeDance() { | |
this.character.classList.add('dancing'); | |
this.sounds.dance(); | |
await this.wait(1000); | |
this.character.classList.remove('dancing'); | |
} | |
async makeJump() { | |
this.character.classList.add('jumping'); | |
this.sounds.jump(); | |
await this.wait(600); | |
this.character.classList.remove('jumping'); | |
} | |
async makeWave() { | |
this.character.classList.add('waving'); | |
this.sounds.wave(); | |
await this.wait(1000); | |
this.character.classList.remove('waving'); | |
} | |
async makeSound() { | |
this.character.classList.add('bouncing'); | |
this.sounds.sound(); | |
await this.wait(500); | |
this.character.classList.remove('bouncing'); | |
} | |
async makeSpin() { | |
this.character.classList.add('spinning'); | |
this.sounds.spin(); | |
await this.wait(800); | |
this.character.classList.remove('spinning'); | |
} | |
async makeSleep() { | |
this.character.classList.add('sleeping'); | |
this.sounds.sleep(); | |
await this.wait(1200); | |
this.character.classList.remove('sleeping'); | |
} | |
async makeGrow() { | |
this.character.classList.add('growing'); | |
this.sounds.grow(); | |
await this.wait(600); | |
this.character.classList.remove('growing'); | |
} | |
async makeShrink() { | |
this.character.classList.add('shrinking'); | |
this.sounds.shrink(); | |
await this.wait(600); | |
this.character.classList.remove('shrinking'); | |
} | |
async changeColor() { | |
this.currentColorIndex = (this.currentColorIndex + 1) % this.characterColors.length; | |
const characterBody = this.character.querySelector('.character-body'); | |
characterBody.textContent = this.characterColors[this.currentColorIndex]; | |
this.sounds.color(); | |
await this.wait(300); | |
} | |
clearCode() { | |
this.codeBlocks.forEach(block => { | |
block.element.remove(); | |
}); | |
const connectors = this.dropZone.querySelectorAll('.block-connector'); | |
connectors.forEach(connector => connector.remove()); | |
this.codeBlocks = []; | |
this.showDropHint(); | |
this.showMessage('All cleared! Ready for new code π§Ή'); | |
} | |
showMessage(text) { | |
const existingMessage = document.querySelector('.message-popup'); | |
if (existingMessage) { | |
existingMessage.remove(); | |
} | |
const message = document.createElement('div'); | |
message.className = 'message-popup'; | |
message.textContent = text; | |
message.style.cssText = ` | |
position: fixed; | |
top: 50%; | |
left: 50%; | |
transform: translate(-50%, -50%); | |
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); | |
color: white; | |
padding: 20px 30px; | |
border-radius: 20px; | |
font-size: 24px; | |
font-weight: bold; | |
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); | |
z-index: 1000; | |
animation: messagePopup 0.3s ease-out; | |
`; | |
const style = document.createElement('style'); | |
style.textContent = ` | |
@keyframes messagePopup { | |
0% { transform: translate(-50%, -50%) scale(0.5); opacity: 0; } | |
100% { transform: translate(-50%, -50%) scale(1); opacity: 1; } | |
} | |
`; | |
document.head.appendChild(style); | |
document.body.appendChild(message); | |
setTimeout(() => { | |
message.style.animation = 'messagePopup 0.3s ease-out reverse'; | |
setTimeout(() => { | |
message.remove(); | |
style.remove(); | |
}, 300); | |
}, 2000); | |
} | |
setupAssignmentPanel() { | |
const showBtn = document.getElementById('show-assignment'); | |
const closeBtn = document.getElementById('close-assignment'); | |
const prevBtn = document.getElementById('prev-assignment'); | |
const nextBtn = document.getElementById('next-assignment'); | |
const panel = document.getElementById('assignment-panel'); | |
showBtn.addEventListener('click', () => { | |
panel.style.display = 'block'; | |
this.checkButton.style.display = 'inline-flex'; | |
}); | |
closeBtn.addEventListener('click', () => { | |
panel.style.display = 'none'; | |
this.checkButton.style.display = 'none'; | |
}); | |
prevBtn.addEventListener('click', () => { | |
if (this.currentAssignment > 0) { | |
this.currentAssignment--; | |
this.loadAssignment(this.currentAssignment); | |
} | |
}); | |
nextBtn.addEventListener('click', () => { | |
if (this.currentAssignment < this.assignments.length - 1) { | |
this.currentAssignment++; | |
this.loadAssignment(this.currentAssignment); | |
} | |
}); | |
} | |
loadAssignment(index) { | |
const assignment = this.assignments[index]; | |
if (!assignment) return; | |
document.querySelector('.assignment-title').textContent = assignment.title; | |
document.querySelector('.assignment-description').textContent = assignment.description; | |
document.querySelector('.assignment-goal').textContent = `π― Goal: ${assignment.goal}`; | |
document.querySelector('.assignment-counter').textContent = `${index + 1} / ${this.assignments.length}`; | |
const prevBtn = document.getElementById('prev-assignment'); | |
const nextBtn = document.getElementById('next-assignment'); | |
prevBtn.disabled = index === 0; | |
nextBtn.disabled = index === this.assignments.length - 1; | |
this.clearCode(); | |
} | |
checkAssignment() { | |
const assignment = this.assignments[this.currentAssignment]; | |
if (!assignment) return; | |
const currentBlocks = this.codeBlocks.map(block => block.type); | |
let isCorrect = false; | |
if (assignment.flexible && assignment.title.includes('Grand Finale')) { | |
const uniqueActionBlocks = currentBlocks.filter(block => | |
['dance', 'jump', 'wave', 'sound', 'spin', 'sleep', 'grow', 'shrink', 'color'].includes(block) | |
); | |
const uniqueActions = new Set(uniqueActionBlocks); | |
isCorrect = uniqueActions.size >= 4; | |
} else { | |
isCorrect = JSON.stringify(currentBlocks) === JSON.stringify(assignment.solution); | |
} | |
if (isCorrect) { | |
this.sounds.complete(); | |
this.showMessage(`π Amazing! You completed: ${assignment.title}!`); | |
setTimeout(() => { | |
if (this.currentAssignment < this.assignments.length - 1) { | |
this.showMessage('π Ready for the next challenge?'); | |
} else { | |
this.showMessage('π Congratulations! You completed all missions!'); | |
} | |
}, 2500); | |
} else { | |
this.showMessage(`π‘ Hint: ${assignment.hint}`); | |
} | |
} | |
wait(ms) { | |
return new Promise(resolve => setTimeout(resolve, ms)); | |
} | |
} | |
document.addEventListener('DOMContentLoaded', () => { | |
new KidsCodeApp(); | |
}); |