/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Represents a placed bomb var Bomb = Container.expand(function (col, row) { var self = Container.call(this); var graphics = self.attachAsset('bomb', { anchorX: 0.5, anchorY: 0.5 }); self.col = col; self.row = row; self.x = getWorldCoords(col, row).x; self.y = getWorldCoords(col, row).y; self.isBomb = true; // Identifier self.pulsatingTween = null; // Initialize tween variable // Define the pulsation functions function scaleDown() { // Prevent tweening if the bomb is already destroyed if (self.destroyed) { return; } self.pulsatingTween = tween(self.scale, { x: 1.0, y: 1.0 }, { duration: 200, ease: tween.easeInOut, onFinish: scaleUp // When shrinking finishes, start growing }); } function scaleUp() { // Prevent tweening if the bomb is already destroyed if (self.destroyed) { return; } self.pulsatingTween = tween(self.scale, { x: 1.1, y: 1.1 }, { duration: 200, ease: tween.easeInOut, onFinish: scaleDown // When growing finishes, start shrinking }); } scaleUp(); // Start the explosion timer var explosionTimer = LK.setTimeout(function () { explodeBomb(self); }, BOMB_TIMER); // Public method to trigger explosion early (chain reaction) self.triggerExplosion = function () { LK.clearTimeout(explosionTimer); explodeBomb(self); }; // Override destroy to clear the timer var baseDestroy = self.destroy; self.destroy = function () { LK.clearTimeout(explosionTimer); // Stop the specific pulsating tween instance if (self.pulsatingTween) { self.pulsatingTween.kill(); // Use the kill() method on the stored tween instance self.pulsatingTween = null; // Clear the reference } // Make sure the grid cell is marked empty when bomb is destroyed (e.g., by explosion) if (grid[self.col] && grid[self.col][self.row] === CELL_TYPE.BOMB) { setCell(self.col, self.row, CELL_TYPE.EMPTY); } // Remove from active bombs list in game scope var index = activeBombs.indexOf(self); if (index !== -1) { activeBombs.splice(index, 1); } // Mark as destroyed so ongoing tween callbacks won't try to run self.destroyed = true; if (baseDestroy) { baseDestroy.call(self); } // Call original destroy if exists }; return self; }); // Class for bullets fired from the gun var Bullet = Container.expand(function (startX, startY, targetX, targetY) { var self = Container.call(this); // Create a simple bullet graphic var bulletGraphics = self.attachAsset('powerup_bomb', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.5, scaleY: 0.5 }); self.x = startX; self.y = startY; self.isBullet = true; // Calculate direction vector var dx = targetX - startX; var dy = targetY - startY; var magnitude = Math.sqrt(dx * dx + dy * dy); self.velocityX = dx / magnitude * 15; // Speed factor self.velocityY = dy / magnitude * 15; // Store last position for collision detection self.lastX = self.x; self.lastY = self.y; self.update = function () { // Store last position self.lastX = self.x; self.lastY = self.y; // Update position based on velocity self.x += self.velocityX; self.y += self.velocityY; // Check if bullet is outside screen if (self.x < 0 || self.x > 2048 || self.y < 0 || self.y > 2732) { self.destroy(); } }; return self; }); // Represents an enemy that moves between grid cells var Enemy = Container.expand(function (startCol, startRow) { var self = Container.call(this); var graphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); self.col = startCol; self.row = startRow; self.isEnemy = true; // Identifier self.isMoving = false; // Flag to track movement animation state self.moveDelay = 500 + Math.random() * 500; // Reduced delay between moves (0.5-1s) self.lastMoveTime = Date.now(); // Track when last moved // Snap to initial grid position var initialPos = getWorldCoords(self.col, self.row); self.x = initialPos.x; self.y = initialPos.y; // Try to move, prioritizing adjacent player cell self.tryRandomMove = function () { if (self.isMoving || !player || player.isMoving) { // Don't move if animating or player doesn't exist/is moving return false; } // Check if player is adjacent var playerCol = player.col; var playerRow = player.row; var dx = playerCol - self.col; var dy = playerRow - self.row; // Check for adjacency (Manhattan distance of 1) if (Math.abs(dx) + Math.abs(dy) === 1) { // Player is adjacent. Try to move to the player's cell. var targetCol = playerCol; var targetRow = playerRow; var moveDx = dx; // Direction towards player // Check if the player's cell is walkable (it might not be if another enemy is there temporarily, unlikely but defensive check) if (isWalkable(targetCol, targetRow)) { // Move towards the player self.moveTo(targetCol, targetRow, moveDx); return true; // Moved towards player } // If player's cell is not walkable, fall through to random move } // Player is not adjacent, or adjacent cell is blocked: Try to move in a random direction var directions = [{ dx: 0, dy: -1 }, // up { dx: 1, dy: 0 }, // right { dx: 0, dy: 1 }, // down { dx: -1, dy: 0 } // left ]; // Shuffle directions for randomness for (var i = directions.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var temp = directions[i]; directions[i] = directions[j]; directions[j] = temp; } // Try each random direction until a valid move is found for (var i = 0; i < directions.length; i++) { var newCol = self.col + directions[i].dx; var newRow = self.row + directions[i].dy; if (isWalkable(newCol, newRow)) { // Found valid random move self.moveTo(newCol, newRow, directions[i].dx); return true; } } return false; // No valid moves found }; // Move to a new cell self.moveTo = function (newCol, newRow, dx) { self.isMoving = true; self.col = newCol; self.row = newRow; var targetPos = getWorldCoords(newCol, newRow); // Flip sprite based on movement direction if (dx < 0) { graphics.scale.x = -1; } else if (dx > 0) { graphics.scale.x = 1; } // Select a random easing function var randomEasing = enemyEasings[Math.floor(Math.random() * enemyEasings.length)]; // Animate movement tween(self, { x: targetPos.x, y: targetPos.y }, { duration: 500, // Make movement animation faster easing: randomEasing, // Use the randomly selected easing function onFinish: function onFinish() { self.isMoving = false; self.lastMoveTime = Date.now(); } }); return true; }; return self; }); // Spawn enemies at random empty cells // Represents a single explosion particle/cell var Explosion = Container.expand(function (col, row) { var self = Container.call(this); var graphics = self.attachAsset('explosion', { anchorX: 0.5, anchorY: 0.5 }); self.col = col; self.row = row; self.x = getWorldCoords(col, row).x; self.y = getWorldCoords(col, row).y; self.isExplosion = true; // Identifier for collision checks // Start scaled down for explosion effect self.scale.set(0); // Animate the explosion appearing tween(self.scale, { x: 1, y: 1 }, { duration: 150, ease: tween.easeInOut }); // Fade out and destroy after duration var timer = LK.setTimeout(function () { self.destroy(); // Destroy handled by Game update loop removal }, EXPLOSION_DURATION); // Override destroy to clear the timer var baseDestroy = self.destroy; self.destroy = function () { LK.clearTimeout(timer); if (baseDestroy) { baseDestroy.call(self); } // Call original destroy if exists }; return self; }); // Represents the player character var Player = Container.expand(function (startCol, startRow) { var self = Container.call(this); var graphics = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); self.col = startCol; self.row = startRow; self.isPlayer = true; // Identifier // Snap to initial grid position var initialPos = getWorldCoords(self.col, self.row); self.x = initialPos.x; self.y = initialPos.y; self.isMoving = false; // Flag to track movement animation state // Empty placeholder for movement - functionality removed self.moveTo = function (newCol, newRow) { // Movement functionality removed return false; // Movement disabled }; // Empty placeholder for bomb placement - functionality removed self.placeBomb = function () { // Bomb placement functionality removed }; }); // Represents a power-up item dropped on the grid var PowerUp = Container.expand(function (col, row, type) { var self = Container.call(this); self.col = col; self.row = row; self.type = type; // 'extra_bomb' or 'bigger_explosion' self.isPowerUp = true; // Identifier var assetId = type === 'extra_bomb' ? 'powerup_bomb' : 'powerup_range'; var graphics = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); var coords = getWorldCoords(col, row); self.x = coords.x; self.y = coords.y; // Simple bobbing animation var startY = self.y; var bobTween; function bobUp() { if (self.destroyed) { return; } bobTween = tween(self, { y: startY - 10 }, { duration: 500, ease: tween.easeInOutSine, onFinish: bobDown }); } function bobDown() { if (self.destroyed) { return; } bobTween = tween(self, { y: startY }, { duration: 500, ease: tween.easeInOutSine, onFinish: bobUp }); } bobUp(); // Start animation // Override destroy to stop tween var baseDestroy = self.destroy; self.destroy = function () { self.destroyed = true; // Flag to stop tween callbacks if (bobTween) { bobTween.kill(); bobTween = null; } // Remove from active power-ups list var index = activePowerUps.indexOf(self); if (index !== -1) { activePowerUps.splice(index, 1); } if (baseDestroy) { baseDestroy.call(self); } }; return self; }); // Class to display a floating message when picking up a powerup var PowerUpMessage = Container.expand(function (message, x, y) { var self = Container.call(this); // Create text with the powerup message var messageTxt = new Text2(message, { size: 60, fill: 0xFFFF00 // Yellow color for visibility }); messageTxt.anchor.set(0.5, 0.5); self.addChild(messageTxt); // Position at the specified location self.x = x; self.y = y; // Animate the message: move up and fade out function animateAndDestroy() { tween(self, { y: self.y - 100, // Move up alpha: 0 // Fade out }, { duration: 1000, // Animation lasts 1 second ease: tween.easeOutQuad, onFinish: function onFinish() { // Remove from parent when animation completes self.destroy(); } }); } // Start the animation animateAndDestroy(); return self; }); // Class for the weapon slot var Slot = Container.expand(function (type, x, y) { var self = Container.call(this); var slotGraphics = self.attachAsset('Slot', { anchorX: 0.5, anchorY: 0.5 }); self.type = type; // 'bomb' or 'gun' self.x = x; self.y = y; self.occupied = false; self.item = null; // Method to add an item to the slot self.addItem = function (itemType) { if (self.occupied) { return false; } var itemAsset = itemType === 'bomb' ? 'bomb' : 'Gun'; var item = LK.getAsset(itemAsset, { anchorX: 0.5, anchorY: 0.5 }); self.addChild(item); self.item = item; self.occupied = true; self.itemType = itemType; return true; }; // Method to remove item from slot self.removeItem = function () { if (!self.occupied) { return null; } var itemType = self.itemType; if (self.item) { self.item.destroy(); self.item = null; } self.occupied = false; self.itemType = null; return itemType; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: Math.floor(Math.random() * 0xFFFFFF) // Random background color }); /**** * Game Code ****/ // Placeholder ID for the logo // Placeholder ID // Placeholder ID // --- Constants --- // Engine will implicitly create assets based on usage below. // Let's define the shapes we'll use: var GRID_COLS = 15; // Ensure odd number for wall placement var GRID_ROWS = 20; // Remove the last row var CELL_SIZE = 128; var GRID_WIDTH = GRID_COLS * CELL_SIZE; var GRID_HEIGHT = GRID_ROWS * CELL_SIZE; var GRID_OFFSET_X = (2048 - GRID_WIDTH) / 2; var GRID_OFFSET_Y = 200; // Position grid 200 pixels from the top var CELL_TYPE = { EMPTY: 0, DESTRUCTIBLE: 1, INDESTRUCTIBLE: 2, BOMB: 3, // Cell temporarily occupied by a bomb EXPLOSION: 4 // Cell temporarily occupied by explosion }; var BOMB_TIMER = 2500; // Milliseconds before bomb explodes var EXPLOSION_DURATION = 400; // Milliseconds explosion visuals last var BASE_BOMB_RANGE = 2; // Default explosion range var BASE_MAX_BOMBS = 1; // Default max bombs var BOMB_RANGE; // Current explosion range (can be modified by powerups) var MAX_BOMBS; // Current max bombs (can be modified by powerups) var POWERUP_CHANCE = 0.3; // 30% chance to drop a powerup from a brick var BRICK_DENSITY = 0.2; // Further reduced probability of bricks to make more room for enemies // Array of tween easing functions for enemy movement var enemyEasings = [ // Basic easing functions tween.linear, // Quad easing tween.easeIn, tween.easeOut, tween.easeInOut, // Cubic easing tween.cubicIn, tween.cubicOut, tween.cubicInOut, // Quartic easing tween.quarticIn, tween.quarticOut, tween.quarticInOut, // Quintic easing tween.quinticIn, tween.quinticOut, tween.quinticInOut, // Sine easing tween.sineIn, tween.sineOut, tween.sineInOut, // Exponential easing tween.expoIn, tween.expoOut, tween.expoInOut, // Circular easing tween.easeInCirc, tween.easeOutCirc, tween.easeInOutCirc, // Include previously commented out elastic easing which can create interesting movements tween.elasticIn, tween.elasticOut, tween.elasticInOut]; // --- Game State Variables --- var grid = []; // 2D array holding CELL_TYPE for each cell var gridSprites = []; // 2D array holding visual sprites for walls/bricks var player; var activeBombs = []; var activeExplosions = []; // Array to hold active Explosion objects var enemies = []; // Array to hold all enemy instances var score = 0; var scoreTxt; var isGameOver = false; var ENEMY_COUNT = 6; // Increased number of enemies to spawn var startScreenContainer = null; // Container for the start screen elements var gameReady = false; // Flag to track if the game has started var bombSlot; var gunSlot; var activeBullets = []; // Track bullets for updates var lastShotTime = 0; // Used for rate limiting shots var SHOT_COOLDOWN = 500; // Milliseconds between shots // --- Helper Functions --- function getWorldCoords(col, row) { var x = GRID_OFFSET_X + col * CELL_SIZE + CELL_SIZE / 2; var y = GRID_OFFSET_Y + row * CELL_SIZE + CELL_SIZE / 2; return { x: x, y: y }; } // Get the type of a cell, handling out-of-bounds function getCellType(col, row) { if (col < 0 || col >= GRID_COLS || row < 0 || row >= GRID_ROWS) { return CELL_TYPE.INDESTRUCTIBLE; // Treat out of bounds as walls } return grid[col][row]; } // Set the type of a cell function setCell(col, row, type) { if (col >= 0 && col < GRID_COLS && row >= 0 && row < GRID_ROWS) { grid[col][row] = type; } } // Check if a cell is valid and empty for movement // Check if a cell is occupied by an enemy function isEnemyOccupied(col, row) { for (var i = 0; i < enemies.length; i++) { if (enemies[i].col === col && enemies[i].row === row) { return true; } } return false; } // Check if a cell is valid and empty for movement (for both player and enemies) function isWalkable(col, row) { var cellType = getCellType(col, row); // A cell is walkable only if it's currently empty AND not occupied by another enemy. return cellType === CELL_TYPE.EMPTY && !isEnemyOccupied(col, row); } // --- Grid Generation --- function generateGrid() { grid = []; gridSprites = []; // Clear previous sprites if any game.children.forEach(function (child) { if (child.isGridElement) { // Add a flag to identify grid sprites child.destroy(); } }); game.removeChildren(); // Clear all children before regenerating // Add floor tiles everywhere first for (var c = 0; c < GRID_COLS; c++) { for (var r = 0; r < GRID_ROWS; r++) { var floorTile = LK.getAsset('floor', { anchorX: 0.0, anchorY: 0.0, alpha: 0 }); floorTile.x = GRID_OFFSET_X + c * CELL_SIZE; floorTile.y = GRID_OFFSET_Y + r * CELL_SIZE; floorTile.isGridElement = true; game.addChild(floorTile); } } for (var c = 0; c < GRID_COLS; c++) { grid[c] = []; gridSprites[c] = []; for (var r = 0; r < GRID_ROWS; r++) { var cellType; var sprite = null; // Place indestructible walls around border and in a checkerboard pattern inside if (c === 0 || c === GRID_COLS - 1 || r === 0 || r === GRID_ROWS - 1 || c % 2 === 0 && r % 2 === 0) { cellType = CELL_TYPE.INDESTRUCTIBLE; sprite = LK.getAsset('wall', { anchorX: 0.0, anchorY: 0.0 }); } else { // Place destructible bricks randomly, ensuring player start area is clear if (c <= 2 && r <= 2 || c >= GRID_COLS - 3 && r >= GRID_ROWS - 3) { // Clear corners for potential spawns cellType = CELL_TYPE.EMPTY; } else if (Math.random() < BRICK_DENSITY) { cellType = CELL_TYPE.DESTRUCTIBLE; sprite = LK.getAsset('brick', { anchorX: 0.0, anchorY: 0.0 }); } else { cellType = CELL_TYPE.EMPTY; } } grid[c][r] = cellType; if (sprite) { sprite.x = GRID_OFFSET_X + c * CELL_SIZE; sprite.y = GRID_OFFSET_Y + r * CELL_SIZE; sprite.isGridElement = true; // Mark as part of the grid visuals game.addChild(sprite); gridSprites[c][r] = sprite; } else { gridSprites[c][r] = null; } } } // Ensure player start is clear setCell(1, 1, CELL_TYPE.EMPTY); setCell(1, 2, CELL_TYPE.EMPTY); setCell(2, 1, CELL_TYPE.EMPTY); if (gridSprites[1] && gridSprites[1][1]) { var _gridSprites$1$; (_gridSprites$1$ = gridSprites[1][1]) === null || _gridSprites$1$ === void 0 || _gridSprites$1$.destroy(); gridSprites[1][1] = null; } if (gridSprites[1] && gridSprites[1][2]) { var _gridSprites$1$2; (_gridSprites$1$2 = gridSprites[1][2]) === null || _gridSprites$1$2 === void 0 || _gridSprites$1$2.destroy(); gridSprites[1][2] = null; } if (gridSprites[2] && gridSprites[2][1]) { var _gridSprites$2$; (_gridSprites$2$ = gridSprites[2][1]) === null || _gridSprites$2$ === void 0 || _gridSprites$2$.destroy(); gridSprites[2][1] = null; } } // --- Explosion Handling --- function createExplosion(col, row) { if (col < 0 || col >= GRID_COLS || row < 0 || row >= GRID_ROWS) { return false; } // Out of bounds var cellType = getCellType(col, row); if (cellType === CELL_TYPE.INDESTRUCTIBLE) { return false; // Explosion stops at indestructible walls } // Create visual explosion part var explosionPart = new Explosion(col, row); game.addChild(explosionPart); activeExplosions.push(explosionPart); setCell(col, row, CELL_TYPE.EXPLOSION); // Mark cell as exploding // Check for chain reactions or destroying bricks if (cellType === CELL_TYPE.DESTRUCTIBLE) { var brickSprite = gridSprites[col][row]; if (brickSprite) { brickSprite.destroy(); gridSprites[col][row] = null; } // Chance to spawn a power-up if (Math.random() < POWERUP_CHANCE) { var powerUpType = Math.random() < 0.5 ? 'extra_bomb' : 'bigger_explosion'; var newPowerUp = new PowerUp(col, row, powerUpType); activePowerUps.push(newPowerUp); // Add powerup slightly above floor, but below other dynamic elements // Find the index of the player to insert before it, otherwise add to end (simplistic layering) var playerIndex = game.children.indexOf(player); if (playerIndex !== -1) { game.addChildAt(newPowerUp, playerIndex); } else { game.addChild(newPowerUp); } } else { // Only set to empty if no powerup spawned here setCell(col, row, CELL_TYPE.EMPTY); // Brick destroyed, becomes empty } setCell(col, row, CELL_TYPE.EXPLOSION); // Temporarily becomes explosion regardless of powerup spawn LK.getSound('destroy_brick').play(); LK.setScore(LK.getScore() + 10); // Award score for destroying brick scoreTxt.setText(LK.getScore()); return false; // Explosion stops after destroying a brick } else if (cellType === CELL_TYPE.BOMB) { // Find the bomb object at this location and trigger it var bombToTrigger = null; for (var i = 0; i < activeBombs.length; i++) { if (activeBombs[i].col === col && activeBombs[i].row === row) { bombToTrigger = activeBombs[i]; break; } } if (bombToTrigger && !bombToTrigger.isExploding) { // Prevent infinite loops if already triggered bombToTrigger.isExploding = true; // Mark as exploding to avoid re-triggering bombToTrigger.triggerExplosion(); } return false; // Chain reaction handles further explosion; stop this particular ray } return true; // Explosion continues } function explodeBomb(bomb) { if (!bomb || bomb.destroyed) { return; } // Bomb might already be destroyed by chain reaction LK.getSound('explosion_sound').play(); var centerCol = bomb.col; var centerRow = bomb.row; // Remove bomb object itself first to prevent chain reaction with self bomb.destroy(); // This also removes from activeBombs and sets cell to EMPTY // Create explosion at the center createExplosion(centerCol, centerRow); // Create explosions outwards in four directions var directions = [[0, 1], [0, -1], [1, 0], [-1, 0]]; // Down, Up, Right, Left for (var i = 0; i < directions.length; i++) { var dx = directions[i][0]; var dy = directions[i][1]; for (var r = 1; r <= BOMB_RANGE; r++) { var currentC = centerCol + dx * r; var currentR = centerRow + dy * r; if (!createExplosion(currentC, currentR)) { break; // Stop this direction if explosion is blocked } } } } // --- Game Initialization --- function initGame() { isGameOver = false; score = 0; LK.setScore(0); activeBombs = []; activeExplosions = []; activePowerUps = []; // Reset active powerups enemies = []; MAX_BOMBS = BASE_MAX_BOMBS; // Reset max bombs BOMB_RANGE = BASE_BOMB_RANGE; // Reset bomb range generateGrid(); // Create Player player = new Player(1, 1); // Start at top-left clear area game.addChild(player); // Spawn enemies on empty cells spawnEnemies(); // Score Display scoreTxt = new Text2('0', { size: 80, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Add score to top center UI // Play background music LK.playMusic('backgroundMusic'); // Create slots for bomb and gun bombSlot = new Slot('bomb', 200, 200); gunSlot = new Slot('gun', 400, 200); game.addChild(bombSlot); game.addChild(gunSlot); // Add bomb to bomb slot bombSlot.addItem('bomb'); // Add gun to gun slot gunSlot.addItem('gun'); } // --- Event Handlers --- // Handle player movement and bomb placement based on click/tap position game.down = function (x, y, obj) { // Only handle input if the game has started if (!gameReady) { return; } // Prevent actions if game is over or player is already moving/animating if (isGameOver || !player || player.isMoving) { // Added !player check for safety return; } // Check if clicked on the gunSlot to shoot if (gunSlot && gunSlot.occupied && Math.abs(x - gunSlot.x) < 50 && Math.abs(y - gunSlot.y) < 50) { // Clicked on gun slot - enable shooting mode var currentTime = Date.now(); if (currentTime - lastShotTime > SHOT_COOLDOWN) { // Find a random enemy to target if (enemies.length > 0) { var randomEnemy = enemies[Math.floor(Math.random() * enemies.length)]; var playerPos = getWorldCoords(player.col, player.row); // Create a bullet from the player position toward the enemy var bullet = new Bullet(playerPos.x, playerPos.y, randomEnemy.x, randomEnemy.y); game.addChild(bullet); activeBullets.push(bullet); // Play shoot sound LK.getSound('placeBomb').play(); lastShotTime = currentTime; } return; } } // Get click position relative to the game area var clickGameX = x; var clickGameY = y; // Determine the target grid cell based on click position var targetCol = Math.floor((clickGameX - GRID_OFFSET_X) / CELL_SIZE); var targetRow = Math.floor((clickGameY - GRID_OFFSET_Y) / CELL_SIZE); // Ensure target is within grid bounds if (targetCol < 0 || targetCol >= GRID_COLS || targetRow < 0 || targetRow >= GRID_ROWS) { return; // Click outside the grid } // Check if the clicked cell is the player's current cell if (targetCol === player.col && targetRow === player.row) { // Clicked on player's cell, place a bomb if possible if (activeBombs.length < MAX_BOMBS && getCellType(player.col, player.row) !== CELL_TYPE.BOMB && bombSlot && bombSlot.occupied) { var newBomb = new Bomb(player.col, player.row); game.addChild(newBomb); activeBombs.push(newBomb); setCell(player.col, player.row, CELL_TYPE.BOMB); LK.getSound('placeBomb').play(); // Play sound when placing a bomb } } else { // Clicked on a different cell, attempt to move var dx = targetCol - player.col; var dy = targetRow - player.row; // Determine direction of movement based on the larger difference if (Math.abs(dx) > Math.abs(dy)) { // Horizontal movement if (dx > 0) { // Move Right if (isWalkable(player.col + 1, player.row)) { player.col++; player.isMoving = true; } } else { // Move Left if (isWalkable(player.col - 1, player.row)) { player.col--; player.isMoving = true; } } } else { // Vertical movement if (dy > 0) { // Move Down if (isWalkable(player.col, player.row + 1)) { player.row++; player.isMoving = true; } } else { // Move Up if (isWalkable(player.col, player.row - 1)) { player.row--; player.isMoving = true; } } } // If player is moving, tween the position if (player.isMoving) { var targetPos = getWorldCoords(player.col, player.row); // Mirror the player graphic if moving left if (dx < 0) { player.children[0].scale.x = -1; } else if (dx > 0) { // Un-mirror if moving right player.children[0].scale.x = 1; } tween(player, { x: targetPos.x, y: targetPos.y }, { duration: 150, onFinish: function onFinish() { player.isMoving = false; } }); } } }; // --- Game Update Loop --- game.update = function () { // Only run game logic if the start screen has been dismissed if (!gameReady) { return; } if (isGameOver) { return; } // Update bullets for (var b = activeBullets.length - 1; b >= 0; b--) { var bullet = activeBullets[b]; // Update bullet position bullet.update(); // Check for collision with enemies for (var e = enemies.length - 1; e >= 0; e--) { var enemy = enemies[e]; // Simple distance-based collision detection var dx = bullet.x - enemy.x; var dy = bullet.y - enemy.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 50) { // Collision radius // Enemy hit by bullet LK.effects.flashObject(enemy, 0xffff00, 200); LK.getSound('tinyMonsterDie').play(); // Destroy the enemy enemy.destroy(); enemies.splice(e, 1); // Add score LK.setScore(LK.getScore() + 50); scoreTxt.setText(LK.getScore()); // Destroy the bullet bullet.destroy(); activeBullets.splice(b, 1); break; // Bullet is used up } } // Check if bullet was destroyed due to going off screen if (bullet.parent && (bullet.x < 0 || bullet.x > 2048 || bullet.y < 0 || bullet.y > 2732)) { bullet.destroy(); activeBullets.splice(b, 1); } } // Check for player collision with explosions var playerCol = player.col; var playerRow = player.row; if (getCellType(playerCol, playerRow) === CELL_TYPE.EXPLOSION) { LK.getSound('player_die').play(); isGameOver = true; // Set game over flag immediately to prevent further actions // Stop any ongoing player movement tween tween.stop(player); // Death animation: Spin and fade out tween(player, { rotation: player.rotation + Math.PI * 2, // Spin 360 degrees alpha: 0 // Fade out }, { duration: 500, // Animation duration ease: tween.easeInQuad, onFinish: function onFinish() { LK.showGameOver(); // Show game over screen after animation } }); return; // Stop further updates } // Check for player collision with enemies for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (enemy.col === playerCol && enemy.row === playerRow) { LK.getSound('player_die').play(); isGameOver = true; // Set game over flag immediately to prevent further actions // Stop any ongoing player movement tween tween.stop(player); // Death animation: Spin and fade out tween(player, { rotation: player.rotation + Math.PI * 2, // Spin 360 degrees alpha: 0 // Fade out }, { duration: 500, // Animation duration ease: tween.easeInQuad, onFinish: function onFinish() { LK.showGameOver(); // Show game over screen after animation } }); return; // Stop further updates } // Check for player collision with powerups for (var p = activePowerUps.length - 1; p >= 0; p--) { // Ensure powerUp is valid before accessing properties if (!activePowerUps[p]) { continue; } var powerUp = activePowerUps[p]; if (powerUp.col === playerCol && powerUp.row === playerRow) { // Player picked up powerup var message = ""; if (powerUp.type === 'extra_bomb') { MAX_BOMBS++; message = "+1 bomb!"; } else if (powerUp.type === 'bigger_explosion') { BOMB_RANGE++; message = "Range increased!"; } LK.getSound('powerup_pickup').play(); // Create floating message at player position var messagePos = getWorldCoords(playerCol, playerRow); var powerUpMessage = new PowerUpMessage(message, messagePos.x, messagePos.y - 50); game.addChild(powerUpMessage); // Cell should already be EMPTY, but ensure consistency setCell(powerUp.col, powerUp.row, CELL_TYPE.EMPTY); powerUp.destroy(); // Handles removal from array and visual stage. This also splices activePowerUps. // No need to adjust loop index `p` because destroy() splices the array we are iterating backward. } } // Powerups are no longer destroyed by explosions upon spawning. // Check for enemy collision with explosions if (getCellType(enemy.col, enemy.row) === CELL_TYPE.EXPLOSION) { // Enemy hit by explosion LK.effects.flashObject(enemy, 0xffff00, 200); // Play tinyMonsterDie sound when enemy dies LK.getSound('tinyMonsterDie').play(); enemy.destroy(); enemies.splice(i, 1); // Add score for killing enemy LK.setScore(LK.getScore() + 50); scoreTxt.setText(LK.getScore()); i--; // Adjust index after removing element } } // Move enemies randomly with their individual timing var now = Date.now(); for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (!enemy.isMoving && now - enemy.lastMoveTime > enemy.moveDelay) { enemy.tryRandomMove(); } } // Clean up finished explosions and reset cell types for (var i = activeExplosions.length - 1; i >= 0; i--) { var explosion = activeExplosions[i]; // Check if the explosion sprite itself has been destroyed (by its internal timer) // Accessing internal properties like `_destroyed` is generally discouraged, // but necessary if the destroy mechanism is purely timer-based without explicit flags. // A better approach would be for Explosion class to set a 'finished' flag. // Let's assume Explosion.destroy correctly removes it from the stage. // We need a reliable way to know when to remove from activeExplosions and reset the grid cell. // We'll check if it's still parented to the game stage as a proxy. if (!explosion.parent) { // If it's been removed from the stage (destroyed by its timer) var exCol = explosion.col; var exRow = explosion.row; // Reset the grid cell to EMPTY once the explosion effect finishes. // Powerups visually occupy the cell but the underlying grid state should be walkable. if (getCellType(exCol, exRow) === CELL_TYPE.EXPLOSION) { setCell(exCol, exRow, CELL_TYPE.EMPTY); } activeExplosions.splice(i, 1); // Remove from active list } } // Win condition: all enemies destroyed if (enemies.length === 0) { LK.showYouWin(); } }; // --- Create Start Screen --- function createStartScreen() { startScreenContainer = new Container(); startScreenContainer.interactive = true; // Make it cover the whole screen for interaction startScreenContainer.hitArea = new Rectangle(0, 0, 2048, 2732); // Add logo background var logo = startScreenContainer.attachAsset('logo', { anchorX: 0.5, anchorY: 0.5, width: 2048, height: 2732 }); logo.x = 2048 / 2; logo.y = 2732 / 2; var logoBomber = logo.attachAsset('player', { anchorX: 0.5, anchorY: 0.5, width: 500, height: 600, y: -500 }); // Simple bobbing animation for the logoBomber var logoBomberStartY = logoBomber.y; var logoBobTween; function logoBobUp() { if (!logoBomber || !logoBomber.parent) { return; } // Stop if destroyed logoBobTween = tween(logoBomber, { y: logoBomberStartY - 20 }, { duration: 800, ease: tween.easeInOutSine, onFinish: logoBobDown }); } function logoBobDown() { if (!logoBomber || !logoBomber.parent) { return; } // Stop if destroyed logoBobTween = tween(logoBomber, { y: logoBomberStartY }, { duration: 800, ease: tween.easeInOutSine, onFinish: logoBobUp }); } logoBobUp(); // Start the bobbing animation var title = new Text2("BOMB 'N BLOOM", { size: 70, // Adjusted size for better readability on full screen fill: 0xffffff, align: 'left', wordWrap: true, wordWrapWidth: 1800 // Ensure text wraps within screen bounds }); title.anchor.set(0.5, 0.5); title.x = 2048 / 2; title.y = 2732 / 2; // Center the text var instructionsText = "🏁 Click on an adjacent empty cell to move.\n💣 Click on yourself to place a bomb.\n🆙 Collect power ups!\n💥 Run from explosions!"; var instructions = new Text2(instructionsText, { size: 70, // Adjusted size for better readability on full screen fill: 0xffffff, align: 'left', wordWrap: true, wordWrapWidth: 1800 }); instructions.anchor.set(0.5, 0.5); title.addChild(instructions); instructions.y = 300; startScreenContainer.addChild(title); // Rainbow color effect for instructions var rainbowColors = [0xFF0000, 0xFF7F00, 0xFFFF00, 0x00FF00, 0x0000FF, 0x4B0082, 0x9400D3]; var rainbowIndex = 0; var rainbowInterval = LK.setInterval(function () { if (instructions) { // Check if instructions still exist title.tint = rainbowColors[rainbowIndex]; rainbowIndex = (rainbowIndex + 1) % rainbowColors.length; } else { // Clean up if instructions are gone but timer persists (shouldn't happen with current flow) LK.clearInterval(rainbowInterval); rainbowInterval = null; } }, 300); // Change color every 300ms // Function to start the game when clicked startScreenContainer.down = function () { if (gameReady) { return; } // Prevent multiple starts // Stop start screen animations and timers if (logoBobTween) { logoBobTween.kill(); logoBobTween = null; } if (rainbowInterval) { LK.clearInterval(rainbowInterval); rainbowInterval = null; } gameReady = true; // Remove start screen startScreenContainer.destroy(); startScreenContainer = null; // Initialize the actual game initGame(); // Start game timers only after game begins // Set up timer to play random enemy voice sound every 5 seconds var voiceTimer = LK.setInterval(function () { // Array of available voice sounds var voiceSounds = ['voices', 'voices1', 'voices2', 'voices3']; // Select a random voice sound from the array var randomVoice = voiceSounds[Math.floor(Math.random() * voiceSounds.length)]; // Play the selected random voice sound LK.getSound(randomVoice).play(); }, 5000); // 5000 milliseconds = 5 seconds // Set up timer to shake a random brick every few seconds var shakeTimer = LK.setInterval(function () { // Collect all brick sprites var bricks = []; for (var c = 0; c < GRID_COLS; c++) { for (var r = 0; r < GRID_ROWS; r++) { if (grid[c] && grid[c][r] === CELL_TYPE.DESTRUCTIBLE && gridSprites[c] && gridSprites[c][r]) { bricks.push(gridSprites[c][r]); } } } // Only proceed if there are bricks to shake if (bricks.length > 0) { // Pick a random brick var randomBrick = bricks[Math.floor(Math.random() * bricks.length)]; // Store the original position var originalX = randomBrick.x; var originalY = randomBrick.y; // Shake animation sequence tween(randomBrick, { x: originalX - 5, y: originalY - 5 }, { duration: 50, ease: tween.easeInOut, onFinish: function onFinish() { tween(randomBrick, { x: originalX + 5, y: originalY + 5 }, { duration: 50, ease: tween.easeInOut, onFinish: function onFinish() { tween(randomBrick, { x: originalX, y: originalY }, { duration: 50, ease: tween.easeInOut }); } }); } }); } }, 3000); // Shake a brick every 3 seconds }; game.addChild(startScreenContainer); } // --- Initialize the Start Screen --- createStartScreen(); // Spawn enemies at random empty cells function spawnEnemies() { // Clear any existing enemies for (var i = enemies.length - 1; i >= 0; i--) { enemies[i].destroy(); } enemies = []; // Find all empty cells var emptyCells = []; for (var c = 0; c < GRID_COLS; c++) { for (var r = 0; r < GRID_ROWS; r++) { // Don't spawn near player start position if (getCellType(c, r) === CELL_TYPE.EMPTY && !(c <= 3 && r <= 3)) { // Avoid player's starting area emptyCells.push({ col: c, row: r }); } } } // Shuffle empty cells for (var i = emptyCells.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var temp = emptyCells[i]; emptyCells[i] = emptyCells[j]; emptyCells[j] = temp; } // Create enemies var enemiesToSpawn = Math.min(ENEMY_COUNT, emptyCells.length); for (var i = 0; i < enemiesToSpawn; i++) { var enemyCell = emptyCells[i]; // Randomly select an enemy type from the available options var enemyTypes = ['enemy', 'enemy1', 'enemy2', 'enemy3', 'enemy4']; var randomEnemyType = enemyTypes[Math.floor(Math.random() * enemyTypes.length)]; var enemy = new Enemy(enemyCell.col, enemyCell.row); // Set the random enemy type asset enemy.children[0].destroy(); // Remove the default 'enemy' asset var graphics = enemy.attachAsset(randomEnemyType, { anchorX: 0.5, anchorY: 0.5 }); // Apply a random color tint to each enemy enemies.push(enemy); game.addChild(enemy); } }
===================================================================
--- original.js
+++ change.js
@@ -83,8 +83,44 @@
} // Call original destroy if exists
};
return self;
});
+// Class for bullets fired from the gun
+var Bullet = Container.expand(function (startX, startY, targetX, targetY) {
+ var self = Container.call(this);
+ // Create a simple bullet graphic
+ var bulletGraphics = self.attachAsset('powerup_bomb', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ scaleX: 0.5,
+ scaleY: 0.5
+ });
+ self.x = startX;
+ self.y = startY;
+ self.isBullet = true;
+ // Calculate direction vector
+ var dx = targetX - startX;
+ var dy = targetY - startY;
+ var magnitude = Math.sqrt(dx * dx + dy * dy);
+ self.velocityX = dx / magnitude * 15; // Speed factor
+ self.velocityY = dy / magnitude * 15;
+ // Store last position for collision detection
+ self.lastX = self.x;
+ self.lastY = self.y;
+ self.update = function () {
+ // Store last position
+ self.lastX = self.x;
+ self.lastY = self.y;
+ // Update position based on velocity
+ self.x += self.velocityX;
+ self.y += self.velocityY;
+ // Check if bullet is outside screen
+ if (self.x < 0 || self.x > 2048 || self.y < 0 || self.y > 2732) {
+ self.destroy();
+ }
+ };
+ return self;
+});
// Represents an enemy that moves between grid cells
var Enemy = Container.expand(function (startCol, startRow) {
var self = Container.call(this);
var graphics = self.attachAsset('enemy', {
@@ -354,8 +390,52 @@
// Start the animation
animateAndDestroy();
return self;
});
+// Class for the weapon slot
+var Slot = Container.expand(function (type, x, y) {
+ var self = Container.call(this);
+ var slotGraphics = self.attachAsset('Slot', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ self.type = type; // 'bomb' or 'gun'
+ self.x = x;
+ self.y = y;
+ self.occupied = false;
+ self.item = null;
+ // Method to add an item to the slot
+ self.addItem = function (itemType) {
+ if (self.occupied) {
+ return false;
+ }
+ var itemAsset = itemType === 'bomb' ? 'bomb' : 'Gun';
+ var item = LK.getAsset(itemAsset, {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ self.addChild(item);
+ self.item = item;
+ self.occupied = true;
+ self.itemType = itemType;
+ return true;
+ };
+ // Method to remove item from slot
+ self.removeItem = function () {
+ if (!self.occupied) {
+ return null;
+ }
+ var itemType = self.itemType;
+ if (self.item) {
+ self.item.destroy();
+ self.item = null;
+ }
+ self.occupied = false;
+ self.itemType = null;
+ return itemType;
+ };
+ return self;
+});
/****
* Initialize Game
****/
@@ -365,14 +445,14 @@
/****
* Game Code
****/
-// Let's define the shapes we'll use:
-// Engine will implicitly create assets based on usage below.
-// --- Constants ---
+// Placeholder ID for the logo
// Placeholder ID
// Placeholder ID
-// Placeholder ID for the logo
+// --- Constants ---
+// Engine will implicitly create assets based on usage below.
+// Let's define the shapes we'll use:
var GRID_COLS = 15; // Ensure odd number for wall placement
var GRID_ROWS = 20; // Remove the last row
var CELL_SIZE = 128;
var GRID_WIDTH = GRID_COLS * CELL_SIZE;
@@ -427,8 +507,13 @@
var isGameOver = false;
var ENEMY_COUNT = 6; // Increased number of enemies to spawn
var startScreenContainer = null; // Container for the start screen elements
var gameReady = false; // Flag to track if the game has started
+var bombSlot;
+var gunSlot;
+var activeBullets = []; // Track bullets for updates
+var lastShotTime = 0; // Used for rate limiting shots
+var SHOT_COOLDOWN = 500; // Milliseconds between shots
// --- Helper Functions ---
function getWorldCoords(col, row) {
var x = GRID_OFFSET_X + col * CELL_SIZE + CELL_SIZE / 2;
var y = GRID_OFFSET_Y + row * CELL_SIZE + CELL_SIZE / 2;
@@ -663,8 +748,17 @@
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt); // Add score to top center UI
// Play background music
LK.playMusic('backgroundMusic');
+ // Create slots for bomb and gun
+ bombSlot = new Slot('bomb', 200, 200);
+ gunSlot = new Slot('gun', 400, 200);
+ game.addChild(bombSlot);
+ game.addChild(gunSlot);
+ // Add bomb to bomb slot
+ bombSlot.addItem('bomb');
+ // Add gun to gun slot
+ gunSlot.addItem('gun');
}
// --- Event Handlers ---
// Handle player movement and bomb placement based on click/tap position
game.down = function (x, y, obj) {
@@ -676,8 +770,28 @@
if (isGameOver || !player || player.isMoving) {
// Added !player check for safety
return;
}
+ // Check if clicked on the gunSlot to shoot
+ if (gunSlot && gunSlot.occupied && Math.abs(x - gunSlot.x) < 50 && Math.abs(y - gunSlot.y) < 50) {
+ // Clicked on gun slot - enable shooting mode
+ var currentTime = Date.now();
+ if (currentTime - lastShotTime > SHOT_COOLDOWN) {
+ // Find a random enemy to target
+ if (enemies.length > 0) {
+ var randomEnemy = enemies[Math.floor(Math.random() * enemies.length)];
+ var playerPos = getWorldCoords(player.col, player.row);
+ // Create a bullet from the player position toward the enemy
+ var bullet = new Bullet(playerPos.x, playerPos.y, randomEnemy.x, randomEnemy.y);
+ game.addChild(bullet);
+ activeBullets.push(bullet);
+ // Play shoot sound
+ LK.getSound('placeBomb').play();
+ lastShotTime = currentTime;
+ }
+ return;
+ }
+ }
// Get click position relative to the game area
var clickGameX = x;
var clickGameY = y;
// Determine the target grid cell based on click position
@@ -689,9 +803,9 @@
}
// Check if the clicked cell is the player's current cell
if (targetCol === player.col && targetRow === player.row) {
// Clicked on player's cell, place a bomb if possible
- if (activeBombs.length < MAX_BOMBS && getCellType(player.col, player.row) !== CELL_TYPE.BOMB) {
+ if (activeBombs.length < MAX_BOMBS && getCellType(player.col, player.row) !== CELL_TYPE.BOMB && bombSlot && bombSlot.occupied) {
var newBomb = new Bomb(player.col, player.row);
game.addChild(newBomb);
activeBombs.push(newBomb);
setCell(player.col, player.row, CELL_TYPE.BOMB);
@@ -763,8 +877,43 @@
}
if (isGameOver) {
return;
}
+ // Update bullets
+ for (var b = activeBullets.length - 1; b >= 0; b--) {
+ var bullet = activeBullets[b];
+ // Update bullet position
+ bullet.update();
+ // Check for collision with enemies
+ for (var e = enemies.length - 1; e >= 0; e--) {
+ var enemy = enemies[e];
+ // Simple distance-based collision detection
+ var dx = bullet.x - enemy.x;
+ var dy = bullet.y - enemy.y;
+ var distance = Math.sqrt(dx * dx + dy * dy);
+ if (distance < 50) {
+ // Collision radius
+ // Enemy hit by bullet
+ LK.effects.flashObject(enemy, 0xffff00, 200);
+ LK.getSound('tinyMonsterDie').play();
+ // Destroy the enemy
+ enemy.destroy();
+ enemies.splice(e, 1);
+ // Add score
+ LK.setScore(LK.getScore() + 50);
+ scoreTxt.setText(LK.getScore());
+ // Destroy the bullet
+ bullet.destroy();
+ activeBullets.splice(b, 1);
+ break; // Bullet is used up
+ }
+ }
+ // Check if bullet was destroyed due to going off screen
+ if (bullet.parent && (bullet.x < 0 || bullet.x > 2048 || bullet.y < 0 || bullet.y > 2732)) {
+ bullet.destroy();
+ activeBullets.splice(b, 1);
+ }
+ }
// Check for player collision with explosions
var playerCol = player.col;
var playerRow = player.row;
if (getCellType(playerCol, playerRow) === CELL_TYPE.EXPLOSION) {
concrete floor tile, retro, pixel style. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
bomb, pixel style, retro. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
a demonic strawberry, pixel style. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
a demonic cherry, pixel style. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
a demonic kiwi, pixel style. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A human character, player from an arcade retro game, looking like in the 80s 90s, hair, male, looking right, pixel style. In-Game asset. 2d. High contrast. No shadows
fire texture pixel style retro square. In-Game asset. 2d. High contrast. No shadows
powerup icon for an additional charge of a bomb, retro arcade game. In-Game asset. 2d. High contrast. No shadows
powerup icon for an additional range of the bomb explosion you can throw, retro arcade game. In-Game asset. 2d. High contrast. No shadows
A pixelated gun. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A gray square with rounded edges. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows