let whiteKeyNotes = ['C', 'D', 'E', 'F', 'G', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'A', 'B'];
let blackKeyNotes = ['C#', 'D#', '', 'F#', 'G#', 'A#', '', 'C#', 'D#', '', 'F#', 'G#', 'A#', ''];
let backgroundEffects = [];
"Twinkle Twinkle": ['C', 'C', 'G', 'G', 'A', 'A', 'G', 'F', 'F', 'E', 'E', 'D', 'D', 'C',
'G', 'G', 'F', 'F', 'E', 'E', 'D', 'G', 'G', 'F', 'F', 'E', 'E', 'D',
'C', 'C', 'G', 'G', 'A', 'A', 'G', 'F', 'F', 'E', 'E', 'D', 'D', 'C'],
"Mary Had a Little Lamb": ['E', 'D', 'C', 'D', 'E', 'E', 'E', 'D', 'D', 'D', 'E', 'G', 'G'],
"Chopsticks": ['C', 'C', 'E', 'E', 'F', 'F', 'E', 'E', 'D', 'D', 'C', 'C',
'C', 'C', 'E', 'E', 'F', 'F', 'E', 'E', 'D', 'D', 'C', 'C'],
"Ode to Joy": ['E', 'E', 'F', 'G', 'G', 'F', 'E', 'D', 'C', 'C', 'D', 'E', 'E', 'D', 'D']
for (let note in noteFiles) {
sounds[note] = loadSound(noteFiles[note]);
let keyWidth = width / whiteKeyNotes.length;
for (let i = 0; i < whiteKeyNotes.length; i++) {
whiteKeys.push(new Key(i * keyWidth, 0, keyWidth, height, whiteKeyNotes[i], 'white'));
for (let i = 0; i < blackKeyNotes.length; i++) {
if (blackKeyNotes[i] !== '') {
blackKeys.push(new Key(i * keyWidth + keyWidth * 0.75, 0, keyWidth * 0.5, height * 0.6, blackKeyNotes[i], 'black'));
for (let i = backgroundEffects.length - 1; i >= 0; i--) {
backgroundEffects[i].update();
backgroundEffects[i].display();
if (backgroundEffects[i].isDead()) {
backgroundEffects.splice(i, 1);
for (let key of whiteKeys) {
for (let key of blackKeys) {
if (gameState === 'start') {
} else if (gameState === 'win') {
} else if (gameState === 'lose') {
if (gameState === 'playing') {
showNextNoteInSequence();
function mousePressed() {
if (gameState === 'start') {
for (let key of [...blackKeys, ...whiteKeys]) {
if (key.contains(mouseX, mouseY)) {
function displayPieceSelection() {
textAlign(CENTER, CENTER);
rect(width / 2 - 180, height / 4 - 30, 360, 60);
text('Choose a Piece to Learn:', width / 2, height / 4);
let yOffset = height / 2;
let pieceNames = Object.keys(pieces);
for (let i = 0; i < pieceNames.length; i++) {
rect(width / 2 - 180, yOffset + (i * 40) - 20, 360, 40);
text(pieceNames[i], width / 2, yOffset + (i * 40));
text('Select a piano piece!', width / 2, height - 30);
let pieceNames = Object.keys(pieces);
let yOffset = height / 2;
for (let i = 0; i < pieceNames.length; i++) {
if (mouseY > (yOffset + (i * 40) - 20) && mouseY < (yOffset + (i * 40) + 20)) {
selectedPiece = pieceNames[i];
gameSequence = pieces[selectedPiece];
function checkAnswer(note) {
if (note === gameSequence[gameIndex]) {
createBackgroundEffect(mouseX, mouseY);
if (gameIndex === gameSequence.length) {
function displayGameInfo() {
textAlign(CENTER, CENTER);
rect(width / 2 - 100, height - 50, 200, 40);
text(`Score: ${score} | Level: ${level}`, width / 2, height - 30);
function showNextNoteInSequence() {
currentNote = gameSequence[gameIndex];
textAlign(CENTER, CENTER);
rect(width / 2 - 100, 50 - 30, 200, 60);
text(`Press: ${currentNote}`, width / 2, 50);
function displayWinScreen() {
textAlign(CENTER, CENTER);
rect(width / 2 - 180, height / 2 - 60, 360, 60);
text('Congratulations! You won!', width / 2, height / 2 - 30);
rect(width / 2 - 100, height / 2 + 10, 200, 40);
text('Click to play again!', width / 2, height / 2 + 30);
function displayLoseScreen() {
textAlign(CENTER, CENTER);
rect(width / 2 - 180, height / 2 - 60, 360, 60);
text('Oops! You lost!', width / 2, height / 2 - 30);
rect(width / 2 - 100, height / 2 + 10, 200, 40);
text('Click to try again!', width / 2, height / 2 + 30);
constructor(x, y, w, h, note, color) {
this.particleSystem = new ParticleSystem();
fill(this.isPressed ? 'lightgray' : this.color);
rect(this.x, this.y, this.w, this.h);
this.particleSystem.emit(this.x + this.w / 2, this.y + this.h / 2);
this.particleSystem.update();
this.particleSystem.display();
return px > this.x && px < this.x + this.w && py > this.y && py < this.h;
sounds[this.note].play();
setTimeout(() => (this.isPressed = false), 200);
this.size = random(10, 20);
this.vy = random(-4, -2);
this.color = color(random(100, 255), random(100, 255), random(100, 255), this.lifespan);
ellipse(this.x, this.y, this.size);
return this.lifespan <= 0;
for (let i = 0; i < 10; i++) {
this.particles.push(new Particle(x, y));
for (let i = this.particles.length - 1; i >= 0; i--) {
let particle = this.particles[i];
this.particles.splice(i, 1);
for (let particle of this.particles) {
function createBackgroundEffect(x, y) {
backgroundEffects.push(new BackgroundEffect(x, y));
this.radius = random(20, 50);
this.color = color(random(50, 255), random(50, 255), random(50, 255), this.lifespan);
this.color.setAlpha(this.lifespan);
ellipse(this.x, this.y, this.radius * 2);
return this.lifespan <= 0;
function displayWinScreen() {
textAlign(CENTER, CENTER);
rect(width / 2 - 180, height / 2 - 60, 360, 60);
text('Congratulations! You won!', width / 2, height / 2 - 30);
let playAgainButton = createButton('Play Again');
playAgainButton.position(width / 2 - 50, height / 2 + 20);
playAgainButton.mousePressed(() => restartGame(playAgainButton));
playAgainButton.style('font-size', '16px');
playAgainButton.style('padding', '10px');
playAgainButton.style('position', 'absolute');
playAgainButton.style('left', `${width / 2 - 50}px`);
playAgainButton.style('top', `${height / 2 + 20}px`);
function displayLoseScreen() {
textAlign(CENTER, CENTER);
rect(width / 2 - 180, height / 2 - 60, 360, 60);
text('Oops! You lost!', width / 2, height / 2 - 30);
let tryAgainButton = createButton('Try Again');
tryAgainButton.position(width / 2 - 50, height / 2 + 20);
tryAgainButton.mousePressed(() => restartGame(tryAgainButton));
tryAgainButton.style('font-size', '16px');
tryAgainButton.style('padding', '10px');
tryAgainButton.style('position', 'absolute');
tryAgainButton.style('left', `${width / 2 - 50}px`);
tryAgainButton.style('top', `${height / 2 + 20}px`);
function restartGame(button) {
let restartButton = null;
function displayWinScreen() {
textAlign(CENTER, CENTER);
rect(width / 2 - 180, height / 2 - 60, 360, 60);
text('Congratulations! You won!', width / 2, height / 2 - 30);
restartButton = createButton('Play Again');
restartButton.style('font-size', '16px');
restartButton.style('padding', '10px');
restartButton.style('position', 'absolute');
restartButton.style('left', `${width / 2 - 50}px`);
restartButton.style('top', `${height / 2 + 20}px`);
restartButton.mousePressed(() => restartGame());
function displayLoseScreen() {
textAlign(CENTER, CENTER);
rect(width / 2 - 180, height / 2 - 60, 360, 60);
text('Oops! You lost!', width / 2, height / 2 - 30);
restartButton = createButton('Try Again');
restartButton.style('font-size', '16px');
restartButton.style('padding', '10px');
restartButton.style('position', 'absolute');
restartButton.style('left', `${width / 2 - 50}px`);
restartButton.style('top', `${height / 2 + 20}px`);
restartButton.mousePressed(() => restartGame());
let keyWidth = width / whiteKeyNotes.length;
for (let i = 0; i < whiteKeyNotes.length; i++) {
whiteKeys.push(new Key(i * keyWidth, 0, keyWidth, height, whiteKeyNotes[i], 'white'));
for (let i = 0; i < blackKeyNotes.length; i++) {
if (blackKeyNotes[i] !== '') {
blackKeys.push(new Key(i * keyWidth + keyWidth * 0.75, 0, keyWidth * 0.5, height * 0.6, blackKeyNotes[i], 'black'));
function displayPieceSelection() {
let boxX = width / 2 - boxWidth / 2;
let boxY = height / 4 - boxHeight / 2;
for (let i = 0; i < boxHeight; i++) {
let inter = map(i, 0, boxHeight, 0, 1);
let c = lerpColor(color(255, 100, 150), color(100, 150, 255), inter);
line(boxX, boxY + i, boxX + boxWidth, boxY + i);
textAlign(CENTER, CENTER);
text('Magic Keys', width / 2, height / 4);
let yOffset = height / 2;
let pieceNames = Object.keys(pieces);
for (let i = 0; i < pieceNames.length; i++) {
rect(width / 2 - 180, yOffset + (i * 40) - 20, 360, 40);
text(pieceNames[i], width / 2, yOffset + (i * 40));
text('Click on a piece name to begin!', width / 2, height - 30);
for (let key of whiteKeys) {
for (let key of blackKeys) {
if (gameState === 'start') {
} else if (gameState === 'win') {
} else if (gameState === 'lose') {
if (gameState === 'playing') {
showNextNoteInSequence();
for (let i = backgroundEffects.length - 1; i >= 0; i--) {
backgroundEffects[i].update();
backgroundEffects[i].display();
if (backgroundEffects[i].isDead()) {
backgroundEffects.splice(i, 1);