/**** * 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();
===================================================================
--- original.js
+++ change.js
@@ -1,6 +1,815 @@
-/****
+/****
+* 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: 0x000000
-});
\ No newline at end of file
+ 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();
\ No newline at end of file