/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { currentLevel: 1, unlockedLevels: 1 }); /**** * Classes ****/ var CelestialBody = Container.expand(function (type, noteId, harmonicValue) { var self = Container.call(this); self.type = type; self.noteId = noteId; self.harmonicValue = harmonicValue; self.isPlaced = false; self.currentGridPosition = null; // Create the visual representation self.visual = self.attachAsset(type, { anchorX: 0.5, anchorY: 0.5 }); // Add pulsing effect self.pulseScale = 1; self.pulseDirection = 1; self.pulseSpeed = 0.01; self.pulseMin = 0.9; self.pulseMax = 1.1; // When a celestial body is pressed self.down = function (x, y, obj) { if (!self.isPlaced) { // If not placed, prepare to drag draggedBody = self; isDragging = true; // Visual feedback tween(self.visual, { scaleX: 1.2, scaleY: 1.2 }, { duration: 200, easing: tween.easeOut }); // Play the note LK.getSound(self.noteId).play(); } else if (gameState === "playing") { // If already placed, pick it up var oldGrid = self.currentGridPosition; if (oldGrid) { gameGrid[oldGrid.row][oldGrid.col].occupiedBy = null; } self.isPlaced = false; self.currentGridPosition = null; draggedBody = self; isDragging = true; // Visual feedback tween(self.visual, { scaleX: 1.2, scaleY: 1.2 }, { duration: 200, easing: tween.easeOut }); // Update harmonics checkHarmonics(); } }; // Play the celestial body's note self.playNote = function () { LK.getSound(self.noteId).play(); }; // Update visual effects self.update = function () { if (!isDragging || draggedBody !== self) { // Pulsing animation when not being dragged self.pulseScale += self.pulseDirection * self.pulseSpeed; if (self.pulseScale > self.pulseMax) { self.pulseScale = self.pulseMax; self.pulseDirection = -1; } else if (self.pulseScale < self.pulseMin) { self.pulseScale = self.pulseMin; self.pulseDirection = 1; } self.visual.scale.set(self.pulseScale); } }; return self; }); var GridCell = Container.expand(function (row, col) { var self = Container.call(this); self.row = row; self.col = col; self.occupiedBy = null; self.isHarmonic = false; self.harmonicLines = []; // Create the cell background self.background = self.attachAsset('gridCell', { anchorX: 0.5, anchorY: 0.5, alpha: 0.3 }); // Highlight effect for valid placement self.highlight = self.attachAsset('selectedOutline', { anchorX: 0.5, anchorY: 0.5, alpha: 0 }); // Show highlight when cell is valid self.showHighlight = function (isValid) { if (isValid) { tween(self.highlight, { alpha: 0.6 }, { duration: 200, easing: tween.linear }); } else { tween(self.highlight, { alpha: 0 }, { duration: 200, easing: tween.linear }); } }; // Highlight cell when part of a harmonic self.setHarmonic = function (isHarmonic) { self.isHarmonic = isHarmonic; if (isHarmonic) { tween(self.background, { alpha: 0.6, tint: 0x2ecc71 }, { duration: 300, easing: tween.easeOut }); } else { tween(self.background, { alpha: 0.3, tint: 0xffffff }, { duration: 300, easing: tween.easeOut }); } }; return self; }); var HarmonicLine = Container.expand(function (startCell, endCell, harmonyStrength) { var self = Container.call(this); self.startCell = startCell; self.endCell = endCell; self.harmonyStrength = harmonyStrength || 1; // Create the visual line self.line = self.attachAsset('harmonicLine', { anchorX: 0.5, anchorY: 0 }); // Calculate the position and rotation self.updatePosition = function () { if (!startCell || !endCell) { return; } // Get the global positions of cells var start = game.toLocal(startCell.parent.toGlobal(startCell.position)); var end = game.toLocal(endCell.parent.toGlobal(endCell.position)); // Position at start self.x = start.x; self.y = start.y; // Calculate distance between points var dx = end.x - start.x; var dy = end.y - start.y; var distance = Math.sqrt(dx * dx + dy * dy); // Set the line length self.line.height = distance; // Calculate rotation (in radians) var angle = Math.atan2(dy, dx); self.rotation = angle + Math.PI / 2; // Adjust for line being vertical by default // Set the line alpha based on harmony strength self.line.alpha = 0.2 + self.harmonyStrength * 0.6; // Set color based on harmony type if (self.harmonyStrength > 0.8) { self.line.tint = 0xf1c40f; // Gold for strong harmony } else if (self.harmonyStrength > 0.4) { self.line.tint = 0x2ecc71; // Green for medium harmony } else { self.line.tint = 0x3498db; // Blue for light harmony } }; // Initialize the position self.updatePosition(); return self; }); var MusicNode = Container.expand(function (x, y, size) { var self = Container.call(this); self.x = x; self.y = y; self.activated = false; // Create the visual node self.visual = self.attachAsset('musicNode', { anchorX: 0.5, anchorY: 0.5, scaleX: size || 1, scaleY: size || 1, alpha: 0.5 }); // Pulsing effect self.pulseScale = 1; self.pulseDirection = 1; self.pulseSpeed = 0.02; // Activate node self.activate = function () { if (!self.activated) { self.activated = true; tween(self.visual, { alpha: 1, tint: 0xf1c40f }, { duration: 500, easing: tween.easeOut }); } }; // Deactivate node self.deactivate = function () { if (self.activated) { self.activated = false; tween(self.visual, { alpha: 0.5, tint: 0xffffff }, { duration: 500, easing: tween.easeOut }); } }; // Update visual effects self.update = function () { if (self.activated) { self.pulseScale += self.pulseDirection * self.pulseSpeed; if (self.pulseScale > 1.3) { self.pulseScale = 1.3; self.pulseDirection = -1; } else if (self.pulseScale < 0.7) { self.pulseScale = 0.7; self.pulseDirection = 1; } self.visual.scale.set(self.pulseScale); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x121b2c }); /**** * Game Code ****/ // Game state var gameState = "start"; var currentLevel = storage.currentLevel || 1; var maxLevel = 10; var draggedBody = null; var isDragging = false; var harmonics = []; var musicNodes = []; var activatedNodes = 0; var totalNodes = 0; var celestialBodies = []; var gameGrid = []; var gridSize = 4; // 4x4 grid var cellSize = 180; var gridOffsetX = 0; var gridOffsetY = 0; // UI Elements var levelText = new Text2('Level ' + currentLevel, { size: 70, fill: 0xFFFFFF }); levelText.anchor.set(0.5, 0); LK.gui.top.addChild(levelText); var progressText = new Text2('Harmonics: 0/0', { size: 50, fill: 0xFFFFFF }); progressText.anchor.set(0.5, 0); progressText.y = 100; LK.gui.top.addChild(progressText); var instructionText = new Text2('Arrange celestial bodies to create harmonies', { size: 40, fill: 0xFFFFFF }); instructionText.anchor.set(0.5, 1); LK.gui.bottom.addChild(instructionText); // Initialize game function initGame() { gameState = "playing"; // Clear any existing elements clearLevel(); // Calculate grid offset to center it gridOffsetX = (2048 - gridSize * cellSize) / 2; gridOffsetY = (2732 - gridSize * cellSize) / 2 - 100; // Adjust for header // Create grid createGrid(); // Load level loadLevel(currentLevel); // Play background music LK.playMusic('cosmic_ambient'); } // Create the game grid function createGrid() { for (var row = 0; row < gridSize; row++) { gameGrid[row] = []; for (var col = 0; col < gridSize; col++) { var cell = new GridCell(row, col); cell.x = gridOffsetX + col * cellSize + cellSize / 2; cell.y = gridOffsetY + row * cellSize + cellSize / 2; gameGrid[row][col] = cell; game.addChild(cell); } } } // Clear all level elements function clearLevel() { // Remove all celestial bodies for (var i = 0; i < celestialBodies.length; i++) { if (celestialBodies[i].parent) { celestialBodies[i].parent.removeChild(celestialBodies[i]); } } celestialBodies = []; // Remove all grid cells for (var row = 0; row < gameGrid.length; row++) { if (gameGrid[row]) { for (var col = 0; col < gameGrid[row].length; col++) { if (gameGrid[row][col] && gameGrid[row][col].parent) { gameGrid[row][col].parent.removeChild(gameGrid[row][col]); } } } } gameGrid = []; // Remove all harmonic lines for (var i = 0; i < harmonics.length; i++) { if (harmonics[i].parent) { harmonics[i].parent.removeChild(harmonics[i]); } } harmonics = []; // Remove all music nodes for (var i = 0; i < musicNodes.length; i++) { if (musicNodes[i].parent) { musicNodes[i].parent.removeChild(musicNodes[i]); } } musicNodes = []; // Reset counters activatedNodes = 0; totalNodes = 0; } // Load a specific level function loadLevel(level) { // Update level text levelText.setText('Level ' + level); // Define different level configurations switch (level) { case 1: // Tutorial level - Simple harmony with stars createCelestialBody('star', 'star_note', 1, 400, 300); createCelestialBody('star', 'star_note', 1, 400, 450); // Create music nodes to activate createMusicNode(gridOffsetX + cellSize * 0.5 + cellSize * 1, gridOffsetY + cellSize * 0.5 + cellSize * 1, 1); createMusicNode(gridOffsetX + cellSize * 0.5 + cellSize * 2, gridOffsetY + cellSize * 0.5 + cellSize * 2, 1); updateProgressText(); instructionText.setText('Place stars to create a diagonal harmony'); break; case 2: // Introduce planets createCelestialBody('star', 'star_note', 1, 400, 300); createCelestialBody('planet', 'planet_note', 2, 600, 300); createCelestialBody('star', 'star_note', 1, 400, 500); // Create music nodes createMusicNode(gridOffsetX + cellSize * 0.5 + cellSize * 0, gridOffsetY + cellSize * 0.5 + cellSize * 0, 1); createMusicNode(gridOffsetX + cellSize * 0.5 + cellSize * 1, gridOffsetY + cellSize * 0.5 + cellSize * 1, 1); createMusicNode(gridOffsetX + cellSize * 0.5 + cellSize * 2, gridOffsetY + cellSize * 0.5 + cellSize * 2, 1); updateProgressText(); instructionText.setText('Create a diagonal harmony with star-planet-star'); break; case 3: // Introduce moons and more complex harmony createCelestialBody('star', 'star_note', 1, 400, 300); createCelestialBody('planet', 'planet_note', 2, 600, 300); createCelestialBody('moon', 'moon_note', 3, 800, 300); createCelestialBody('star', 'star_note', 1, 1000, 300); // Create music nodes createMusicNode(gridOffsetX + cellSize * 0.5 + cellSize * 0, gridOffsetY + cellSize * 0.5 + cellSize * 0, 1); createMusicNode(gridOffsetX + cellSize * 0.5 + cellSize * 1, gridOffsetY + cellSize * 0.5 + cellSize * 1, 1); createMusicNode(gridOffsetX + cellSize * 0.5 + cellSize * 2, gridOffsetY + cellSize * 0.5 + cellSize * 0, 1); createMusicNode(gridOffsetX + cellSize * 0.5 + cellSize * 3, gridOffsetY + cellSize * 0.5 + cellSize * 1, 1); updateProgressText(); instructionText.setText('Create a zigzag harmony pattern'); break; // Additional levels can be added here default: // Generate a random level if beyond our defined levels generateRandomLevel(level); break; } } // Generate a random level based on difficulty function generateRandomLevel(level) { // Scale difficulty based on level var numBodies = Math.min(3 + Math.floor(level / 2), 8); var numNodes = Math.min(level + 2, 12); // Create a mix of celestial bodies for (var i = 0; i < numBodies; i++) { var type, noteId, value; var rand = Math.random(); if (rand < 0.4) { type = 'star'; noteId = 'star_note'; value = 1; } else if (rand < 0.8) { type = 'planet'; noteId = 'planet_note'; value = 2; } else { type = 'moon'; noteId = 'moon_note'; value = 3; } // Place bodies on left side of screen var x = 300 + Math.random() * 300; var y = 500 + i * 200; createCelestialBody(type, noteId, value, x, y); } // Create music nodes in interesting patterns for (var i = 0; i < numNodes; i++) { var row, col; if (level % 3 === 0) { // Circular pattern var angle = i / numNodes * Math.PI * 2; var radius = gridSize * 0.3; row = Math.floor(gridSize / 2 + Math.cos(angle) * radius); col = Math.floor(gridSize / 2 + Math.sin(angle) * radius); } else if (level % 3 === 1) { // Spiral pattern var angle = i / numNodes * Math.PI * 4; var radius = i / numNodes * gridSize * 0.4; row = Math.floor(gridSize / 2 + Math.cos(angle) * radius); col = Math.floor(gridSize / 2 + Math.sin(angle) * radius); } else { // Random with some structure row = Math.floor(Math.random() * gridSize); col = Math.floor(Math.random() * gridSize); } // Keep within grid bounds row = Math.max(0, Math.min(gridSize - 1, row)); col = Math.max(0, Math.min(gridSize - 1, col)); createMusicNode(gridOffsetX + cellSize * 0.5 + cellSize * col, gridOffsetY + cellSize * 0.5 + cellSize * row, 0.8 + Math.random() * 0.4); } updateProgressText(); instructionText.setText('Create cosmic harmonies to complete the level'); } // Create a celestial body function createCelestialBody(type, noteId, harmonicValue, x, y) { var body = new CelestialBody(type, noteId, harmonicValue); body.x = x; body.y = y; celestialBodies.push(body); game.addChild(body); return body; } // Create a music node function createMusicNode(x, y, size) { var node = new MusicNode(x, y, size); musicNodes.push(node); game.addChild(node); totalNodes++; updateProgressText(); return node; } // Update the progress text function updateProgressText() { progressText.setText('Harmonics: ' + activatedNodes + '/' + totalNodes); } // Find the nearest grid cell to a point function findNearestGridCell(x, y) { var closestCell = null; var closestDistance = Number.MAX_VALUE; for (var row = 0; row < gridSize; row++) { for (var col = 0; col < gridSize; col++) { var cell = gameGrid[row][col]; var dx = cell.x - x; var dy = cell.y - y; var distance = dx * dx + dy * dy; if (distance < closestDistance) { closestDistance = distance; closestCell = cell; } } } // Only return the cell if it's close enough if (closestDistance < cellSize * cellSize / 4) { return closestCell; } return null; } // Highlight valid grid cells for placement function highlightValidCells() { if (!draggedBody) { return; } for (var row = 0; row < gridSize; row++) { for (var col = 0; col < gridSize; col++) { var cell = gameGrid[row][col]; var isValid = cell.occupiedBy === null; cell.showHighlight(isValid); } } } // Clear all cell highlights function clearCellHighlights() { for (var row = 0; row < gridSize; row++) { for (var col = 0; col < gridSize; col++) { gameGrid[row][col].showHighlight(false); } } } // Place a celestial body on a grid cell function placeCelestialBody(body, cell) { if (!body || !cell) { return false; } // Check if cell is already occupied if (cell.occupiedBy !== null) { // Play error sound LK.getSound('incorrect_placement').play(); return false; } // Place the body cell.occupiedBy = body; body.isPlaced = true; body.currentGridPosition = { row: cell.row, col: cell.col }; // Move body to cell position tween(body, { x: cell.x, y: cell.y }, { duration: 300, easing: tween.easeOut }); // Scale back to normal tween(body.visual, { scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.easeOut }); // Play the note body.playNote(); // Check for harmonics checkHarmonics(); return true; } // Check for harmonic patterns on the grid function checkHarmonics() { // Clear existing harmonics for (var i = 0; i < harmonics.length; i++) { if (harmonics[i].parent) { harmonics[i].parent.removeChild(harmonics[i]); } } harmonics = []; // Reset all cells and nodes for (var row = 0; row < gridSize; row++) { for (var col = 0; col < gridSize; col++) { gameGrid[row][col].setHarmonic(false); } } for (var i = 0; i < musicNodes.length; i++) { musicNodes[i].deactivate(); } activatedNodes = 0; // Check for harmonic patterns checkLineHarmonics(); checkDiagonalHarmonics(); // Update progress updateProgressText(); // Check for level completion if (activatedNodes >= totalNodes) { levelComplete(); } } // Check for harmonic patterns in straight lines function checkLineHarmonics() { // Check rows for (var row = 0; row < gridSize; row++) { var rowBodies = []; for (var col = 0; col < gridSize; col++) { if (gameGrid[row][col].occupiedBy) { rowBodies.push({ cell: gameGrid[row][col], body: gameGrid[row][col].occupiedBy }); } } processHarmonicLine(rowBodies); } // Check columns for (var col = 0; col < gridSize; col++) { var colBodies = []; for (var row = 0; row < gridSize; row++) { if (gameGrid[row][col].occupiedBy) { colBodies.push({ cell: gameGrid[row][col], body: gameGrid[row][col].occupiedBy }); } } processHarmonicLine(colBodies); } } // Check for harmonic patterns in diagonals function checkDiagonalHarmonics() { // Check main diagonal (top-left to bottom-right) var mainDiagBodies = []; for (var i = 0; i < gridSize; i++) { if (gameGrid[i][i].occupiedBy) { mainDiagBodies.push({ cell: gameGrid[i][i], body: gameGrid[i][i].occupiedBy }); } } processHarmonicLine(mainDiagBodies); // Check other diagonal (top-right to bottom-left) var otherDiagBodies = []; for (var i = 0; i < gridSize; i++) { if (gameGrid[i][gridSize - 1 - i].occupiedBy) { otherDiagBodies.push({ cell: gameGrid[i][gridSize - 1 - i], body: gameGrid[i][gridSize - 1 - i].occupiedBy }); } } processHarmonicLine(otherDiagBodies); } // Process a potential harmonic line function processHarmonicLine(bodies) { if (bodies.length < 2) { return; } // Need at least 2 bodies to form a harmony // Different harmonic patterns var isAscending = true; var isDescending = true; var isSame = true; var lastValue = bodies[0].body.harmonicValue; for (var i = 1; i < bodies.length; i++) { var currentValue = bodies[i].body.harmonicValue; if (currentValue <= lastValue) { isAscending = false; } if (currentValue >= lastValue) { isDescending = false; } if (currentValue !== lastValue) { isSame = false; } lastValue = currentValue; } // Check if any harmonic pattern is formed var isHarmonic = isAscending || isDescending || isSame; if (isHarmonic && bodies.length >= 2) { // Create visual connections for (var i = 1; i < bodies.length; i++) { var harmonyStrength = 0.3 + bodies.length / 10; var line = new HarmonicLine(bodies[i - 1].cell, bodies[i].cell, harmonyStrength); harmonics.push(line); game.addChild(line); } // Mark cells as harmonic for (var i = 0; i < bodies.length; i++) { bodies[i].cell.setHarmonic(true); } // Play harmony complete sound for new harmonics LK.getSound('harmony_complete').play(); // Activate nearby music nodes activateNearbyMusicNodes(bodies); } } // Activate music nodes near harmonic lines function activateNearbyMusicNodes(harmonicCells) { for (var i = 0; i < musicNodes.length; i++) { var node = musicNodes[i]; // Skip already activated nodes if (node.activated) { continue; } // Check if node is near any cell in the harmonic for (var j = 0; j < harmonicCells.length; j++) { var cell = harmonicCells[j].cell; var dx = node.x - cell.x; var dy = node.y - cell.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < cellSize * 0.8) { node.activate(); activatedNodes++; break; } } } } // Level complete function function levelComplete() { // Play completion sound LK.getSound('level_complete').play(); // Update game state gameState = "levelComplete"; // Update storage var nextLevel = currentLevel + 1; if (nextLevel > storage.unlockedLevels) { storage.unlockedLevels = nextLevel; } // Show level complete message instructionText.setText('Level Complete! Well done!'); // Move to next level after a delay LK.setTimeout(function () { if (nextLevel > maxLevel) { // Game completed! LK.showYouWin(); } else { // Load next level currentLevel = nextLevel; storage.currentLevel = currentLevel; initGame(); } }, 2000); } // Handle mouse/touch movement function handleMove(x, y, obj) { if (isDragging && draggedBody) { // Move the dragged body draggedBody.x = x; draggedBody.y = y; // Highlight valid cells highlightValidCells(); } } // Game event handlers game.down = function (x, y, obj) { // This is handled by the celestial bodies themselves }; game.move = handleMove; game.up = function (x, y, obj) { if (isDragging && draggedBody) { // Find the nearest grid cell var nearestCell = findNearestGridCell(x, y); // Try to place the body if (nearestCell) { placeCelestialBody(draggedBody, nearestCell); } else { // If not on a valid cell, return to original position or reset if (draggedBody.currentGridPosition) { var oldCell = gameGrid[draggedBody.currentGridPosition.row][draggedBody.currentGridPosition.col]; tween(draggedBody, { x: oldCell.x, y: oldCell.y }, { duration: 300, easing: tween.easeOut }); oldCell.occupiedBy = draggedBody; draggedBody.isPlaced = true; } else { // Return to starting position if no valid cell tween(draggedBody.visual, { scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.easeOut }); } } // Clear highlights clearCellHighlights(); // Reset dragging state isDragging = false; draggedBody = null; } }; // Main game update function game.update = function () { // Update all celestial bodies for (var i = 0; i < celestialBodies.length; i++) { celestialBodies[i].update(); } // Update all music nodes for (var i = 0; i < musicNodes.length; i++) { musicNodes[i].update(); } }; // Initialize the game initGame();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
currentLevel: 1,
unlockedLevels: 1
});
/****
* Classes
****/
var CelestialBody = Container.expand(function (type, noteId, harmonicValue) {
var self = Container.call(this);
self.type = type;
self.noteId = noteId;
self.harmonicValue = harmonicValue;
self.isPlaced = false;
self.currentGridPosition = null;
// Create the visual representation
self.visual = self.attachAsset(type, {
anchorX: 0.5,
anchorY: 0.5
});
// Add pulsing effect
self.pulseScale = 1;
self.pulseDirection = 1;
self.pulseSpeed = 0.01;
self.pulseMin = 0.9;
self.pulseMax = 1.1;
// When a celestial body is pressed
self.down = function (x, y, obj) {
if (!self.isPlaced) {
// If not placed, prepare to drag
draggedBody = self;
isDragging = true;
// Visual feedback
tween(self.visual, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeOut
});
// Play the note
LK.getSound(self.noteId).play();
} else if (gameState === "playing") {
// If already placed, pick it up
var oldGrid = self.currentGridPosition;
if (oldGrid) {
gameGrid[oldGrid.row][oldGrid.col].occupiedBy = null;
}
self.isPlaced = false;
self.currentGridPosition = null;
draggedBody = self;
isDragging = true;
// Visual feedback
tween(self.visual, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeOut
});
// Update harmonics
checkHarmonics();
}
};
// Play the celestial body's note
self.playNote = function () {
LK.getSound(self.noteId).play();
};
// Update visual effects
self.update = function () {
if (!isDragging || draggedBody !== self) {
// Pulsing animation when not being dragged
self.pulseScale += self.pulseDirection * self.pulseSpeed;
if (self.pulseScale > self.pulseMax) {
self.pulseScale = self.pulseMax;
self.pulseDirection = -1;
} else if (self.pulseScale < self.pulseMin) {
self.pulseScale = self.pulseMin;
self.pulseDirection = 1;
}
self.visual.scale.set(self.pulseScale);
}
};
return self;
});
var GridCell = Container.expand(function (row, col) {
var self = Container.call(this);
self.row = row;
self.col = col;
self.occupiedBy = null;
self.isHarmonic = false;
self.harmonicLines = [];
// Create the cell background
self.background = self.attachAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3
});
// Highlight effect for valid placement
self.highlight = self.attachAsset('selectedOutline', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
// Show highlight when cell is valid
self.showHighlight = function (isValid) {
if (isValid) {
tween(self.highlight, {
alpha: 0.6
}, {
duration: 200,
easing: tween.linear
});
} else {
tween(self.highlight, {
alpha: 0
}, {
duration: 200,
easing: tween.linear
});
}
};
// Highlight cell when part of a harmonic
self.setHarmonic = function (isHarmonic) {
self.isHarmonic = isHarmonic;
if (isHarmonic) {
tween(self.background, {
alpha: 0.6,
tint: 0x2ecc71
}, {
duration: 300,
easing: tween.easeOut
});
} else {
tween(self.background, {
alpha: 0.3,
tint: 0xffffff
}, {
duration: 300,
easing: tween.easeOut
});
}
};
return self;
});
var HarmonicLine = Container.expand(function (startCell, endCell, harmonyStrength) {
var self = Container.call(this);
self.startCell = startCell;
self.endCell = endCell;
self.harmonyStrength = harmonyStrength || 1;
// Create the visual line
self.line = self.attachAsset('harmonicLine', {
anchorX: 0.5,
anchorY: 0
});
// Calculate the position and rotation
self.updatePosition = function () {
if (!startCell || !endCell) {
return;
}
// Get the global positions of cells
var start = game.toLocal(startCell.parent.toGlobal(startCell.position));
var end = game.toLocal(endCell.parent.toGlobal(endCell.position));
// Position at start
self.x = start.x;
self.y = start.y;
// Calculate distance between points
var dx = end.x - start.x;
var dy = end.y - start.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Set the line length
self.line.height = distance;
// Calculate rotation (in radians)
var angle = Math.atan2(dy, dx);
self.rotation = angle + Math.PI / 2; // Adjust for line being vertical by default
// Set the line alpha based on harmony strength
self.line.alpha = 0.2 + self.harmonyStrength * 0.6;
// Set color based on harmony type
if (self.harmonyStrength > 0.8) {
self.line.tint = 0xf1c40f; // Gold for strong harmony
} else if (self.harmonyStrength > 0.4) {
self.line.tint = 0x2ecc71; // Green for medium harmony
} else {
self.line.tint = 0x3498db; // Blue for light harmony
}
};
// Initialize the position
self.updatePosition();
return self;
});
var MusicNode = Container.expand(function (x, y, size) {
var self = Container.call(this);
self.x = x;
self.y = y;
self.activated = false;
// Create the visual node
self.visual = self.attachAsset('musicNode', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: size || 1,
scaleY: size || 1,
alpha: 0.5
});
// Pulsing effect
self.pulseScale = 1;
self.pulseDirection = 1;
self.pulseSpeed = 0.02;
// Activate node
self.activate = function () {
if (!self.activated) {
self.activated = true;
tween(self.visual, {
alpha: 1,
tint: 0xf1c40f
}, {
duration: 500,
easing: tween.easeOut
});
}
};
// Deactivate node
self.deactivate = function () {
if (self.activated) {
self.activated = false;
tween(self.visual, {
alpha: 0.5,
tint: 0xffffff
}, {
duration: 500,
easing: tween.easeOut
});
}
};
// Update visual effects
self.update = function () {
if (self.activated) {
self.pulseScale += self.pulseDirection * self.pulseSpeed;
if (self.pulseScale > 1.3) {
self.pulseScale = 1.3;
self.pulseDirection = -1;
} else if (self.pulseScale < 0.7) {
self.pulseScale = 0.7;
self.pulseDirection = 1;
}
self.visual.scale.set(self.pulseScale);
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x121b2c
});
/****
* Game Code
****/
// Game state
var gameState = "start";
var currentLevel = storage.currentLevel || 1;
var maxLevel = 10;
var draggedBody = null;
var isDragging = false;
var harmonics = [];
var musicNodes = [];
var activatedNodes = 0;
var totalNodes = 0;
var celestialBodies = [];
var gameGrid = [];
var gridSize = 4; // 4x4 grid
var cellSize = 180;
var gridOffsetX = 0;
var gridOffsetY = 0;
// UI Elements
var levelText = new Text2('Level ' + currentLevel, {
size: 70,
fill: 0xFFFFFF
});
levelText.anchor.set(0.5, 0);
LK.gui.top.addChild(levelText);
var progressText = new Text2('Harmonics: 0/0', {
size: 50,
fill: 0xFFFFFF
});
progressText.anchor.set(0.5, 0);
progressText.y = 100;
LK.gui.top.addChild(progressText);
var instructionText = new Text2('Arrange celestial bodies to create harmonies', {
size: 40,
fill: 0xFFFFFF
});
instructionText.anchor.set(0.5, 1);
LK.gui.bottom.addChild(instructionText);
// Initialize game
function initGame() {
gameState = "playing";
// Clear any existing elements
clearLevel();
// Calculate grid offset to center it
gridOffsetX = (2048 - gridSize * cellSize) / 2;
gridOffsetY = (2732 - gridSize * cellSize) / 2 - 100; // Adjust for header
// Create grid
createGrid();
// Load level
loadLevel(currentLevel);
// Play background music
LK.playMusic('cosmic_ambient');
}
// Create the game grid
function createGrid() {
for (var row = 0; row < gridSize; row++) {
gameGrid[row] = [];
for (var col = 0; col < gridSize; col++) {
var cell = new GridCell(row, col);
cell.x = gridOffsetX + col * cellSize + cellSize / 2;
cell.y = gridOffsetY + row * cellSize + cellSize / 2;
gameGrid[row][col] = cell;
game.addChild(cell);
}
}
}
// Clear all level elements
function clearLevel() {
// Remove all celestial bodies
for (var i = 0; i < celestialBodies.length; i++) {
if (celestialBodies[i].parent) {
celestialBodies[i].parent.removeChild(celestialBodies[i]);
}
}
celestialBodies = [];
// Remove all grid cells
for (var row = 0; row < gameGrid.length; row++) {
if (gameGrid[row]) {
for (var col = 0; col < gameGrid[row].length; col++) {
if (gameGrid[row][col] && gameGrid[row][col].parent) {
gameGrid[row][col].parent.removeChild(gameGrid[row][col]);
}
}
}
}
gameGrid = [];
// Remove all harmonic lines
for (var i = 0; i < harmonics.length; i++) {
if (harmonics[i].parent) {
harmonics[i].parent.removeChild(harmonics[i]);
}
}
harmonics = [];
// Remove all music nodes
for (var i = 0; i < musicNodes.length; i++) {
if (musicNodes[i].parent) {
musicNodes[i].parent.removeChild(musicNodes[i]);
}
}
musicNodes = [];
// Reset counters
activatedNodes = 0;
totalNodes = 0;
}
// Load a specific level
function loadLevel(level) {
// Update level text
levelText.setText('Level ' + level);
// Define different level configurations
switch (level) {
case 1:
// Tutorial level - Simple harmony with stars
createCelestialBody('star', 'star_note', 1, 400, 300);
createCelestialBody('star', 'star_note', 1, 400, 450);
// Create music nodes to activate
createMusicNode(gridOffsetX + cellSize * 0.5 + cellSize * 1, gridOffsetY + cellSize * 0.5 + cellSize * 1, 1);
createMusicNode(gridOffsetX + cellSize * 0.5 + cellSize * 2, gridOffsetY + cellSize * 0.5 + cellSize * 2, 1);
updateProgressText();
instructionText.setText('Place stars to create a diagonal harmony');
break;
case 2:
// Introduce planets
createCelestialBody('star', 'star_note', 1, 400, 300);
createCelestialBody('planet', 'planet_note', 2, 600, 300);
createCelestialBody('star', 'star_note', 1, 400, 500);
// Create music nodes
createMusicNode(gridOffsetX + cellSize * 0.5 + cellSize * 0, gridOffsetY + cellSize * 0.5 + cellSize * 0, 1);
createMusicNode(gridOffsetX + cellSize * 0.5 + cellSize * 1, gridOffsetY + cellSize * 0.5 + cellSize * 1, 1);
createMusicNode(gridOffsetX + cellSize * 0.5 + cellSize * 2, gridOffsetY + cellSize * 0.5 + cellSize * 2, 1);
updateProgressText();
instructionText.setText('Create a diagonal harmony with star-planet-star');
break;
case 3:
// Introduce moons and more complex harmony
createCelestialBody('star', 'star_note', 1, 400, 300);
createCelestialBody('planet', 'planet_note', 2, 600, 300);
createCelestialBody('moon', 'moon_note', 3, 800, 300);
createCelestialBody('star', 'star_note', 1, 1000, 300);
// Create music nodes
createMusicNode(gridOffsetX + cellSize * 0.5 + cellSize * 0, gridOffsetY + cellSize * 0.5 + cellSize * 0, 1);
createMusicNode(gridOffsetX + cellSize * 0.5 + cellSize * 1, gridOffsetY + cellSize * 0.5 + cellSize * 1, 1);
createMusicNode(gridOffsetX + cellSize * 0.5 + cellSize * 2, gridOffsetY + cellSize * 0.5 + cellSize * 0, 1);
createMusicNode(gridOffsetX + cellSize * 0.5 + cellSize * 3, gridOffsetY + cellSize * 0.5 + cellSize * 1, 1);
updateProgressText();
instructionText.setText('Create a zigzag harmony pattern');
break;
// Additional levels can be added here
default:
// Generate a random level if beyond our defined levels
generateRandomLevel(level);
break;
}
}
// Generate a random level based on difficulty
function generateRandomLevel(level) {
// Scale difficulty based on level
var numBodies = Math.min(3 + Math.floor(level / 2), 8);
var numNodes = Math.min(level + 2, 12);
// Create a mix of celestial bodies
for (var i = 0; i < numBodies; i++) {
var type, noteId, value;
var rand = Math.random();
if (rand < 0.4) {
type = 'star';
noteId = 'star_note';
value = 1;
} else if (rand < 0.8) {
type = 'planet';
noteId = 'planet_note';
value = 2;
} else {
type = 'moon';
noteId = 'moon_note';
value = 3;
}
// Place bodies on left side of screen
var x = 300 + Math.random() * 300;
var y = 500 + i * 200;
createCelestialBody(type, noteId, value, x, y);
}
// Create music nodes in interesting patterns
for (var i = 0; i < numNodes; i++) {
var row, col;
if (level % 3 === 0) {
// Circular pattern
var angle = i / numNodes * Math.PI * 2;
var radius = gridSize * 0.3;
row = Math.floor(gridSize / 2 + Math.cos(angle) * radius);
col = Math.floor(gridSize / 2 + Math.sin(angle) * radius);
} else if (level % 3 === 1) {
// Spiral pattern
var angle = i / numNodes * Math.PI * 4;
var radius = i / numNodes * gridSize * 0.4;
row = Math.floor(gridSize / 2 + Math.cos(angle) * radius);
col = Math.floor(gridSize / 2 + Math.sin(angle) * radius);
} else {
// Random with some structure
row = Math.floor(Math.random() * gridSize);
col = Math.floor(Math.random() * gridSize);
}
// Keep within grid bounds
row = Math.max(0, Math.min(gridSize - 1, row));
col = Math.max(0, Math.min(gridSize - 1, col));
createMusicNode(gridOffsetX + cellSize * 0.5 + cellSize * col, gridOffsetY + cellSize * 0.5 + cellSize * row, 0.8 + Math.random() * 0.4);
}
updateProgressText();
instructionText.setText('Create cosmic harmonies to complete the level');
}
// Create a celestial body
function createCelestialBody(type, noteId, harmonicValue, x, y) {
var body = new CelestialBody(type, noteId, harmonicValue);
body.x = x;
body.y = y;
celestialBodies.push(body);
game.addChild(body);
return body;
}
// Create a music node
function createMusicNode(x, y, size) {
var node = new MusicNode(x, y, size);
musicNodes.push(node);
game.addChild(node);
totalNodes++;
updateProgressText();
return node;
}
// Update the progress text
function updateProgressText() {
progressText.setText('Harmonics: ' + activatedNodes + '/' + totalNodes);
}
// Find the nearest grid cell to a point
function findNearestGridCell(x, y) {
var closestCell = null;
var closestDistance = Number.MAX_VALUE;
for (var row = 0; row < gridSize; row++) {
for (var col = 0; col < gridSize; col++) {
var cell = gameGrid[row][col];
var dx = cell.x - x;
var dy = cell.y - y;
var distance = dx * dx + dy * dy;
if (distance < closestDistance) {
closestDistance = distance;
closestCell = cell;
}
}
}
// Only return the cell if it's close enough
if (closestDistance < cellSize * cellSize / 4) {
return closestCell;
}
return null;
}
// Highlight valid grid cells for placement
function highlightValidCells() {
if (!draggedBody) {
return;
}
for (var row = 0; row < gridSize; row++) {
for (var col = 0; col < gridSize; col++) {
var cell = gameGrid[row][col];
var isValid = cell.occupiedBy === null;
cell.showHighlight(isValid);
}
}
}
// Clear all cell highlights
function clearCellHighlights() {
for (var row = 0; row < gridSize; row++) {
for (var col = 0; col < gridSize; col++) {
gameGrid[row][col].showHighlight(false);
}
}
}
// Place a celestial body on a grid cell
function placeCelestialBody(body, cell) {
if (!body || !cell) {
return false;
}
// Check if cell is already occupied
if (cell.occupiedBy !== null) {
// Play error sound
LK.getSound('incorrect_placement').play();
return false;
}
// Place the body
cell.occupiedBy = body;
body.isPlaced = true;
body.currentGridPosition = {
row: cell.row,
col: cell.col
};
// Move body to cell position
tween(body, {
x: cell.x,
y: cell.y
}, {
duration: 300,
easing: tween.easeOut
});
// Scale back to normal
tween(body.visual, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeOut
});
// Play the note
body.playNote();
// Check for harmonics
checkHarmonics();
return true;
}
// Check for harmonic patterns on the grid
function checkHarmonics() {
// Clear existing harmonics
for (var i = 0; i < harmonics.length; i++) {
if (harmonics[i].parent) {
harmonics[i].parent.removeChild(harmonics[i]);
}
}
harmonics = [];
// Reset all cells and nodes
for (var row = 0; row < gridSize; row++) {
for (var col = 0; col < gridSize; col++) {
gameGrid[row][col].setHarmonic(false);
}
}
for (var i = 0; i < musicNodes.length; i++) {
musicNodes[i].deactivate();
}
activatedNodes = 0;
// Check for harmonic patterns
checkLineHarmonics();
checkDiagonalHarmonics();
// Update progress
updateProgressText();
// Check for level completion
if (activatedNodes >= totalNodes) {
levelComplete();
}
}
// Check for harmonic patterns in straight lines
function checkLineHarmonics() {
// Check rows
for (var row = 0; row < gridSize; row++) {
var rowBodies = [];
for (var col = 0; col < gridSize; col++) {
if (gameGrid[row][col].occupiedBy) {
rowBodies.push({
cell: gameGrid[row][col],
body: gameGrid[row][col].occupiedBy
});
}
}
processHarmonicLine(rowBodies);
}
// Check columns
for (var col = 0; col < gridSize; col++) {
var colBodies = [];
for (var row = 0; row < gridSize; row++) {
if (gameGrid[row][col].occupiedBy) {
colBodies.push({
cell: gameGrid[row][col],
body: gameGrid[row][col].occupiedBy
});
}
}
processHarmonicLine(colBodies);
}
}
// Check for harmonic patterns in diagonals
function checkDiagonalHarmonics() {
// Check main diagonal (top-left to bottom-right)
var mainDiagBodies = [];
for (var i = 0; i < gridSize; i++) {
if (gameGrid[i][i].occupiedBy) {
mainDiagBodies.push({
cell: gameGrid[i][i],
body: gameGrid[i][i].occupiedBy
});
}
}
processHarmonicLine(mainDiagBodies);
// Check other diagonal (top-right to bottom-left)
var otherDiagBodies = [];
for (var i = 0; i < gridSize; i++) {
if (gameGrid[i][gridSize - 1 - i].occupiedBy) {
otherDiagBodies.push({
cell: gameGrid[i][gridSize - 1 - i],
body: gameGrid[i][gridSize - 1 - i].occupiedBy
});
}
}
processHarmonicLine(otherDiagBodies);
}
// Process a potential harmonic line
function processHarmonicLine(bodies) {
if (bodies.length < 2) {
return;
} // Need at least 2 bodies to form a harmony
// Different harmonic patterns
var isAscending = true;
var isDescending = true;
var isSame = true;
var lastValue = bodies[0].body.harmonicValue;
for (var i = 1; i < bodies.length; i++) {
var currentValue = bodies[i].body.harmonicValue;
if (currentValue <= lastValue) {
isAscending = false;
}
if (currentValue >= lastValue) {
isDescending = false;
}
if (currentValue !== lastValue) {
isSame = false;
}
lastValue = currentValue;
}
// Check if any harmonic pattern is formed
var isHarmonic = isAscending || isDescending || isSame;
if (isHarmonic && bodies.length >= 2) {
// Create visual connections
for (var i = 1; i < bodies.length; i++) {
var harmonyStrength = 0.3 + bodies.length / 10;
var line = new HarmonicLine(bodies[i - 1].cell, bodies[i].cell, harmonyStrength);
harmonics.push(line);
game.addChild(line);
}
// Mark cells as harmonic
for (var i = 0; i < bodies.length; i++) {
bodies[i].cell.setHarmonic(true);
}
// Play harmony complete sound for new harmonics
LK.getSound('harmony_complete').play();
// Activate nearby music nodes
activateNearbyMusicNodes(bodies);
}
}
// Activate music nodes near harmonic lines
function activateNearbyMusicNodes(harmonicCells) {
for (var i = 0; i < musicNodes.length; i++) {
var node = musicNodes[i];
// Skip already activated nodes
if (node.activated) {
continue;
}
// Check if node is near any cell in the harmonic
for (var j = 0; j < harmonicCells.length; j++) {
var cell = harmonicCells[j].cell;
var dx = node.x - cell.x;
var dy = node.y - cell.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < cellSize * 0.8) {
node.activate();
activatedNodes++;
break;
}
}
}
}
// Level complete function
function levelComplete() {
// Play completion sound
LK.getSound('level_complete').play();
// Update game state
gameState = "levelComplete";
// Update storage
var nextLevel = currentLevel + 1;
if (nextLevel > storage.unlockedLevels) {
storage.unlockedLevels = nextLevel;
}
// Show level complete message
instructionText.setText('Level Complete! Well done!');
// Move to next level after a delay
LK.setTimeout(function () {
if (nextLevel > maxLevel) {
// Game completed!
LK.showYouWin();
} else {
// Load next level
currentLevel = nextLevel;
storage.currentLevel = currentLevel;
initGame();
}
}, 2000);
}
// Handle mouse/touch movement
function handleMove(x, y, obj) {
if (isDragging && draggedBody) {
// Move the dragged body
draggedBody.x = x;
draggedBody.y = y;
// Highlight valid cells
highlightValidCells();
}
}
// Game event handlers
game.down = function (x, y, obj) {
// This is handled by the celestial bodies themselves
};
game.move = handleMove;
game.up = function (x, y, obj) {
if (isDragging && draggedBody) {
// Find the nearest grid cell
var nearestCell = findNearestGridCell(x, y);
// Try to place the body
if (nearestCell) {
placeCelestialBody(draggedBody, nearestCell);
} else {
// If not on a valid cell, return to original position or reset
if (draggedBody.currentGridPosition) {
var oldCell = gameGrid[draggedBody.currentGridPosition.row][draggedBody.currentGridPosition.col];
tween(draggedBody, {
x: oldCell.x,
y: oldCell.y
}, {
duration: 300,
easing: tween.easeOut
});
oldCell.occupiedBy = draggedBody;
draggedBody.isPlaced = true;
} else {
// Return to starting position if no valid cell
tween(draggedBody.visual, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeOut
});
}
}
// Clear highlights
clearCellHighlights();
// Reset dragging state
isDragging = false;
draggedBody = null;
}
};
// Main game update function
game.update = function () {
// Update all celestial bodies
for (var i = 0; i < celestialBodies.length; i++) {
celestialBodies[i].update();
}
// Update all music nodes
for (var i = 0; i < musicNodes.length; i++) {
musicNodes[i].update();
}
};
// Initialize the game
initGame();