User prompt
Add down movement for the enemies even if i go down and screen scrolled up don't freeze enemies in their horizontal grid line let them can move like the player can go down.
User prompt
Add free movement for the enemies so they can come to player even if the screen is moving.
User prompt
respawn enemy from the bottom. Double the enemy to respawn 2 each 20 miles. Don't limit the distance for enemies shots.
User prompt
if enemy mine an ore he can shot player by it "Bronze,silver,gold,diamond,crystal"
User prompt
Respawn enemy each 20 miles
User prompt
Add startsound1 for start button when clicked
User prompt
Add CollectedTreasuresound1 for treasures when collected by player
User prompt
Add Gamemusic1 when game start
User prompt
Add EnemyDiggingsound1 for all enemies when they are digging the terrain
User prompt
Add Playerdiggingsound1 when player is digging on the terrain
User prompt
save then update my score from last game played each new highscore change my score on the Scorebackground. ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Don't do random players do my and other real players who they played my game on Upit. ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Show my and other players highscores by their ID and by name of user in Upit in the scorebackground. ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
let enemies shot exactly when they mine the treasures
Code edit (1 edits merged)
Please save this source code
User prompt
Add tap to close score background
User prompt
Create Scorebackground for the High Score button when clicked to show the scores of other players when they are logged in to Upit.
User prompt
Reduce area for each treasure to be from 5000 miles to 2000 miles
User prompt
remove pixels 200x200px for enemies movements
User prompt
let enemies digg terrain like player and if they find treasure they mine it to shot with it.
User prompt
fix enemies digging not working well they walk through the terrain but no pixels removed!
User prompt
Please fix the bug: 'Uncaught TypeError: LK.showLeaderboard is not a function' in or related to this line: 'LK.showLeaderboard();' Line Number: 817
User prompt
add HighScore button to the intro beside start game button from the right
User prompt
show enemies front of terrain to be seen on the screen.
User prompt
The enemies not digging the terrain by 200x200 like the player! Change enemies can shot only if collect a treasure.
/**** * Plugins ****/ var storage = LK.import("@upit/storage.v1"); var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Enemy = Container.expand(function () { var self = Container.call(this); self.enemyTypeString = null; self.config = null; self.graphics = null; self.movementSpeed = 4; // Slightly slower than player's base dig speed self.targetOreBlock = null; self.shootCooldown = 0; self.maxShootCooldown = 240; // 4 seconds at 60FPS self.associatedZoneIndex = -1; self.initEnemy = function (enemyTypeString, initialX, initialY, zoneIndex) { self.enemyTypeString = enemyTypeString; self.config = ENEMY_CONFIG[enemyTypeString]; // ENEMY_CONFIG must be global if (!self.config) { console.log("Error: Invalid enemyTypeString for Enemy: " + enemyTypeString); self.shouldBeDestroyed = true; return; } self.graphics = self.attachAsset(self.config.asset, { anchorX: 0.5, anchorY: 0.5, scaleX: 1.8, // Enemies are 100x100, scale up to be more visible (like 180x180) scaleY: 1.8 }); self.x = initialX; self.y = initialY; self.associatedZoneIndex = zoneIndex; self.shootCooldown = Math.random() * self.maxShootCooldown; self.isAttackingPlayer = false; // Initialize new state property self.hasCollectedTreasure = false; // Track if enemy has collected treasure for shooting self.lastX = self.x; // Initialize last positions self.lastY = self.y; }; self.findTargetOre = function () { if (isScrolling) return; var closestOre = null; var minDistanceSq = Infinity; for (var r = 0; r < terrain.length; r++) { for (var c = 0; c < terrain[r].length; c++) { var block = terrain[r][c]; if (block && block.blockType === 'terrain' && block.visible && block.hasTreasure && block.treasureType === self.config.ore) { if (typeof block.originalDepth === 'undefined') continue; var oreDepthInMiles = Math.floor(block.originalDepth / (blockSize / 2)); var oreTreasureZone = Math.floor(oreDepthInMiles / 5000) % 5; if (oreTreasureZone !== self.associatedZoneIndex) { continue; } // Ensure target is on screen (roughly) if (block.y + blockSize < 0 || block.y > 2732) continue; var dx = block.x + blockSize / 2 - self.x; var dy = block.y + blockSize / 2 - self.y; var distSq = dx * dx + dy * dy; if (distSq < minDistanceSq) { minDistanceSq = distSq; closestOre = block; } } } } self.targetOreBlock = closestOre; }; self.shoot = function () { if (!player || player.destroyed || !gameStarted) return; var newShot = new EnemyShot(); // Ensure newShot is properly initialized before adding to game scene game.addChild(newShot); // Add to scene first so assets can be loaded if attachAsset is deferred // Pass the enemy's configured ore type for the shot's appearance newShot.initShot(self.x, self.y, player.x, player.y, self.config.ore); enemyShots.push(newShot); // enemyShots must be global self.shootCooldown = self.maxShootCooldown + (Math.random() * 60 - 30); // Add some variance }; self.update = function () { if (self.shouldBeDestroyed || !self.graphics) return; self.lastX = self.x; self.lastY = self.y; // If enemy scrolled off top by main scroll, mark for destroy // (graphics height check in game.update is a fallback) if (self.y < -(self.graphics.height * self.graphics.scale.y) && !isScrolling) { self.shouldBeDestroyed = true; return; } if (self.isAttackingPlayer) { if (player && !player.destroyed && gameStarted) { var playerTargetX = player.x; var playerTargetY = player.y; var dxToPlayer = playerTargetX - self.x; var dyToPlayer = playerTargetY - self.y; var distToPlayer = Math.sqrt(dxToPlayer * dxToPlayer + dyToPlayer * dyToPlayer); var effectiveSpeedAttack = self.movementSpeed; if (isScrolling) effectiveSpeedAttack /= 2; if (distToPlayer < self.graphics.width * self.graphics.scale.x / 2 + player.playerGraphics.width / 2 && distToPlayer < self.graphics.height * self.graphics.scale.y / 2 + player.playerGraphics.height / 2) { // Check for intersection / close proximity self.x = playerTargetX; // Snap to player for effect self.y = playerTargetY; LK.effects.flashScreen(0xff0000, 300); // Flash screen red LK.showGameOver(); // Player is caught return; // Stop further updates for this enemy } else if (distToPlayer > 0) { // Check distToPlayer > 0 to prevent division by zero self.x += dxToPlayer / distToPlayer * effectiveSpeedAttack; self.y += dyToPlayer / distToPlayer * effectiveSpeedAttack; } } else { // Player doesn't exist or is destroyed, revert to ore-seeking self.isAttackingPlayer = false; } } else { // Not attacking player, try to find/mine ore if (!self.targetOreBlock || !self.targetOreBlock.visible || !self.targetOreBlock.hasTreasure || self.targetOreBlock.treasureType !== self.config.ore) { self.findTargetOre(); if (!self.targetOreBlock) { // No ore found, switch to attacking player self.isAttackingPlayer = true; // Optionally, immediately try to move towards player this frame (or wait for next update cycle) // For now, state change is enough, next update cycle will handle movement. } } if (!self.isAttackingPlayer && self.targetOreBlock) { // Check again in case state just changed var targetX = self.targetOreBlock.x + blockSize / 2; var targetY = self.targetOreBlock.y + blockSize / 2; var dx = targetX - self.x; var dy = targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); var effectiveSpeed = self.movementSpeed; if (isScrolling) effectiveSpeed /= 2; if (distance < effectiveSpeed) { self.x = targetX; self.y = targetY; // Let enemy dig any terrain block (not just ore) if (self.targetOreBlock && self.targetOreBlock.visible) { // Check if this is a treasure block before mining if (self.targetOreBlock.hasTreasure && self.targetOreBlock.treasureType === self.config.ore) { self.hasCollectedTreasure = true; // Mark that enemy collected treasure } // Mine the block at enemy position (200x200 area like player) mineBlock(self.x, self.y, false); // false = not player action } self.targetOreBlock = null; } else if (distance > 0) { // Check distance > 0 to prevent division by zero self.x += dx / distance * effectiveSpeed; self.y += dy / distance * effectiveSpeed; } } } // Enemy sprite flipping logic (after position update) if (self.graphics) { var baseScaleX = 1.8; // Defined in attachAsset for enemies // Game width is 2048, center is 1024 if (self.x < 1024) { // Facing left self.graphics.scale.x = -baseScaleX; } else { // Facing right or center self.graphics.scale.x = baseScaleX; } } self.shootCooldown--; if (self.shootCooldown <= 0 && player && !player.destroyed && gameStarted && self.hasCollectedTreasure) { var pDx = player.x - self.x; var pDy = player.y - self.y; if (Math.sqrt(pDx * pDx + pDy * pDy) < 1200 && Math.abs(pDx) < 2048 && Math.abs(pDy) < 2732) { // Check player is generally on screen self.shoot(); } else { self.shootCooldown = 60; } } }; return self; }); var EnemyShot = Container.expand(function () { var self = Container.call(this); self.graphics = null; self.speed = 15; // pixels per frame self.maxDistance = 800; self.distanceTraveled = 0; self.velocity = { x: 0, y: 0 }; // Normalized direction vector // Added shotTypeAssetId parameter self.initShot = function (startX, startY, targetX, targetY, shotTypeAssetId) { self.x = startX; self.y = startY; if (!self.graphics) { if (shotTypeAssetId) { // Treasure assets are 200x200, shots are 50x50. Scale factor = 50/200 = 0.25 var scaleFactor = 0.25; self.graphics = self.attachAsset(shotTypeAssetId, { anchorX: 0.5, anchorY: 0.5, scaleX: scaleFactor, scaleY: scaleFactor }); } else { // Fallback to the generic 'EnemyShot' asset if no specific type is provided // This case should ideally not be hit if Enemies always specify an ore type. self.graphics = self.attachAsset('EnemyShot', { anchorX: 0.5, anchorY: 0.5 }); } } var dx = targetX - startX; var dy = targetY - startY; var magnitude = Math.sqrt(dx * dx + dy * dy); if (magnitude > 0) { self.velocity.x = dx / magnitude; self.velocity.y = dy / magnitude; } else { self.velocity.x = 0; self.velocity.y = 1; // Default to shooting downwards if target is same position } self.lastX = self.x; // Initialize last positions self.lastY = self.y; }; self.update = function () { if (self.shouldBeDestroyed || !self.graphics) return; var moveX = self.velocity.x * self.speed; var moveY = self.velocity.y * self.speed; self.lastX = self.x; // Store previous position for checks self.lastY = self.y; self.x += moveX; self.y += moveY; self.distanceTraveled += Math.sqrt(moveX * moveX + moveY * moveY); // Check for max distance or off-screen // Ensure graphics exist before checking width/height var graphicWidth = self.graphics ? self.graphics.width : 50; var graphicHeight = self.graphics ? self.graphics.height : 50; if (self.distanceTraveled >= self.maxDistance || self.y < -graphicHeight || self.y > 2732 + graphicHeight || self.x < -graphicWidth || self.x > 2048 + graphicWidth) { self.shouldBeDestroyed = true; } // Collision with player (player is global) if (player && !player.destroyed && self.intersects(player) && !self.shouldBeDestroyed) { LK.effects.flashScreen(0xff0000, 300); LK.showGameOver(); self.shouldBeDestroyed = true; } }; return self; }); var Player = Container.expand(function () { var self = Container.call(this); // Store playerGraphics on self to allow access from game code for flipping self.playerGraphics = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); self.playerGraphics.alpha = 1; return self; }); var StoneBlock = Container.expand(function () { var self = Container.call(this); self.blockType = 'stone'; var stoneGraphics = self.attachAsset('stone', { anchorX: 0, anchorY: 0 }); self.mine = function () { return false; // Stone blocks cannot be mined }; return self; }); var TerrainBlock = Container.expand(function () { var self = Container.call(this); self.blockType = 'terrain'; self.hasTreasure = false; self.treasureType = null; self.treasureGraphics = null; var blockGraphics = self.attachAsset('terrain', { anchorX: 0, anchorY: 0 }); self.graphics = blockGraphics; self.setTreasure = function (type, blockDepth) { // Added blockDepth argument self.hasTreasure = true; self.treasureType = type; self.originalDepth = blockDepth; // Store the depth at which this treasure was generated // Create and display the actual treasure image if (type) { self.treasureGraphics = self.attachAsset(type, { anchorX: 0.5, anchorY: 0.5, x: blockSize / 2, y: blockSize / 2, scaleX: 0.8, scaleY: 0.8 }); } }; self.mine = function () { if (self.blockType === 'stone') { return false; } self.visible = false; if (self.treasureGraphics) { self.treasureGraphics.visible = false; } return true; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ game.setBackgroundColor(0x000000); var blockSize = 200; var gridWidth = Math.floor(2048 / blockSize); var gridHeight = Math.floor(2732 / blockSize); var stoneWallWidth = 1; var mineableStartX = stoneWallWidth; var mineableEndX = gridWidth - stoneWallWidth; var terrain = []; var player; var depth = 0; var score = 0; var isDragging = false; var targetX = null; var targetY = null; var isScrolling = false; // Flag to indicate if a scroll animation is in progress var playerSpeed = 40; // Pixels per frame var scrollAnimationDuration = 500; // Duration for scroll animation in milliseconds var treasureCounts = { bronze: 0, silver: 0, gold: 0, diamond: 0, crystal: 0 }; var enemies = []; var enemyShots = []; var ENEMY_SPAWN_CHANCE = 0.03; // 3% chance per eligible block in a new row var MAX_ENEMIES_PER_ZONE_ONSCREEN = 1; // Limit enemies per zone currently visible var ENEMY_CONFIG = { 'bronze': { asset: 'BronzeEnemy', ore: 'bronze', zone: 0 }, 'silver': { asset: 'SilverEnemy', ore: 'silver', zone: 1 }, 'gold': { asset: 'GoldEnemy', ore: 'gold', zone: 2 }, 'diamond': { asset: 'DiamondEnemy', ore: 'diamond', zone: 3 }, 'crystal': { asset: 'CrystalEnemy', ore: 'crystal', zone: 4 } }; var ZONE_TO_ENEMY_TYPE = ['bronze', 'silver', 'gold', 'diamond', 'crystal']; // Game state var gameStarted = false; var introContainer = null; function movePlayerTowardsTarget() { if (targetX === null || targetY === null || !player) { return; } // Move player towards target position, restricting to X or Y axis per update var dx = targetX - player.x; var dy = targetY - player.y; if (dx === 0 && dy === 0) { // Player is already at the target, no movement calculation needed. // The mineBlock() call after this block will handle mining at the current position. } else if (Math.abs(dx) > Math.abs(dy)) { // Prioritize horizontal movement if (Math.abs(dx) > playerSpeed) { player.x += Math.sign(dx) * playerSpeed; } else { player.x = targetX; // Snap to target X } } else { // Prioritize vertical movement if |dy| >= |dx| (this includes dx === 0, or |dx| === |dy|) if (Math.abs(dy) > playerSpeed) { player.y += Math.sign(dy) * playerSpeed; } else { player.y = targetY; // Snap to target Y } } // Mine at player position (true = player action) mineBlock(player.x, player.y, true); // Player sprite flipping logic if (player && player.playerGraphics) { // Game width is 2048, center is 1024 if (player.x < 1024) { // Facing left player.playerGraphics.scale.x = -1; // Default scale is 1 } else { // Facing right or center player.playerGraphics.scale.x = 1; // Default scale is 1 } } } // Create score text first (will be at the back) var scoreText = new Text2('Score: 0', { size: 60, fill: 0xFFFFFF }); scoreText.anchor.set(0.5, 0); scoreText.y = 90; scoreText.visible = false; // Hide initially LK.gui.top.addChild(scoreText); // Create depth text second (will be in the middle) var depthText = new Text2('Depth: 0m', { size: 60, fill: 0xFFFFFF }); depthText.anchor.set(0.5, 0); depthText.y = 30; depthText.visible = false; // Hide initially LK.gui.top.addChild(depthText); // Create treasure count displays var treasureTexts = {}; var treasureTypes = ['bronze', 'silver', 'gold', 'diamond', 'crystal']; var treasureColors = { bronze: 0xCD7F32, silver: 0xC0C0C0, gold: 0xFFD700, diamond: 0x00FFFF, crystal: 0xFF69B4 }; // Create container for treasure counts var treasureContainer = new Container(); treasureContainer.y = -20; // Move to the very top edge treasureContainer.visible = false; // Hide initially // Create text for each treasure type var xOffset = -500; for (var i = 0; i < treasureTypes.length; i++) { var type = treasureTypes[i]; var text = new Text2(type.charAt(0).toUpperCase() + type.slice(1) + ': 0', { size: 50, fill: treasureColors[type] }); text.anchor.set(0.5, 0); text.x = xOffset + i * 250; treasureContainer.addChild(text); treasureTexts[type] = text; } // Add container to GUI after all texts are created to ensure it appears on top LK.gui.top.addChild(treasureContainer); function getTreasureType(currentDepth) { // Each 5000 miles (250000 pixels) has a specific treasure type var depthInMiles = Math.floor(currentDepth / (blockSize / 2)); // Convert pixels to abstract depth units (e.g., "miles" in description). 1 unit = blockSize / 2 pixels. var treasureZone = Math.floor(depthInMiles / 5000); // Which 5000-mile zone var rand = Math.random(); var spawnChance = 0.1; // 10% chance to spawn treasure if (rand > spawnChance) { return null; // No treasure } // Make treasure zones cycle every 5 zones (25000 miles) var cycledZone = treasureZone % 5; // Return specific treasure based on cycled zone switch (cycledZone) { case 0: // Bronze zone return 'bronze'; case 1: // Silver zone return 'silver'; case 2: // Gold zone return 'gold'; case 3: // Diamond zone return 'diamond'; case 4: // Crystal zone return 'crystal'; } } function generateRow(y_rowIndex_on_screen) { var row = []; // Calculate the actual depth of this row being generated var currentBlockDepth = depth + y_rowIndex_on_screen * blockSize; for (var x = 0; x < gridWidth; x++) { var block; if (x < mineableStartX || x >= mineableEndX) { block = new StoneBlock(); } else { block = new TerrainBlock(); var treasureTypeForBlock = getTreasureType(currentBlockDepth); if (treasureTypeForBlock) { block.setTreasure(treasureTypeForBlock, currentBlockDepth); // Pass currentBlockDepth } // Enemy Spawning Logic (only for new rows at the bottom, y_rowIndex_on_screen === gridHeight) if (treasureTypeForBlock && y_rowIndex_on_screen === gridHeight && Math.random() < ENEMY_SPAWN_CHANCE) { var depthInMiles = Math.floor(currentBlockDepth / (blockSize / 2)); var treasureZoneIndex = Math.floor(depthInMiles / 5000) % 5; var enemyTypeString = ZONE_TO_ENEMY_TYPE[treasureZoneIndex]; if (enemyTypeString) { // Check if enemyTypeString is valid var enemiesInThisZone = 0; for (var k = 0; k < enemies.length; k++) { if (!enemies[k].shouldBeDestroyed && enemies[k].associatedZoneIndex === treasureZoneIndex) { enemiesInThisZone++; } } if (enemiesInThisZone < MAX_ENEMIES_PER_ZONE_ONSCREEN) { var newEnemy = new Enemy(); var spawnX = x * blockSize + blockSize / 2; // Enemies spawn at the y-level of the new row. var spawnY = y_rowIndex_on_screen * blockSize + blockSize / 2; game.addChild(newEnemy); // Add to scene before init for safety with asset loading newEnemy.initEnemy(enemyTypeString, spawnX, spawnY, treasureZoneIndex); if (!newEnemy.shouldBeDestroyed) { // Check if init was successful enemies.push(newEnemy); } else { newEnemy.destroy(); // Clean up if init failed } } } } } block.x = x * blockSize; block.y = y_rowIndex_on_screen * blockSize; // Position block based on its screen row index game.addChild(block); row.push(block); } return row; } function initializeTerrain() { terrain = []; for (var y = 0; y < gridHeight + 1; y++) { terrain.push(generateRow(y)); } } function completeScrollCleanup() { // The blocks in terrain[0] are the ones that moved off-screen and whose tweens just finished. var topRowToDestroy = terrain[0]; for (var x = 0; x < topRowToDestroy.length; x++) { var block = topRowToDestroy[x]; // Destroy treasure graphics if they exist if (block.treasureGraphics) { block.treasureGraphics.destroy(); } block.destroy(); // This removes the container and its children from the stage } terrain.shift(); // Now remove the row from our logical terrain array depth += blockSize; // Add new row at the bottom. generateRow expects the screen row index. For the new bottom row, this is gridHeight. terrain.push(generateRow(gridHeight)); depthText.setText('Depth: ' + Math.floor(depth / (blockSize / 2)) + 'm'); isScrolling = false; // Allow new scrolls } function scrollTerrain() { if (isScrolling) { return; // Don't start a new scroll if one is already in progress } isScrolling = true; var tweensCompleted = 0; var totalTweens = 0; // Calculate total number of blocks to be tweened for (var y_coord = 0; y_coord < terrain.length; y_coord++) { totalTweens += terrain[y_coord].length; } var itemsToScroll = []; // Add terrain blocks for (var r = 0; r < terrain.length; r++) { for (var c = 0; c < terrain[r].length; c++) { itemsToScroll.push(terrain[r][c]); } } // Add active enemies for (var i = 0; i < enemies.length; i++) { if (!enemies[i].shouldBeDestroyed) { itemsToScroll.push(enemies[i]); } } // Add active enemy shots for (var i = 0; i < enemyShots.length; i++) { if (!enemyShots[i].shouldBeDestroyed) { itemsToScroll.push(enemyShots[i]); } } totalTweens = itemsToScroll.length; if (totalTweens === 0) { isScrolling = false; // It's possible no terrain exists but other items do, but cleanup is tied to terrain. // If itemsToScroll is empty, nothing to do. // If only enemies/shots, they scroll, but no new terrain row generation. if (terrain.length === 0 || terrain[0].length === 0) { // If no terrain to manage, but scroll was initiated (e.g. by player action) // and there might be other things to scroll, depth might still conceptually increase. // However, completeScrollCleanup is the one that advances depth and adds rows. // This path implies no terrain related cleanup. } return; } // Animate each item var terrainBlocksWereScrolled = false; for (var i = 0; i < itemsToScroll.length; i++) { var itemToAnimate = itemsToScroll[i]; if (itemToAnimate instanceof TerrainBlock || itemToAnimate instanceof StoneBlock) { terrainBlocksWereScrolled = true; } tween(itemToAnimate, { y: itemToAnimate.y - blockSize }, { duration: scrollAnimationDuration, easing: tween.linear, onFinish: function onScrollItemFinish() { tweensCompleted++; if (tweensCompleted === totalTweens) { // All items done tweening if (terrainBlocksWereScrolled && terrain.length > 0 && terrain[0].length > 0) { completeScrollCleanup(); // Call cleanup if terrain was involved } else { // No terrain was scrolled, or terrain is now empty after scrolling. isScrolling = false; // If terrain was scrolled and became empty, still update depth. if (terrainBlocksWereScrolled && (terrain.length === 0 || terrain.length > 0 && terrain[0].length === 0)) { depth += blockSize; depthText.setText('Depth: ' + Math.floor(depth / (blockSize / 2)) + 'm'); } } } } }); } } function mineBlock(worldX, worldY, isPlayerAction) { // Default isPlayerAction to true if not specified for backward compatibility if (typeof isPlayerAction === 'undefined') { isPlayerAction = true; } var gridX = Math.floor(worldX / blockSize); var gridY = Math.floor(worldY / blockSize); if (gridX < 0 || gridX >= gridWidth || gridY < 0 || gridY >= gridHeight) { return; } // Check if terrain row exists before accessing if (!terrain[gridY]) { return; } var block = terrain[gridY][gridX]; if (!block || !block.visible) { return; } if (block.mine()) { if (block.hasTreasure) { var treasureValue = 0; switch (block.treasureType) { case 'bronze': treasureValue = 10; break; case 'silver': treasureValue = 25; break; case 'gold': treasureValue = 50; break; case 'diamond': treasureValue = 100; break; case 'crystal': treasureValue = 150; break; } // Only update score and treasure counts for player actions if (isPlayerAction) { score += treasureValue; LK.setScore(score); scoreText.setText('Score: ' + score); treasureCounts[block.treasureType]++; treasureTexts[block.treasureType].setText(block.treasureType.charAt(0).toUpperCase() + block.treasureType.slice(1) + ': ' + treasureCounts[block.treasureType]); } } // Only trigger scrolling for player actions if (isPlayerAction && gridY > gridHeight / 2) { scrollTerrain(); } } } // Create intro screen function createIntroScreen() { introContainer = new Container(); // Add intro background - since we're adding to GUI, we need different scaling approach var introBackground = introContainer.attachAsset('IntroBackground', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 }); // Since we're adding to GUI.center, we need to scale based on the actual GUI dimensions // The GUI system automatically scales, so we need to set the background to fill the screen // by making it large enough to cover the viewport var assetWidth = introBackground.width; var assetHeight = introBackground.height; // Defensively check for valid, positive asset dimensions before calculating scale if (assetWidth > 0 && assetHeight > 0) { // For GUI elements, we want to scale to fit the entire image within the screen (contain/fit) // Since GUI.center is already centered, this will ensure the image is centered and fully visible. var scaleX = 2048 / assetWidth; var scaleY = 2732 / assetHeight; // Use the smaller scale to ensure the entire image fits within screen boundaries var scale = Math.min(scaleX, scaleY); introBackground.scale.set(scale, scale); // Position is already centered at 0,0 in the GUI.center container } else { // If asset dimensions are invalid (e.g., zero or negative), // set scale to zero to effectively hide it and log a warning. introBackground.scale.set(0, 0); console.log("Warning: IntroBackground asset has invalid dimensions (width: " + assetWidth + ", height: " + assetHeight + "). Cannot scale appropriately."); } // Add start button var startButton = introContainer.attachAsset('Startbutton', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 400, // Lowered by 200px (was 200) scaleX: 4, scaleY: 1.5 }); // Make button interactive startButton.interactive = true; // Add button press handler startButton.down = function () { startGame(); }; // Add blinking animation to the start button tween(startButton, { alpha: 0.5 }, { duration: 800, // Duration of the blink easing: tween.linear, onFinish: function onFinish() { tween(startButton, { alpha: 1 }, { duration: 800, easing: tween.linear, onFinish: function onFinish() { // Loop the animation tween(startButton, { alpha: 0.5 }, { duration: 800, easing: tween.linear, onFinish: arguments.callee }); } }); } }); // Add to GUI center instead of game LK.gui.center.addChild(introContainer); } function startGame() { if (gameStarted) { return; } // Set gameStarted immediately to prevent multiple calls gameStarted = true; // Remove intro screen if (introContainer) { introContainer.destroy(); introContainer = null; } // Show game UI elements scoreText.visible = true; depthText.visible = true; treasureContainer.visible = true; // Initialize game initializeTerrain(); player = game.addChild(new Player()); player.x = 1024; player.y = 200; } // Create intro screen instead of starting game immediately createIntroScreen(); game.down = function (x, y, obj) { if (!gameStarted) { return; } isDragging = true; // Clamp target position to game boundaries targetX = Math.max(0, Math.min(x, 2048)); targetY = Math.max(0, Math.min(y, 2732)); }; game.move = function (x, y, obj) { if (!gameStarted) { return; } // Clamp target position to game boundaries targetX = Math.max(0, Math.min(x, 2048)); targetY = Math.max(0, Math.min(y, 2732)); }; game.up = function (x, y, obj) { if (!gameStarted) { return; } isDragging = false; }; game.update = function () { if (!gameStarted) { return; } if (player && targetX !== null && targetY !== null) { movePlayerTowardsTarget(); } // Update enemies for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; if (enemy.shouldBeDestroyed) { enemy.destroy(); enemies.splice(i, 1); } else { enemy.update(); // Additional check: if enemy graphic exists and it scrolled too high (e.g. its zone is long gone) if (!isScrolling && enemy.graphics && enemy.y < -(enemy.graphics.height * enemy.graphics.scale.y * 2)) { enemy.shouldBeDestroyed = true; } } } // Update enemy shots for (var i = enemyShots.length - 1; i >= 0; i--) { var shot = enemyShots[i]; if (shot.shouldBeDestroyed) { shot.destroy(); enemyShots.splice(i, 1); } else { shot.update(); } } };
/****
* Plugins
****/
var storage = LK.import("@upit/storage.v1");
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Enemy = Container.expand(function () {
var self = Container.call(this);
self.enemyTypeString = null;
self.config = null;
self.graphics = null;
self.movementSpeed = 4; // Slightly slower than player's base dig speed
self.targetOreBlock = null;
self.shootCooldown = 0;
self.maxShootCooldown = 240; // 4 seconds at 60FPS
self.associatedZoneIndex = -1;
self.initEnemy = function (enemyTypeString, initialX, initialY, zoneIndex) {
self.enemyTypeString = enemyTypeString;
self.config = ENEMY_CONFIG[enemyTypeString]; // ENEMY_CONFIG must be global
if (!self.config) {
console.log("Error: Invalid enemyTypeString for Enemy: " + enemyTypeString);
self.shouldBeDestroyed = true;
return;
}
self.graphics = self.attachAsset(self.config.asset, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.8,
// Enemies are 100x100, scale up to be more visible (like 180x180)
scaleY: 1.8
});
self.x = initialX;
self.y = initialY;
self.associatedZoneIndex = zoneIndex;
self.shootCooldown = Math.random() * self.maxShootCooldown;
self.isAttackingPlayer = false; // Initialize new state property
self.hasCollectedTreasure = false; // Track if enemy has collected treasure for shooting
self.lastX = self.x; // Initialize last positions
self.lastY = self.y;
};
self.findTargetOre = function () {
if (isScrolling) return;
var closestOre = null;
var minDistanceSq = Infinity;
for (var r = 0; r < terrain.length; r++) {
for (var c = 0; c < terrain[r].length; c++) {
var block = terrain[r][c];
if (block && block.blockType === 'terrain' && block.visible && block.hasTreasure && block.treasureType === self.config.ore) {
if (typeof block.originalDepth === 'undefined') continue;
var oreDepthInMiles = Math.floor(block.originalDepth / (blockSize / 2));
var oreTreasureZone = Math.floor(oreDepthInMiles / 5000) % 5;
if (oreTreasureZone !== self.associatedZoneIndex) {
continue;
}
// Ensure target is on screen (roughly)
if (block.y + blockSize < 0 || block.y > 2732) continue;
var dx = block.x + blockSize / 2 - self.x;
var dy = block.y + blockSize / 2 - self.y;
var distSq = dx * dx + dy * dy;
if (distSq < minDistanceSq) {
minDistanceSq = distSq;
closestOre = block;
}
}
}
}
self.targetOreBlock = closestOre;
};
self.shoot = function () {
if (!player || player.destroyed || !gameStarted) return;
var newShot = new EnemyShot();
// Ensure newShot is properly initialized before adding to game scene
game.addChild(newShot); // Add to scene first so assets can be loaded if attachAsset is deferred
// Pass the enemy's configured ore type for the shot's appearance
newShot.initShot(self.x, self.y, player.x, player.y, self.config.ore);
enemyShots.push(newShot); // enemyShots must be global
self.shootCooldown = self.maxShootCooldown + (Math.random() * 60 - 30); // Add some variance
};
self.update = function () {
if (self.shouldBeDestroyed || !self.graphics) return;
self.lastX = self.x;
self.lastY = self.y;
// If enemy scrolled off top by main scroll, mark for destroy
// (graphics height check in game.update is a fallback)
if (self.y < -(self.graphics.height * self.graphics.scale.y) && !isScrolling) {
self.shouldBeDestroyed = true;
return;
}
if (self.isAttackingPlayer) {
if (player && !player.destroyed && gameStarted) {
var playerTargetX = player.x;
var playerTargetY = player.y;
var dxToPlayer = playerTargetX - self.x;
var dyToPlayer = playerTargetY - self.y;
var distToPlayer = Math.sqrt(dxToPlayer * dxToPlayer + dyToPlayer * dyToPlayer);
var effectiveSpeedAttack = self.movementSpeed;
if (isScrolling) effectiveSpeedAttack /= 2;
if (distToPlayer < self.graphics.width * self.graphics.scale.x / 2 + player.playerGraphics.width / 2 && distToPlayer < self.graphics.height * self.graphics.scale.y / 2 + player.playerGraphics.height / 2) {
// Check for intersection / close proximity
self.x = playerTargetX; // Snap to player for effect
self.y = playerTargetY;
LK.effects.flashScreen(0xff0000, 300); // Flash screen red
LK.showGameOver(); // Player is caught
return; // Stop further updates for this enemy
} else if (distToPlayer > 0) {
// Check distToPlayer > 0 to prevent division by zero
self.x += dxToPlayer / distToPlayer * effectiveSpeedAttack;
self.y += dyToPlayer / distToPlayer * effectiveSpeedAttack;
}
} else {
// Player doesn't exist or is destroyed, revert to ore-seeking
self.isAttackingPlayer = false;
}
} else {
// Not attacking player, try to find/mine ore
if (!self.targetOreBlock || !self.targetOreBlock.visible || !self.targetOreBlock.hasTreasure || self.targetOreBlock.treasureType !== self.config.ore) {
self.findTargetOre();
if (!self.targetOreBlock) {
// No ore found, switch to attacking player
self.isAttackingPlayer = true;
// Optionally, immediately try to move towards player this frame (or wait for next update cycle)
// For now, state change is enough, next update cycle will handle movement.
}
}
if (!self.isAttackingPlayer && self.targetOreBlock) {
// Check again in case state just changed
var targetX = self.targetOreBlock.x + blockSize / 2;
var targetY = self.targetOreBlock.y + blockSize / 2;
var dx = targetX - self.x;
var dy = targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var effectiveSpeed = self.movementSpeed;
if (isScrolling) effectiveSpeed /= 2;
if (distance < effectiveSpeed) {
self.x = targetX;
self.y = targetY;
// Let enemy dig any terrain block (not just ore)
if (self.targetOreBlock && self.targetOreBlock.visible) {
// Check if this is a treasure block before mining
if (self.targetOreBlock.hasTreasure && self.targetOreBlock.treasureType === self.config.ore) {
self.hasCollectedTreasure = true; // Mark that enemy collected treasure
}
// Mine the block at enemy position (200x200 area like player)
mineBlock(self.x, self.y, false); // false = not player action
}
self.targetOreBlock = null;
} else if (distance > 0) {
// Check distance > 0 to prevent division by zero
self.x += dx / distance * effectiveSpeed;
self.y += dy / distance * effectiveSpeed;
}
}
}
// Enemy sprite flipping logic (after position update)
if (self.graphics) {
var baseScaleX = 1.8; // Defined in attachAsset for enemies
// Game width is 2048, center is 1024
if (self.x < 1024) {
// Facing left
self.graphics.scale.x = -baseScaleX;
} else {
// Facing right or center
self.graphics.scale.x = baseScaleX;
}
}
self.shootCooldown--;
if (self.shootCooldown <= 0 && player && !player.destroyed && gameStarted && self.hasCollectedTreasure) {
var pDx = player.x - self.x;
var pDy = player.y - self.y;
if (Math.sqrt(pDx * pDx + pDy * pDy) < 1200 && Math.abs(pDx) < 2048 && Math.abs(pDy) < 2732) {
// Check player is generally on screen
self.shoot();
} else {
self.shootCooldown = 60;
}
}
};
return self;
});
var EnemyShot = Container.expand(function () {
var self = Container.call(this);
self.graphics = null;
self.speed = 15; // pixels per frame
self.maxDistance = 800;
self.distanceTraveled = 0;
self.velocity = {
x: 0,
y: 0
}; // Normalized direction vector
// Added shotTypeAssetId parameter
self.initShot = function (startX, startY, targetX, targetY, shotTypeAssetId) {
self.x = startX;
self.y = startY;
if (!self.graphics) {
if (shotTypeAssetId) {
// Treasure assets are 200x200, shots are 50x50. Scale factor = 50/200 = 0.25
var scaleFactor = 0.25;
self.graphics = self.attachAsset(shotTypeAssetId, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: scaleFactor,
scaleY: scaleFactor
});
} else {
// Fallback to the generic 'EnemyShot' asset if no specific type is provided
// This case should ideally not be hit if Enemies always specify an ore type.
self.graphics = self.attachAsset('EnemyShot', {
anchorX: 0.5,
anchorY: 0.5
});
}
}
var dx = targetX - startX;
var dy = targetY - startY;
var magnitude = Math.sqrt(dx * dx + dy * dy);
if (magnitude > 0) {
self.velocity.x = dx / magnitude;
self.velocity.y = dy / magnitude;
} else {
self.velocity.x = 0;
self.velocity.y = 1; // Default to shooting downwards if target is same position
}
self.lastX = self.x; // Initialize last positions
self.lastY = self.y;
};
self.update = function () {
if (self.shouldBeDestroyed || !self.graphics) return;
var moveX = self.velocity.x * self.speed;
var moveY = self.velocity.y * self.speed;
self.lastX = self.x; // Store previous position for checks
self.lastY = self.y;
self.x += moveX;
self.y += moveY;
self.distanceTraveled += Math.sqrt(moveX * moveX + moveY * moveY);
// Check for max distance or off-screen
// Ensure graphics exist before checking width/height
var graphicWidth = self.graphics ? self.graphics.width : 50;
var graphicHeight = self.graphics ? self.graphics.height : 50;
if (self.distanceTraveled >= self.maxDistance || self.y < -graphicHeight || self.y > 2732 + graphicHeight || self.x < -graphicWidth || self.x > 2048 + graphicWidth) {
self.shouldBeDestroyed = true;
}
// Collision with player (player is global)
if (player && !player.destroyed && self.intersects(player) && !self.shouldBeDestroyed) {
LK.effects.flashScreen(0xff0000, 300);
LK.showGameOver();
self.shouldBeDestroyed = true;
}
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
// Store playerGraphics on self to allow access from game code for flipping
self.playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.playerGraphics.alpha = 1;
return self;
});
var StoneBlock = Container.expand(function () {
var self = Container.call(this);
self.blockType = 'stone';
var stoneGraphics = self.attachAsset('stone', {
anchorX: 0,
anchorY: 0
});
self.mine = function () {
return false; // Stone blocks cannot be mined
};
return self;
});
var TerrainBlock = Container.expand(function () {
var self = Container.call(this);
self.blockType = 'terrain';
self.hasTreasure = false;
self.treasureType = null;
self.treasureGraphics = null;
var blockGraphics = self.attachAsset('terrain', {
anchorX: 0,
anchorY: 0
});
self.graphics = blockGraphics;
self.setTreasure = function (type, blockDepth) {
// Added blockDepth argument
self.hasTreasure = true;
self.treasureType = type;
self.originalDepth = blockDepth; // Store the depth at which this treasure was generated
// Create and display the actual treasure image
if (type) {
self.treasureGraphics = self.attachAsset(type, {
anchorX: 0.5,
anchorY: 0.5,
x: blockSize / 2,
y: blockSize / 2,
scaleX: 0.8,
scaleY: 0.8
});
}
};
self.mine = function () {
if (self.blockType === 'stone') {
return false;
}
self.visible = false;
if (self.treasureGraphics) {
self.treasureGraphics.visible = false;
}
return true;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
game.setBackgroundColor(0x000000);
var blockSize = 200;
var gridWidth = Math.floor(2048 / blockSize);
var gridHeight = Math.floor(2732 / blockSize);
var stoneWallWidth = 1;
var mineableStartX = stoneWallWidth;
var mineableEndX = gridWidth - stoneWallWidth;
var terrain = [];
var player;
var depth = 0;
var score = 0;
var isDragging = false;
var targetX = null;
var targetY = null;
var isScrolling = false; // Flag to indicate if a scroll animation is in progress
var playerSpeed = 40; // Pixels per frame
var scrollAnimationDuration = 500; // Duration for scroll animation in milliseconds
var treasureCounts = {
bronze: 0,
silver: 0,
gold: 0,
diamond: 0,
crystal: 0
};
var enemies = [];
var enemyShots = [];
var ENEMY_SPAWN_CHANCE = 0.03; // 3% chance per eligible block in a new row
var MAX_ENEMIES_PER_ZONE_ONSCREEN = 1; // Limit enemies per zone currently visible
var ENEMY_CONFIG = {
'bronze': {
asset: 'BronzeEnemy',
ore: 'bronze',
zone: 0
},
'silver': {
asset: 'SilverEnemy',
ore: 'silver',
zone: 1
},
'gold': {
asset: 'GoldEnemy',
ore: 'gold',
zone: 2
},
'diamond': {
asset: 'DiamondEnemy',
ore: 'diamond',
zone: 3
},
'crystal': {
asset: 'CrystalEnemy',
ore: 'crystal',
zone: 4
}
};
var ZONE_TO_ENEMY_TYPE = ['bronze', 'silver', 'gold', 'diamond', 'crystal'];
// Game state
var gameStarted = false;
var introContainer = null;
function movePlayerTowardsTarget() {
if (targetX === null || targetY === null || !player) {
return;
}
// Move player towards target position, restricting to X or Y axis per update
var dx = targetX - player.x;
var dy = targetY - player.y;
if (dx === 0 && dy === 0) {
// Player is already at the target, no movement calculation needed.
// The mineBlock() call after this block will handle mining at the current position.
} else if (Math.abs(dx) > Math.abs(dy)) {
// Prioritize horizontal movement
if (Math.abs(dx) > playerSpeed) {
player.x += Math.sign(dx) * playerSpeed;
} else {
player.x = targetX; // Snap to target X
}
} else {
// Prioritize vertical movement if |dy| >= |dx| (this includes dx === 0, or |dx| === |dy|)
if (Math.abs(dy) > playerSpeed) {
player.y += Math.sign(dy) * playerSpeed;
} else {
player.y = targetY; // Snap to target Y
}
}
// Mine at player position (true = player action)
mineBlock(player.x, player.y, true);
// Player sprite flipping logic
if (player && player.playerGraphics) {
// Game width is 2048, center is 1024
if (player.x < 1024) {
// Facing left
player.playerGraphics.scale.x = -1; // Default scale is 1
} else {
// Facing right or center
player.playerGraphics.scale.x = 1; // Default scale is 1
}
}
}
// Create score text first (will be at the back)
var scoreText = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
scoreText.y = 90;
scoreText.visible = false; // Hide initially
LK.gui.top.addChild(scoreText);
// Create depth text second (will be in the middle)
var depthText = new Text2('Depth: 0m', {
size: 60,
fill: 0xFFFFFF
});
depthText.anchor.set(0.5, 0);
depthText.y = 30;
depthText.visible = false; // Hide initially
LK.gui.top.addChild(depthText);
// Create treasure count displays
var treasureTexts = {};
var treasureTypes = ['bronze', 'silver', 'gold', 'diamond', 'crystal'];
var treasureColors = {
bronze: 0xCD7F32,
silver: 0xC0C0C0,
gold: 0xFFD700,
diamond: 0x00FFFF,
crystal: 0xFF69B4
};
// Create container for treasure counts
var treasureContainer = new Container();
treasureContainer.y = -20; // Move to the very top edge
treasureContainer.visible = false; // Hide initially
// Create text for each treasure type
var xOffset = -500;
for (var i = 0; i < treasureTypes.length; i++) {
var type = treasureTypes[i];
var text = new Text2(type.charAt(0).toUpperCase() + type.slice(1) + ': 0', {
size: 50,
fill: treasureColors[type]
});
text.anchor.set(0.5, 0);
text.x = xOffset + i * 250;
treasureContainer.addChild(text);
treasureTexts[type] = text;
}
// Add container to GUI after all texts are created to ensure it appears on top
LK.gui.top.addChild(treasureContainer);
function getTreasureType(currentDepth) {
// Each 5000 miles (250000 pixels) has a specific treasure type
var depthInMiles = Math.floor(currentDepth / (blockSize / 2)); // Convert pixels to abstract depth units (e.g., "miles" in description). 1 unit = blockSize / 2 pixels.
var treasureZone = Math.floor(depthInMiles / 5000); // Which 5000-mile zone
var rand = Math.random();
var spawnChance = 0.1; // 10% chance to spawn treasure
if (rand > spawnChance) {
return null; // No treasure
}
// Make treasure zones cycle every 5 zones (25000 miles)
var cycledZone = treasureZone % 5;
// Return specific treasure based on cycled zone
switch (cycledZone) {
case 0:
// Bronze zone
return 'bronze';
case 1:
// Silver zone
return 'silver';
case 2:
// Gold zone
return 'gold';
case 3:
// Diamond zone
return 'diamond';
case 4:
// Crystal zone
return 'crystal';
}
}
function generateRow(y_rowIndex_on_screen) {
var row = [];
// Calculate the actual depth of this row being generated
var currentBlockDepth = depth + y_rowIndex_on_screen * blockSize;
for (var x = 0; x < gridWidth; x++) {
var block;
if (x < mineableStartX || x >= mineableEndX) {
block = new StoneBlock();
} else {
block = new TerrainBlock();
var treasureTypeForBlock = getTreasureType(currentBlockDepth);
if (treasureTypeForBlock) {
block.setTreasure(treasureTypeForBlock, currentBlockDepth); // Pass currentBlockDepth
}
// Enemy Spawning Logic (only for new rows at the bottom, y_rowIndex_on_screen === gridHeight)
if (treasureTypeForBlock && y_rowIndex_on_screen === gridHeight && Math.random() < ENEMY_SPAWN_CHANCE) {
var depthInMiles = Math.floor(currentBlockDepth / (blockSize / 2));
var treasureZoneIndex = Math.floor(depthInMiles / 5000) % 5;
var enemyTypeString = ZONE_TO_ENEMY_TYPE[treasureZoneIndex];
if (enemyTypeString) {
// Check if enemyTypeString is valid
var enemiesInThisZone = 0;
for (var k = 0; k < enemies.length; k++) {
if (!enemies[k].shouldBeDestroyed && enemies[k].associatedZoneIndex === treasureZoneIndex) {
enemiesInThisZone++;
}
}
if (enemiesInThisZone < MAX_ENEMIES_PER_ZONE_ONSCREEN) {
var newEnemy = new Enemy();
var spawnX = x * blockSize + blockSize / 2;
// Enemies spawn at the y-level of the new row.
var spawnY = y_rowIndex_on_screen * blockSize + blockSize / 2;
game.addChild(newEnemy); // Add to scene before init for safety with asset loading
newEnemy.initEnemy(enemyTypeString, spawnX, spawnY, treasureZoneIndex);
if (!newEnemy.shouldBeDestroyed) {
// Check if init was successful
enemies.push(newEnemy);
} else {
newEnemy.destroy(); // Clean up if init failed
}
}
}
}
}
block.x = x * blockSize;
block.y = y_rowIndex_on_screen * blockSize; // Position block based on its screen row index
game.addChild(block);
row.push(block);
}
return row;
}
function initializeTerrain() {
terrain = [];
for (var y = 0; y < gridHeight + 1; y++) {
terrain.push(generateRow(y));
}
}
function completeScrollCleanup() {
// The blocks in terrain[0] are the ones that moved off-screen and whose tweens just finished.
var topRowToDestroy = terrain[0];
for (var x = 0; x < topRowToDestroy.length; x++) {
var block = topRowToDestroy[x];
// Destroy treasure graphics if they exist
if (block.treasureGraphics) {
block.treasureGraphics.destroy();
}
block.destroy(); // This removes the container and its children from the stage
}
terrain.shift(); // Now remove the row from our logical terrain array
depth += blockSize;
// Add new row at the bottom. generateRow expects the screen row index. For the new bottom row, this is gridHeight.
terrain.push(generateRow(gridHeight));
depthText.setText('Depth: ' + Math.floor(depth / (blockSize / 2)) + 'm');
isScrolling = false; // Allow new scrolls
}
function scrollTerrain() {
if (isScrolling) {
return; // Don't start a new scroll if one is already in progress
}
isScrolling = true;
var tweensCompleted = 0;
var totalTweens = 0;
// Calculate total number of blocks to be tweened
for (var y_coord = 0; y_coord < terrain.length; y_coord++) {
totalTweens += terrain[y_coord].length;
}
var itemsToScroll = [];
// Add terrain blocks
for (var r = 0; r < terrain.length; r++) {
for (var c = 0; c < terrain[r].length; c++) {
itemsToScroll.push(terrain[r][c]);
}
}
// Add active enemies
for (var i = 0; i < enemies.length; i++) {
if (!enemies[i].shouldBeDestroyed) {
itemsToScroll.push(enemies[i]);
}
}
// Add active enemy shots
for (var i = 0; i < enemyShots.length; i++) {
if (!enemyShots[i].shouldBeDestroyed) {
itemsToScroll.push(enemyShots[i]);
}
}
totalTweens = itemsToScroll.length;
if (totalTweens === 0) {
isScrolling = false;
// It's possible no terrain exists but other items do, but cleanup is tied to terrain.
// If itemsToScroll is empty, nothing to do.
// If only enemies/shots, they scroll, but no new terrain row generation.
if (terrain.length === 0 || terrain[0].length === 0) {
// If no terrain to manage, but scroll was initiated (e.g. by player action)
// and there might be other things to scroll, depth might still conceptually increase.
// However, completeScrollCleanup is the one that advances depth and adds rows.
// This path implies no terrain related cleanup.
}
return;
}
// Animate each item
var terrainBlocksWereScrolled = false;
for (var i = 0; i < itemsToScroll.length; i++) {
var itemToAnimate = itemsToScroll[i];
if (itemToAnimate instanceof TerrainBlock || itemToAnimate instanceof StoneBlock) {
terrainBlocksWereScrolled = true;
}
tween(itemToAnimate, {
y: itemToAnimate.y - blockSize
}, {
duration: scrollAnimationDuration,
easing: tween.linear,
onFinish: function onScrollItemFinish() {
tweensCompleted++;
if (tweensCompleted === totalTweens) {
// All items done tweening
if (terrainBlocksWereScrolled && terrain.length > 0 && terrain[0].length > 0) {
completeScrollCleanup(); // Call cleanup if terrain was involved
} else {
// No terrain was scrolled, or terrain is now empty after scrolling.
isScrolling = false;
// If terrain was scrolled and became empty, still update depth.
if (terrainBlocksWereScrolled && (terrain.length === 0 || terrain.length > 0 && terrain[0].length === 0)) {
depth += blockSize;
depthText.setText('Depth: ' + Math.floor(depth / (blockSize / 2)) + 'm');
}
}
}
}
});
}
}
function mineBlock(worldX, worldY, isPlayerAction) {
// Default isPlayerAction to true if not specified for backward compatibility
if (typeof isPlayerAction === 'undefined') {
isPlayerAction = true;
}
var gridX = Math.floor(worldX / blockSize);
var gridY = Math.floor(worldY / blockSize);
if (gridX < 0 || gridX >= gridWidth || gridY < 0 || gridY >= gridHeight) {
return;
}
// Check if terrain row exists before accessing
if (!terrain[gridY]) {
return;
}
var block = terrain[gridY][gridX];
if (!block || !block.visible) {
return;
}
if (block.mine()) {
if (block.hasTreasure) {
var treasureValue = 0;
switch (block.treasureType) {
case 'bronze':
treasureValue = 10;
break;
case 'silver':
treasureValue = 25;
break;
case 'gold':
treasureValue = 50;
break;
case 'diamond':
treasureValue = 100;
break;
case 'crystal':
treasureValue = 150;
break;
}
// Only update score and treasure counts for player actions
if (isPlayerAction) {
score += treasureValue;
LK.setScore(score);
scoreText.setText('Score: ' + score);
treasureCounts[block.treasureType]++;
treasureTexts[block.treasureType].setText(block.treasureType.charAt(0).toUpperCase() + block.treasureType.slice(1) + ': ' + treasureCounts[block.treasureType]);
}
}
// Only trigger scrolling for player actions
if (isPlayerAction && gridY > gridHeight / 2) {
scrollTerrain();
}
}
}
// Create intro screen
function createIntroScreen() {
introContainer = new Container();
// Add intro background - since we're adding to GUI, we need different scaling approach
var introBackground = introContainer.attachAsset('IntroBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
});
// Since we're adding to GUI.center, we need to scale based on the actual GUI dimensions
// The GUI system automatically scales, so we need to set the background to fill the screen
// by making it large enough to cover the viewport
var assetWidth = introBackground.width;
var assetHeight = introBackground.height;
// Defensively check for valid, positive asset dimensions before calculating scale
if (assetWidth > 0 && assetHeight > 0) {
// For GUI elements, we want to scale to fit the entire image within the screen (contain/fit)
// Since GUI.center is already centered, this will ensure the image is centered and fully visible.
var scaleX = 2048 / assetWidth;
var scaleY = 2732 / assetHeight;
// Use the smaller scale to ensure the entire image fits within screen boundaries
var scale = Math.min(scaleX, scaleY);
introBackground.scale.set(scale, scale);
// Position is already centered at 0,0 in the GUI.center container
} else {
// If asset dimensions are invalid (e.g., zero or negative),
// set scale to zero to effectively hide it and log a warning.
introBackground.scale.set(0, 0);
console.log("Warning: IntroBackground asset has invalid dimensions (width: " + assetWidth + ", height: " + assetHeight + "). Cannot scale appropriately.");
}
// Add start button
var startButton = introContainer.attachAsset('Startbutton', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 400,
// Lowered by 200px (was 200)
scaleX: 4,
scaleY: 1.5
});
// Make button interactive
startButton.interactive = true;
// Add button press handler
startButton.down = function () {
startGame();
};
// Add blinking animation to the start button
tween(startButton, {
alpha: 0.5
}, {
duration: 800,
// Duration of the blink
easing: tween.linear,
onFinish: function onFinish() {
tween(startButton, {
alpha: 1
}, {
duration: 800,
easing: tween.linear,
onFinish: function onFinish() {
// Loop the animation
tween(startButton, {
alpha: 0.5
}, {
duration: 800,
easing: tween.linear,
onFinish: arguments.callee
});
}
});
}
});
// Add to GUI center instead of game
LK.gui.center.addChild(introContainer);
}
function startGame() {
if (gameStarted) {
return;
}
// Set gameStarted immediately to prevent multiple calls
gameStarted = true;
// Remove intro screen
if (introContainer) {
introContainer.destroy();
introContainer = null;
}
// Show game UI elements
scoreText.visible = true;
depthText.visible = true;
treasureContainer.visible = true;
// Initialize game
initializeTerrain();
player = game.addChild(new Player());
player.x = 1024;
player.y = 200;
}
// Create intro screen instead of starting game immediately
createIntroScreen();
game.down = function (x, y, obj) {
if (!gameStarted) {
return;
}
isDragging = true;
// Clamp target position to game boundaries
targetX = Math.max(0, Math.min(x, 2048));
targetY = Math.max(0, Math.min(y, 2732));
};
game.move = function (x, y, obj) {
if (!gameStarted) {
return;
}
// Clamp target position to game boundaries
targetX = Math.max(0, Math.min(x, 2048));
targetY = Math.max(0, Math.min(y, 2732));
};
game.up = function (x, y, obj) {
if (!gameStarted) {
return;
}
isDragging = false;
};
game.update = function () {
if (!gameStarted) {
return;
}
if (player && targetX !== null && targetY !== null) {
movePlayerTowardsTarget();
}
// Update enemies
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
if (enemy.shouldBeDestroyed) {
enemy.destroy();
enemies.splice(i, 1);
} else {
enemy.update();
// Additional check: if enemy graphic exists and it scrolled too high (e.g. its zone is long gone)
if (!isScrolling && enemy.graphics && enemy.y < -(enemy.graphics.height * enemy.graphics.scale.y * 2)) {
enemy.shouldBeDestroyed = true;
}
}
}
// Update enemy shots
for (var i = enemyShots.length - 1; i >= 0; i--) {
var shot = enemyShots[i];
if (shot.shouldBeDestroyed) {
shot.destroy();
enemyShots.splice(i, 1);
} else {
shot.update();
}
}
};
Same ninja character, with pack of all colors
Same ninja character, with pack of colors silver,gold,diamond,pink crystal
Same ninja character, with silver color
Same ninja character, with gold color
Same ninja character, with diamond color
Same ninja character, with crystal pink color
Pack of crystals, pink color In-Game asset. 2d. High contrast. No shadows
same image but add beside the character +1 with green color.
Same image of ninja with dig machine but with different colors. HD colors. yellow
Same image of ninja with dig machine but with different colors. HD colors. red
Same image of ninja with dig machine but with different colors. HD colors. blue
Same image of ninja with dig machine but with different colors. HD colors. black ninja
Same image of ninja with dig machine but with different colors. HD colors. green
Playerdiggingsound1
Sound effect
Enemydiggingsound1
Sound effect
Gamemusic1
Music
CollectedTreasuresound1
Sound effect
Startsound1
Sound effect
Intromusic1
Music
PlayerShotSound
Sound effect
EnemyShotshound1
Sound effect
Lifesound1
Sound effect
enemyshotsound1
Sound effect
playershotsound1
Sound effect