/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var PlayerCharacter = Container.expand(function () { var self = Container.call(this); var characterGraphics = self.attachAsset('playerCharacter', { anchorX: 0.5, anchorY: 0.5 }); // Set brown transparent color characterGraphics.alpha = 0.7; return self; }); //Storage library which should be used for persistent game data // var storage = LK.import('@upit/storage.v1'); //Library for using the camera (the background becomes the user's camera video feed) and the microphone. It can access face coordinates for interactive play, as well detect microphone volume / voice interactions // var facekit = LK.import('@upit/facekit.v1'); //Classes can only be defined here. You cannot create inline classes in the games code. var TerrainBlock = Container.expand(function (color, speed, noteId) { var self = Container.call(this); self.color = color; self.speed = speed; self.noteId = noteId; var blockGraphics = self.attachAsset('terrainBlock' + self.color, { anchorX: 0.5, anchorY: 0.5 }); // Create vertical line extending from block self.line = new Container(); // Get color hex value from colorMap var lineColor = 0xFFFFFF; // Default white if (color === 'Red') { lineColor = 0xFF0000; } else if (color === 'Orange') { lineColor = 0xFFA500; } else if (color === 'Yellow') { lineColor = 0xFFFF00; } else if (color === 'Green') { lineColor = 0x008000; } else if (color === 'Blue') { lineColor = 0x0000FF; } else if (color === 'Indigo') { lineColor = 0x4B0082; } else if (color === 'Violet') { lineColor = 0xEE82EE; } // Create thin vertical line shape var lineGraphics = LK.getAsset('line' + self.color, { width: 50, height: 500, // Shorter line color: lineColor, shape: 'box', anchorX: 0.5, anchorY: 1 }); self.line.addChild(lineGraphics); self.addChild(self.line); self.update = function () { self.y += self.speed; // Keep line positioned at block center extending upward if (self.line) { self.line.x = 0; self.line.y = 0; } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 //Init game with black background }); /**** * Game Code ****/ //Minimalistic tween library which should be used for animations over time, including tinting / colouring an object, scaling, rotating, or changing any game object property. //Only include the plugins you need to create the game. //We have access to the following plugins. (Note that the variable names used are mandetory for each plugin) // Initialize assets used in this game. Scale them according to what is needed for the game. // or via static code analysis based on their usage in the code. // Assets are automatically created and loaded either dynamically during gameplay /* Supported Types: 1. Shape: - Simple geometric figures with these properties: * width: (required) pixel width of the shape. * height: (required) pixel height of the shape. * color: (required) color of the shape. * shape: (required) type of shape. Valid options: 'box', 'ellipse'. 2. Image: - Imported images with these properties: * width: (required) pixel resolution width. * height: (required) pixel resolution height. * id: (required) identifier for the image. * flipX: (optional) horizontal flip. Valid values: 0 (no flip), 1 (flip). * flipY: (optional) vertical flip. Valid values: 0 (no flip), 1 (flip). * orientation: (optional) rotation in multiples of 90 degrees, clockwise. Valid values: - 0: No rotation. - 1: Rotate 90 degrees. - 2: Rotate 180 degrees. - 3: Rotate 270 degrees. Note: Width and height remain unchanged upon flipping. 3. Sound: - Sound effects with these properties: * id: (required) identifier for the sound. * volume: (optional) custom volume. Valid values are a float from 0 to 1. 4. Music: - In contract to sound effects, only one music can be played at a time - Music is using the same API to initilize just like sound. - Music loops by default - Music with these config options: * id: (required) identifier for the sound. * volume: (optional) custom volume. Valid values are a float from 0 to 1. * start: (optional) a float from 0 to 1 used for cropping and indicates the start of the cropping * end: (optional) a float from 0 to 1 used for cropping and indicates the end of the cropping */ //Note game dimensions are 2048x2732 var gameState = 'intro'; // 'intro', 'playing', 'finished', 'finalScore', 'win', or 'scores' var introContainer = null; var startButton = null; var scoreButton = null; var finalScoreScreenContainer = null; // For final score screen elements var finalScoreTextElement = null; // For the text on final score screen var scoreDisplayContainer = null; // For score display screen var gameBackground = null; // To manage the game's background image var dragNode = null; // Centralized declaration, used by player controls var terrainBlocks = []; // Original line // PlayerCharacter and scoreTxt are already declared globally and initialized to null later // var playerCharacter = null; // var scoreTxt = null; // Forward declaration for the main game initialization logic function initializeMainGame() { // Add game background, ensuring any old one is removed if (gameBackground && typeof gameBackground.destroy === 'function') { gameBackground.destroy(); } gameBackground = LK.getAsset('Gamebackground', { // Use the global gameBackground variable x: 0, y: 0, width: 2048, height: 2732, anchorX: 0, // Optional, default is 0 for x=0, y=0 anchorY: 0 // Optional, default is 0 for x=0, y=0 }); game.addChildAt(gameBackground, 0); // Add at index 0 to ensure it's in the back LK.playMusic('Gamemusic1', { loop: false, onFinish: handleMusicEnd }); // Play music without looping and set onFinish callback // Re-initialize scoreTxt if it was part of a previous game state and cleaned up if (scoreTxt && typeof scoreTxt.destroy === 'function') { if (scoreTxt.parent && typeof scoreTxt.parent.removeChild === 'function') { scoreTxt.parent.removeChild(scoreTxt); } scoreTxt.destroy(); } scoreTxt = new Text2('0', { size: 150, fill: 0xFFFFFF }); scoreTxt.setText(LK.getScore()); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); playerCharacter = new PlayerCharacter(); // Position player character near the bottom center playerCharacter.x = 2048 / 2; playerCharacter.y = 2732 - playerCharacter.height - 100; // Offset from bottom game.addChild(playerCharacter); // Event listeners for player control // These are assigned when the game starts, so gameState is 'playing' game.down = function (x, y, obj) { if (!playerCharacter) { return; } // Guard against playerCharacter not being ready // Check if the touch is on the player character dragNode = playerCharacter; }; game.move = function (x, y, obj) { if (!playerCharacter) { return; } // Guard // Move the player character horizontally based on the cursor's x position, // ensuring it stays within the horizontal bounds of the screen. playerCharacter.x = Math.max(playerCharacter.width / 2, Math.min(2048 - playerCharacter.width / 2, x)); }; game.up = function (x, y, obj) { if (!playerCharacter) { return; } // Guard dragNode = null; // Stop dragging }; // Define the rhythm pattern (intervals in milliseconds between spawns) beatIntervalsPattern = [600, 600, 600, 600, 400, 400, 600, 600, 600, 600, 400, 400, 500, 500, 500, 500, 400, 400, 600, 600]; currentBeatInPatternIndex = 0; timeForNextBlockSpawn = Date.now() + beatIntervalsPattern[0]; // lastSpawnTime = Date.now(); // Initialize block spawning timer to start spawning blocks - Replaced by new system gameStartTime = Date.now(); // Track when the game started } var playerCharacter = null; var scoreTxt = null; // var spawnInterval = 1200; // Milliseconds between block spawns - Replaced by beatIntervalsPattern // var lastSpawnTime = 4; - Replaced by timeForNextBlockSpawn var beatIntervalsPattern = []; // Defines the ms duration between each block spawn var currentBeatInPatternIndex = 0; // Tracks the current position in the beatIntervalsPattern var timeForNextBlockSpawn = 0; // Timestamp for when the next block should spawn var terrainSpeed = 9; var gameStartTime = 0; // Track when the game started var gameDuration = 173000; // 2:53 in milliseconds (173 seconds) // Mapping colors to hex and sound IDs var colorMap = { 'Red': '#FF0000', 'Orange': '#FFA500', 'Yellow': '#FFFF00', 'Green': '#008000', 'Blue': '#0000FF', 'Indigo': '#4B0082', 'Violet': '#EE82EE' }; // Play background music // Music, score, player, and event handlers are now initialized in initializeMainGame(). var colorNoteMap = { 'Red': 'noteC', 'Orange': 'noteD', 'Yellow': 'noteE', 'Green': 'noteF', 'Blue': 'noteG', 'Indigo': 'noteA', 'Violet': 'noteB' }; game.setBackgroundColor(0x000000); // Change background color to black function cleanupGameElements() { if (playerCharacter && typeof playerCharacter.destroy === 'function') { playerCharacter.destroy(); playerCharacter = null; } for (var i = terrainBlocks.length - 1; i >= 0; i--) { if (terrainBlocks[i] && typeof terrainBlocks[i].destroy === 'function') { terrainBlocks[i].destroy(); } } terrainBlocks = []; if (scoreTxt) { if (scoreTxt.parent && typeof scoreTxt.parent.removeChild === 'function') { scoreTxt.parent.removeChild(scoreTxt); } if (typeof scoreTxt.destroy === 'function') { scoreTxt.destroy(); } scoreTxt = null; } if (gameBackground && typeof gameBackground.destroy === 'function') { gameBackground.destroy(); gameBackground = null; } // Clear game-specific event listeners game.down = null; game.move = null; game.up = null; } function showFinalScoreScreen() { gameState = 'finalScore'; // Set state before cleanup cleanupGameElements(); // Clean up active game elements first finalScoreScreenContainer = new Container(); game.addChild(finalScoreScreenContainer); // Reuse Introbackground for the final score screen var finalScoreBackgroundAsset = LK.getAsset('Introbackground', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, width: 2048, height: 2732 }); finalScoreScreenContainer.addChild(finalScoreBackgroundAsset); finalScoreBackgroundAsset.interactive = true; // Make the background tappable var currentScore = LK.getScore(); // Save the score saveHighScore(currentScore); // Add "You Lose" text var loseText = new Text2('YOU LOSE', { size: 180, fill: 0xFF0000, // Red color align: 'center' }); loseText.anchor.set(0.5, 0.5); loseText.x = 2048 / 2; loseText.y = 2732 / 2 - 300; finalScoreScreenContainer.addChild(loseText); finalScoreTextElement = new Text2('Final Score: ' + currentScore, { size: 120, fill: 0xFFFFFF, align: 'center' // PIXI v4 Text style property }); finalScoreTextElement.anchor.set(0.5, 0.5); finalScoreTextElement.x = 2048 / 2; finalScoreTextElement.y = 2732 / 2 - 50; // Position above center finalScoreScreenContainer.addChild(finalScoreTextElement); var tapToContinueText = new Text2('Tap to Restart', { size: 80, fill: 0xFFFFFF, align: 'center' // PIXI v4 Text style property }); tapToContinueText.anchor.set(0.5, 0.5); tapToContinueText.x = 2048 / 2; tapToContinueText.y = 2732 / 2 + 150; // Position below the score finalScoreScreenContainer.addChild(tapToContinueText); finalScoreBackgroundAsset.down = function () { // Attach tap listener to the background if (gameState === 'finalScore') { resetToIntro(); } }; } function showWinScreen() { gameState = 'win'; // Set state to win cleanupGameElements(); // Clean up active game elements first // Save the score before showing win screen saveHighScore(LK.getScore()); var winScreenContainer = new Container(); game.addChild(winScreenContainer); // Create transparent score background var scoreBackgroundAsset = LK.getAsset('scoreBackground', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, alpha: 0.7 // Transparent background }); winScreenContainer.addChild(scoreBackgroundAsset); var winText = new Text2('YOU WIN!', { size: 180, fill: 0xFFD700, // Gold color align: 'center' }); winText.anchor.set(0.5, 0.5); winText.x = 2048 / 2; winText.y = 2732 / 2 - 200; winScreenContainer.addChild(winText); var finalScoreText = new Text2('Final Score: ' + LK.getScore(), { size: 120, fill: 0xFFFFFF, align: 'center' }); finalScoreText.anchor.set(0.5, 0.5); finalScoreText.x = 2048 / 2; finalScoreText.y = 2732 / 2; winScreenContainer.addChild(finalScoreText); // Show You Win after a short delay LK.setTimeout(function () { LK.showYouWin(); }, 2000); } function handleMusicEnd() { // This function is called when the game music finishes if (gameState === 'playing') { // Proceed only if the game was actively playing gameState = 'finished'; // Set state to finished to stop all game updates // Stop any remaining game elements from updating for (var i = terrainBlocks.length - 1; i >= 0; i--) { if (terrainBlocks[i]) { terrainBlocks[i].update = function () {}; // Disable update function } } // Small delay to ensure smooth transition LK.setTimeout(function () { // Check if player scored more than 2000 for win condition if (LK.getScore() >= 2000) { showWinScreen(); // Show custom win screen with score background } else { showFinalScoreScreen(); } }, 500); } } function showScoreDisplay() { gameState = 'scores'; // Clean up intro screen if (introContainer && typeof introContainer.destroy === 'function') { introContainer.destroy(); introContainer = null; } startButton = null; scoreButton = null; // Create score display container scoreDisplayContainer = new Container(); game.addChild(scoreDisplayContainer); // Add score background var scoreBackgroundAsset = LK.getAsset('scoreBackground', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, alpha: 0.9 }); scoreDisplayContainer.addChild(scoreBackgroundAsset); scoreBackgroundAsset.interactive = true; // Add title text var titleText = new Text2('HIGH SCORES', { size: 120, fill: 0xFFD700, align: 'center' }); titleText.anchor.set(0.5, 0.5); titleText.x = 2048 / 2; titleText.y = 400; scoreDisplayContainer.addChild(titleText); // Get stored scores or initialize var highScores = storage.highScores || []; // Display scores var scoreY = 600; if (highScores.length === 0) { var noScoresText = new Text2('No scores yet!', { size: 80, fill: 0xFFFFFF, align: 'center' }); noScoresText.anchor.set(0.5, 0.5); noScoresText.x = 2048 / 2; noScoresText.y = scoreY; scoreDisplayContainer.addChild(noScoresText); } else { // Show top 10 scores var displayCount = Math.min(10, highScores.length); for (var i = 0; i < displayCount; i++) { var scoreText = new Text2(i + 1 + '. ' + highScores[i], { size: 70, fill: 0xFFFFFF, align: 'center' }); scoreText.anchor.set(0.5, 0.5); scoreText.x = 2048 / 2; scoreText.y = scoreY + i * 100; scoreDisplayContainer.addChild(scoreText); } } // Add back button text var backText = new Text2('Tap to go back', { size: 60, fill: 0xFFFFFF, align: 'center' }); backText.anchor.set(0.5, 0.5); backText.x = 2048 / 2; backText.y = 2732 - 200; scoreDisplayContainer.addChild(backText); // Handle tap to go back scoreBackgroundAsset.down = function () { if (scoreDisplayContainer && typeof scoreDisplayContainer.destroy === 'function') { scoreDisplayContainer.destroy(); scoreDisplayContainer = null; } setupIntroScreen(); }; } function saveHighScore(score) { // Get existing high scores or initialize empty array var highScores = storage.highScores || []; // Add new score highScores.push(score); // Sort in descending order highScores.sort(function (a, b) { return b - a; }); // Keep only top 10 scores if (highScores.length > 10) { highScores = highScores.slice(0, 10); } // Save back to storage storage.highScores = highScores; } function resetToIntro() { if (finalScoreScreenContainer && typeof finalScoreScreenContainer.destroy === 'function') { finalScoreScreenContainer.destroy(); finalScoreScreenContainer = null; } // finalScoreTextElement is a child of finalScoreScreenContainer, so it's destroyed with it. finalScoreTextElement = null; LK.setScore(0); // Reset the score for the new game session setupIntroScreen(); // Transition back to the intro screen } function setupIntroScreen() { gameState = 'intro'; // Set current game state to intro // Clean up previous intro elements if any (e.g., if returning from final score) if (introContainer && typeof introContainer.destroy === 'function') { introContainer.destroy(); } introContainer = new Container(); game.addChild(introContainer); var introBackgroundAsset = LK.getAsset('Introbackground', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, width: 2048, height: 2732 }); introContainer.addChild(introBackgroundAsset); // Clean up previous start button if any if (startButton && typeof startButton.destroy === 'function') { startButton.destroy(); } // Clean up previous score button if any if (scoreButton && typeof scoreButton.destroy === 'function') { scoreButton.destroy(); } var startButtonAsset = LK.getAsset('Startbutton', { anchorX: 0.5, anchorY: 0.5 }); startButtonAsset.x = 2048 / 2 - 250; // Move left to make room for score button startButtonAsset.y = 2732 - 100 - startButtonAsset.height / 2; // Position near bottom introContainer.addChild(startButtonAsset); startButton = startButtonAsset; // Assign to global variable // Create and add score button var scoreButtonAsset = LK.getAsset('Scorebutton', { anchorX: 0.5, anchorY: 0.5 }); scoreButtonAsset.x = startButtonAsset.x + startButtonAsset.width / 2 + 100 + scoreButtonAsset.width / 2; // Position to the right of start button with 100px gap scoreButtonAsset.y = startButtonAsset.y; // Same height as start button introContainer.addChild(scoreButtonAsset); scoreButton = scoreButtonAsset; // Assign to global variable startButton.down = function () { if (gameState !== 'intro') { // Ensure action only if in intro state return; } gameState = 'playing'; // Transition to playing state if (introContainer && typeof introContainer.destroy === 'function') { introContainer.destroy(); // Clean up intro screen elements introContainer = null; } startButton = null; // Clear button reference scoreButton = null; // Clear button reference initializeMainGame(); // Initialize and start the main game }; scoreButton.down = function () { if (gameState !== 'intro') { return; } showScoreDisplay(); }; } // Initial call to set up the intro screen when the game first loads setupIntroScreen(); // The original scoreTxt, playerCharacter, and game event handlers (down, move, up) // are removed from here as they are now part of initializeMainGame. // The old dragNode declaration was also removed as it's now global. game.update = function () { if (gameState !== 'playing') { return; // Don't run game logic if not in 'playing' state } var currentTime = Date.now(); // Check if game duration has exceeded 2:53 if (currentTime - gameStartTime >= gameDuration) { // Stop the game after 2:53 gameState = 'finished'; // Stop music if still playing LK.stopMusic(); // Stop all blocks from updating for (var i = terrainBlocks.length - 1; i >= 0; i--) { if (terrainBlocks[i]) { terrainBlocks[i].update = function () {}; // Disable update function } } // Show final score after a short delay LK.setTimeout(function () { // Check if player scored more than 2000 for win condition if (LK.getScore() >= 2000) { showWinScreen(); // Show custom win screen with score background } else { showFinalScoreScreen(); } }, 500); return; // Exit update function } // Spawn new terrain blocks based on music rhythm if (currentTime >= timeForNextBlockSpawn && gameState === 'playing') { var colors = Object.keys(colorMap); var randomColor = colors[Math.floor(Math.random() * colors.length)]; var noteId = colorNoteMap[randomColor]; var newBlock = new TerrainBlock(randomColor, terrainSpeed, noteId); // Position block randomly at the top within game width newBlock.x = Math.random() * (2048 - newBlock.width) + newBlock.width / 2; newBlock.y = -newBlock.height; // Start above the screen terrainBlocks.push(newBlock); game.addChild(newBlock); currentBeatInPatternIndex++; if (currentBeatInPatternIndex >= beatIntervalsPattern.length) { currentBeatInPatternIndex = 0; // Loop the pattern } timeForNextBlockSpawn = currentTime + beatIntervalsPattern[currentBeatInPatternIndex]; // Gradually increase game speed / difficulty (terrainSpeed only) terrainSpeed = Math.min(15, terrainSpeed + 0.05); } // Update and check collisions for terrain blocks // Update and check collisions for terrain blocks for (var i = terrainBlocks.length - 1; i >= 0; i--) { var block = terrainBlocks[i]; if (block.lastY === undefined) { block.lastY = block.y; } // Check if block is off-screen if (block.lastY < 2732 + block.height && block.y >= 2732 + block.height) { // Missed block - just remove it without penalty block.destroy(); terrainBlocks.splice(i, 1); continue; // Skip to next block } // Check for intersection with player character var currentIntersecting = block.intersects(playerCharacter); if (currentIntersecting) { // Collect any color block LK.setScore(LK.getScore() + 10); scoreTxt.setText(LK.getScore()); // Create animation effect based on block color var animationAsset = null; if (block.color === 'Red') { animationAsset = LK.getAsset('Redanimation1', { anchorX: 0.5, anchorY: 0.5 }); } else if (block.color === 'Orange') { animationAsset = LK.getAsset('Orangeanimation1', { anchorX: 0.5, anchorY: 0.5 }); } else if (block.color === 'Yellow') { animationAsset = LK.getAsset('Yellowanimation1', { anchorX: 0.5, anchorY: 0.5 }); } else if (block.color === 'Green') { animationAsset = LK.getAsset('Greenanimation1', { anchorX: 0.5, anchorY: 0.5 }); } else if (block.color === 'Blue') { animationAsset = LK.getAsset('Blueanimation1', { anchorX: 0.5, anchorY: 0.5 }); } else if (block.color === 'Indigo') { animationAsset = LK.getAsset('Purpleanimation1', { anchorX: 0.5, anchorY: 0.5 }); } else if (block.color === 'Violet') { animationAsset = LK.getAsset('Pinkanimation1', { anchorX: 0.5, anchorY: 0.5 }); } if (animationAsset) { // Position animation at block location animationAsset.x = block.x; animationAsset.y = block.y; game.addChild(animationAsset); // Animate the animation asset tween(animationAsset, { scaleX: 3, scaleY: 3, alpha: 0, rotation: Math.PI * 2 }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { animationAsset.destroy(); } }); } // Add visual feedback with scale animation on player tween(playerCharacter, { scaleX: 1.2, scaleY: 1.2 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(playerCharacter, { scaleX: 1, scaleY: 1 }, { duration: 100, easing: tween.easeIn }); } }); // Flash effect on collected block tween(block, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 200, easing: tween.easeOut }); // Also fade out the line if (block.line) { tween(block.line, { alpha: 0 }, { duration: 200, easing: tween.easeOut }); } // Play specific sound based on block color with smooth volume fade var sound = null; if (block.color === 'Red') { sound = LK.getSound('redsound1'); } else if (block.color === 'Orange') { sound = LK.getSound('orangesound1'); } else if (block.color === 'Yellow') { sound = LK.getSound('yellowsound1'); } else if (block.color === 'Green') { sound = LK.getSound('greensound1'); } else if (block.color === 'Blue') { sound = LK.getSound('bluesound1'); } else if (block.color === 'Indigo') { sound = LK.getSound('purplesound1'); } else if (block.color === 'Violet') { sound = LK.getSound('pinksound1'); } if (sound) { // Set initial volume to 0 for fade-in effect sound.volume = 0; sound.play(); // Tween volume from 0 to 1 over 200ms for smooth fade-in tween(sound, { volume: 1 }, { duration: 200, easing: tween.easeOut }); } block.destroy(); terrainBlocks.splice(i, 1); } block.lastY = block.y; } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var PlayerCharacter = Container.expand(function () {
var self = Container.call(this);
var characterGraphics = self.attachAsset('playerCharacter', {
anchorX: 0.5,
anchorY: 0.5
});
// Set brown transparent color
characterGraphics.alpha = 0.7;
return self;
});
//Storage library which should be used for persistent game data
// var storage = LK.import('@upit/storage.v1');
//Library for using the camera (the background becomes the user's camera video feed) and the microphone. It can access face coordinates for interactive play, as well detect microphone volume / voice interactions
// var facekit = LK.import('@upit/facekit.v1');
//Classes can only be defined here. You cannot create inline classes in the games code.
var TerrainBlock = Container.expand(function (color, speed, noteId) {
var self = Container.call(this);
self.color = color;
self.speed = speed;
self.noteId = noteId;
var blockGraphics = self.attachAsset('terrainBlock' + self.color, {
anchorX: 0.5,
anchorY: 0.5
});
// Create vertical line extending from block
self.line = new Container();
// Get color hex value from colorMap
var lineColor = 0xFFFFFF; // Default white
if (color === 'Red') {
lineColor = 0xFF0000;
} else if (color === 'Orange') {
lineColor = 0xFFA500;
} else if (color === 'Yellow') {
lineColor = 0xFFFF00;
} else if (color === 'Green') {
lineColor = 0x008000;
} else if (color === 'Blue') {
lineColor = 0x0000FF;
} else if (color === 'Indigo') {
lineColor = 0x4B0082;
} else if (color === 'Violet') {
lineColor = 0xEE82EE;
}
// Create thin vertical line shape
var lineGraphics = LK.getAsset('line' + self.color, {
width: 50,
height: 500,
// Shorter line
color: lineColor,
shape: 'box',
anchorX: 0.5,
anchorY: 1
});
self.line.addChild(lineGraphics);
self.addChild(self.line);
self.update = function () {
self.y += self.speed;
// Keep line positioned at block center extending upward
if (self.line) {
self.line.x = 0;
self.line.y = 0;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000 //Init game with black background
});
/****
* Game Code
****/
//Minimalistic tween library which should be used for animations over time, including tinting / colouring an object, scaling, rotating, or changing any game object property.
//Only include the plugins you need to create the game.
//We have access to the following plugins. (Note that the variable names used are mandetory for each plugin)
// Initialize assets used in this game. Scale them according to what is needed for the game.
// or via static code analysis based on their usage in the code.
// Assets are automatically created and loaded either dynamically during gameplay
/*
Supported Types:
1. Shape:
- Simple geometric figures with these properties:
* width: (required) pixel width of the shape.
* height: (required) pixel height of the shape.
* color: (required) color of the shape.
* shape: (required) type of shape. Valid options: 'box', 'ellipse'.
2. Image:
- Imported images with these properties:
* width: (required) pixel resolution width.
* height: (required) pixel resolution height.
* id: (required) identifier for the image.
* flipX: (optional) horizontal flip. Valid values: 0 (no flip), 1 (flip).
* flipY: (optional) vertical flip. Valid values: 0 (no flip), 1 (flip).
* orientation: (optional) rotation in multiples of 90 degrees, clockwise. Valid values:
- 0: No rotation.
- 1: Rotate 90 degrees.
- 2: Rotate 180 degrees.
- 3: Rotate 270 degrees.
Note: Width and height remain unchanged upon flipping.
3. Sound:
- Sound effects with these properties:
* id: (required) identifier for the sound.
* volume: (optional) custom volume. Valid values are a float from 0 to 1.
4. Music:
- In contract to sound effects, only one music can be played at a time
- Music is using the same API to initilize just like sound.
- Music loops by default
- Music with these config options:
* id: (required) identifier for the sound.
* volume: (optional) custom volume. Valid values are a float from 0 to 1.
* start: (optional) a float from 0 to 1 used for cropping and indicates the start of the cropping
* end: (optional) a float from 0 to 1 used for cropping and indicates the end of the cropping
*/
//Note game dimensions are 2048x2732
var gameState = 'intro'; // 'intro', 'playing', 'finished', 'finalScore', 'win', or 'scores'
var introContainer = null;
var startButton = null;
var scoreButton = null;
var finalScoreScreenContainer = null; // For final score screen elements
var finalScoreTextElement = null; // For the text on final score screen
var scoreDisplayContainer = null; // For score display screen
var gameBackground = null; // To manage the game's background image
var dragNode = null; // Centralized declaration, used by player controls
var terrainBlocks = []; // Original line
// PlayerCharacter and scoreTxt are already declared globally and initialized to null later
// var playerCharacter = null;
// var scoreTxt = null;
// Forward declaration for the main game initialization logic
function initializeMainGame() {
// Add game background, ensuring any old one is removed
if (gameBackground && typeof gameBackground.destroy === 'function') {
gameBackground.destroy();
}
gameBackground = LK.getAsset('Gamebackground', {
// Use the global gameBackground variable
x: 0,
y: 0,
width: 2048,
height: 2732,
anchorX: 0,
// Optional, default is 0 for x=0, y=0
anchorY: 0 // Optional, default is 0 for x=0, y=0
});
game.addChildAt(gameBackground, 0); // Add at index 0 to ensure it's in the back
LK.playMusic('Gamemusic1', {
loop: false,
onFinish: handleMusicEnd
}); // Play music without looping and set onFinish callback
// Re-initialize scoreTxt if it was part of a previous game state and cleaned up
if (scoreTxt && typeof scoreTxt.destroy === 'function') {
if (scoreTxt.parent && typeof scoreTxt.parent.removeChild === 'function') {
scoreTxt.parent.removeChild(scoreTxt);
}
scoreTxt.destroy();
}
scoreTxt = new Text2('0', {
size: 150,
fill: 0xFFFFFF
});
scoreTxt.setText(LK.getScore());
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
playerCharacter = new PlayerCharacter();
// Position player character near the bottom center
playerCharacter.x = 2048 / 2;
playerCharacter.y = 2732 - playerCharacter.height - 100; // Offset from bottom
game.addChild(playerCharacter);
// Event listeners for player control
// These are assigned when the game starts, so gameState is 'playing'
game.down = function (x, y, obj) {
if (!playerCharacter) {
return;
} // Guard against playerCharacter not being ready
// Check if the touch is on the player character
dragNode = playerCharacter;
};
game.move = function (x, y, obj) {
if (!playerCharacter) {
return;
} // Guard
// Move the player character horizontally based on the cursor's x position,
// ensuring it stays within the horizontal bounds of the screen.
playerCharacter.x = Math.max(playerCharacter.width / 2, Math.min(2048 - playerCharacter.width / 2, x));
};
game.up = function (x, y, obj) {
if (!playerCharacter) {
return;
} // Guard
dragNode = null; // Stop dragging
};
// Define the rhythm pattern (intervals in milliseconds between spawns)
beatIntervalsPattern = [600, 600, 600, 600, 400, 400, 600, 600, 600, 600, 400, 400, 500, 500, 500, 500, 400, 400, 600, 600];
currentBeatInPatternIndex = 0;
timeForNextBlockSpawn = Date.now() + beatIntervalsPattern[0];
// lastSpawnTime = Date.now(); // Initialize block spawning timer to start spawning blocks - Replaced by new system
gameStartTime = Date.now(); // Track when the game started
}
var playerCharacter = null;
var scoreTxt = null;
// var spawnInterval = 1200; // Milliseconds between block spawns - Replaced by beatIntervalsPattern
// var lastSpawnTime = 4; - Replaced by timeForNextBlockSpawn
var beatIntervalsPattern = []; // Defines the ms duration between each block spawn
var currentBeatInPatternIndex = 0; // Tracks the current position in the beatIntervalsPattern
var timeForNextBlockSpawn = 0; // Timestamp for when the next block should spawn
var terrainSpeed = 9;
var gameStartTime = 0; // Track when the game started
var gameDuration = 173000; // 2:53 in milliseconds (173 seconds)
// Mapping colors to hex and sound IDs
var colorMap = {
'Red': '#FF0000',
'Orange': '#FFA500',
'Yellow': '#FFFF00',
'Green': '#008000',
'Blue': '#0000FF',
'Indigo': '#4B0082',
'Violet': '#EE82EE'
};
// Play background music
// Music, score, player, and event handlers are now initialized in initializeMainGame().
var colorNoteMap = {
'Red': 'noteC',
'Orange': 'noteD',
'Yellow': 'noteE',
'Green': 'noteF',
'Blue': 'noteG',
'Indigo': 'noteA',
'Violet': 'noteB'
};
game.setBackgroundColor(0x000000); // Change background color to black
function cleanupGameElements() {
if (playerCharacter && typeof playerCharacter.destroy === 'function') {
playerCharacter.destroy();
playerCharacter = null;
}
for (var i = terrainBlocks.length - 1; i >= 0; i--) {
if (terrainBlocks[i] && typeof terrainBlocks[i].destroy === 'function') {
terrainBlocks[i].destroy();
}
}
terrainBlocks = [];
if (scoreTxt) {
if (scoreTxt.parent && typeof scoreTxt.parent.removeChild === 'function') {
scoreTxt.parent.removeChild(scoreTxt);
}
if (typeof scoreTxt.destroy === 'function') {
scoreTxt.destroy();
}
scoreTxt = null;
}
if (gameBackground && typeof gameBackground.destroy === 'function') {
gameBackground.destroy();
gameBackground = null;
}
// Clear game-specific event listeners
game.down = null;
game.move = null;
game.up = null;
}
function showFinalScoreScreen() {
gameState = 'finalScore'; // Set state before cleanup
cleanupGameElements(); // Clean up active game elements first
finalScoreScreenContainer = new Container();
game.addChild(finalScoreScreenContainer);
// Reuse Introbackground for the final score screen
var finalScoreBackgroundAsset = LK.getAsset('Introbackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
width: 2048,
height: 2732
});
finalScoreScreenContainer.addChild(finalScoreBackgroundAsset);
finalScoreBackgroundAsset.interactive = true; // Make the background tappable
var currentScore = LK.getScore();
// Save the score
saveHighScore(currentScore);
// Add "You Lose" text
var loseText = new Text2('YOU LOSE', {
size: 180,
fill: 0xFF0000,
// Red color
align: 'center'
});
loseText.anchor.set(0.5, 0.5);
loseText.x = 2048 / 2;
loseText.y = 2732 / 2 - 300;
finalScoreScreenContainer.addChild(loseText);
finalScoreTextElement = new Text2('Final Score: ' + currentScore, {
size: 120,
fill: 0xFFFFFF,
align: 'center' // PIXI v4 Text style property
});
finalScoreTextElement.anchor.set(0.5, 0.5);
finalScoreTextElement.x = 2048 / 2;
finalScoreTextElement.y = 2732 / 2 - 50; // Position above center
finalScoreScreenContainer.addChild(finalScoreTextElement);
var tapToContinueText = new Text2('Tap to Restart', {
size: 80,
fill: 0xFFFFFF,
align: 'center' // PIXI v4 Text style property
});
tapToContinueText.anchor.set(0.5, 0.5);
tapToContinueText.x = 2048 / 2;
tapToContinueText.y = 2732 / 2 + 150; // Position below the score
finalScoreScreenContainer.addChild(tapToContinueText);
finalScoreBackgroundAsset.down = function () {
// Attach tap listener to the background
if (gameState === 'finalScore') {
resetToIntro();
}
};
}
function showWinScreen() {
gameState = 'win'; // Set state to win
cleanupGameElements(); // Clean up active game elements first
// Save the score before showing win screen
saveHighScore(LK.getScore());
var winScreenContainer = new Container();
game.addChild(winScreenContainer);
// Create transparent score background
var scoreBackgroundAsset = LK.getAsset('scoreBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
alpha: 0.7 // Transparent background
});
winScreenContainer.addChild(scoreBackgroundAsset);
var winText = new Text2('YOU WIN!', {
size: 180,
fill: 0xFFD700,
// Gold color
align: 'center'
});
winText.anchor.set(0.5, 0.5);
winText.x = 2048 / 2;
winText.y = 2732 / 2 - 200;
winScreenContainer.addChild(winText);
var finalScoreText = new Text2('Final Score: ' + LK.getScore(), {
size: 120,
fill: 0xFFFFFF,
align: 'center'
});
finalScoreText.anchor.set(0.5, 0.5);
finalScoreText.x = 2048 / 2;
finalScoreText.y = 2732 / 2;
winScreenContainer.addChild(finalScoreText);
// Show You Win after a short delay
LK.setTimeout(function () {
LK.showYouWin();
}, 2000);
}
function handleMusicEnd() {
// This function is called when the game music finishes
if (gameState === 'playing') {
// Proceed only if the game was actively playing
gameState = 'finished'; // Set state to finished to stop all game updates
// Stop any remaining game elements from updating
for (var i = terrainBlocks.length - 1; i >= 0; i--) {
if (terrainBlocks[i]) {
terrainBlocks[i].update = function () {}; // Disable update function
}
}
// Small delay to ensure smooth transition
LK.setTimeout(function () {
// Check if player scored more than 2000 for win condition
if (LK.getScore() >= 2000) {
showWinScreen(); // Show custom win screen with score background
} else {
showFinalScoreScreen();
}
}, 500);
}
}
function showScoreDisplay() {
gameState = 'scores';
// Clean up intro screen
if (introContainer && typeof introContainer.destroy === 'function') {
introContainer.destroy();
introContainer = null;
}
startButton = null;
scoreButton = null;
// Create score display container
scoreDisplayContainer = new Container();
game.addChild(scoreDisplayContainer);
// Add score background
var scoreBackgroundAsset = LK.getAsset('scoreBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
alpha: 0.9
});
scoreDisplayContainer.addChild(scoreBackgroundAsset);
scoreBackgroundAsset.interactive = true;
// Add title text
var titleText = new Text2('HIGH SCORES', {
size: 120,
fill: 0xFFD700,
align: 'center'
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 2048 / 2;
titleText.y = 400;
scoreDisplayContainer.addChild(titleText);
// Get stored scores or initialize
var highScores = storage.highScores || [];
// Display scores
var scoreY = 600;
if (highScores.length === 0) {
var noScoresText = new Text2('No scores yet!', {
size: 80,
fill: 0xFFFFFF,
align: 'center'
});
noScoresText.anchor.set(0.5, 0.5);
noScoresText.x = 2048 / 2;
noScoresText.y = scoreY;
scoreDisplayContainer.addChild(noScoresText);
} else {
// Show top 10 scores
var displayCount = Math.min(10, highScores.length);
for (var i = 0; i < displayCount; i++) {
var scoreText = new Text2(i + 1 + '. ' + highScores[i], {
size: 70,
fill: 0xFFFFFF,
align: 'center'
});
scoreText.anchor.set(0.5, 0.5);
scoreText.x = 2048 / 2;
scoreText.y = scoreY + i * 100;
scoreDisplayContainer.addChild(scoreText);
}
}
// Add back button text
var backText = new Text2('Tap to go back', {
size: 60,
fill: 0xFFFFFF,
align: 'center'
});
backText.anchor.set(0.5, 0.5);
backText.x = 2048 / 2;
backText.y = 2732 - 200;
scoreDisplayContainer.addChild(backText);
// Handle tap to go back
scoreBackgroundAsset.down = function () {
if (scoreDisplayContainer && typeof scoreDisplayContainer.destroy === 'function') {
scoreDisplayContainer.destroy();
scoreDisplayContainer = null;
}
setupIntroScreen();
};
}
function saveHighScore(score) {
// Get existing high scores or initialize empty array
var highScores = storage.highScores || [];
// Add new score
highScores.push(score);
// Sort in descending order
highScores.sort(function (a, b) {
return b - a;
});
// Keep only top 10 scores
if (highScores.length > 10) {
highScores = highScores.slice(0, 10);
}
// Save back to storage
storage.highScores = highScores;
}
function resetToIntro() {
if (finalScoreScreenContainer && typeof finalScoreScreenContainer.destroy === 'function') {
finalScoreScreenContainer.destroy();
finalScoreScreenContainer = null;
}
// finalScoreTextElement is a child of finalScoreScreenContainer, so it's destroyed with it.
finalScoreTextElement = null;
LK.setScore(0); // Reset the score for the new game session
setupIntroScreen(); // Transition back to the intro screen
}
function setupIntroScreen() {
gameState = 'intro'; // Set current game state to intro
// Clean up previous intro elements if any (e.g., if returning from final score)
if (introContainer && typeof introContainer.destroy === 'function') {
introContainer.destroy();
}
introContainer = new Container();
game.addChild(introContainer);
var introBackgroundAsset = LK.getAsset('Introbackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
width: 2048,
height: 2732
});
introContainer.addChild(introBackgroundAsset);
// Clean up previous start button if any
if (startButton && typeof startButton.destroy === 'function') {
startButton.destroy();
}
// Clean up previous score button if any
if (scoreButton && typeof scoreButton.destroy === 'function') {
scoreButton.destroy();
}
var startButtonAsset = LK.getAsset('Startbutton', {
anchorX: 0.5,
anchorY: 0.5
});
startButtonAsset.x = 2048 / 2 - 250; // Move left to make room for score button
startButtonAsset.y = 2732 - 100 - startButtonAsset.height / 2; // Position near bottom
introContainer.addChild(startButtonAsset);
startButton = startButtonAsset; // Assign to global variable
// Create and add score button
var scoreButtonAsset = LK.getAsset('Scorebutton', {
anchorX: 0.5,
anchorY: 0.5
});
scoreButtonAsset.x = startButtonAsset.x + startButtonAsset.width / 2 + 100 + scoreButtonAsset.width / 2; // Position to the right of start button with 100px gap
scoreButtonAsset.y = startButtonAsset.y; // Same height as start button
introContainer.addChild(scoreButtonAsset);
scoreButton = scoreButtonAsset; // Assign to global variable
startButton.down = function () {
if (gameState !== 'intro') {
// Ensure action only if in intro state
return;
}
gameState = 'playing'; // Transition to playing state
if (introContainer && typeof introContainer.destroy === 'function') {
introContainer.destroy(); // Clean up intro screen elements
introContainer = null;
}
startButton = null; // Clear button reference
scoreButton = null; // Clear button reference
initializeMainGame(); // Initialize and start the main game
};
scoreButton.down = function () {
if (gameState !== 'intro') {
return;
}
showScoreDisplay();
};
}
// Initial call to set up the intro screen when the game first loads
setupIntroScreen();
// The original scoreTxt, playerCharacter, and game event handlers (down, move, up)
// are removed from here as they are now part of initializeMainGame.
// The old dragNode declaration was also removed as it's now global.
game.update = function () {
if (gameState !== 'playing') {
return; // Don't run game logic if not in 'playing' state
}
var currentTime = Date.now();
// Check if game duration has exceeded 2:53
if (currentTime - gameStartTime >= gameDuration) {
// Stop the game after 2:53
gameState = 'finished';
// Stop music if still playing
LK.stopMusic();
// Stop all blocks from updating
for (var i = terrainBlocks.length - 1; i >= 0; i--) {
if (terrainBlocks[i]) {
terrainBlocks[i].update = function () {}; // Disable update function
}
}
// Show final score after a short delay
LK.setTimeout(function () {
// Check if player scored more than 2000 for win condition
if (LK.getScore() >= 2000) {
showWinScreen(); // Show custom win screen with score background
} else {
showFinalScoreScreen();
}
}, 500);
return; // Exit update function
}
// Spawn new terrain blocks based on music rhythm
if (currentTime >= timeForNextBlockSpawn && gameState === 'playing') {
var colors = Object.keys(colorMap);
var randomColor = colors[Math.floor(Math.random() * colors.length)];
var noteId = colorNoteMap[randomColor];
var newBlock = new TerrainBlock(randomColor, terrainSpeed, noteId);
// Position block randomly at the top within game width
newBlock.x = Math.random() * (2048 - newBlock.width) + newBlock.width / 2;
newBlock.y = -newBlock.height; // Start above the screen
terrainBlocks.push(newBlock);
game.addChild(newBlock);
currentBeatInPatternIndex++;
if (currentBeatInPatternIndex >= beatIntervalsPattern.length) {
currentBeatInPatternIndex = 0; // Loop the pattern
}
timeForNextBlockSpawn = currentTime + beatIntervalsPattern[currentBeatInPatternIndex];
// Gradually increase game speed / difficulty (terrainSpeed only)
terrainSpeed = Math.min(15, terrainSpeed + 0.05);
}
// Update and check collisions for terrain blocks
// Update and check collisions for terrain blocks
for (var i = terrainBlocks.length - 1; i >= 0; i--) {
var block = terrainBlocks[i];
if (block.lastY === undefined) {
block.lastY = block.y;
}
// Check if block is off-screen
if (block.lastY < 2732 + block.height && block.y >= 2732 + block.height) {
// Missed block - just remove it without penalty
block.destroy();
terrainBlocks.splice(i, 1);
continue; // Skip to next block
}
// Check for intersection with player character
var currentIntersecting = block.intersects(playerCharacter);
if (currentIntersecting) {
// Collect any color block
LK.setScore(LK.getScore() + 10);
scoreTxt.setText(LK.getScore());
// Create animation effect based on block color
var animationAsset = null;
if (block.color === 'Red') {
animationAsset = LK.getAsset('Redanimation1', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (block.color === 'Orange') {
animationAsset = LK.getAsset('Orangeanimation1', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (block.color === 'Yellow') {
animationAsset = LK.getAsset('Yellowanimation1', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (block.color === 'Green') {
animationAsset = LK.getAsset('Greenanimation1', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (block.color === 'Blue') {
animationAsset = LK.getAsset('Blueanimation1', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (block.color === 'Indigo') {
animationAsset = LK.getAsset('Purpleanimation1', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (block.color === 'Violet') {
animationAsset = LK.getAsset('Pinkanimation1', {
anchorX: 0.5,
anchorY: 0.5
});
}
if (animationAsset) {
// Position animation at block location
animationAsset.x = block.x;
animationAsset.y = block.y;
game.addChild(animationAsset);
// Animate the animation asset
tween(animationAsset, {
scaleX: 3,
scaleY: 3,
alpha: 0,
rotation: Math.PI * 2
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
animationAsset.destroy();
}
});
}
// Add visual feedback with scale animation on player
tween(playerCharacter, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(playerCharacter, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
easing: tween.easeIn
});
}
});
// Flash effect on collected block
tween(block, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
easing: tween.easeOut
});
// Also fade out the line
if (block.line) {
tween(block.line, {
alpha: 0
}, {
duration: 200,
easing: tween.easeOut
});
}
// Play specific sound based on block color with smooth volume fade
var sound = null;
if (block.color === 'Red') {
sound = LK.getSound('redsound1');
} else if (block.color === 'Orange') {
sound = LK.getSound('orangesound1');
} else if (block.color === 'Yellow') {
sound = LK.getSound('yellowsound1');
} else if (block.color === 'Green') {
sound = LK.getSound('greensound1');
} else if (block.color === 'Blue') {
sound = LK.getSound('bluesound1');
} else if (block.color === 'Indigo') {
sound = LK.getSound('purplesound1');
} else if (block.color === 'Violet') {
sound = LK.getSound('pinksound1');
}
if (sound) {
// Set initial volume to 0 for fade-in effect
sound.volume = 0;
sound.play();
// Tween volume from 0 to 1 over 200ms for smooth fade-in
tween(sound, {
volume: 1
}, {
duration: 200,
easing: tween.easeOut
});
}
block.destroy();
terrainBlocks.splice(i, 1);
}
block.lastY = block.y;
}
};