/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { coins: 0 }); /**** * Classes ****/ var AutomatedPlayer = Container.expand(function () { var self = Container.call(this); self.runAnimation = ['player_run1', 'player_run2', 'player_run3']; self.sprites = []; self.runFrame = 0; self.animationCounter = 0; self.animationSpeed = 0.1; // Pre-attach all sprites for (var i = 0; i < self.runAnimation.length; i++) { var sprite = self.attachAsset(self.runAnimation[i], { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); sprite.alpha = i === 0 ? 1 : 0; self.sprites.push(sprite); } self.groundY = 2732 - 400; // 400 pixels from bottom self.y = -200; // Start off-screen at the top self.showFrame = function (index) { // Hide all sprites first for (var i = 0; i < self.sprites.length; i++) { self.sprites[i].alpha = 0; } // Show the appropriate sprite self.sprites[index].alpha = 1; }; self.update = function () { // Running animation self.animationCounter += self.animationSpeed; if (self.animationCounter >= 1) { self.animationCounter = 0; self.runFrame = (self.runFrame + 1) % self.runAnimation.length; self.showFrame(self.runFrame); } // Move right automatically self.x += 5; // Wrap around when reaching edge if (self.x > 2048 + 100) { self.x = -100; } }; return self; }); var Background = Container.expand(function () { var self = Container.call(this); var backgrounds = ['background1', 'background2', 'background3']; var randomBackground = backgrounds[Math.floor(Math.random() * backgrounds.length)]; var backgroundGraphics = self.attachAsset(randomBackground, { anchorX: 0, anchorY: 0 }); self.sprites = [backgroundGraphics]; // Store sprites for access self.speed = 3; self.update = function () { if (!game.playerDead) { // Increase speed over time, up to a reasonable maximum self.speed = 3 + Math.min(8, Math.floor(LK.ticks / 300) * 0.3); // Double speed during star power if (starPowerActive) { self.speed *= 2; } self.x -= self.speed; } if (self.x <= -2048) { self.x = 2048; } }; }); var Background2 = Container.expand(function () { var self = Container.call(this); var backgrounds = ['background1', 'background2', 'background3']; var randomBackground = backgrounds[Math.floor(Math.random() * backgrounds.length)]; var backgroundGraphics = self.attachAsset(randomBackground, { anchorX: 0, anchorY: 0 }); self.sprites = [backgroundGraphics]; // Store sprites for access self.speed = 3; self.update = function () { if (!game.playerDead) { // Increase speed over time, up to a reasonable maximum self.speed = 3 + Math.min(8, Math.floor(LK.ticks / 300) * 0.3); // Double speed during star power if (starPowerActive) { self.speed *= 2; } self.x -= self.speed; } if (self.x <= -2048) { self.x = 2048; } }; }); var Coin = Container.expand(function () { var self = Container.call(this); var coinGraphics = self.attachAsset('coin', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); self.speed = 3; self.collected = false; self.y = 2732 - 600; // Position coins higher than the ground self.update = function () { if (!game.playerDead) { // Increase speed over time, up to a reasonable maximum self.speed = 3 + Math.min(8, Math.floor(LK.ticks / 300) * 0.3); // Double speed during star power if (starPowerActive) { self.speed *= 2; } self.x -= self.speed; // Removed oscillation to keep coins from moving up and down if (!self.oscillating) { self.oscillating = true; // No pulsing animation for coins self.scale.x = 1; self.scale.y = 1; } } if (self.x < -100) { // Cancel any active tweens when destroying tween.stop(self); tween.stop(self.scale); self.destroy(); } }; }); // ComboDisplay handles showing combo text on screen var ComboDisplay = Container.expand(function () { var self = Container.call(this); var comboText = new Text2('', { size: 60, fill: 0xFFD700, // Gold color for combo text font: "'Press Start 2P', monospace" }); comboText.anchor.set(0.5, 0.5); self.addChild(comboText); self.showCombo = function (comboCount, points) { comboText.setText('COMBO x' + comboCount + '!\n+' + points + ' POINTS!'); comboText.alpha = 1; // Animate the combo text with a scale effect comboText.scale.x = 1.2; comboText.scale.y = 1.2; tween(comboText, { alpha: 0, scaleX: 0.8, scaleY: 0.8, y: comboText.y - 60 // Move up as it fades but not as far }, { duration: 800, easing: tween.easeOut }); }; return self; }); var Enemy = Container.expand(function () { var self = Container.call(this); self.runFrames = ['turtle1', 'turtle2']; self.sprites = []; // Pre-attach all sprites for (var i = 0; i < self.runFrames.length; i++) { var sprite = self.attachAsset(self.runFrames[i], { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); sprite.alpha = i === 0 ? 1 : 0; self.sprites.push(sprite); } self.speed = 6 + Math.random() * 2; self.passed = false; self.isJumping = false; self.velocityY = 0; self.canHarmPlayer = true; // Flag to track if enemy can harm player self.groundY = 2732 - 400; // 400 pixels from bottom self.y = self.groundY; // Set initial position to ground level self.runFrameIndex = 0; self.runFrameCounter = 0; self.runFrameDelay = 15; self.showFrame = function (index) { // Hide all sprites first for (var i = 0; i < self.sprites.length; i++) { self.sprites[i].alpha = 0; } // Show the appropriate sprite self.sprites[index].alpha = 1; }; self.update = function () { // Calculate base speed var currentSpeed = self.speed + Math.floor(LK.ticks / 300) * 0.2; // Increase speed over time faster // Double speed during star power if (starPowerActive) { currentSpeed *= 2; } self.x -= currentSpeed; if (self.isJumping) { self.y += self.velocityY; self.velocityY += 0.5; // Gravity effect if (self.y > 2732) { self.destroy(); } } else { self.runFrameCounter++; if (self.runFrameCounter >= self.runFrameDelay) { self.runFrameIndex = (self.runFrameIndex + 1) % self.runFrames.length; self.showFrame(self.runFrameIndex); self.runFrameCounter = 0; } } if (self.x < -100) { self.destroy(); } }; }); var FlyKoopa = Container.expand(function () { var self = Container.call(this); self.runFrames = ['flykoopa1', 'flykoopa2']; self.sprites = []; // Pre-attach all sprites for (var i = 0; i < self.runFrames.length; i++) { var sprite = self.attachAsset(self.runFrames[i], { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); sprite.alpha = i === 0 ? 1 : 0; self.sprites.push(sprite); } self.speed = 4 + Math.random() * 2; self.runFrameIndex = 0; self.runFrameCounter = 0; self.runFrameDelay = 15; self.isJumping = false; self.velocityY = 0; self.canHarmPlayer = true; // Flag to track if enemy can harm player self.showFrame = function (index) { // Hide all sprites first for (var i = 0; i < self.sprites.length; i++) { self.sprites[i].alpha = 0; } // Show the appropriate sprite self.sprites[index].alpha = 1; }; self.update = function () { // Increase speed over time, up to a reasonable maximum self.speed = 4 + Math.random() * 2 + Math.min(6, Math.floor(LK.ticks / 300) * 0.3); // Double speed during star power if (starPowerActive) { self.speed *= 2; } self.x -= self.speed; if (self.isJumping) { self.y += self.velocityY; self.velocityY += 0.5; // Gravity effect if (self.y > 2732) { self.destroy(); } } else { self.runFrameCounter++; if (self.runFrameCounter >= self.runFrameDelay) { self.runFrameIndex = (self.runFrameIndex + 1) % self.runFrames.length; self.showFrame(self.runFrameIndex); self.runFrameCounter = 0; } } if (self.x < -100) { self.destroy(); } }; }); var Goomba = Container.expand(function () { var self = Container.call(this); self.runFrames = ['goomba1', 'goomba2']; self.sprites = []; // Pre-attach all sprites for (var i = 0; i < self.runFrames.length; i++) { var sprite = self.attachAsset(self.runFrames[i], { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); sprite.alpha = i === 0 ? 1 : 0; self.sprites.push(sprite); } self.speed = 4 + Math.random() * 2; self.passed = false; self.isJumping = false; self.velocityY = 0; self.canHarmPlayer = true; // Flag to track if enemy can harm player self.groundY = 2732 - 400; // 400 pixels from bottom self.y = self.groundY; // Set initial position to ground level self.runFrameIndex = 0; self.runFrameCounter = 0; self.runFrameDelay = 15; self.showFrame = function (index) { // Hide all sprites first for (var i = 0; i < self.sprites.length; i++) { self.sprites[i].alpha = 0; } // Show the appropriate sprite self.sprites[index].alpha = 1; }; self.update = function () { // Increase speed over time, up to a reasonable maximum self.speed = 4 + Math.random() * 2 + Math.min(6, Math.floor(LK.ticks / 300) * 0.3); // Double speed during star power if (starPowerActive) { self.speed *= 2; } self.x -= self.speed; if (self.isJumping) { self.y += self.velocityY; self.velocityY += 0.5; // Gravity effect if (self.y > 2732) { self.destroy(); } } else { self.runFrameCounter++; if (self.runFrameCounter >= self.runFrameDelay) { self.runFrameIndex = (self.runFrameIndex + 1) % self.runFrames.length; self.showFrame(self.runFrameIndex); self.runFrameCounter = 0; } } if (self.x < -100) { self.destroy(); } }; }); var MainMenu = Container.expand(function () { var self = Container.call(this); // Add background image var background = self.attachAsset('background3', { anchorX: 0, anchorY: 0 }); // Title text var titleText = new Text2('OG Mario\nVs\nMonsters', { size: 180, fill: 0xD94A38, //{3t} // NES Mario brick-red color font: "'Press Start 2P', monospace", align: 'center', // Add text alignment directly in the options stroke: 0xFFFFFF, // White border strokeThickness: 8 // Border thickness }); titleText.anchor.set(0.5, 0.5); titleText.x = 2048 / 2; titleText.y = 2732 / 4; // Position higher on screen self.addChild(titleText); // No subtitle text // Play button var playButton = new Text2('TAP TO START', { size: 120, fill: 0xFCA817, // NES Mario gold coin color font: "'Press Start 2P', monospace", stroke: 0xFFFFFF, // Add white border strokeThickness: 6 // Set border thickness }); playButton.anchor.set(0.5, 0.5); playButton.x = 2048 / 2; playButton.y = 2732 / 2; // Centered vertically self.addChild(playButton); // Make the play button "pulse" for attention self.animationCounter = 0; self.update = function () { self.animationCounter += 0.05; playButton.scale.x = 1 + Math.sin(self.animationCounter) * 0.1; playButton.scale.y = 1 + Math.sin(self.animationCounter) * 0.1; }; // Handle button clicks self.down = function (x, y, obj) { // Default behavior - start game if (self.onPlayClick) { self.onPlayClick(); } }; return self; }); var Player = Container.expand(function () { var self = Container.call(this); self.runAnimation = ['player_run1', 'player_run2', 'player_run3']; self.jumpAnimation = ['player_jump']; self.currentState = 'running'; self.sprites = []; self.runFrame = 0; self.jumpFrame = 0; self.animationCounter = 0; self.animationSpeed = 0.1; // Pre-attach all sprites for (var i = 0; i < self.runAnimation.length; i++) { var sprite = self.attachAsset(self.runAnimation[i], { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); sprite.alpha = i === 0 ? 1 : 0; self.sprites.push(sprite); } for (var j = 0; j < self.jumpAnimation.length; j++) { var jumpSprite = self.attachAsset(self.jumpAnimation[j], { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); jumpSprite.alpha = 0; self.sprites.push(jumpSprite); } self.speed = 5; // Base speed, will increase in update function self.jumpHeight = 25; self.isJumping = false; self.canDoubleJump = false; // Track if double jump is available self.hasDoubleJumped = false; // Track if double jump was used self.isDashing = false; // Track if player is currently dashing self.isInvincible = false; // Track if player is invincible self.dashCooldown = false; // Dash cooldown self.swipeStartX = null; // Track swipe start position self.swipeStartY = null; self.velocityY = 0; self.groundY = 2732 - 400; // 400 pixels from bottom self.y = self.groundY; // Set initial position to ground level self.runFrameDelay = 10; self.showFrame = function (frameType, index) { // Hide all sprites first for (var i = 0; i < self.sprites.length; i++) { self.sprites[i].alpha = 0; } // Show the appropriate sprite based on state and frame index if (frameType === 'run') { self.sprites[index].alpha = 1; } else if (frameType === 'jump') { self.sprites[self.runAnimation.length + index].alpha = 1; } }; // Dash functionality removed // Swipe handling removed self.down = function (x, y, obj) { // Down handler simplified, no swipe tracking }; self.up = function (x, y, obj) { // Up handler simplified, no swipe handling }; self.move = function (x, y, obj) { // Move handler simplified, no swipe detection }; self.update = function () { // Update player speed based on game progress and background speed // Use background speed as base for player speed scaling if available var speedMultiplier = 1; if (game.background && game.background.speed) { speedMultiplier = game.background.speed / 3; } self.speed = 5 + Math.min(5, Math.floor(LK.ticks / 300) * 0.3 * speedMultiplier); // Make player blink fast during star power if (starPowerActive) { // Cancel any existing tween on player tween.stop(self); // Use tween to create a more advanced flashing effect with color changes if (!self.starTweenActive) { var _flashNextColor = function flashNextColor() { // Apply the next color in the sequence tween(self, { tint: colors[colorIndex] }, { duration: 150, // Fast color transitions easing: tween.linear, onFinish: function onFinish() { // Cycle to the next color colorIndex = (colorIndex + 1) % colors.length; // Continue the cycle if star power is still active if (starPowerActive) { _flashNextColor(); } else { self.tint = 0xFFFFFF; // Reset tint when star power ends self.starTweenActive = false; } } }); }; // Start the flash sequence self.starTweenActive = true; // Create rapid color cycling effect var colors = [0xFFFF00, 0xFF0000, 0x00FF00, 0x0000FF, 0xFF00FF]; var colorIndex = 0; _flashNextColor(); } } else { // Ensure player is fully visible when star power ends self.alpha = 1; self.tint = 0xFFFFFF; // Reset tint self.starTweenActive = false; } if (self.isJumping) { self.y += self.velocityY; // Scale gravity based on game speed - faster speed = faster falling var gravityMultiplier = speedMultiplier > 1 ? speedMultiplier * 0.8 : 1; self.velocityY += 0.3 * gravityMultiplier; // Show jump sprite self.showFrame('jump', 0); if (self.y >= self.groundY - 30) { if (self.y < 0) { // Prevent jump from exceeding the top of the screen self.y = 0; self.velocityY = 0; } if (!self.isDead) { self.y = self.groundY - 30; self.isJumping = false; self.hasDoubleJumped = false; // Reset double jump when landing self.velocityY = 0; self.currentState = 'running'; // Reset combo state when landing if (comboActive) { comboActive = false; // comboCount will be reset to 0 when the next combo starts // No need to display combo here, it's shown on each stomp // No need to manage comboDisplayObj here anymore } } } } else { // Running animation self.animationCounter += self.animationSpeed; if (self.animationCounter >= 1) { self.animationCounter = 0; self.runFrame = (self.runFrame + 1) % self.runAnimation.length; self.showFrame('run', self.runFrame); } } }; self.jump = function () { // Get speed multiplier based on background speed var speedMultiplier = 1; if (game.background && game.background.speed) { speedMultiplier = game.background.speed / 3; } // Increase jump height slightly with game speed to help player clear obstacles var jumpHeightMultiplier = Math.min(1.3, 1 + (speedMultiplier - 1) * 0.3); // Regular jump when not jumping if (!self.isJumping) { self.isJumping = true; LK.getSound('jump').play(); self.velocityY = -self.jumpHeight * jumpHeightMultiplier; self.currentState = 'jumping'; self.showFrame('jump', 0); } // Double jump when already jumping (always available) else if (self.isJumping && !self.hasDoubleJumped) { LK.getSound('jump').play(); self.velocityY = -self.jumpHeight * 0.8 * jumpHeightMultiplier; // Slightly lower second jump self.hasDoubleJumped = true; // No flash effect for double jump to make it distinct from dash } }; }); var Star = Container.expand(function () { var self = Container.call(this); // Use star asset directly instead of recoloring coin var starGraphics = self.attachAsset('star', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.2, scaleY: 1.2 }); // Set star color to bright yellow for emphasis starGraphics.tint = 0xFFFF00; self.speed = 3; self.collected = false; self.speed = 5; // Initial horizontal speed self.velocityY = 0; // Vertical velocity for gravity/bouncing self.gravity = 0.5; // Gravity strength self.bounceVelocity = -12; // Upward velocity on bounce self.groundY = 2732 - 400; // Ground level, same as player/enemies self.y = 2732 - 600; // Initial Y position (can be adjusted) self.collected = false; // Oscillation tween removed // Add animation (if needed later, currently unused) self.animationCounter = 0; self.update = function () { if (!game.playerDead) { // --- Horizontal Movement --- // Increase base horizontal speed over time, matching background speed scaling var baseSpeed = 5 + Math.min(8, Math.floor(LK.ticks / 300) * 0.3); var currentSpeed = baseSpeed; // Double speed during star power if (starPowerActive) { currentSpeed *= 2; } self.x -= currentSpeed; // --- Vertical Movement (Gravity & Bouncing) --- self.velocityY += self.gravity; // Apply gravity self.y += self.velocityY; // Update vertical position // Check for ground collision and bounce if (self.y >= self.groundY) { self.y = self.groundY; // Correct position to ground level self.velocityY = self.bounceVelocity; // Apply bounce velocity upwards } } else { // If player is dead, star just falls off screen (original simple fall) self.y += self.velocityY; self.velocityY += self.gravity; // Apply gravity even when player is dead if (self.y > 2732 + 100) { // Check slightly below screen bottom self.destroy(); } } // --- Off-screen Check (Left Edge) --- if (self.x < -100) { // No tweens to stop anymore self.destroy(); } }; return self; }); /**** * Initialize Game ****/ // Helper function to adjust music playback rate as LK.setMusicPlaybackRate is not available /**** *-Seperator- ****/ var game = new LK.Game({ backgroundColor: 0x5C94FC // NES Mario sky blue background color }); /**** * Game Code ****/ // We can't use document.createElement in this environment // Instead, we'll rely on the font already being available in the system // or being loaded by the LK engine automatically // No additional code needed for font loading in this environment // Game state variables // Helper function to adjust music playback rate as LK.setMusicPlaybackRate is not available // The font 'Press Start 2P' is a web font that should be loaded from Google Fonts // For reference: the font is used with font: "'Press Start 2P', monospace" function setMusicPlaybackRate(rate) { // Check if music is playing before trying to adjust it // Use the sound API instead of the non-existent LK.getMusic function var music = LK.getSound('theme'); if (music) { // Set the playback rate if available on the audio element if (music.playbackRate !== undefined) { music.playbackRate = rate; } } } var gameStarted = false; var showingUpgradeMenu = false; var mainMenu; var upgradeMenu; var background; var background2; var player; var enemies = []; var enemySpawnInterval = 80; var enemySpawnCounter = 0; var coinObjects = []; // Renamed from 'coins' to avoid conflict with coin counter var coinSpawnInterval = Math.max(40, 150 - Math.floor(LK.ticks / 300) * 2); // Decrease interval faster, minimum 40 var coinSpawnCounter = 0; var stars = []; // Array to store star power-ups var starSpawnInterval = 800; // Spawn stars less frequently than coins var starSpawnCounter = 0; var starPowerActive = false; // Track if star power is active var starPowerDuration = 10 * 60; // 10 seconds at 60fps var starPowerTimer = 0; // Timer for star power // Combo system variables var comboCount = 0; var comboActive = false; var comboDisplayObj = null; // Score system var score = 0; var coins = storage.coins || 0; // Get persistent coin count var scoreText = new Text2('Score: 0', { size: 60, fill: 0xFCE29F, //{67} // NES light tan/cream color font: "'Press Start 2P', monospace", stroke: 0xFFFFFF, // White border strokeThickness: 4 // Border thickness }); // Speed indicator removed from HUD scoreText.anchor.set(1, 0); scoreText.x = -50; scoreText.y = 50; LK.gui.topRight.addChild(scoreText); // Coin count display var coinText = new Text2('Coins: ' + coins, { size: 50, fill: 0xFCA817, //{6d} // NES Mario gold coin color // Gold color for coins font: "'Press Start 2P', monospace", stroke: 0xFFFFFF, // White border strokeThickness: 4 // Border thickness }); coinText.anchor.set(1, 0); coinText.x = -50; coinText.y = 120; // Position below score LK.gui.topRight.addChild(coinText); // Combo indicator removed from HUD scoreText.alpha = 0; // Hide score initially coinText.alpha = 0; // Hide coin count initially // Always enable double jump, no upgrades needed storage.doubleJumpPurchased = true; // Dash is removed // Create parallax backgrounds for main menu animation background = game.addChild(new Background()); background.x = 0; background.y = 0; background2 = game.addChild(new Background2()); background2.x = 2048; background2.y = 0; // Create automated player for main menu var automatedPlayer = game.addChild(new AutomatedPlayer()); automatedPlayer.x = -100; // Show main menu with elements on top of background mainMenu = game.addChild(new MainMenu()); // Animate title text with tween // First position title off-screen at the top if (mainMenu.children && mainMenu.children[1]) { var titleText = mainMenu.children[1]; // Get the title text titleText.y = -200; // Start from above the screen // Animate title dropping from top tween(titleText, { y: 2732 / 4 // Final position (center) }, { duration: 1000, easing: tween.bounceOut, onFinish: function onFinish() { // Add shake effect after dropping var originalX = titleText.x; var originalY = titleText.y; var shakeCount = 0; var maxShakes = 5; function shakeTitle() { // Shake in random direction var shakeX = originalX + (Math.random() * 40 - 20); var shakeY = originalY + (Math.random() * 20 - 10); tween(titleText, { x: shakeX, y: shakeY }, { duration: 100, easing: tween.linear, onFinish: function onFinish() { shakeCount++; if (shakeCount < maxShakes) { shakeTitle(); // Continue shaking } else { // Return to original position tween(titleText, { x: originalX, y: originalY }, { duration: 100, easing: tween.linear, onFinish: function onFinish() { // Start playing game theme music LK.playMusic('theme'); // Drop the automated player from top if it exists if (automatedPlayer && automatedPlayer.groundY !== undefined) { tween(automatedPlayer, { y: automatedPlayer.groundY - 30 }, { duration: 1000, easing: tween.bounceOut }); } } }); } } }); } shakeTitle(); // Start the shake sequence } }); } mainMenu.onPlayClick = function () { // Start the game gameStarted = true; if (mainMenu) { // Add null check before destroying mainMenu.destroy(); mainMenu = null; } // Initialize game elements - handled in game.down }; game.update = function () { // Handle menu states if (!gameStarted) { // When in menu mode, update backgrounds for parallax effect and menu if (background) { background.update(); } if (background2) { background2.update(); } // Update automated player if it exists if (typeof automatedPlayer !== 'undefined' && automatedPlayer) { automatedPlayer.update(); } // Update the menu if (mainMenu) { mainMenu.update(); } return; } // Game hasn't been initialized yet - start it if (!player) { // Initialize game elements // Player and GUI should already be created in game.down // Set background reference for other objects game.background = background; } // Game play update logic background.update(); background2.update(); player.update(); enemySpawnCounter++; coinSpawnCounter++; starSpawnCounter++; // Handle star power-up timer if (starPowerActive) { starPowerTimer--; if (starPowerTimer <= 0) { starPowerActive = false; player.isInvincible = false; // Return background tint to normal background.sprites[0].tint = 0xFFFFFF; background2.sprites[0].tint = 0xFFFFFF; // Switch back to regular theme music LK.stopMusic(); LK.playMusic('theme'); } } if (coinSpawnCounter >= coinSpawnInterval) { var coin = new Coin(); coin.x = 2048 + Math.random() * 200; // Set a reasonable height range for coins (not too low, not too high) coin.y = Math.max(300, Math.min(2732 - 600, Math.random() * (2732 - 800))); coinObjects.push(coin); game.addChild(coin); coinSpawnCounter = 0; } // Spawn star power-ups occasionally, but only if star power is not active if (starSpawnCounter >= starSpawnInterval && !starPowerActive) { var star = new Star(); star.x = 2048 + Math.random() * 200; // Set a reasonable height range for stars (not too low, not too high) star.y = Math.max(300, Math.min(2732 - 600, Math.random() * (2732 - 800))); stars.push(star); game.addChild(star); starSpawnCounter = 0; starSpawnInterval = Math.floor(Math.random() * 300) + 300; // Random interval between 300-600 ticks } if (enemySpawnCounter >= enemySpawnInterval) { var enemyType = Math.random() < 0.33 ? 'Enemy' : Math.random() < 0.5 ? 'Goomba' : 'FlyKoopa'; var enemy; if (enemyType === 'Enemy') { enemy = new Enemy(); } else if (enemyType === 'Goomba') { enemy = new Goomba(); } else { enemy = new FlyKoopa(); // Ensure FlyKoopas spawn in the middle to upper part of the screen, not too low enemy.y = Math.max(300, Math.min(2732 - 600, Math.random() * (2732 - 800))); } enemy.x = 2048 + Math.random() * 200; if (enemyType === 'FlyKoopa') { // Height for FlyKoopa is already set earlier, no need to set it again } else { enemy.y = enemy.groundY; // Use groundY for initial position } enemies.push(enemy); game.addChild(enemy); enemySpawnInterval = Math.max(15, Math.floor(Math.random() * 100) + 60 - Math.floor(LK.ticks / 300) * 2); // Decrease interval faster, minimum 15 enemySpawnCounter = 0; } for (var j = enemies.length - 1; j >= 0; j--) { enemies[j].update(); if (player.intersects(enemies[j])) { if (starPowerActive && enemies[j].canHarmPlayer !== false) { // Star power is active - instantly defeat enemy with special effect enemies[j].velocityY = -15; // Stronger pop up effect for star power enemies[j].isJumping = true; enemies[j].canHarmPlayer = false; LK.getSound('stomp').play(); // Create a sparkle effect LK.effects.flashObject(enemies[j], 0xFFFF00, 300); // Handle combo tracking - activate combo mode if not already active if (!comboActive) { comboActive = true; comboCount = 0; // Start count at 0, first stomp makes it 1 } // Increment combo count comboCount++; // Calculate score with combo bonus var starEnemyPoints = 100 * comboCount; // Points scale with combo score += starEnemyPoints; // Display flash text for combo count if more than 1 if (comboCount > 1) { // Create a *new* combo display object for each stomp in the combo var currentComboDisplay = game.addChild(new ComboDisplay()); currentComboDisplay.x = enemies[j].x; currentComboDisplay.y = enemies[j].y - 50; currentComboDisplay.showCombo(comboCount, starEnemyPoints); // We don't store this in the global comboDisplayObj anymore } scoreText.setText('Score: ' + score); LK.setScore(score); } else if (!player.isDead && player.isJumping && player.velocityY > 0 && player.y < enemies[j].y) { // Player is falling and lands on enemy enemies[j].velocityY = -10; // Pop up effect for enemy player.velocityY = -15; // Bounce Mario upwards LK.getSound('stomp').play(); // Play stomp sound // Handle combo tracking - activate combo mode if not already active if (!comboActive) { comboActive = true; comboCount = 0; // Start count at 0, first stomp makes it 1 } // Increment combo count comboCount++; // Calculate score with combo bonus var enemyPoints = 50 * comboCount; // Points scale with combo score += enemyPoints; // Display flash text for combo count if more than 1 if (comboCount > 1) { // Create a *new* combo display object for each stomp in the combo var currentComboDisplay = game.addChild(new ComboDisplay()); currentComboDisplay.x = enemies[j].x; currentComboDisplay.y = enemies[j].y - 50; currentComboDisplay.showCombo(comboCount, enemyPoints); // We don't store this in the global comboDisplayObj anymore } scoreText.setText('Score: ' + score); LK.setScore(score); enemies[j].isJumping = true; enemies[j].canHarmPlayer = false; // Enemy can no longer harm player } else if (enemies[j].canHarmPlayer !== false && !player.isInvincible) { if (!player.isDead && enemies[j].canHarmPlayer !== false) { LK.getSound('dead').play(); // Play dead sound player.isDead = true; game.playerDead = true; player.velocityY = -10; // Pop up effect similar to enemy player.isJumping = true; LK.setTimeout(function () { // Save final score before game over LK.setScore(score); LK.showGameOver(); }, 3000); // Delay game over by 3 seconds } } } else if (player.x > enemies[j].x && !enemies[j].passed) { enemies[j].passed = true; } if (enemies[j].x < -100) { enemies.splice(j, 1); } } for (var k = coinObjects.length - 1; k >= 0; k--) { coinObjects[k].update(); if (player.intersects(coinObjects[k])) { if (!coinObjects[k].collected) { coinObjects[k].collected = true; LK.getSound('Coin').play(); // Play coin sound // Increase score when collecting coins score += 10; // Separate coin counter coins++; // Update storage for persistence storage.coins = coins; // Update displays scoreText.setText('Score: ' + score); coinText.setText('Coins: ' + coins); LK.setScore(score); // Create a coin pickup effect // Visual effects for coin collection - similar to star power but smaller LK.effects.flashObject(player, 0xFCA817, 300); // Gold flash // Create a coin burst effect for (var i = 0; i < 8; i++) { var angle = i / 8 * Math.PI * 2; var distance = 60; var particleX = coinObjects[k].x + Math.cos(angle) * distance; var particleY = coinObjects[k].y + Math.sin(angle) * distance; var particle = LK.getAsset('coin', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.4, scaleY: 0.4, x: coinObjects[k].x, y: coinObjects[k].y, alpha: 0.8 }); particle.tint = 0xFCA817; // Gold tint game.addChild(particle); // Animate particles outward tween(particle, { x: particleX, y: particleY, alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 800, easing: tween.easeOut }); // Remove particles after animation LK.setTimeout(function (p) { return function () { if (p && p.parent) { p.parent.removeChild(p); } }; }(particle), 800); } coinObjects[k].destroy(); coinObjects.splice(k, 1); } } } // Update and check star power-ups for (var s = stars.length - 1; s >= 0; s--) { stars[s].update(); if (player.intersects(stars[s])) { if (!stars[s].collected) { stars[s].collected = true; // Switch music to star power theme LK.stopMusic(); LK.playMusic('starman2'); // Activate star power starPowerActive = true; starPowerTimer = starPowerDuration; player.isInvincible = true; // Visual effects for star power LK.effects.flashObject(player, 0xFFFF00, 500); // Yellow flash // Create a star burst effect for (var i = 0; i < 12; i++) { var angle = i / 12 * Math.PI * 2; var distance = 100; var particleX = player.x + Math.cos(angle) * distance; var particleY = player.y + Math.sin(angle) * distance; var particle = LK.getAsset('coin', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.5, scaleY: 0.5, x: player.x, y: player.y, alpha: 0.8 }); particle.tint = 0xFFFF00; // Yellow tint game.addChild(particle); // Animate particles outward tween(particle, { x: particleX, y: particleY, alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 1000, easing: tween.easeOut }); // Remove particles after animation LK.setTimeout(function (p) { return function () { if (p && p.parent) { p.parent.removeChild(p); } }; }(particle), 1000); } // Change background tint for star power effect if (background.sprites && background.sprites.length > 0) { background.sprites[0].tint = 0xFFFFAA; // Slight yellow tint } if (background2.sprites && background2.sprites.length > 0) { background2.sprites[0].tint = 0xFFFFAA; // Slight yellow tint } // Add extra points for star score += 100; scoreText.setText('Score: ' + score); LK.setScore(score); // Remove the star stars[s].destroy(); stars.splice(s, 1); } } else if (stars[s].x < -100) { stars[s].destroy(); stars.splice(s, 1); } } // Update speed display and music speed if (background && !game.playerDead) { var speedMultiplier = (background.speed / 3).toFixed(1); // Speed text update removed // Combo text update removed // Adjust music playback rate based on game speed var musicPlaybackRate = Math.min(1.5, 0.8 + background.speed / 3 * 0.2); // Increase music speed during star power if (starPowerActive) { musicPlaybackRate = Math.min(2.0, musicPlaybackRate * 1.2); } setMusicPlaybackRate(musicPlaybackRate); } }; game.down = function (x, y, obj) { if (!gameStarted) { // Transition to game on tap gameStarted = true; if (mainMenu && mainMenu.destroy) { mainMenu.destroy(); mainMenu = null; } if (typeof automatedPlayer !== 'undefined' && automatedPlayer) { automatedPlayer.destroy(); automatedPlayer = null; } // Background will be kept and reused for actual gameplay // We don't need to initialize background in the game.update function // since we already have it // Show score and coin count scoreText.alpha = 1; coinText.alpha = 1; // Create player player = game.addChild(new Player()); player.x = 2048 / 4; player.y = player.groundY - 30; return; } else { // Pass event to player player.down(x, y, obj); // In-game tap makes player jump player.jump(); } }; game.up = function (x, y, obj) { if (!gameStarted) { return; } // Pass touch up event to player if (player) { player.up(x, y, obj); } }; game.move = function (x, y, obj) { if (!gameStarted) { return; } // Pass touch move event to player if (player) { player.move(x, y, obj); } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
coins: 0
});
/****
* Classes
****/
var AutomatedPlayer = Container.expand(function () {
var self = Container.call(this);
self.runAnimation = ['player_run1', 'player_run2', 'player_run3'];
self.sprites = [];
self.runFrame = 0;
self.animationCounter = 0;
self.animationSpeed = 0.1;
// Pre-attach all sprites
for (var i = 0; i < self.runAnimation.length; i++) {
var sprite = self.attachAsset(self.runAnimation[i], {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
sprite.alpha = i === 0 ? 1 : 0;
self.sprites.push(sprite);
}
self.groundY = 2732 - 400; // 400 pixels from bottom
self.y = -200; // Start off-screen at the top
self.showFrame = function (index) {
// Hide all sprites first
for (var i = 0; i < self.sprites.length; i++) {
self.sprites[i].alpha = 0;
}
// Show the appropriate sprite
self.sprites[index].alpha = 1;
};
self.update = function () {
// Running animation
self.animationCounter += self.animationSpeed;
if (self.animationCounter >= 1) {
self.animationCounter = 0;
self.runFrame = (self.runFrame + 1) % self.runAnimation.length;
self.showFrame(self.runFrame);
}
// Move right automatically
self.x += 5;
// Wrap around when reaching edge
if (self.x > 2048 + 100) {
self.x = -100;
}
};
return self;
});
var Background = Container.expand(function () {
var self = Container.call(this);
var backgrounds = ['background1', 'background2', 'background3'];
var randomBackground = backgrounds[Math.floor(Math.random() * backgrounds.length)];
var backgroundGraphics = self.attachAsset(randomBackground, {
anchorX: 0,
anchorY: 0
});
self.sprites = [backgroundGraphics]; // Store sprites for access
self.speed = 3;
self.update = function () {
if (!game.playerDead) {
// Increase speed over time, up to a reasonable maximum
self.speed = 3 + Math.min(8, Math.floor(LK.ticks / 300) * 0.3);
// Double speed during star power
if (starPowerActive) {
self.speed *= 2;
}
self.x -= self.speed;
}
if (self.x <= -2048) {
self.x = 2048;
}
};
});
var Background2 = Container.expand(function () {
var self = Container.call(this);
var backgrounds = ['background1', 'background2', 'background3'];
var randomBackground = backgrounds[Math.floor(Math.random() * backgrounds.length)];
var backgroundGraphics = self.attachAsset(randomBackground, {
anchorX: 0,
anchorY: 0
});
self.sprites = [backgroundGraphics]; // Store sprites for access
self.speed = 3;
self.update = function () {
if (!game.playerDead) {
// Increase speed over time, up to a reasonable maximum
self.speed = 3 + Math.min(8, Math.floor(LK.ticks / 300) * 0.3);
// Double speed during star power
if (starPowerActive) {
self.speed *= 2;
}
self.x -= self.speed;
}
if (self.x <= -2048) {
self.x = 2048;
}
};
});
var Coin = Container.expand(function () {
var self = Container.call(this);
var coinGraphics = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
self.speed = 3;
self.collected = false;
self.y = 2732 - 600; // Position coins higher than the ground
self.update = function () {
if (!game.playerDead) {
// Increase speed over time, up to a reasonable maximum
self.speed = 3 + Math.min(8, Math.floor(LK.ticks / 300) * 0.3);
// Double speed during star power
if (starPowerActive) {
self.speed *= 2;
}
self.x -= self.speed;
// Removed oscillation to keep coins from moving up and down
if (!self.oscillating) {
self.oscillating = true;
// No pulsing animation for coins
self.scale.x = 1;
self.scale.y = 1;
}
}
if (self.x < -100) {
// Cancel any active tweens when destroying
tween.stop(self);
tween.stop(self.scale);
self.destroy();
}
};
});
// ComboDisplay handles showing combo text on screen
var ComboDisplay = Container.expand(function () {
var self = Container.call(this);
var comboText = new Text2('', {
size: 60,
fill: 0xFFD700,
// Gold color for combo text
font: "'Press Start 2P', monospace"
});
comboText.anchor.set(0.5, 0.5);
self.addChild(comboText);
self.showCombo = function (comboCount, points) {
comboText.setText('COMBO x' + comboCount + '!\n+' + points + ' POINTS!');
comboText.alpha = 1;
// Animate the combo text with a scale effect
comboText.scale.x = 1.2;
comboText.scale.y = 1.2;
tween(comboText, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8,
y: comboText.y - 60 // Move up as it fades but not as far
}, {
duration: 800,
easing: tween.easeOut
});
};
return self;
});
var Enemy = Container.expand(function () {
var self = Container.call(this);
self.runFrames = ['turtle1', 'turtle2'];
self.sprites = [];
// Pre-attach all sprites
for (var i = 0; i < self.runFrames.length; i++) {
var sprite = self.attachAsset(self.runFrames[i], {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
sprite.alpha = i === 0 ? 1 : 0;
self.sprites.push(sprite);
}
self.speed = 6 + Math.random() * 2;
self.passed = false;
self.isJumping = false;
self.velocityY = 0;
self.canHarmPlayer = true; // Flag to track if enemy can harm player
self.groundY = 2732 - 400; // 400 pixels from bottom
self.y = self.groundY; // Set initial position to ground level
self.runFrameIndex = 0;
self.runFrameCounter = 0;
self.runFrameDelay = 15;
self.showFrame = function (index) {
// Hide all sprites first
for (var i = 0; i < self.sprites.length; i++) {
self.sprites[i].alpha = 0;
}
// Show the appropriate sprite
self.sprites[index].alpha = 1;
};
self.update = function () {
// Calculate base speed
var currentSpeed = self.speed + Math.floor(LK.ticks / 300) * 0.2; // Increase speed over time faster
// Double speed during star power
if (starPowerActive) {
currentSpeed *= 2;
}
self.x -= currentSpeed;
if (self.isJumping) {
self.y += self.velocityY;
self.velocityY += 0.5; // Gravity effect
if (self.y > 2732) {
self.destroy();
}
} else {
self.runFrameCounter++;
if (self.runFrameCounter >= self.runFrameDelay) {
self.runFrameIndex = (self.runFrameIndex + 1) % self.runFrames.length;
self.showFrame(self.runFrameIndex);
self.runFrameCounter = 0;
}
}
if (self.x < -100) {
self.destroy();
}
};
});
var FlyKoopa = Container.expand(function () {
var self = Container.call(this);
self.runFrames = ['flykoopa1', 'flykoopa2'];
self.sprites = [];
// Pre-attach all sprites
for (var i = 0; i < self.runFrames.length; i++) {
var sprite = self.attachAsset(self.runFrames[i], {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
sprite.alpha = i === 0 ? 1 : 0;
self.sprites.push(sprite);
}
self.speed = 4 + Math.random() * 2;
self.runFrameIndex = 0;
self.runFrameCounter = 0;
self.runFrameDelay = 15;
self.isJumping = false;
self.velocityY = 0;
self.canHarmPlayer = true; // Flag to track if enemy can harm player
self.showFrame = function (index) {
// Hide all sprites first
for (var i = 0; i < self.sprites.length; i++) {
self.sprites[i].alpha = 0;
}
// Show the appropriate sprite
self.sprites[index].alpha = 1;
};
self.update = function () {
// Increase speed over time, up to a reasonable maximum
self.speed = 4 + Math.random() * 2 + Math.min(6, Math.floor(LK.ticks / 300) * 0.3);
// Double speed during star power
if (starPowerActive) {
self.speed *= 2;
}
self.x -= self.speed;
if (self.isJumping) {
self.y += self.velocityY;
self.velocityY += 0.5; // Gravity effect
if (self.y > 2732) {
self.destroy();
}
} else {
self.runFrameCounter++;
if (self.runFrameCounter >= self.runFrameDelay) {
self.runFrameIndex = (self.runFrameIndex + 1) % self.runFrames.length;
self.showFrame(self.runFrameIndex);
self.runFrameCounter = 0;
}
}
if (self.x < -100) {
self.destroy();
}
};
});
var Goomba = Container.expand(function () {
var self = Container.call(this);
self.runFrames = ['goomba1', 'goomba2'];
self.sprites = [];
// Pre-attach all sprites
for (var i = 0; i < self.runFrames.length; i++) {
var sprite = self.attachAsset(self.runFrames[i], {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
sprite.alpha = i === 0 ? 1 : 0;
self.sprites.push(sprite);
}
self.speed = 4 + Math.random() * 2;
self.passed = false;
self.isJumping = false;
self.velocityY = 0;
self.canHarmPlayer = true; // Flag to track if enemy can harm player
self.groundY = 2732 - 400; // 400 pixels from bottom
self.y = self.groundY; // Set initial position to ground level
self.runFrameIndex = 0;
self.runFrameCounter = 0;
self.runFrameDelay = 15;
self.showFrame = function (index) {
// Hide all sprites first
for (var i = 0; i < self.sprites.length; i++) {
self.sprites[i].alpha = 0;
}
// Show the appropriate sprite
self.sprites[index].alpha = 1;
};
self.update = function () {
// Increase speed over time, up to a reasonable maximum
self.speed = 4 + Math.random() * 2 + Math.min(6, Math.floor(LK.ticks / 300) * 0.3);
// Double speed during star power
if (starPowerActive) {
self.speed *= 2;
}
self.x -= self.speed;
if (self.isJumping) {
self.y += self.velocityY;
self.velocityY += 0.5; // Gravity effect
if (self.y > 2732) {
self.destroy();
}
} else {
self.runFrameCounter++;
if (self.runFrameCounter >= self.runFrameDelay) {
self.runFrameIndex = (self.runFrameIndex + 1) % self.runFrames.length;
self.showFrame(self.runFrameIndex);
self.runFrameCounter = 0;
}
}
if (self.x < -100) {
self.destroy();
}
};
});
var MainMenu = Container.expand(function () {
var self = Container.call(this);
// Add background image
var background = self.attachAsset('background3', {
anchorX: 0,
anchorY: 0
});
// Title text
var titleText = new Text2('OG Mario\nVs\nMonsters', {
size: 180,
fill: 0xD94A38,
//{3t} // NES Mario brick-red color
font: "'Press Start 2P', monospace",
align: 'center',
// Add text alignment directly in the options
stroke: 0xFFFFFF,
// White border
strokeThickness: 8 // Border thickness
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 2048 / 2;
titleText.y = 2732 / 4; // Position higher on screen
self.addChild(titleText);
// No subtitle text
// Play button
var playButton = new Text2('TAP TO START', {
size: 120,
fill: 0xFCA817,
// NES Mario gold coin color
font: "'Press Start 2P', monospace",
stroke: 0xFFFFFF,
// Add white border
strokeThickness: 6 // Set border thickness
});
playButton.anchor.set(0.5, 0.5);
playButton.x = 2048 / 2;
playButton.y = 2732 / 2; // Centered vertically
self.addChild(playButton);
// Make the play button "pulse" for attention
self.animationCounter = 0;
self.update = function () {
self.animationCounter += 0.05;
playButton.scale.x = 1 + Math.sin(self.animationCounter) * 0.1;
playButton.scale.y = 1 + Math.sin(self.animationCounter) * 0.1;
};
// Handle button clicks
self.down = function (x, y, obj) {
// Default behavior - start game
if (self.onPlayClick) {
self.onPlayClick();
}
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
self.runAnimation = ['player_run1', 'player_run2', 'player_run3'];
self.jumpAnimation = ['player_jump'];
self.currentState = 'running';
self.sprites = [];
self.runFrame = 0;
self.jumpFrame = 0;
self.animationCounter = 0;
self.animationSpeed = 0.1;
// Pre-attach all sprites
for (var i = 0; i < self.runAnimation.length; i++) {
var sprite = self.attachAsset(self.runAnimation[i], {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
sprite.alpha = i === 0 ? 1 : 0;
self.sprites.push(sprite);
}
for (var j = 0; j < self.jumpAnimation.length; j++) {
var jumpSprite = self.attachAsset(self.jumpAnimation[j], {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
jumpSprite.alpha = 0;
self.sprites.push(jumpSprite);
}
self.speed = 5; // Base speed, will increase in update function
self.jumpHeight = 25;
self.isJumping = false;
self.canDoubleJump = false; // Track if double jump is available
self.hasDoubleJumped = false; // Track if double jump was used
self.isDashing = false; // Track if player is currently dashing
self.isInvincible = false; // Track if player is invincible
self.dashCooldown = false; // Dash cooldown
self.swipeStartX = null; // Track swipe start position
self.swipeStartY = null;
self.velocityY = 0;
self.groundY = 2732 - 400; // 400 pixels from bottom
self.y = self.groundY; // Set initial position to ground level
self.runFrameDelay = 10;
self.showFrame = function (frameType, index) {
// Hide all sprites first
for (var i = 0; i < self.sprites.length; i++) {
self.sprites[i].alpha = 0;
}
// Show the appropriate sprite based on state and frame index
if (frameType === 'run') {
self.sprites[index].alpha = 1;
} else if (frameType === 'jump') {
self.sprites[self.runAnimation.length + index].alpha = 1;
}
};
// Dash functionality removed
// Swipe handling removed
self.down = function (x, y, obj) {
// Down handler simplified, no swipe tracking
};
self.up = function (x, y, obj) {
// Up handler simplified, no swipe handling
};
self.move = function (x, y, obj) {
// Move handler simplified, no swipe detection
};
self.update = function () {
// Update player speed based on game progress and background speed
// Use background speed as base for player speed scaling if available
var speedMultiplier = 1;
if (game.background && game.background.speed) {
speedMultiplier = game.background.speed / 3;
}
self.speed = 5 + Math.min(5, Math.floor(LK.ticks / 300) * 0.3 * speedMultiplier);
// Make player blink fast during star power
if (starPowerActive) {
// Cancel any existing tween on player
tween.stop(self);
// Use tween to create a more advanced flashing effect with color changes
if (!self.starTweenActive) {
var _flashNextColor = function flashNextColor() {
// Apply the next color in the sequence
tween(self, {
tint: colors[colorIndex]
}, {
duration: 150,
// Fast color transitions
easing: tween.linear,
onFinish: function onFinish() {
// Cycle to the next color
colorIndex = (colorIndex + 1) % colors.length;
// Continue the cycle if star power is still active
if (starPowerActive) {
_flashNextColor();
} else {
self.tint = 0xFFFFFF; // Reset tint when star power ends
self.starTweenActive = false;
}
}
});
}; // Start the flash sequence
self.starTweenActive = true;
// Create rapid color cycling effect
var colors = [0xFFFF00, 0xFF0000, 0x00FF00, 0x0000FF, 0xFF00FF];
var colorIndex = 0;
_flashNextColor();
}
} else {
// Ensure player is fully visible when star power ends
self.alpha = 1;
self.tint = 0xFFFFFF; // Reset tint
self.starTweenActive = false;
}
if (self.isJumping) {
self.y += self.velocityY;
// Scale gravity based on game speed - faster speed = faster falling
var gravityMultiplier = speedMultiplier > 1 ? speedMultiplier * 0.8 : 1;
self.velocityY += 0.3 * gravityMultiplier;
// Show jump sprite
self.showFrame('jump', 0);
if (self.y >= self.groundY - 30) {
if (self.y < 0) {
// Prevent jump from exceeding the top of the screen
self.y = 0;
self.velocityY = 0;
}
if (!self.isDead) {
self.y = self.groundY - 30;
self.isJumping = false;
self.hasDoubleJumped = false; // Reset double jump when landing
self.velocityY = 0;
self.currentState = 'running';
// Reset combo state when landing
if (comboActive) {
comboActive = false;
// comboCount will be reset to 0 when the next combo starts
// No need to display combo here, it's shown on each stomp
// No need to manage comboDisplayObj here anymore
}
}
}
} else {
// Running animation
self.animationCounter += self.animationSpeed;
if (self.animationCounter >= 1) {
self.animationCounter = 0;
self.runFrame = (self.runFrame + 1) % self.runAnimation.length;
self.showFrame('run', self.runFrame);
}
}
};
self.jump = function () {
// Get speed multiplier based on background speed
var speedMultiplier = 1;
if (game.background && game.background.speed) {
speedMultiplier = game.background.speed / 3;
}
// Increase jump height slightly with game speed to help player clear obstacles
var jumpHeightMultiplier = Math.min(1.3, 1 + (speedMultiplier - 1) * 0.3);
// Regular jump when not jumping
if (!self.isJumping) {
self.isJumping = true;
LK.getSound('jump').play();
self.velocityY = -self.jumpHeight * jumpHeightMultiplier;
self.currentState = 'jumping';
self.showFrame('jump', 0);
}
// Double jump when already jumping (always available)
else if (self.isJumping && !self.hasDoubleJumped) {
LK.getSound('jump').play();
self.velocityY = -self.jumpHeight * 0.8 * jumpHeightMultiplier; // Slightly lower second jump
self.hasDoubleJumped = true;
// No flash effect for double jump to make it distinct from dash
}
};
});
var Star = Container.expand(function () {
var self = Container.call(this);
// Use star asset directly instead of recoloring coin
var starGraphics = self.attachAsset('star', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
// Set star color to bright yellow for emphasis
starGraphics.tint = 0xFFFF00;
self.speed = 3;
self.collected = false;
self.speed = 5; // Initial horizontal speed
self.velocityY = 0; // Vertical velocity for gravity/bouncing
self.gravity = 0.5; // Gravity strength
self.bounceVelocity = -12; // Upward velocity on bounce
self.groundY = 2732 - 400; // Ground level, same as player/enemies
self.y = 2732 - 600; // Initial Y position (can be adjusted)
self.collected = false;
// Oscillation tween removed
// Add animation (if needed later, currently unused)
self.animationCounter = 0;
self.update = function () {
if (!game.playerDead) {
// --- Horizontal Movement ---
// Increase base horizontal speed over time, matching background speed scaling
var baseSpeed = 5 + Math.min(8, Math.floor(LK.ticks / 300) * 0.3);
var currentSpeed = baseSpeed;
// Double speed during star power
if (starPowerActive) {
currentSpeed *= 2;
}
self.x -= currentSpeed;
// --- Vertical Movement (Gravity & Bouncing) ---
self.velocityY += self.gravity; // Apply gravity
self.y += self.velocityY; // Update vertical position
// Check for ground collision and bounce
if (self.y >= self.groundY) {
self.y = self.groundY; // Correct position to ground level
self.velocityY = self.bounceVelocity; // Apply bounce velocity upwards
}
} else {
// If player is dead, star just falls off screen (original simple fall)
self.y += self.velocityY;
self.velocityY += self.gravity; // Apply gravity even when player is dead
if (self.y > 2732 + 100) {
// Check slightly below screen bottom
self.destroy();
}
}
// --- Off-screen Check (Left Edge) ---
if (self.x < -100) {
// No tweens to stop anymore
self.destroy();
}
};
return self;
});
/****
* Initialize Game
****/
// Helper function to adjust music playback rate as LK.setMusicPlaybackRate is not available
/****
*-Seperator-
****/
var game = new LK.Game({
backgroundColor: 0x5C94FC // NES Mario sky blue background color
});
/****
* Game Code
****/
// We can't use document.createElement in this environment
// Instead, we'll rely on the font already being available in the system
// or being loaded by the LK engine automatically
// No additional code needed for font loading in this environment
// Game state variables
// Helper function to adjust music playback rate as LK.setMusicPlaybackRate is not available
// The font 'Press Start 2P' is a web font that should be loaded from Google Fonts
// For reference: the font is used with font: "'Press Start 2P', monospace"
function setMusicPlaybackRate(rate) {
// Check if music is playing before trying to adjust it
// Use the sound API instead of the non-existent LK.getMusic function
var music = LK.getSound('theme');
if (music) {
// Set the playback rate if available on the audio element
if (music.playbackRate !== undefined) {
music.playbackRate = rate;
}
}
}
var gameStarted = false;
var showingUpgradeMenu = false;
var mainMenu;
var upgradeMenu;
var background;
var background2;
var player;
var enemies = [];
var enemySpawnInterval = 80;
var enemySpawnCounter = 0;
var coinObjects = []; // Renamed from 'coins' to avoid conflict with coin counter
var coinSpawnInterval = Math.max(40, 150 - Math.floor(LK.ticks / 300) * 2); // Decrease interval faster, minimum 40
var coinSpawnCounter = 0;
var stars = []; // Array to store star power-ups
var starSpawnInterval = 800; // Spawn stars less frequently than coins
var starSpawnCounter = 0;
var starPowerActive = false; // Track if star power is active
var starPowerDuration = 10 * 60; // 10 seconds at 60fps
var starPowerTimer = 0; // Timer for star power
// Combo system variables
var comboCount = 0;
var comboActive = false;
var comboDisplayObj = null;
// Score system
var score = 0;
var coins = storage.coins || 0; // Get persistent coin count
var scoreText = new Text2('Score: 0', {
size: 60,
fill: 0xFCE29F,
//{67} // NES light tan/cream color
font: "'Press Start 2P', monospace",
stroke: 0xFFFFFF,
// White border
strokeThickness: 4 // Border thickness
});
// Speed indicator removed from HUD
scoreText.anchor.set(1, 0);
scoreText.x = -50;
scoreText.y = 50;
LK.gui.topRight.addChild(scoreText);
// Coin count display
var coinText = new Text2('Coins: ' + coins, {
size: 50,
fill: 0xFCA817,
//{6d} // NES Mario gold coin color
// Gold color for coins
font: "'Press Start 2P', monospace",
stroke: 0xFFFFFF,
// White border
strokeThickness: 4 // Border thickness
});
coinText.anchor.set(1, 0);
coinText.x = -50;
coinText.y = 120; // Position below score
LK.gui.topRight.addChild(coinText);
// Combo indicator removed from HUD
scoreText.alpha = 0; // Hide score initially
coinText.alpha = 0; // Hide coin count initially
// Always enable double jump, no upgrades needed
storage.doubleJumpPurchased = true;
// Dash is removed
// Create parallax backgrounds for main menu animation
background = game.addChild(new Background());
background.x = 0;
background.y = 0;
background2 = game.addChild(new Background2());
background2.x = 2048;
background2.y = 0;
// Create automated player for main menu
var automatedPlayer = game.addChild(new AutomatedPlayer());
automatedPlayer.x = -100;
// Show main menu with elements on top of background
mainMenu = game.addChild(new MainMenu());
// Animate title text with tween
// First position title off-screen at the top
if (mainMenu.children && mainMenu.children[1]) {
var titleText = mainMenu.children[1]; // Get the title text
titleText.y = -200; // Start from above the screen
// Animate title dropping from top
tween(titleText, {
y: 2732 / 4 // Final position (center)
}, {
duration: 1000,
easing: tween.bounceOut,
onFinish: function onFinish() {
// Add shake effect after dropping
var originalX = titleText.x;
var originalY = titleText.y;
var shakeCount = 0;
var maxShakes = 5;
function shakeTitle() {
// Shake in random direction
var shakeX = originalX + (Math.random() * 40 - 20);
var shakeY = originalY + (Math.random() * 20 - 10);
tween(titleText, {
x: shakeX,
y: shakeY
}, {
duration: 100,
easing: tween.linear,
onFinish: function onFinish() {
shakeCount++;
if (shakeCount < maxShakes) {
shakeTitle(); // Continue shaking
} else {
// Return to original position
tween(titleText, {
x: originalX,
y: originalY
}, {
duration: 100,
easing: tween.linear,
onFinish: function onFinish() {
// Start playing game theme music
LK.playMusic('theme');
// Drop the automated player from top if it exists
if (automatedPlayer && automatedPlayer.groundY !== undefined) {
tween(automatedPlayer, {
y: automatedPlayer.groundY - 30
}, {
duration: 1000,
easing: tween.bounceOut
});
}
}
});
}
}
});
}
shakeTitle(); // Start the shake sequence
}
});
}
mainMenu.onPlayClick = function () {
// Start the game
gameStarted = true;
if (mainMenu) {
// Add null check before destroying
mainMenu.destroy();
mainMenu = null;
}
// Initialize game elements - handled in game.down
};
game.update = function () {
// Handle menu states
if (!gameStarted) {
// When in menu mode, update backgrounds for parallax effect and menu
if (background) {
background.update();
}
if (background2) {
background2.update();
}
// Update automated player if it exists
if (typeof automatedPlayer !== 'undefined' && automatedPlayer) {
automatedPlayer.update();
}
// Update the menu
if (mainMenu) {
mainMenu.update();
}
return;
}
// Game hasn't been initialized yet - start it
if (!player) {
// Initialize game elements
// Player and GUI should already be created in game.down
// Set background reference for other objects
game.background = background;
}
// Game play update logic
background.update();
background2.update();
player.update();
enemySpawnCounter++;
coinSpawnCounter++;
starSpawnCounter++;
// Handle star power-up timer
if (starPowerActive) {
starPowerTimer--;
if (starPowerTimer <= 0) {
starPowerActive = false;
player.isInvincible = false;
// Return background tint to normal
background.sprites[0].tint = 0xFFFFFF;
background2.sprites[0].tint = 0xFFFFFF;
// Switch back to regular theme music
LK.stopMusic();
LK.playMusic('theme');
}
}
if (coinSpawnCounter >= coinSpawnInterval) {
var coin = new Coin();
coin.x = 2048 + Math.random() * 200;
// Set a reasonable height range for coins (not too low, not too high)
coin.y = Math.max(300, Math.min(2732 - 600, Math.random() * (2732 - 800)));
coinObjects.push(coin);
game.addChild(coin);
coinSpawnCounter = 0;
}
// Spawn star power-ups occasionally, but only if star power is not active
if (starSpawnCounter >= starSpawnInterval && !starPowerActive) {
var star = new Star();
star.x = 2048 + Math.random() * 200;
// Set a reasonable height range for stars (not too low, not too high)
star.y = Math.max(300, Math.min(2732 - 600, Math.random() * (2732 - 800)));
stars.push(star);
game.addChild(star);
starSpawnCounter = 0;
starSpawnInterval = Math.floor(Math.random() * 300) + 300; // Random interval between 300-600 ticks
}
if (enemySpawnCounter >= enemySpawnInterval) {
var enemyType = Math.random() < 0.33 ? 'Enemy' : Math.random() < 0.5 ? 'Goomba' : 'FlyKoopa';
var enemy;
if (enemyType === 'Enemy') {
enemy = new Enemy();
} else if (enemyType === 'Goomba') {
enemy = new Goomba();
} else {
enemy = new FlyKoopa();
// Ensure FlyKoopas spawn in the middle to upper part of the screen, not too low
enemy.y = Math.max(300, Math.min(2732 - 600, Math.random() * (2732 - 800)));
}
enemy.x = 2048 + Math.random() * 200;
if (enemyType === 'FlyKoopa') {
// Height for FlyKoopa is already set earlier, no need to set it again
} else {
enemy.y = enemy.groundY; // Use groundY for initial position
}
enemies.push(enemy);
game.addChild(enemy);
enemySpawnInterval = Math.max(15, Math.floor(Math.random() * 100) + 60 - Math.floor(LK.ticks / 300) * 2); // Decrease interval faster, minimum 15
enemySpawnCounter = 0;
}
for (var j = enemies.length - 1; j >= 0; j--) {
enemies[j].update();
if (player.intersects(enemies[j])) {
if (starPowerActive && enemies[j].canHarmPlayer !== false) {
// Star power is active - instantly defeat enemy with special effect
enemies[j].velocityY = -15; // Stronger pop up effect for star power
enemies[j].isJumping = true;
enemies[j].canHarmPlayer = false;
LK.getSound('stomp').play();
// Create a sparkle effect
LK.effects.flashObject(enemies[j], 0xFFFF00, 300);
// Handle combo tracking - activate combo mode if not already active
if (!comboActive) {
comboActive = true;
comboCount = 0; // Start count at 0, first stomp makes it 1
}
// Increment combo count
comboCount++;
// Calculate score with combo bonus
var starEnemyPoints = 100 * comboCount; // Points scale with combo
score += starEnemyPoints;
// Display flash text for combo count if more than 1
if (comboCount > 1) {
// Create a *new* combo display object for each stomp in the combo
var currentComboDisplay = game.addChild(new ComboDisplay());
currentComboDisplay.x = enemies[j].x;
currentComboDisplay.y = enemies[j].y - 50;
currentComboDisplay.showCombo(comboCount, starEnemyPoints);
// We don't store this in the global comboDisplayObj anymore
}
scoreText.setText('Score: ' + score);
LK.setScore(score);
} else if (!player.isDead && player.isJumping && player.velocityY > 0 && player.y < enemies[j].y) {
// Player is falling and lands on enemy
enemies[j].velocityY = -10; // Pop up effect for enemy
player.velocityY = -15; // Bounce Mario upwards
LK.getSound('stomp').play(); // Play stomp sound
// Handle combo tracking - activate combo mode if not already active
if (!comboActive) {
comboActive = true;
comboCount = 0; // Start count at 0, first stomp makes it 1
}
// Increment combo count
comboCount++;
// Calculate score with combo bonus
var enemyPoints = 50 * comboCount; // Points scale with combo
score += enemyPoints;
// Display flash text for combo count if more than 1
if (comboCount > 1) {
// Create a *new* combo display object for each stomp in the combo
var currentComboDisplay = game.addChild(new ComboDisplay());
currentComboDisplay.x = enemies[j].x;
currentComboDisplay.y = enemies[j].y - 50;
currentComboDisplay.showCombo(comboCount, enemyPoints);
// We don't store this in the global comboDisplayObj anymore
}
scoreText.setText('Score: ' + score);
LK.setScore(score);
enemies[j].isJumping = true;
enemies[j].canHarmPlayer = false; // Enemy can no longer harm player
} else if (enemies[j].canHarmPlayer !== false && !player.isInvincible) {
if (!player.isDead && enemies[j].canHarmPlayer !== false) {
LK.getSound('dead').play(); // Play dead sound
player.isDead = true;
game.playerDead = true;
player.velocityY = -10; // Pop up effect similar to enemy
player.isJumping = true;
LK.setTimeout(function () {
// Save final score before game over
LK.setScore(score);
LK.showGameOver();
}, 3000); // Delay game over by 3 seconds
}
}
} else if (player.x > enemies[j].x && !enemies[j].passed) {
enemies[j].passed = true;
}
if (enemies[j].x < -100) {
enemies.splice(j, 1);
}
}
for (var k = coinObjects.length - 1; k >= 0; k--) {
coinObjects[k].update();
if (player.intersects(coinObjects[k])) {
if (!coinObjects[k].collected) {
coinObjects[k].collected = true;
LK.getSound('Coin').play(); // Play coin sound
// Increase score when collecting coins
score += 10;
// Separate coin counter
coins++;
// Update storage for persistence
storage.coins = coins;
// Update displays
scoreText.setText('Score: ' + score);
coinText.setText('Coins: ' + coins);
LK.setScore(score);
// Create a coin pickup effect
// Visual effects for coin collection - similar to star power but smaller
LK.effects.flashObject(player, 0xFCA817, 300); // Gold flash
// Create a coin burst effect
for (var i = 0; i < 8; i++) {
var angle = i / 8 * Math.PI * 2;
var distance = 60;
var particleX = coinObjects[k].x + Math.cos(angle) * distance;
var particleY = coinObjects[k].y + Math.sin(angle) * distance;
var particle = LK.getAsset('coin', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.4,
x: coinObjects[k].x,
y: coinObjects[k].y,
alpha: 0.8
});
particle.tint = 0xFCA817; // Gold tint
game.addChild(particle);
// Animate particles outward
tween(particle, {
x: particleX,
y: particleY,
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 800,
easing: tween.easeOut
});
// Remove particles after animation
LK.setTimeout(function (p) {
return function () {
if (p && p.parent) {
p.parent.removeChild(p);
}
};
}(particle), 800);
}
coinObjects[k].destroy();
coinObjects.splice(k, 1);
}
}
}
// Update and check star power-ups
for (var s = stars.length - 1; s >= 0; s--) {
stars[s].update();
if (player.intersects(stars[s])) {
if (!stars[s].collected) {
stars[s].collected = true;
// Switch music to star power theme
LK.stopMusic();
LK.playMusic('starman2');
// Activate star power
starPowerActive = true;
starPowerTimer = starPowerDuration;
player.isInvincible = true;
// Visual effects for star power
LK.effects.flashObject(player, 0xFFFF00, 500); // Yellow flash
// Create a star burst effect
for (var i = 0; i < 12; i++) {
var angle = i / 12 * Math.PI * 2;
var distance = 100;
var particleX = player.x + Math.cos(angle) * distance;
var particleY = player.y + Math.sin(angle) * distance;
var particle = LK.getAsset('coin', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5,
x: player.x,
y: player.y,
alpha: 0.8
});
particle.tint = 0xFFFF00; // Yellow tint
game.addChild(particle);
// Animate particles outward
tween(particle, {
x: particleX,
y: particleY,
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 1000,
easing: tween.easeOut
});
// Remove particles after animation
LK.setTimeout(function (p) {
return function () {
if (p && p.parent) {
p.parent.removeChild(p);
}
};
}(particle), 1000);
}
// Change background tint for star power effect
if (background.sprites && background.sprites.length > 0) {
background.sprites[0].tint = 0xFFFFAA; // Slight yellow tint
}
if (background2.sprites && background2.sprites.length > 0) {
background2.sprites[0].tint = 0xFFFFAA; // Slight yellow tint
}
// Add extra points for star
score += 100;
scoreText.setText('Score: ' + score);
LK.setScore(score);
// Remove the star
stars[s].destroy();
stars.splice(s, 1);
}
} else if (stars[s].x < -100) {
stars[s].destroy();
stars.splice(s, 1);
}
}
// Update speed display and music speed
if (background && !game.playerDead) {
var speedMultiplier = (background.speed / 3).toFixed(1);
// Speed text update removed
// Combo text update removed
// Adjust music playback rate based on game speed
var musicPlaybackRate = Math.min(1.5, 0.8 + background.speed / 3 * 0.2);
// Increase music speed during star power
if (starPowerActive) {
musicPlaybackRate = Math.min(2.0, musicPlaybackRate * 1.2);
}
setMusicPlaybackRate(musicPlaybackRate);
}
};
game.down = function (x, y, obj) {
if (!gameStarted) {
// Transition to game on tap
gameStarted = true;
if (mainMenu && mainMenu.destroy) {
mainMenu.destroy();
mainMenu = null;
}
if (typeof automatedPlayer !== 'undefined' && automatedPlayer) {
automatedPlayer.destroy();
automatedPlayer = null;
}
// Background will be kept and reused for actual gameplay
// We don't need to initialize background in the game.update function
// since we already have it
// Show score and coin count
scoreText.alpha = 1;
coinText.alpha = 1;
// Create player
player = game.addChild(new Player());
player.x = 2048 / 4;
player.y = player.groundY - 30;
return;
} else {
// Pass event to player
player.down(x, y, obj);
// In-game tap makes player jump
player.jump();
}
};
game.up = function (x, y, obj) {
if (!gameStarted) {
return;
}
// Pass touch up event to player
if (player) {
player.up(x, y, obj);
}
};
game.move = function (x, y, obj) {
if (!gameStarted) {
return;
}
// Pass touch move event to player
if (player) {
player.move(x, y, obj);
}
};