Code edit (3 edits merged)
Please save this source code
Code edit (16 edits merged)
Please save this source code
User prompt
Remove the last row of the grid.
User prompt
Please start drawing the grid, 200 pixels from the top of the screen to the bottom.
User prompt
Please start drawing the grid 400 pixels from the top of the screen to the bottom.
User prompt
Don't draw any assets on row number 1.
User prompt
Please leave row number 1 empty without any sprites or assets. Start rendering assets, walls, bricks, etc. from row number 2 on.
Code edit (1 edits merged)
Please save this source code
/**** * 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; }); // 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.easeOutQuad }); // 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; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: Math.floor(Math.random() * 0xFFFFFF) // Random background color }); /**** * Game Code ****/ // Let's define the shapes we'll use: // Engine will implicitly create assets based on usage below. // --- Constants --- // Placeholder ID // Placeholder ID // Placeholder ID for the logo var GRID_COLS = 15; // Ensure odd number for wall placement var GRID_ROWS = 21; // Ensure odd number for wall placement 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 = (2732 - GRID_HEIGHT) / 2 + 50; // Adjust Y offset slightly down 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 // --- 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'); } // --- 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; } // 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) { 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; } // 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: 300, 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 // Add instructions 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 // Ensure text wraps within screen bounds }); instructions.anchor.set(0.5, 0.5); instructions.x = 2048 / 2; instructions.y = 2732 / 2; // Center the text startScreenContainer.addChild(instructions); // 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 instructions.style.fill = 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); } }
/****
* 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;
});
// 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.easeOutQuad
});
// 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;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: Math.floor(Math.random() * 0xFFFFFF) // Random background color
});
/****
* Game Code
****/
// Let's define the shapes we'll use:
// Engine will implicitly create assets based on usage below.
// --- Constants ---
// Placeholder ID
// Placeholder ID
// Placeholder ID for the logo
var GRID_COLS = 15; // Ensure odd number for wall placement
var GRID_ROWS = 21; // Ensure odd number for wall placement
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 = (2732 - GRID_HEIGHT) / 2 + 50; // Adjust Y offset slightly down
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
// --- 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');
}
// --- 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;
}
// 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) {
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;
}
// 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: 300,
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
// Add instructions 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 // Ensure text wraps within screen bounds
});
instructions.anchor.set(0.5, 0.5);
instructions.x = 2048 / 2;
instructions.y = 2732 / 2; // Center the text
startScreenContainer.addChild(instructions);
// 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
instructions.style.fill = 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);
}
}
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