/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var BlockPiece = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('blockPieceRed', { anchorX: 0.5, anchorY: 0.5 }); self.gridX = 0; self.gridY = 0; self.colorAsset = 'blockPieceRed'; self.setColor = function (assetName) { self.colorAsset = assetName; self.removeChild(graphics); graphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); }; self.setExplodingAsset = function (assetId) { self.removeChild(graphics); graphics = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); }; return self; }); var GamePiece = Container.expand(function () { var self = Container.call(this); self.blocks = []; self.shape = []; self.isDragging = false; self.canPlace = false; self.createShape = function (shapeData) { self.shape = shapeData; // Pick one color for the entire piece var pieceColor = pieceColors[Math.floor(Math.random() * pieceColors.length)]; for (var i = 0; i < shapeData.length; i++) { for (var j = 0; j < shapeData[i].length; j++) { if (shapeData[i][j] === 1) { var block = new BlockPiece(); block.x = j * 176; // Updated to match new CELL_SIZE block.y = i * 176; // Updated to match new CELL_SIZE block.setColor(pieceColor); self.blocks.push(block); self.addChild(block); } } } }; self.down = function (x, y, obj) { if (!self.isDragging) { self.isDragging = true; draggedPiece = self; self.originalX = self.x; self.originalY = self.y; self.alpha = 0.7; } }; return self; }); var Particle = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('particle', { anchorX: 0.5, anchorY: 0.5 }); self.velocityX = 0; self.velocityY = 0; self.lifetime = 1000; self.maxLifetime = 1000; self.update = function () { self.x += self.velocityX; self.y += self.velocityY; self.velocityY += 0.5; // gravity self.lifetime -= 16.67; // 60fps var alpha = self.lifetime / self.maxLifetime; graphics.alpha = alpha; if (self.lifetime <= 0) { self.destroy(); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x0a0a0a }); /**** * Game Code ****/ // Increased by 35% (160 * 1.35) // Increased by 35% (160 * 1.35) // Increased by 35% (120 * 1.35) // Increased by 35% (120 * 1.35) // Increased by 35% (120 * 1.35) // Increased by 35% (120 * 1.35) // Increased by 35% (120 * 1.35) // Increased by 35% (120 * 1.35) // Increased by 35% (120 * 1.35) // Increased by 35% (120 * 1.35) // Increased by 35% (120 * 1.35) // Increased by 35% (120 * 1.35) // Increased by 35% (120 * 1.35) // Increased by 35% (120 * 1.35) // Increased by 35% (120 * 1.35) // Increased by 35% (120 * 1.35) var GRID_SIZE = 9; var CELL_SIZE = 176; // Increased by 35% (130 * 1.35) var GRID_OFFSET_X = 1024 - GRID_SIZE * CELL_SIZE / 2; // Center grid horizontally var GRID_OFFSET_Y = 1366 - GRID_SIZE * CELL_SIZE / 2; // Center grid vertically var gameState = 'menu'; // 'menu', 'playing', or 'paused' var highScore = storage.highScore || 0; var unlockedLevels = storage.unlockedLevels || 1; var currentChallengeLevel = 0; var challengeGoal = 0; var isChallengeMode = false; var challengeTimer = 0; var challengeTimeLimit = 0; var challengeTimerText = null; var level2ComboTarget = 2; var level2MaxCombo = 0; var playButton = null; var playButtonText = null; var arcadeButton = null; var arcadeButtonText = null; var challengeButton = null; var challengeButtonText = null; var levelSelectTitle = null; var levelButtons = []; var levelButtonTexts = []; var backButton = null; var backButtonText = null; var menuTitle = null; var highScoreText = null; var pauseOverlay = null; var pauseTitle = null; var quitButton = null; var quitButtonText = null; var resumeButton = null; var resumeButtonText = null; var pauseButton = null; var pauseButtonText = null; var grid = []; var gameGrid = []; var pieceShapes = [[[1]], // Single block [[1, 1]], // Line 2 [[1, 1, 1]], // Line 3 [[1], [1]], // Vertical 2 [[1], [1], [1]], // Vertical 3 [[1, 1], [1, 1]], // Square 2x2 [[1, 1, 1], [1, 0, 0]], // L shape [[1, 1, 1], [0, 0, 1]], // Reverse L [[1, 1, 0], [0, 1, 1]], // Z shape [[0, 1, 0], [1, 1, 1]], // T shape [[1, 1, 1], [1, 1, 1], [1, 1, 1]], // 3x3 square [[1, 1], [1, 1], [1, 1]], // 2x3 rectangle // Bigger satisfying shapes for early game [[1, 1, 1, 1]], // Line 4 [[1], [1], [1], [1]], // Vertical 4 [[1, 1, 1], [1, 1, 1]], // 3x2 rectangle [[1, 1], [1, 1], [1, 1], [1, 1]], // 2x4 rectangle [[1, 1, 1, 1], [1, 1, 1, 1]], // 4x2 rectangle [[1, 1, 1], [1, 1, 1], [1, 1, 1]], // 3x3 square [[1, 1, 1, 1], [1, 0, 0, 0]], // Big L shape [[1, 1, 1, 1], [0, 0, 0, 1]] // Big reverse L ]; var pieceColors = ['blockPieceRed', 'blockPieceCyan', 'blockPieceBlue', 'blockPieceYellow', 'blockPiecePurple', 'blockPieceGreen', 'blockPieceOrange']; var currentPieces = []; var draggedPiece = null; var previewBlocks = []; var particles = []; var comboCounter = 0; var lastClearTime = 0; var comboMultiplier = 1; var lastPlaceTime = 0; var consecutivePlacements = 0; var recentlyUsedShapes = []; // Track recently used shapes to avoid repetition var isAIMode = false; // Track if AI is helping with suitable blocks var aiModeBlocksGenerated = 0; // Count blocks generated in AI mode var gridHadBlocks = false; // Track if grid ever had blocks placed var cleanText = null; // Clean message display var lastPlacedPieceColor = null; // Track color of last placed piece // Add grid background to define the block placement area var gridBg = LK.getAsset('gridBackground', { anchorX: 0.5, anchorY: 0.5, x: GRID_OFFSET_X + GRID_SIZE * CELL_SIZE / 2, y: GRID_OFFSET_Y + GRID_SIZE * CELL_SIZE / 2 }); game.addChild(gridBg); // Initialize grid for (var i = 0; i < GRID_SIZE; i++) { grid[i] = []; gameGrid[i] = []; for (var j = 0; j < GRID_SIZE; j++) { var cell = LK.getAsset('gridCell', { anchorX: 0.5, anchorY: 0.5, x: GRID_OFFSET_X + j * CELL_SIZE + CELL_SIZE / 2, y: GRID_OFFSET_Y + i * CELL_SIZE + CELL_SIZE / 2 }); grid[i][j] = cell; gameGrid[i][j] = null; game.addChild(cell); } } // Initialize UI var scoreTxt = new Text2('0', { size: 80, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); var comboTxt = new Text2('', { size: 60, fill: 0xFFFF00 }); comboTxt.anchor.set(1, 0); LK.gui.topRight.addChild(comboTxt); // Center screen combo text var centerComboTxt = new Text2('', { size: 120, fill: 0xFFFF00 }); centerComboTxt.anchor.set(0.5, 0.5); centerComboTxt.x = 1024; centerComboTxt.y = 1366; centerComboTxt.alpha = 0; game.addChild(centerComboTxt); // Piece panel var piecePanel = LK.getAsset('piecePanel', { anchorX: 0.5, anchorY: 1, x: 1024, y: 2732 }); game.addChild(piecePanel); function showGameOverScreen() { // Create game over overlay var gameOverOverlay = LK.getAsset('gridBackground', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 2, scaleY: 2.5, alpha: 0.9 }); game.addChild(gameOverOverlay); // Game Over title var gameOverTitle = new Text2('GAME OVER', { size: 120, fill: 0xFF0000 }); gameOverTitle.anchor.set(0.5, 0.5); gameOverTitle.x = 1024; gameOverTitle.y = 1200; game.addChild(gameOverTitle); // Score display var finalScoreText = new Text2('Score: ' + LK.getScore(), { size: 80, fill: 0xFFFFFF }); finalScoreText.anchor.set(0.5, 0.5); finalScoreText.x = 1024; finalScoreText.y = 1300; game.addChild(finalScoreText); // High Score display var highScoreDisplayText = new Text2('High Score: ' + highScore, { size: 60, fill: 0xFFD700 }); highScoreDisplayText.anchor.set(0.5, 0.5); highScoreDisplayText.x = 1024; highScoreDisplayText.y = 1400; game.addChild(highScoreDisplayText); // New High Score display (if new high score) var newHighScoreDisplay = null; if (LK.getScore() >= highScore) { newHighScoreDisplay = new Text2('NEW HIGH SCORE!', { size: 60, fill: 0xFF0000 }); newHighScoreDisplay.anchor.set(0.5, 0.5); newHighScoreDisplay.x = 1024; newHighScoreDisplay.y = 1500; game.addChild(newHighScoreDisplay); } // Main Menu Button var gameOverMenuButton = LK.getAsset('piecePanel', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: newHighScoreDisplay ? 1650 : 1550, scaleX: 0.4, scaleY: 0.25 }); game.addChild(gameOverMenuButton); var gameOverMenuButtonText = new Text2('MAIN MENU', { size: 60, fill: 0xFFFFFF }); gameOverMenuButtonText.anchor.set(0.5, 0.5); gameOverMenuButtonText.x = 1024; gameOverMenuButtonText.y = newHighScoreDisplay ? 1650 : 1550; game.addChild(gameOverMenuButtonText); gameOverMenuButton.down = function () { // Clean up game over screen gameOverOverlay.destroy(); gameOverTitle.destroy(); finalScoreText.destroy(); highScoreDisplayText.destroy(); if (newHighScoreDisplay) newHighScoreDisplay.destroy(); gameOverMenuButton.destroy(); gameOverMenuButtonText.destroy(); // Return to main menu createMainMenu(); }; } function createPauseMenu() { // Semi-transparent overlay pauseOverlay = LK.getAsset('gridBackground', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 2, scaleY: 2.5, alpha: 0.8 }); game.addChild(pauseOverlay); // Pause title pauseTitle = new Text2('PAUSED', { size: 100, fill: 0xFFFFFF }); pauseTitle.anchor.set(0.5, 0.5); pauseTitle.x = 1024; pauseTitle.y = 1200; game.addChild(pauseTitle); // Resume Button resumeButton = LK.getAsset('piecePanel', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1400, scaleX: 0.4, scaleY: 0.25 }); game.addChild(resumeButton); resumeButtonText = new Text2('RESUME', { size: 60, fill: 0xFFFFFF }); resumeButtonText.anchor.set(0.5, 0.5); resumeButtonText.x = 1024; resumeButtonText.y = 1400; game.addChild(resumeButtonText); // Quit Button quitButton = LK.getAsset('piecePanel', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1600, scaleX: 0.4, scaleY: 0.25 }); game.addChild(quitButton); quitButtonText = new Text2('QUIT TO MENU', { size: 50, fill: 0xFFFFFF }); quitButtonText.anchor.set(0.5, 0.5); quitButtonText.x = 1024; quitButtonText.y = 1600; game.addChild(quitButtonText); // Button handlers resumeButton.down = function () { resumeGame(); }; quitButton.down = function () { quitToMenu(); }; } function createMainMenu() { // Title menuTitle = new Text2('Block Exploder!', { size: 120, fill: 0xFFFFFF }); menuTitle.anchor.set(0.5, 0.5); menuTitle.x = 1024; menuTitle.y = 800; game.addChild(menuTitle); // High Score highScoreText = new Text2('High Score: ' + highScore, { size: 60, fill: 0xFFFF00 }); highScoreText.anchor.set(0.5, 0.5); highScoreText.x = 1024; highScoreText.y = 1000; game.addChild(highScoreText); // Arcade Button arcadeButton = LK.getAsset('piecePanel', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1300, scaleX: 0.3, scaleY: 0.3 }); game.addChild(arcadeButton); arcadeButtonText = new Text2('ARCADE', { size: 70, fill: 0xFFFFFF }); arcadeButtonText.anchor.set(0.5, 0.5); arcadeButtonText.x = 1024; arcadeButtonText.y = 1300; game.addChild(arcadeButtonText); arcadeButton.down = function () { startGame(); }; // Challenge Button challengeButton = LK.getAsset('piecePanel', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1500, scaleX: 0.3, scaleY: 0.3 }); game.addChild(challengeButton); challengeButtonText = new Text2('CHALLENGE', { size: 60, fill: 0xFFFFFF }); challengeButtonText.anchor.set(0.5, 0.5); challengeButtonText.x = 1024; challengeButtonText.y = 1500; game.addChild(challengeButtonText); challengeButton.down = function () { showLevelSelection(); }; // Autoplay Test Button var autoplayButton = LK.getAsset('piecePanel', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1700, scaleX: 0.3, scaleY: 0.3 }); game.addChild(autoplayButton); var autoplayButtonText = new Text2('AUTOPLAY TEST', { size: 50, fill: 0xFFFF00 }); autoplayButtonText.anchor.set(0.5, 0.5); autoplayButtonText.x = 1024; autoplayButtonText.y = 1700; game.addChild(autoplayButtonText); autoplayButton.down = function () { autoPlay = true; startGame(); }; } function showLevelSelection() { // Hide main menu elements if (menuTitle) menuTitle.destroy(); if (highScoreText) highScoreText.destroy(); if (arcadeButton) arcadeButton.destroy(); if (arcadeButtonText) arcadeButtonText.destroy(); if (challengeButton) challengeButton.destroy(); if (challengeButtonText) challengeButtonText.destroy(); // Reset references to null menuTitle = null; highScoreText = null; arcadeButton = null; arcadeButtonText = null; challengeButton = null; challengeButtonText = null; // Create level selection title levelSelectTitle = new Text2('SELECT LEVEL', { size: 100, fill: 0xFFFFFF }); levelSelectTitle.anchor.set(0.5, 0.5); levelSelectTitle.x = 1024; levelSelectTitle.y = 600; game.addChild(levelSelectTitle); // Create level buttons (2 rows of 5) levelButtons = []; levelButtonTexts = []; for (var i = 1; i <= 10; i++) { var row = Math.floor((i - 1) / 5); var col = (i - 1) % 5; var buttonX = 424 + col * 240; var buttonY = 1000 + row * 200; var isUnlocked = i <= unlockedLevels; var levelButton = LK.getAsset('pauseButton', { anchorX: 0.5, anchorY: 0.5, x: buttonX, y: buttonY, scaleX: 2, scaleY: 2 }); levelButton.tint = isUnlocked ? 0xFFFFFF : 0x666666; game.addChild(levelButton); levelButtons.push(levelButton); var levelText = new Text2(i.toString(), { size: 60, fill: isUnlocked ? 0xFFFFFF : 0x888888 }); levelText.anchor.set(0.5, 0.5); levelText.x = buttonX; levelText.y = buttonY; game.addChild(levelText); levelButtonTexts.push(levelText); // Add click handler for each level if (isUnlocked) { levelButton.levelNumber = i; levelButton.down = function () { startChallengeLevel(this.levelNumber); }; } } // Create back button backButton = LK.getAsset('piecePanel', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1600, scaleX: 0.3, scaleY: 0.3 }); game.addChild(backButton); backButtonText = new Text2('BACK', { size: 60, fill: 0xFFFFFF }); backButtonText.anchor.set(0.5, 0.5); backButtonText.x = 1024; backButtonText.y = 1600; game.addChild(backButtonText); backButton.down = function () { returnToMainMenu(); }; } function startChallengeLevel(levelNumber) { // Clean up level selection elements if (levelSelectTitle) levelSelectTitle.destroy(); if (backButton) backButton.destroy(); if (backButtonText) backButtonText.destroy(); for (var i = 0; i < levelButtons.length; i++) { if (levelButtons[i]) levelButtons[i].destroy(); } for (var i = 0; i < levelButtonTexts.length; i++) { if (levelButtonTexts[i]) levelButtonTexts[i].destroy(); } levelButtons = []; levelButtonTexts = []; // Show level objectives screen showLevelObjectives(levelNumber); } function returnToMainMenu() { // Hide level selection elements if (levelSelectTitle) levelSelectTitle.destroy(); if (backButton) backButton.destroy(); if (backButtonText) backButtonText.destroy(); for (var i = 0; i < levelButtons.length; i++) { if (levelButtons[i]) levelButtons[i].destroy(); } for (var i = 0; i < levelButtonTexts.length; i++) { if (levelButtonTexts[i]) levelButtonTexts[i].destroy(); } levelButtons = []; levelButtonTexts = []; // Reset references to null levelSelectTitle = null; backButton = null; backButtonText = null; // Show main menu again createMainMenu(); } function startGame() { gameState = 'playing'; // Reset challenge mode if starting from arcade button if (!isChallengeMode) { currentChallengeLevel = 0; challengeGoal = 0; } // Hide menu elements if (menuTitle) menuTitle.destroy(); if (highScoreText) highScoreText.destroy(); if (arcadeButton) arcadeButton.destroy(); if (arcadeButtonText) arcadeButtonText.destroy(); if (challengeButton) challengeButton.destroy(); if (challengeButtonText) challengeButtonText.destroy(); // Clean up autoplay button if it exists for (var i = game.children.length - 1; i >= 0; i--) { var child = game.children[i]; if (child.y === 1700 && child.x === 1024) { child.destroy(); } } // Reset references to null menuTitle = null; highScoreText = null; arcadeButton = null; arcadeButtonText = null; challengeButton = null; challengeButtonText = null; // Show game UI scoreTxt.alpha = 1; comboTxt.alpha = 1; // Add game title below score text in arcade screen var gameTitle = new Text2('Block Exploder!', { size: 80, fill: 0xFFFFFF }); gameTitle.anchor.set(0.5, 0); gameTitle.x = 1024; gameTitle.y = 150; game.addChild(gameTitle); // Update score display for challenge mode if (isChallengeMode) { if (currentChallengeLevel === 2) { scoreTxt.setText('Max Combo: 0 / ' + level2ComboTarget); // Create timer text for Level 2 challengeTimerText = new Text2('Time: 45s', { size: 60, fill: 0xFF0000 }); challengeTimerText.anchor.set(0, 0); LK.gui.topLeft.addChild(challengeTimerText); challengeTimerText.x = 120; // Offset from top left corner } else { scoreTxt.setText('0 / ' + challengeGoal); } } else { scoreTxt.setText('0'); } // Show grid background and piece panel gridBg.alpha = 1; for (var i = 0; i < GRID_SIZE; i++) { for (var j = 0; j < GRID_SIZE; j++) { grid[i][j].alpha = 1; } } piecePanel.alpha = 1; // Reset game state LK.setScore(0); scoreTxt.setText('0'); comboCounter = 0; comboTxt.setText(''); // Clear grid for (var i = 0; i < GRID_SIZE; i++) { for (var j = 0; j < GRID_SIZE; j++) { if (gameGrid[i][j]) { gameGrid[i][j].destroy(); gameGrid[i][j] = null; } } } // Initialize AI mode for new games isAIMode = true; aiModeBlocksGenerated = 0; gridHadBlocks = false; createNewPieces(); // Create pause button pauseButton = LK.getAsset('pauseButton', { anchorX: 1, anchorY: 0, x: 1948, y: 100 }); game.addChild(pauseButton); pauseButtonText = new Text2('||', { size: 40, fill: 0xFFFFFF }); pauseButtonText.anchor.set(0.5, 0.5); pauseButtonText.x = 1948 - 40; pauseButtonText.y = 140; game.addChild(pauseButtonText); pauseButton.down = function () { if (gameState === 'playing') { gameState = 'paused'; createPauseMenu(); } }; } function resumeGame() { gameState = 'playing'; // Hide pause menu elements if (pauseOverlay) pauseOverlay.destroy(); if (pauseTitle) pauseTitle.destroy(); if (resumeButton) resumeButton.destroy(); if (resumeButtonText) resumeButtonText.destroy(); if (quitButton) quitButton.destroy(); if (quitButtonText) quitButtonText.destroy(); } function quitToMenu() { gameState = 'menu'; // Hide pause menu elements if (pauseOverlay) pauseOverlay.destroy(); if (pauseTitle) pauseTitle.destroy(); if (resumeButton) resumeButton.destroy(); if (resumeButtonText) resumeButtonText.destroy(); if (quitButton) quitButton.destroy(); if (quitButtonText) quitButtonText.destroy(); // Update high score if needed var currentScore = LK.getScore(); if (currentScore > highScore) { highScore = currentScore; storage.highScore = highScore; } // Clear game elements for (var i = 0; i < currentPieces.length; i++) { currentPieces[i].destroy(); } currentPieces = []; // Clear grid for (var i = 0; i < GRID_SIZE; i++) { for (var j = 0; j < GRID_SIZE; j++) { if (gameGrid[i][j]) { gameGrid[i][j].destroy(); gameGrid[i][j] = null; } } } // Hide game UI scoreTxt.alpha = 0; comboTxt.alpha = 0; centerComboTxt.alpha = 0; // Hide grid background and piece panel gridBg.alpha = 0; for (var i = 0; i < GRID_SIZE; i++) { for (var j = 0; j < GRID_SIZE; j++) { grid[i][j].alpha = 0; } } piecePanel.alpha = 0; // Hide pause button if (pauseButton) pauseButton.destroy(); if (pauseButtonText) pauseButtonText.destroy(); // Clean up timer text if it exists if (challengeTimerText) { challengeTimerText.destroy(); challengeTimerText = null; } // Reset challenge mode isChallengeMode = false; currentChallengeLevel = 0; challengeGoal = 0; challengeTimer = 0; challengeTimeLimit = 0; level2MaxCombo = 0; // Reset AI mode and clean tracking isAIMode = false; aiModeBlocksGenerated = 0; // Reset autoplay autoPlay = false; autoPlayTimer = 0; gridHadBlocks = false; if (cleanText) { cleanText.destroy(); cleanText = null; } createMainMenu(); } function createNewPieces() { // Clear existing pieces for (var i = 0; i < currentPieces.length; i++) { currentPieces[i].destroy(); } currentPieces = []; // Create 3 new pieces with reduced repetition for (var i = 0; i < 3; i++) { var piece = new GamePiece(); var shapeIndex; var attempts = 0; var currentScore = LK.getScore(); if (isAIMode && aiModeBlocksGenerated < 20) { // AI mode: provide suitable blocks for clearing - avoid very small single blocks, use manageable shapes var aiSuitableShapes = [1, 1, 2, 2, 3, 4, 5, 6]; // 2-blocks, 3-blocks, verticals, and L shapes - no single blocks shapeIndex = aiSuitableShapes[Math.floor(Math.random() * aiSuitableShapes.length)]; aiModeBlocksGenerated++; } else if (currentScore < 500) { // Very early game: heavily favor bigger, more satisfying shapes var veryEasyShapes = [11, 12, 13, 14, 15, 16, 17, 18, 11, 12, 13, 14, 15, 16, 6, 10]; // Duplicate bigger shapes for higher probability shapeIndex = veryEasyShapes[Math.floor(Math.random() * veryEasyShapes.length)]; } else if (currentScore < 1500) { // Early game: mix of big shapes and medium shapes, still avoiding single blocks var earlyShapes = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 11, 12, 13, 14, 15]; // Medium to large shapes shapeIndex = earlyShapes[Math.floor(Math.random() * earlyShapes.length)]; } else if (currentScore < 3000) { // Mid-early game: introduce some smaller pieces but keep it manageable var midEasyShapes = [0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 7, 8]; // Small to medium shapes, some single blocks shapeIndex = midEasyShapes[Math.floor(Math.random() * midEasyShapes.length)]; } else if (currentScore < 5000) { // Mid game: balanced mix of all piece sizes var midShapes = [0, 0, 1, 1, 2, 2, 3, 4, 5, 6, 7, 8, 9]; // Balanced mix, more single blocks shapeIndex = midShapes[Math.floor(Math.random() * midShapes.length)]; } else if (currentScore < 8000) { // Mid-late game: favor smaller pieces but still include medium ones var midHardShapes = [0, 0, 0, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // More small pieces shapeIndex = midHardShapes[Math.floor(Math.random() * midHardShapes.length)]; } else { // Late game: Try to avoid recently used shapes, all pieces available do { shapeIndex = Math.floor(Math.random() * pieceShapes.length); attempts++; } while (recentlyUsedShapes.indexOf(shapeIndex) !== -1 && attempts < 10); } // Add to recently used shapes and keep only last 4 recentlyUsedShapes.push(shapeIndex); if (recentlyUsedShapes.length > 4) { recentlyUsedShapes.shift(); } piece.createShape(pieceShapes[shapeIndex]); piece.x = 200 + i * 650; // Increased spacing to prevent overlapping piece.y = 2550; currentPieces.push(piece); game.addChild(piece); } } function getGridPosition(x, y) { var gridX = Math.floor((x - GRID_OFFSET_X) / CELL_SIZE); var gridY = Math.floor((y - GRID_OFFSET_Y) / CELL_SIZE); return { x: gridX, y: gridY }; } function canPlacePiece(piece, gridX, gridY) { for (var i = 0; i < piece.shape.length; i++) { for (var j = 0; j < piece.shape[i].length; j++) { if (piece.shape[i][j] === 1) { var checkX = gridX + j; var checkY = gridY + i; if (checkX < 0 || checkX >= GRID_SIZE || checkY < 0 || checkY >= GRID_SIZE) { return false; } if (gameGrid[checkY][checkX] !== null) { return false; } } } } return true; } function placePiece(piece, gridX, gridY) { // Store the color of the piece being placed lastPlacedPieceColor = piece.blocks[0].colorAsset; for (var i = 0; i < piece.shape.length; i++) { for (var j = 0; j < piece.shape[i].length; j++) { if (piece.shape[i][j] === 1) { var placeX = gridX + j; var placeY = gridY + i; var block = new BlockPiece(); block.x = GRID_OFFSET_X + placeX * CELL_SIZE + CELL_SIZE / 2; block.y = GRID_OFFSET_Y + placeY * CELL_SIZE + CELL_SIZE / 2; block.setColor(piece.blocks[0].colorAsset); block.gridX = placeX; block.gridY = placeY; gameGrid[placeY][placeX] = block; game.addChild(block); } } } // Track consecutive placements for combo continuation var currentTime = Date.now(); if (currentTime - lastPlaceTime < 3000) { consecutivePlacements++; } else { consecutivePlacements = 1; } lastPlaceTime = currentTime; // Track that grid has had blocks and disable AI mode after sufficient placements if (!gridHadBlocks) { gridHadBlocks = true; } if (isAIMode && aiModeBlocksGenerated >= 20) { isAIMode = false; // Disable AI mode after initial guidance } LK.getSound('blockPlace').play(); } function clearPreview() { // Restore original block colors from stored values for (var i = 0; i < originalBlockColors.length; i++) { var colorData = originalBlockColors[i]; if (colorData.block && colorData.block.setColor) { colorData.block.setColor(colorData.originalColor); colorData.block.tint = 0xFFFFFF; } } originalBlockColors = []; for (var i = 0; i < previewBlocks.length; i++) { previewBlocks[i].destroy(); } previewBlocks = []; } function getCompletingLines(piece, gridX, gridY) { var completingRows = []; var completingCols = []; var tempGrid = []; // Create temporary grid copy for (var i = 0; i < GRID_SIZE; i++) { tempGrid[i] = []; for (var j = 0; j < GRID_SIZE; j++) { tempGrid[i][j] = gameGrid[i][j]; } } // Simulate placing the piece for (var i = 0; i < piece.shape.length; i++) { for (var j = 0; j < piece.shape[i].length; j++) { if (piece.shape[i][j] === 1) { var placeX = gridX + j; var placeY = gridY + i; if (placeX >= 0 && placeX < GRID_SIZE && placeY >= 0 && placeY < GRID_SIZE) { tempGrid[placeY][placeX] = true; // Mark as occupied } } } } // Check for complete rows for (var i = 0; i < GRID_SIZE; i++) { var rowComplete = true; for (var j = 0; j < GRID_SIZE; j++) { if (tempGrid[i][j] === null) { rowComplete = false; break; } } if (rowComplete) { completingRows.push(i); } } // Check for complete columns for (var j = 0; j < GRID_SIZE; j++) { var colComplete = true; for (var i = 0; i < GRID_SIZE; i++) { if (tempGrid[i][j] === null) { colComplete = false; break; } } if (colComplete) { completingCols.push(j); } } return { rows: completingRows, cols: completingCols }; } // Add array to store original colors for preview restoration var originalBlockColors = []; function showPreview(piece, gridX, gridY) { clearPreview(); if (canPlacePiece(piece, gridX, gridY)) { // Get piece color for tinting exploding lines var pieceColor = piece.blocks[0].colorAsset; var colorMap = { 'blockPieceRed': 0xff6b6b, 'blockPieceCyan': 0x4ecdc4, 'blockPieceBlue': 0x45b7d1, 'blockPieceYellow': 0xf7dc6f, 'blockPiecePurple': 0xbb8fce, 'blockPieceGreen': 0x58d68d, 'blockPieceOrange': 0xf39c12 }; var tintColor = colorMap[pieceColor] || 0xffffff; // Check which lines would complete var completingLines = getCompletingLines(piece, gridX, gridY); // Show preview for the piece itself for (var i = 0; i < piece.shape.length; i++) { for (var j = 0; j < piece.shape[i].length; j++) { if (piece.shape[i][j] === 1) { var previewX = gridX + j; var previewY = gridY + i; var preview = LK.getAsset('previewBlock', { anchorX: 0.5, anchorY: 0.5, x: GRID_OFFSET_X + previewX * CELL_SIZE + CELL_SIZE / 2, y: GRID_OFFSET_Y + previewY * CELL_SIZE + CELL_SIZE / 2, alpha: 0.5, tint: tintColor }); previewBlocks.push(preview); game.addChild(preview); } } } // Show preview for completing rows and columns - replace blocks with piece color if (completingLines && (completingLines.rows.length > 0 || completingLines.cols.length > 0)) { // Store original colors before changing them originalBlockColors = []; // Preview completing rows - replace existing blocks with piece color for (var r = 0; r < completingLines.rows.length; r++) { var row = completingLines.rows[r]; for (var j = 0; j < GRID_SIZE; j++) { if (gameGrid[row][j] !== null) { // Store original color before changing originalBlockColors.push({ block: gameGrid[row][j], originalColor: gameGrid[row][j].colorAsset }); gameGrid[row][j].setColor(pieceColor); } } } // Preview completing columns - replace existing blocks with piece color for (var c = 0; c < completingLines.cols.length; c++) { var col = completingLines.cols[c]; for (var i = 0; i < GRID_SIZE; i++) { if (gameGrid[i][col] !== null) { // Check if we already stored this block's color to avoid duplicates var alreadyStored = false; for (var k = 0; k < originalBlockColors.length; k++) { if (originalBlockColors[k].block === gameGrid[i][col]) { alreadyStored = true; break; } } if (!alreadyStored) { originalBlockColors.push({ block: gameGrid[i][col], originalColor: gameGrid[i][col].colorAsset }); } gameGrid[i][col].setColor(pieceColor); } } } } } } function checkLines() { var clearedBlocks = []; var completedRows = 0; var completedCols = 0; // Check for complete rows for (var i = 0; i < GRID_SIZE; i++) { var rowComplete = true; for (var j = 0; j < GRID_SIZE; j++) { if (gameGrid[i][j] === null) { rowComplete = false; break; } } if (rowComplete) { completedRows++; for (var j = 0; j < GRID_SIZE; j++) { clearedBlocks.push({ x: j, y: i }); } } } // Check for complete columns for (var j = 0; j < GRID_SIZE; j++) { var colComplete = true; for (var i = 0; i < GRID_SIZE; i++) { if (gameGrid[i][j] === null) { colComplete = false; break; } } if (colComplete) { completedCols++; for (var i = 0; i < GRID_SIZE; i++) { clearedBlocks.push({ x: j, y: i }); } } } if (clearedBlocks.length > 0) { clearBlocks(clearedBlocks, completedRows + completedCols); return true; } return false; } function clearBlocks(blockPositions, lineCount) { var currentTime = Date.now(); // Check if this clear is within 3 blocks of the last clear var timeSinceLastClear = currentTime - lastClearTime; if (timeSinceLastClear < 5000) { // 5 seconds window for combo - continue from where we left off and add line count comboCounter += lineCount || 1; } else { comboCounter = lineCount || 1; } lastClearTime = currentTime; // Calculate multiplier based on combo comboMultiplier = comboCounter; var uniqueBlocks = []; var seen = {}; for (var i = 0; i < blockPositions.length; i++) { var pos = blockPositions[i]; var key = pos.x + "," + pos.y; if (!seen[key] && gameGrid[pos.y][pos.x] !== null) { seen[key] = true; uniqueBlocks.push(pos); } } // Change block appearance before exploding - use last placed piece color for (var i = 0; i < uniqueBlocks.length; i++) { var pos = uniqueBlocks[i]; var block = gameGrid[pos.y][pos.x]; if (block && lastPlacedPieceColor) { // Replace the block with last placed piece color asset before exploding block.setColor(lastPlacedPieceColor); } } // Delay explosion effects slightly to show the new appearance LK.setTimeout(function () { for (var i = 0; i < uniqueBlocks.length; i++) { var pos = uniqueBlocks[i]; var block = gameGrid[pos.y][pos.x]; if (block) { createExplosion(block.x, block.y, lastPlacedPieceColor); block.destroy(); gameGrid[pos.y][pos.x] = null; } } }, 200); // Screen effects var flashColor = comboCounter > 3 ? 0xff0000 : 0xffffff; var flashDuration = Math.min(comboCounter * 100 + 200, 800); LK.effects.flashScreen(flashColor, flashDuration); // Update score with combo multiplier var basePoints = uniqueBlocks.length * 10; var finalPoints = basePoints * comboMultiplier; LK.setScore(LK.getScore() + Math.floor(finalPoints)); // Track max combo for Level 2 if (isChallengeMode && currentChallengeLevel === 2) { if (comboCounter > level2MaxCombo) { level2MaxCombo = comboCounter; } scoreTxt.setText('Max Combo: ' + level2MaxCombo + ' / ' + level2ComboTarget); } else if (isChallengeMode) { scoreTxt.setText(LK.getScore() + ' / ' + challengeGoal); } else { scoreTxt.setText(LK.getScore()); } // Update combo display if (comboCounter > 1) { if (comboCounter >= 4) { comboTxt.setText("PERFECT COMBO x" + comboCounter); } else { comboTxt.setText("COMBO x" + comboCounter); } // Show center screen combo animation showCenterCombo(comboCounter); // Animate combo text tween(comboTxt, { scaleX: 1.5, scaleY: 1.5 }, { duration: 200, easing: tween.easeOut }); tween(comboTxt, { scaleX: 1, scaleY: 1 }, { duration: 300, easing: tween.easeInOut }); // Add color flash to combo text tween(comboTxt, { tint: 0xFFFF00 }, { duration: 100, onFinish: function onFinish() { tween(comboTxt, { tint: 0xFFFFFF }, { duration: 200 }); } }); } else { comboTxt.setText(""); // Show center screen combo for first clear too showCenterCombo(comboCounter); // Reset combo text color comboTxt.tint = 0xFFFFFF; } // Play sound if (comboCounter >= 4) { LK.getSound('perfectCombo').play(); LK.getSound('lineClear').play(); } else if (comboCounter >= 2) { LK.getSound('comboClear').play(); LK.getSound('lineClear').play(); } else { LK.getSound('lineClear').play(); } } function showCenterCombo(combo) { // Stop any existing combo animation tween.stop(centerComboTxt); // Set combo text if (combo >= 4) { centerComboTxt.setText("PERFECT COMBO!"); centerComboTxt.fill = 0xFF00FF; // Magenta for perfect combo } else { centerComboTxt.setText("COMBO x" + combo + "!"); centerComboTxt.fill = 0xFFFF00; // Yellow for regular combo } centerComboTxt.scaleX = 0.5; centerComboTxt.scaleY = 0.5; centerComboTxt.alpha = 0; // Animate in tween(centerComboTxt, { scaleX: 1.2, scaleY: 1.2, alpha: 1 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { // Hold for a moment then animate out tween(centerComboTxt, { scaleX: 0.8, scaleY: 0.8, alpha: 0 }, { duration: 800, easing: tween.easeIn }); } }); } function createExplosion(x, y, color) { var particleCount = Math.min(comboCounter * 2 + 5, 20); for (var i = 0; i < particleCount; i++) { var particle = new Particle(); particle.x = x; particle.y = y; particle.velocityX = (Math.random() - 0.5) * 10; particle.velocityY = (Math.random() - 0.5) * 10 - 5; particle.lifetime = 500 + Math.random() * 500; particle.maxLifetime = particle.lifetime; var particleGraphics = particle.children[0]; // Convert asset name to approximate color for particles var colorMap = { 'blockPieceRed': 0xff6b6b, 'blockPieceCyan': 0x4ecdc4, 'blockPieceBlue': 0x45b7d1, 'blockPieceYellow': 0xf7dc6f, 'blockPiecePurple': 0xbb8fce, 'blockPieceGreen': 0x58d68d, 'blockPieceOrange': 0xf39c12 }; particleGraphics.tint = colorMap[color] || 0xffffff; particles.push(particle); game.addChild(particle); } } function checkGridClean() { if (!gridHadBlocks) return false; // Don't trigger clean if no blocks were ever placed for (var i = 0; i < GRID_SIZE; i++) { for (var j = 0; j < GRID_SIZE; j++) { if (gameGrid[i][j] !== null) { return false; // Grid is not clean } } } return true; // Grid is completely clean } function showCleanMessage() { // Create and show "CLEAN!" message cleanText = new Text2('CLEAN!', { size: 150, fill: 0x00FF00 }); cleanText.anchor.set(0.5, 0.5); cleanText.x = 1024; cleanText.y = 1366; cleanText.alpha = 0; cleanText.scaleX = 0.5; cleanText.scaleY = 0.5; game.addChild(cleanText); // Animate the clean message tween(cleanText, { alpha: 1, scaleX: 1.2, scaleY: 1.2 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { // Hold message briefly then fade out tween(cleanText, { alpha: 0, scaleX: 0.8, scaleY: 0.8 }, { duration: 1000, easing: tween.easeIn, onFinish: function onFinish() { if (cleanText) { cleanText.destroy(); cleanText = null; } } }); } }); // Add bonus points LK.setScore(LK.getScore() + 500); // Update score display if (isChallengeMode && currentChallengeLevel === 2) { scoreTxt.setText('Max Combo: ' + level2MaxCombo + ' / ' + level2ComboTarget); } else if (isChallengeMode) { scoreTxt.setText(LK.getScore() + ' / ' + challengeGoal); } else { scoreTxt.setText(LK.getScore()); } // Play clean sound LK.getSound('clean').play(); // Screen flash effect LK.effects.flashScreen(0x00FF00, 1000); } function checkGameOver() { for (var i = 0; i < currentPieces.length; i++) { var piece = currentPieces[i]; var canPlace = false; for (var row = 0; row < GRID_SIZE; row++) { for (var col = 0; col < GRID_SIZE; col++) { if (canPlacePiece(piece, col, row)) { canPlace = true; break; } } if (canPlace) break; } if (canPlace) return false; } return true; } game.move = function (x, y, obj) { if (gameState === 'playing' && draggedPiece) { draggedPiece.x = x; draggedPiece.y = y; var gridPos = getGridPosition(x, y); showPreview(draggedPiece, gridPos.x, gridPos.y); } }; game.down = function (x, y, obj) { // No longer trigger pause menu from empty space taps }; game.up = function (x, y, obj) { if (gameState === 'playing' && draggedPiece) { var gridPos = getGridPosition(x, y); if (canPlacePiece(draggedPiece, gridPos.x, gridPos.y)) { placePiece(draggedPiece, gridPos.x, gridPos.y); // Remove piece from current pieces var index = currentPieces.indexOf(draggedPiece); if (index > -1) { currentPieces.splice(index, 1); } draggedPiece.destroy(); // Check for clears var blocksWereCleared = checkLines(); // Check if grid is completely clean after clearing blocks if (blocksWereCleared && checkGridClean()) { showCleanMessage(); } // Create new pieces if all are used if (currentPieces.length === 0) { createNewPieces(); } } else { // Return piece to original position tween(draggedPiece, { x: draggedPiece.originalX, y: draggedPiece.originalY, alpha: 1 }, { duration: 300 }); draggedPiece.isDragging = false; } clearPreview(); draggedPiece = null; } else if (gameState === 'playing') { // Clear preview if user touches somewhere else while not dragging clearPreview(); } }; // Auto-play mode for testing var autoPlay = false; var autoPlayTimer = 0; game.update = function () { // Clean up particles for (var i = particles.length - 1; i >= 0; i--) { var particle = particles[i]; if (particle.lifetime <= 0) { particles.splice(i, 1); } } // Auto-play logic for testing if (autoPlay && gameState === 'playing' && currentPieces.length > 0) { autoPlayTimer += 16.67; // 60fps frame time if (autoPlayTimer >= 1000) { // Every 1 second autoPlayTimer = 0; // Try to place the first available piece var piece = currentPieces[0]; var placed = false; // Try to find a valid placement for (var row = 0; row < GRID_SIZE && !placed; row++) { for (var col = 0; col < GRID_SIZE && !placed; col++) { if (canPlacePiece(piece, col, row)) { placePiece(piece, col, row); // Remove piece from current pieces var index = currentPieces.indexOf(piece); if (index > -1) { currentPieces.splice(index, 1); } piece.destroy(); // Check for clears var blocksWereCleared = checkLines(); // Check if grid is completely clean after clearing blocks if (blocksWereCleared && checkGridClean()) { showCleanMessage(); } // Create new pieces if all are used if (currentPieces.length === 0) { createNewPieces(); } placed = true; } } } } } // Handle Level 2 timer countdown if (gameState === 'playing' && isChallengeMode && currentChallengeLevel === 2 && challengeTimeLimit > 0) { challengeTimer -= 16.67; // Subtract frame time (60fps) var remainingSeconds = Math.ceil(challengeTimer / 1000); if (challengeTimerText) { challengeTimerText.setText('Time: ' + Math.max(0, remainingSeconds) + 's'); // Change color as time runs low if (remainingSeconds <= 10) { challengeTimerText.fill = 0xFF0000; // Red } else if (remainingSeconds <= 20) { challengeTimerText.fill = 0xFFFF00; // Yellow } } // Check if time ran out if (challengeTimer <= 0 && level2MaxCombo < level2ComboTarget) { // Time's up and didn't achieve 2x combo - game over if (challengeTimerText) { challengeTimerText.destroy(); challengeTimerText = null; } // Update high score if needed var currentScore = LK.getScore(); if (currentScore > highScore) { highScore = currentScore; storage.highScore = highScore; } // Clear game elements for (var i = 0; i < currentPieces.length; i++) { currentPieces[i].destroy(); } currentPieces = []; // Hide game UI scoreTxt.alpha = 0; comboTxt.alpha = 0; centerComboTxt.alpha = 0; // Hide grid background and piece panel gridBg.alpha = 0; for (var i = 0; i < GRID_SIZE; i++) { for (var j = 0; j < GRID_SIZE; j++) { grid[i][j].alpha = 0; } } piecePanel.alpha = 0; // Reset challenge mode isChallengeMode = false; currentChallengeLevel = 0; challengeGoal = 0; challengeTimer = 0; challengeTimeLimit = 0; level2MaxCombo = 0; gameState = 'menu'; createMainMenu(); return; } } // Check challenge completion var challengeCompleted = false; if (gameState === 'playing' && isChallengeMode) { if (currentChallengeLevel === 1 && LK.getScore() >= challengeGoal) { challengeCompleted = true; } else if (currentChallengeLevel === 2 && level2MaxCombo >= level2ComboTarget) { challengeCompleted = true; } else if (currentChallengeLevel > 2 && LK.getScore() >= challengeGoal) { challengeCompleted = true; } } if (challengeCompleted) { // Challenge completed! if (challengeTimerText) { challengeTimerText.destroy(); challengeTimerText = null; } // Special handling for level 3 completion to unlock level 4 var wasLevel4Unlocked = false; if (currentChallengeLevel === 3 && LK.getScore() >= 3000 && unlockedLevels < 4) { unlockedLevels = 4; storage.unlockedLevels = unlockedLevels; wasLevel4Unlocked = true; } else if (currentChallengeLevel >= unlockedLevels && currentChallengeLevel < 10) { unlockedLevels = currentChallengeLevel + 1; storage.unlockedLevels = unlockedLevels; } // Update high score if needed var currentScore = LK.getScore(); if (currentScore > highScore) { highScore = currentScore; storage.highScore = highScore; } // Clear game elements for (var i = 0; i < currentPieces.length; i++) { currentPieces[i].destroy(); } currentPieces = []; // Hide game UI scoreTxt.alpha = 0; comboTxt.alpha = 0; centerComboTxt.alpha = 0; // Hide grid background and piece panel gridBg.alpha = 0; for (var i = 0; i < GRID_SIZE; i++) { for (var j = 0; j < GRID_SIZE; j++) { grid[i][j].alpha = 0; } } piecePanel.alpha = 0; // Reset challenge mode isChallengeMode = false; currentChallengeLevel = 0; challengeGoal = 0; challengeTimer = 0; challengeTimeLimit = 0; level2MaxCombo = 0; gameState = 'menu'; showCustomWinScreen(wasLevel4Unlocked); return; } // Check game over if (gameState === 'playing' && currentPieces.length > 0 && checkGameOver()) { // Update high score if needed var currentScore = LK.getScore(); if (currentScore > highScore) { highScore = currentScore; storage.highScore = highScore; } // Clear game elements for (var i = 0; i < currentPieces.length; i++) { currentPieces[i].destroy(); } currentPieces = []; // Hide game UI scoreTxt.alpha = 0; comboTxt.alpha = 0; centerComboTxt.alpha = 0; // Hide grid background and piece panel gridBg.alpha = 0; for (var i = 0; i < GRID_SIZE; i++) { for (var j = 0; j < GRID_SIZE; j++) { grid[i][j].alpha = 0; } } piecePanel.alpha = 0; // Hide pause button if (pauseButton) pauseButton.destroy(); if (pauseButtonText) pauseButtonText.destroy(); gameState = 'menu'; showGameOverScreen(); } }; // Initialize game scoreTxt.alpha = 0; comboTxt.alpha = 0; // Hide grid background and grid initially gridBg.alpha = 0; for (var i = 0; i < GRID_SIZE; i++) { for (var j = 0; j < GRID_SIZE; j++) { grid[i][j].alpha = 0; } } piecePanel.alpha = 0; createMainMenu(); LK.playMusic('bgmusic'); function showCustomWinScreen(unlockedLevel4) { // Store the completed level for button logic var completedLevel = currentChallengeLevel; var nextLevel = completedLevel + 1; var hasNextLevel = nextLevel <= 10 && unlockedLevels >= nextLevel; // Create win overlay var winOverlay = LK.getAsset('gridBackground', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 2, scaleY: 2.5, alpha: 0.9 }); game.addChild(winOverlay); // Win title var winTitle = new Text2('YOU WIN!', { size: 120, fill: 0x00FF00 }); winTitle.anchor.set(0.5, 0.5); winTitle.x = 1024; winTitle.y = unlockedLevel4 ? 1100 : 1200; game.addChild(winTitle); // Level 4 unlock congratulations message var congratsText = null; if (unlockedLevel4) { congratsText = new Text2('CONGRATULATIONS!\nLEVEL 4 UNLOCKED!', { size: 80, fill: 0xFFD700 }); congratsText.anchor.set(0.5, 0.5); congratsText.x = 1024; congratsText.y = 1250; game.addChild(congratsText); } // Next Level Button (show if there's a next level available) var nextButton = null; var nextButtonText = null; var buttonYOffset = unlockedLevel4 ? 200 : 0; if (hasNextLevel) { nextButton = LK.getAsset('piecePanel', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1400 + buttonYOffset, scaleX: 0.4, scaleY: 0.25 }); game.addChild(nextButton); nextButtonText = new Text2('LEVEL ' + nextLevel, { size: 60, fill: 0xFFFFFF }); nextButtonText.anchor.set(0.5, 0.5); nextButtonText.x = 1024; nextButtonText.y = 1400 + buttonYOffset; game.addChild(nextButtonText); nextButton.down = function () { // Clean up win screen winOverlay.destroy(); winTitle.destroy(); if (congratsText) congratsText.destroy(); nextButton.destroy(); nextButtonText.destroy(); mainMenuButton.destroy(); mainMenuButtonText.destroy(); // Start next level startChallengeLevel(nextLevel); }; } // Main Menu Button var mainMenuButton = LK.getAsset('piecePanel', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: (nextButton ? 1600 : 1400) + buttonYOffset, scaleX: 0.4, scaleY: 0.25 }); game.addChild(mainMenuButton); var mainMenuButtonText = new Text2('MAIN MENU', { size: 60, fill: 0xFFFFFF }); mainMenuButtonText.anchor.set(0.5, 0.5); mainMenuButtonText.x = 1024; mainMenuButtonText.y = (nextButton ? 1600 : 1400) + buttonYOffset; game.addChild(mainMenuButtonText); mainMenuButton.down = function () { // Clean up win screen winOverlay.destroy(); winTitle.destroy(); if (congratsText) congratsText.destroy(); if (nextButton) nextButton.destroy(); if (nextButtonText) nextButtonText.destroy(); mainMenuButton.destroy(); mainMenuButtonText.destroy(); // Return to main menu createMainMenu(); }; } function showLevelObjectives(levelNumber) { // Create objectives overlay var objectivesOverlay = LK.getAsset('gridBackground', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 2, scaleY: 2.5, alpha: 0.9 }); game.addChild(objectivesOverlay); // Level title var levelTitle = new Text2('LEVEL ' + levelNumber, { size: 120, fill: 0xFFFFFF }); levelTitle.anchor.set(0.5, 0.5); levelTitle.x = 1024; levelTitle.y = 1000; game.addChild(levelTitle); // Objective text based on level var objectiveText = ''; if (levelNumber === 1) { objectiveText = 'OBJECTIVE:\nScore 1000 points\nto complete this level'; } else if (levelNumber === 2) { objectiveText = 'OBJECTIVE:\nAchieve a 2x combo\nwithin 45 seconds'; } else if (levelNumber === 3) { objectiveText = 'OBJECTIVE:\nScore 3000 points\nto unlock Level 4'; } else if (levelNumber === 4) { objectiveText = 'OBJECTIVE:\nScore 4000 points\nto complete this level'; } else { objectiveText = 'OBJECTIVE:\nScore ' + levelNumber * 1000 + ' points\nto complete this level'; } var objectiveDisplay = new Text2(objectiveText, { size: 80, fill: 0xFFFF00 }); objectiveDisplay.anchor.set(0.5, 0.5); objectiveDisplay.x = 1024; objectiveDisplay.y = 1300; game.addChild(objectiveDisplay); // Start button var startButton = LK.getAsset('piecePanel', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1600, scaleX: 0.4, scaleY: 0.25 }); game.addChild(startButton); var startButtonText = new Text2('START', { size: 60, fill: 0xFFFFFF }); startButtonText.anchor.set(0.5, 0.5); startButtonText.x = 1024; startButtonText.y = 1600; game.addChild(startButtonText); startButton.down = function () { // Clean up objectives screen objectivesOverlay.destroy(); levelTitle.destroy(); objectiveDisplay.destroy(); startButton.destroy(); startButtonText.destroy(); // Set challenge mode variables isChallengeMode = true; currentChallengeLevel = levelNumber; if (levelNumber === 1) { challengeGoal = 1000; // Level 1 = 1000 points challengeTimeLimit = 0; // No time limit for level 1 } else if (levelNumber === 2) { challengeGoal = 0; // Level 2 is not about points challengeTimeLimit = 45000; // 45 seconds in milliseconds level2MaxCombo = 0; // Reset max combo for level 2 } else { challengeGoal = levelNumber * 1000; // Other levels challengeTimeLimit = 0; } challengeTimer = challengeTimeLimit; // Set initial timer startGame(); }; }
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var BlockPiece = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('blockPieceRed', {
anchorX: 0.5,
anchorY: 0.5
});
self.gridX = 0;
self.gridY = 0;
self.colorAsset = 'blockPieceRed';
self.setColor = function (assetName) {
self.colorAsset = assetName;
self.removeChild(graphics);
graphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
};
self.setExplodingAsset = function (assetId) {
self.removeChild(graphics);
graphics = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
};
return self;
});
var GamePiece = Container.expand(function () {
var self = Container.call(this);
self.blocks = [];
self.shape = [];
self.isDragging = false;
self.canPlace = false;
self.createShape = function (shapeData) {
self.shape = shapeData;
// Pick one color for the entire piece
var pieceColor = pieceColors[Math.floor(Math.random() * pieceColors.length)];
for (var i = 0; i < shapeData.length; i++) {
for (var j = 0; j < shapeData[i].length; j++) {
if (shapeData[i][j] === 1) {
var block = new BlockPiece();
block.x = j * 176; // Updated to match new CELL_SIZE
block.y = i * 176; // Updated to match new CELL_SIZE
block.setColor(pieceColor);
self.blocks.push(block);
self.addChild(block);
}
}
}
};
self.down = function (x, y, obj) {
if (!self.isDragging) {
self.isDragging = true;
draggedPiece = self;
self.originalX = self.x;
self.originalY = self.y;
self.alpha = 0.7;
}
};
return self;
});
var Particle = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('particle', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = 0;
self.velocityY = 0;
self.lifetime = 1000;
self.maxLifetime = 1000;
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
self.velocityY += 0.5; // gravity
self.lifetime -= 16.67; // 60fps
var alpha = self.lifetime / self.maxLifetime;
graphics.alpha = alpha;
if (self.lifetime <= 0) {
self.destroy();
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x0a0a0a
});
/****
* Game Code
****/
// Increased by 35% (160 * 1.35)
// Increased by 35% (160 * 1.35)
// Increased by 35% (120 * 1.35)
// Increased by 35% (120 * 1.35)
// Increased by 35% (120 * 1.35)
// Increased by 35% (120 * 1.35)
// Increased by 35% (120 * 1.35)
// Increased by 35% (120 * 1.35)
// Increased by 35% (120 * 1.35)
// Increased by 35% (120 * 1.35)
// Increased by 35% (120 * 1.35)
// Increased by 35% (120 * 1.35)
// Increased by 35% (120 * 1.35)
// Increased by 35% (120 * 1.35)
// Increased by 35% (120 * 1.35)
// Increased by 35% (120 * 1.35)
var GRID_SIZE = 9;
var CELL_SIZE = 176; // Increased by 35% (130 * 1.35)
var GRID_OFFSET_X = 1024 - GRID_SIZE * CELL_SIZE / 2; // Center grid horizontally
var GRID_OFFSET_Y = 1366 - GRID_SIZE * CELL_SIZE / 2; // Center grid vertically
var gameState = 'menu'; // 'menu', 'playing', or 'paused'
var highScore = storage.highScore || 0;
var unlockedLevels = storage.unlockedLevels || 1;
var currentChallengeLevel = 0;
var challengeGoal = 0;
var isChallengeMode = false;
var challengeTimer = 0;
var challengeTimeLimit = 0;
var challengeTimerText = null;
var level2ComboTarget = 2;
var level2MaxCombo = 0;
var playButton = null;
var playButtonText = null;
var arcadeButton = null;
var arcadeButtonText = null;
var challengeButton = null;
var challengeButtonText = null;
var levelSelectTitle = null;
var levelButtons = [];
var levelButtonTexts = [];
var backButton = null;
var backButtonText = null;
var menuTitle = null;
var highScoreText = null;
var pauseOverlay = null;
var pauseTitle = null;
var quitButton = null;
var quitButtonText = null;
var resumeButton = null;
var resumeButtonText = null;
var pauseButton = null;
var pauseButtonText = null;
var grid = [];
var gameGrid = [];
var pieceShapes = [[[1]],
// Single block
[[1, 1]],
// Line 2
[[1, 1, 1]],
// Line 3
[[1], [1]],
// Vertical 2
[[1], [1], [1]],
// Vertical 3
[[1, 1], [1, 1]],
// Square 2x2
[[1, 1, 1], [1, 0, 0]],
// L shape
[[1, 1, 1], [0, 0, 1]],
// Reverse L
[[1, 1, 0], [0, 1, 1]],
// Z shape
[[0, 1, 0], [1, 1, 1]],
// T shape
[[1, 1, 1], [1, 1, 1], [1, 1, 1]],
// 3x3 square
[[1, 1], [1, 1], [1, 1]],
// 2x3 rectangle
// Bigger satisfying shapes for early game
[[1, 1, 1, 1]],
// Line 4
[[1], [1], [1], [1]],
// Vertical 4
[[1, 1, 1], [1, 1, 1]],
// 3x2 rectangle
[[1, 1], [1, 1], [1, 1], [1, 1]],
// 2x4 rectangle
[[1, 1, 1, 1], [1, 1, 1, 1]],
// 4x2 rectangle
[[1, 1, 1], [1, 1, 1], [1, 1, 1]],
// 3x3 square
[[1, 1, 1, 1], [1, 0, 0, 0]],
// Big L shape
[[1, 1, 1, 1], [0, 0, 0, 1]]
// Big reverse L
];
var pieceColors = ['blockPieceRed', 'blockPieceCyan', 'blockPieceBlue', 'blockPieceYellow', 'blockPiecePurple', 'blockPieceGreen', 'blockPieceOrange'];
var currentPieces = [];
var draggedPiece = null;
var previewBlocks = [];
var particles = [];
var comboCounter = 0;
var lastClearTime = 0;
var comboMultiplier = 1;
var lastPlaceTime = 0;
var consecutivePlacements = 0;
var recentlyUsedShapes = []; // Track recently used shapes to avoid repetition
var isAIMode = false; // Track if AI is helping with suitable blocks
var aiModeBlocksGenerated = 0; // Count blocks generated in AI mode
var gridHadBlocks = false; // Track if grid ever had blocks placed
var cleanText = null; // Clean message display
var lastPlacedPieceColor = null; // Track color of last placed piece
// Add grid background to define the block placement area
var gridBg = LK.getAsset('gridBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: GRID_OFFSET_X + GRID_SIZE * CELL_SIZE / 2,
y: GRID_OFFSET_Y + GRID_SIZE * CELL_SIZE / 2
});
game.addChild(gridBg);
// Initialize grid
for (var i = 0; i < GRID_SIZE; i++) {
grid[i] = [];
gameGrid[i] = [];
for (var j = 0; j < GRID_SIZE; j++) {
var cell = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
x: GRID_OFFSET_X + j * CELL_SIZE + CELL_SIZE / 2,
y: GRID_OFFSET_Y + i * CELL_SIZE + CELL_SIZE / 2
});
grid[i][j] = cell;
gameGrid[i][j] = null;
game.addChild(cell);
}
}
// Initialize UI
var scoreTxt = new Text2('0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var comboTxt = new Text2('', {
size: 60,
fill: 0xFFFF00
});
comboTxt.anchor.set(1, 0);
LK.gui.topRight.addChild(comboTxt);
// Center screen combo text
var centerComboTxt = new Text2('', {
size: 120,
fill: 0xFFFF00
});
centerComboTxt.anchor.set(0.5, 0.5);
centerComboTxt.x = 1024;
centerComboTxt.y = 1366;
centerComboTxt.alpha = 0;
game.addChild(centerComboTxt);
// Piece panel
var piecePanel = LK.getAsset('piecePanel', {
anchorX: 0.5,
anchorY: 1,
x: 1024,
y: 2732
});
game.addChild(piecePanel);
function showGameOverScreen() {
// Create game over overlay
var gameOverOverlay = LK.getAsset('gridBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 2,
scaleY: 2.5,
alpha: 0.9
});
game.addChild(gameOverOverlay);
// Game Over title
var gameOverTitle = new Text2('GAME OVER', {
size: 120,
fill: 0xFF0000
});
gameOverTitle.anchor.set(0.5, 0.5);
gameOverTitle.x = 1024;
gameOverTitle.y = 1200;
game.addChild(gameOverTitle);
// Score display
var finalScoreText = new Text2('Score: ' + LK.getScore(), {
size: 80,
fill: 0xFFFFFF
});
finalScoreText.anchor.set(0.5, 0.5);
finalScoreText.x = 1024;
finalScoreText.y = 1300;
game.addChild(finalScoreText);
// High Score display
var highScoreDisplayText = new Text2('High Score: ' + highScore, {
size: 60,
fill: 0xFFD700
});
highScoreDisplayText.anchor.set(0.5, 0.5);
highScoreDisplayText.x = 1024;
highScoreDisplayText.y = 1400;
game.addChild(highScoreDisplayText);
// New High Score display (if new high score)
var newHighScoreDisplay = null;
if (LK.getScore() >= highScore) {
newHighScoreDisplay = new Text2('NEW HIGH SCORE!', {
size: 60,
fill: 0xFF0000
});
newHighScoreDisplay.anchor.set(0.5, 0.5);
newHighScoreDisplay.x = 1024;
newHighScoreDisplay.y = 1500;
game.addChild(newHighScoreDisplay);
}
// Main Menu Button
var gameOverMenuButton = LK.getAsset('piecePanel', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: newHighScoreDisplay ? 1650 : 1550,
scaleX: 0.4,
scaleY: 0.25
});
game.addChild(gameOverMenuButton);
var gameOverMenuButtonText = new Text2('MAIN MENU', {
size: 60,
fill: 0xFFFFFF
});
gameOverMenuButtonText.anchor.set(0.5, 0.5);
gameOverMenuButtonText.x = 1024;
gameOverMenuButtonText.y = newHighScoreDisplay ? 1650 : 1550;
game.addChild(gameOverMenuButtonText);
gameOverMenuButton.down = function () {
// Clean up game over screen
gameOverOverlay.destroy();
gameOverTitle.destroy();
finalScoreText.destroy();
highScoreDisplayText.destroy();
if (newHighScoreDisplay) newHighScoreDisplay.destroy();
gameOverMenuButton.destroy();
gameOverMenuButtonText.destroy();
// Return to main menu
createMainMenu();
};
}
function createPauseMenu() {
// Semi-transparent overlay
pauseOverlay = LK.getAsset('gridBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 2,
scaleY: 2.5,
alpha: 0.8
});
game.addChild(pauseOverlay);
// Pause title
pauseTitle = new Text2('PAUSED', {
size: 100,
fill: 0xFFFFFF
});
pauseTitle.anchor.set(0.5, 0.5);
pauseTitle.x = 1024;
pauseTitle.y = 1200;
game.addChild(pauseTitle);
// Resume Button
resumeButton = LK.getAsset('piecePanel', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1400,
scaleX: 0.4,
scaleY: 0.25
});
game.addChild(resumeButton);
resumeButtonText = new Text2('RESUME', {
size: 60,
fill: 0xFFFFFF
});
resumeButtonText.anchor.set(0.5, 0.5);
resumeButtonText.x = 1024;
resumeButtonText.y = 1400;
game.addChild(resumeButtonText);
// Quit Button
quitButton = LK.getAsset('piecePanel', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1600,
scaleX: 0.4,
scaleY: 0.25
});
game.addChild(quitButton);
quitButtonText = new Text2('QUIT TO MENU', {
size: 50,
fill: 0xFFFFFF
});
quitButtonText.anchor.set(0.5, 0.5);
quitButtonText.x = 1024;
quitButtonText.y = 1600;
game.addChild(quitButtonText);
// Button handlers
resumeButton.down = function () {
resumeGame();
};
quitButton.down = function () {
quitToMenu();
};
}
function createMainMenu() {
// Title
menuTitle = new Text2('Block Exploder!', {
size: 120,
fill: 0xFFFFFF
});
menuTitle.anchor.set(0.5, 0.5);
menuTitle.x = 1024;
menuTitle.y = 800;
game.addChild(menuTitle);
// High Score
highScoreText = new Text2('High Score: ' + highScore, {
size: 60,
fill: 0xFFFF00
});
highScoreText.anchor.set(0.5, 0.5);
highScoreText.x = 1024;
highScoreText.y = 1000;
game.addChild(highScoreText);
// Arcade Button
arcadeButton = LK.getAsset('piecePanel', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1300,
scaleX: 0.3,
scaleY: 0.3
});
game.addChild(arcadeButton);
arcadeButtonText = new Text2('ARCADE', {
size: 70,
fill: 0xFFFFFF
});
arcadeButtonText.anchor.set(0.5, 0.5);
arcadeButtonText.x = 1024;
arcadeButtonText.y = 1300;
game.addChild(arcadeButtonText);
arcadeButton.down = function () {
startGame();
};
// Challenge Button
challengeButton = LK.getAsset('piecePanel', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1500,
scaleX: 0.3,
scaleY: 0.3
});
game.addChild(challengeButton);
challengeButtonText = new Text2('CHALLENGE', {
size: 60,
fill: 0xFFFFFF
});
challengeButtonText.anchor.set(0.5, 0.5);
challengeButtonText.x = 1024;
challengeButtonText.y = 1500;
game.addChild(challengeButtonText);
challengeButton.down = function () {
showLevelSelection();
};
// Autoplay Test Button
var autoplayButton = LK.getAsset('piecePanel', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1700,
scaleX: 0.3,
scaleY: 0.3
});
game.addChild(autoplayButton);
var autoplayButtonText = new Text2('AUTOPLAY TEST', {
size: 50,
fill: 0xFFFF00
});
autoplayButtonText.anchor.set(0.5, 0.5);
autoplayButtonText.x = 1024;
autoplayButtonText.y = 1700;
game.addChild(autoplayButtonText);
autoplayButton.down = function () {
autoPlay = true;
startGame();
};
}
function showLevelSelection() {
// Hide main menu elements
if (menuTitle) menuTitle.destroy();
if (highScoreText) highScoreText.destroy();
if (arcadeButton) arcadeButton.destroy();
if (arcadeButtonText) arcadeButtonText.destroy();
if (challengeButton) challengeButton.destroy();
if (challengeButtonText) challengeButtonText.destroy();
// Reset references to null
menuTitle = null;
highScoreText = null;
arcadeButton = null;
arcadeButtonText = null;
challengeButton = null;
challengeButtonText = null;
// Create level selection title
levelSelectTitle = new Text2('SELECT LEVEL', {
size: 100,
fill: 0xFFFFFF
});
levelSelectTitle.anchor.set(0.5, 0.5);
levelSelectTitle.x = 1024;
levelSelectTitle.y = 600;
game.addChild(levelSelectTitle);
// Create level buttons (2 rows of 5)
levelButtons = [];
levelButtonTexts = [];
for (var i = 1; i <= 10; i++) {
var row = Math.floor((i - 1) / 5);
var col = (i - 1) % 5;
var buttonX = 424 + col * 240;
var buttonY = 1000 + row * 200;
var isUnlocked = i <= unlockedLevels;
var levelButton = LK.getAsset('pauseButton', {
anchorX: 0.5,
anchorY: 0.5,
x: buttonX,
y: buttonY,
scaleX: 2,
scaleY: 2
});
levelButton.tint = isUnlocked ? 0xFFFFFF : 0x666666;
game.addChild(levelButton);
levelButtons.push(levelButton);
var levelText = new Text2(i.toString(), {
size: 60,
fill: isUnlocked ? 0xFFFFFF : 0x888888
});
levelText.anchor.set(0.5, 0.5);
levelText.x = buttonX;
levelText.y = buttonY;
game.addChild(levelText);
levelButtonTexts.push(levelText);
// Add click handler for each level
if (isUnlocked) {
levelButton.levelNumber = i;
levelButton.down = function () {
startChallengeLevel(this.levelNumber);
};
}
}
// Create back button
backButton = LK.getAsset('piecePanel', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1600,
scaleX: 0.3,
scaleY: 0.3
});
game.addChild(backButton);
backButtonText = new Text2('BACK', {
size: 60,
fill: 0xFFFFFF
});
backButtonText.anchor.set(0.5, 0.5);
backButtonText.x = 1024;
backButtonText.y = 1600;
game.addChild(backButtonText);
backButton.down = function () {
returnToMainMenu();
};
}
function startChallengeLevel(levelNumber) {
// Clean up level selection elements
if (levelSelectTitle) levelSelectTitle.destroy();
if (backButton) backButton.destroy();
if (backButtonText) backButtonText.destroy();
for (var i = 0; i < levelButtons.length; i++) {
if (levelButtons[i]) levelButtons[i].destroy();
}
for (var i = 0; i < levelButtonTexts.length; i++) {
if (levelButtonTexts[i]) levelButtonTexts[i].destroy();
}
levelButtons = [];
levelButtonTexts = [];
// Show level objectives screen
showLevelObjectives(levelNumber);
}
function returnToMainMenu() {
// Hide level selection elements
if (levelSelectTitle) levelSelectTitle.destroy();
if (backButton) backButton.destroy();
if (backButtonText) backButtonText.destroy();
for (var i = 0; i < levelButtons.length; i++) {
if (levelButtons[i]) levelButtons[i].destroy();
}
for (var i = 0; i < levelButtonTexts.length; i++) {
if (levelButtonTexts[i]) levelButtonTexts[i].destroy();
}
levelButtons = [];
levelButtonTexts = [];
// Reset references to null
levelSelectTitle = null;
backButton = null;
backButtonText = null;
// Show main menu again
createMainMenu();
}
function startGame() {
gameState = 'playing';
// Reset challenge mode if starting from arcade button
if (!isChallengeMode) {
currentChallengeLevel = 0;
challengeGoal = 0;
}
// Hide menu elements
if (menuTitle) menuTitle.destroy();
if (highScoreText) highScoreText.destroy();
if (arcadeButton) arcadeButton.destroy();
if (arcadeButtonText) arcadeButtonText.destroy();
if (challengeButton) challengeButton.destroy();
if (challengeButtonText) challengeButtonText.destroy();
// Clean up autoplay button if it exists
for (var i = game.children.length - 1; i >= 0; i--) {
var child = game.children[i];
if (child.y === 1700 && child.x === 1024) {
child.destroy();
}
}
// Reset references to null
menuTitle = null;
highScoreText = null;
arcadeButton = null;
arcadeButtonText = null;
challengeButton = null;
challengeButtonText = null;
// Show game UI
scoreTxt.alpha = 1;
comboTxt.alpha = 1;
// Add game title below score text in arcade screen
var gameTitle = new Text2('Block Exploder!', {
size: 80,
fill: 0xFFFFFF
});
gameTitle.anchor.set(0.5, 0);
gameTitle.x = 1024;
gameTitle.y = 150;
game.addChild(gameTitle);
// Update score display for challenge mode
if (isChallengeMode) {
if (currentChallengeLevel === 2) {
scoreTxt.setText('Max Combo: 0 / ' + level2ComboTarget);
// Create timer text for Level 2
challengeTimerText = new Text2('Time: 45s', {
size: 60,
fill: 0xFF0000
});
challengeTimerText.anchor.set(0, 0);
LK.gui.topLeft.addChild(challengeTimerText);
challengeTimerText.x = 120; // Offset from top left corner
} else {
scoreTxt.setText('0 / ' + challengeGoal);
}
} else {
scoreTxt.setText('0');
}
// Show grid background and piece panel
gridBg.alpha = 1;
for (var i = 0; i < GRID_SIZE; i++) {
for (var j = 0; j < GRID_SIZE; j++) {
grid[i][j].alpha = 1;
}
}
piecePanel.alpha = 1;
// Reset game state
LK.setScore(0);
scoreTxt.setText('0');
comboCounter = 0;
comboTxt.setText('');
// Clear grid
for (var i = 0; i < GRID_SIZE; i++) {
for (var j = 0; j < GRID_SIZE; j++) {
if (gameGrid[i][j]) {
gameGrid[i][j].destroy();
gameGrid[i][j] = null;
}
}
}
// Initialize AI mode for new games
isAIMode = true;
aiModeBlocksGenerated = 0;
gridHadBlocks = false;
createNewPieces();
// Create pause button
pauseButton = LK.getAsset('pauseButton', {
anchorX: 1,
anchorY: 0,
x: 1948,
y: 100
});
game.addChild(pauseButton);
pauseButtonText = new Text2('||', {
size: 40,
fill: 0xFFFFFF
});
pauseButtonText.anchor.set(0.5, 0.5);
pauseButtonText.x = 1948 - 40;
pauseButtonText.y = 140;
game.addChild(pauseButtonText);
pauseButton.down = function () {
if (gameState === 'playing') {
gameState = 'paused';
createPauseMenu();
}
};
}
function resumeGame() {
gameState = 'playing';
// Hide pause menu elements
if (pauseOverlay) pauseOverlay.destroy();
if (pauseTitle) pauseTitle.destroy();
if (resumeButton) resumeButton.destroy();
if (resumeButtonText) resumeButtonText.destroy();
if (quitButton) quitButton.destroy();
if (quitButtonText) quitButtonText.destroy();
}
function quitToMenu() {
gameState = 'menu';
// Hide pause menu elements
if (pauseOverlay) pauseOverlay.destroy();
if (pauseTitle) pauseTitle.destroy();
if (resumeButton) resumeButton.destroy();
if (resumeButtonText) resumeButtonText.destroy();
if (quitButton) quitButton.destroy();
if (quitButtonText) quitButtonText.destroy();
// Update high score if needed
var currentScore = LK.getScore();
if (currentScore > highScore) {
highScore = currentScore;
storage.highScore = highScore;
}
// Clear game elements
for (var i = 0; i < currentPieces.length; i++) {
currentPieces[i].destroy();
}
currentPieces = [];
// Clear grid
for (var i = 0; i < GRID_SIZE; i++) {
for (var j = 0; j < GRID_SIZE; j++) {
if (gameGrid[i][j]) {
gameGrid[i][j].destroy();
gameGrid[i][j] = null;
}
}
}
// Hide game UI
scoreTxt.alpha = 0;
comboTxt.alpha = 0;
centerComboTxt.alpha = 0;
// Hide grid background and piece panel
gridBg.alpha = 0;
for (var i = 0; i < GRID_SIZE; i++) {
for (var j = 0; j < GRID_SIZE; j++) {
grid[i][j].alpha = 0;
}
}
piecePanel.alpha = 0;
// Hide pause button
if (pauseButton) pauseButton.destroy();
if (pauseButtonText) pauseButtonText.destroy();
// Clean up timer text if it exists
if (challengeTimerText) {
challengeTimerText.destroy();
challengeTimerText = null;
}
// Reset challenge mode
isChallengeMode = false;
currentChallengeLevel = 0;
challengeGoal = 0;
challengeTimer = 0;
challengeTimeLimit = 0;
level2MaxCombo = 0;
// Reset AI mode and clean tracking
isAIMode = false;
aiModeBlocksGenerated = 0;
// Reset autoplay
autoPlay = false;
autoPlayTimer = 0;
gridHadBlocks = false;
if (cleanText) {
cleanText.destroy();
cleanText = null;
}
createMainMenu();
}
function createNewPieces() {
// Clear existing pieces
for (var i = 0; i < currentPieces.length; i++) {
currentPieces[i].destroy();
}
currentPieces = [];
// Create 3 new pieces with reduced repetition
for (var i = 0; i < 3; i++) {
var piece = new GamePiece();
var shapeIndex;
var attempts = 0;
var currentScore = LK.getScore();
if (isAIMode && aiModeBlocksGenerated < 20) {
// AI mode: provide suitable blocks for clearing - avoid very small single blocks, use manageable shapes
var aiSuitableShapes = [1, 1, 2, 2, 3, 4, 5, 6]; // 2-blocks, 3-blocks, verticals, and L shapes - no single blocks
shapeIndex = aiSuitableShapes[Math.floor(Math.random() * aiSuitableShapes.length)];
aiModeBlocksGenerated++;
} else if (currentScore < 500) {
// Very early game: heavily favor bigger, more satisfying shapes
var veryEasyShapes = [11, 12, 13, 14, 15, 16, 17, 18, 11, 12, 13, 14, 15, 16, 6, 10]; // Duplicate bigger shapes for higher probability
shapeIndex = veryEasyShapes[Math.floor(Math.random() * veryEasyShapes.length)];
} else if (currentScore < 1500) {
// Early game: mix of big shapes and medium shapes, still avoiding single blocks
var earlyShapes = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 11, 12, 13, 14, 15]; // Medium to large shapes
shapeIndex = earlyShapes[Math.floor(Math.random() * earlyShapes.length)];
} else if (currentScore < 3000) {
// Mid-early game: introduce some smaller pieces but keep it manageable
var midEasyShapes = [0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 7, 8]; // Small to medium shapes, some single blocks
shapeIndex = midEasyShapes[Math.floor(Math.random() * midEasyShapes.length)];
} else if (currentScore < 5000) {
// Mid game: balanced mix of all piece sizes
var midShapes = [0, 0, 1, 1, 2, 2, 3, 4, 5, 6, 7, 8, 9]; // Balanced mix, more single blocks
shapeIndex = midShapes[Math.floor(Math.random() * midShapes.length)];
} else if (currentScore < 8000) {
// Mid-late game: favor smaller pieces but still include medium ones
var midHardShapes = [0, 0, 0, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // More small pieces
shapeIndex = midHardShapes[Math.floor(Math.random() * midHardShapes.length)];
} else {
// Late game: Try to avoid recently used shapes, all pieces available
do {
shapeIndex = Math.floor(Math.random() * pieceShapes.length);
attempts++;
} while (recentlyUsedShapes.indexOf(shapeIndex) !== -1 && attempts < 10);
}
// Add to recently used shapes and keep only last 4
recentlyUsedShapes.push(shapeIndex);
if (recentlyUsedShapes.length > 4) {
recentlyUsedShapes.shift();
}
piece.createShape(pieceShapes[shapeIndex]);
piece.x = 200 + i * 650; // Increased spacing to prevent overlapping
piece.y = 2550;
currentPieces.push(piece);
game.addChild(piece);
}
}
function getGridPosition(x, y) {
var gridX = Math.floor((x - GRID_OFFSET_X) / CELL_SIZE);
var gridY = Math.floor((y - GRID_OFFSET_Y) / CELL_SIZE);
return {
x: gridX,
y: gridY
};
}
function canPlacePiece(piece, gridX, gridY) {
for (var i = 0; i < piece.shape.length; i++) {
for (var j = 0; j < piece.shape[i].length; j++) {
if (piece.shape[i][j] === 1) {
var checkX = gridX + j;
var checkY = gridY + i;
if (checkX < 0 || checkX >= GRID_SIZE || checkY < 0 || checkY >= GRID_SIZE) {
return false;
}
if (gameGrid[checkY][checkX] !== null) {
return false;
}
}
}
}
return true;
}
function placePiece(piece, gridX, gridY) {
// Store the color of the piece being placed
lastPlacedPieceColor = piece.blocks[0].colorAsset;
for (var i = 0; i < piece.shape.length; i++) {
for (var j = 0; j < piece.shape[i].length; j++) {
if (piece.shape[i][j] === 1) {
var placeX = gridX + j;
var placeY = gridY + i;
var block = new BlockPiece();
block.x = GRID_OFFSET_X + placeX * CELL_SIZE + CELL_SIZE / 2;
block.y = GRID_OFFSET_Y + placeY * CELL_SIZE + CELL_SIZE / 2;
block.setColor(piece.blocks[0].colorAsset);
block.gridX = placeX;
block.gridY = placeY;
gameGrid[placeY][placeX] = block;
game.addChild(block);
}
}
}
// Track consecutive placements for combo continuation
var currentTime = Date.now();
if (currentTime - lastPlaceTime < 3000) {
consecutivePlacements++;
} else {
consecutivePlacements = 1;
}
lastPlaceTime = currentTime;
// Track that grid has had blocks and disable AI mode after sufficient placements
if (!gridHadBlocks) {
gridHadBlocks = true;
}
if (isAIMode && aiModeBlocksGenerated >= 20) {
isAIMode = false; // Disable AI mode after initial guidance
}
LK.getSound('blockPlace').play();
}
function clearPreview() {
// Restore original block colors from stored values
for (var i = 0; i < originalBlockColors.length; i++) {
var colorData = originalBlockColors[i];
if (colorData.block && colorData.block.setColor) {
colorData.block.setColor(colorData.originalColor);
colorData.block.tint = 0xFFFFFF;
}
}
originalBlockColors = [];
for (var i = 0; i < previewBlocks.length; i++) {
previewBlocks[i].destroy();
}
previewBlocks = [];
}
function getCompletingLines(piece, gridX, gridY) {
var completingRows = [];
var completingCols = [];
var tempGrid = [];
// Create temporary grid copy
for (var i = 0; i < GRID_SIZE; i++) {
tempGrid[i] = [];
for (var j = 0; j < GRID_SIZE; j++) {
tempGrid[i][j] = gameGrid[i][j];
}
}
// Simulate placing the piece
for (var i = 0; i < piece.shape.length; i++) {
for (var j = 0; j < piece.shape[i].length; j++) {
if (piece.shape[i][j] === 1) {
var placeX = gridX + j;
var placeY = gridY + i;
if (placeX >= 0 && placeX < GRID_SIZE && placeY >= 0 && placeY < GRID_SIZE) {
tempGrid[placeY][placeX] = true; // Mark as occupied
}
}
}
}
// Check for complete rows
for (var i = 0; i < GRID_SIZE; i++) {
var rowComplete = true;
for (var j = 0; j < GRID_SIZE; j++) {
if (tempGrid[i][j] === null) {
rowComplete = false;
break;
}
}
if (rowComplete) {
completingRows.push(i);
}
}
// Check for complete columns
for (var j = 0; j < GRID_SIZE; j++) {
var colComplete = true;
for (var i = 0; i < GRID_SIZE; i++) {
if (tempGrid[i][j] === null) {
colComplete = false;
break;
}
}
if (colComplete) {
completingCols.push(j);
}
}
return {
rows: completingRows,
cols: completingCols
};
}
// Add array to store original colors for preview restoration
var originalBlockColors = [];
function showPreview(piece, gridX, gridY) {
clearPreview();
if (canPlacePiece(piece, gridX, gridY)) {
// Get piece color for tinting exploding lines
var pieceColor = piece.blocks[0].colorAsset;
var colorMap = {
'blockPieceRed': 0xff6b6b,
'blockPieceCyan': 0x4ecdc4,
'blockPieceBlue': 0x45b7d1,
'blockPieceYellow': 0xf7dc6f,
'blockPiecePurple': 0xbb8fce,
'blockPieceGreen': 0x58d68d,
'blockPieceOrange': 0xf39c12
};
var tintColor = colorMap[pieceColor] || 0xffffff;
// Check which lines would complete
var completingLines = getCompletingLines(piece, gridX, gridY);
// Show preview for the piece itself
for (var i = 0; i < piece.shape.length; i++) {
for (var j = 0; j < piece.shape[i].length; j++) {
if (piece.shape[i][j] === 1) {
var previewX = gridX + j;
var previewY = gridY + i;
var preview = LK.getAsset('previewBlock', {
anchorX: 0.5,
anchorY: 0.5,
x: GRID_OFFSET_X + previewX * CELL_SIZE + CELL_SIZE / 2,
y: GRID_OFFSET_Y + previewY * CELL_SIZE + CELL_SIZE / 2,
alpha: 0.5,
tint: tintColor
});
previewBlocks.push(preview);
game.addChild(preview);
}
}
}
// Show preview for completing rows and columns - replace blocks with piece color
if (completingLines && (completingLines.rows.length > 0 || completingLines.cols.length > 0)) {
// Store original colors before changing them
originalBlockColors = [];
// Preview completing rows - replace existing blocks with piece color
for (var r = 0; r < completingLines.rows.length; r++) {
var row = completingLines.rows[r];
for (var j = 0; j < GRID_SIZE; j++) {
if (gameGrid[row][j] !== null) {
// Store original color before changing
originalBlockColors.push({
block: gameGrid[row][j],
originalColor: gameGrid[row][j].colorAsset
});
gameGrid[row][j].setColor(pieceColor);
}
}
}
// Preview completing columns - replace existing blocks with piece color
for (var c = 0; c < completingLines.cols.length; c++) {
var col = completingLines.cols[c];
for (var i = 0; i < GRID_SIZE; i++) {
if (gameGrid[i][col] !== null) {
// Check if we already stored this block's color to avoid duplicates
var alreadyStored = false;
for (var k = 0; k < originalBlockColors.length; k++) {
if (originalBlockColors[k].block === gameGrid[i][col]) {
alreadyStored = true;
break;
}
}
if (!alreadyStored) {
originalBlockColors.push({
block: gameGrid[i][col],
originalColor: gameGrid[i][col].colorAsset
});
}
gameGrid[i][col].setColor(pieceColor);
}
}
}
}
}
}
function checkLines() {
var clearedBlocks = [];
var completedRows = 0;
var completedCols = 0;
// Check for complete rows
for (var i = 0; i < GRID_SIZE; i++) {
var rowComplete = true;
for (var j = 0; j < GRID_SIZE; j++) {
if (gameGrid[i][j] === null) {
rowComplete = false;
break;
}
}
if (rowComplete) {
completedRows++;
for (var j = 0; j < GRID_SIZE; j++) {
clearedBlocks.push({
x: j,
y: i
});
}
}
}
// Check for complete columns
for (var j = 0; j < GRID_SIZE; j++) {
var colComplete = true;
for (var i = 0; i < GRID_SIZE; i++) {
if (gameGrid[i][j] === null) {
colComplete = false;
break;
}
}
if (colComplete) {
completedCols++;
for (var i = 0; i < GRID_SIZE; i++) {
clearedBlocks.push({
x: j,
y: i
});
}
}
}
if (clearedBlocks.length > 0) {
clearBlocks(clearedBlocks, completedRows + completedCols);
return true;
}
return false;
}
function clearBlocks(blockPositions, lineCount) {
var currentTime = Date.now();
// Check if this clear is within 3 blocks of the last clear
var timeSinceLastClear = currentTime - lastClearTime;
if (timeSinceLastClear < 5000) {
// 5 seconds window for combo - continue from where we left off and add line count
comboCounter += lineCount || 1;
} else {
comboCounter = lineCount || 1;
}
lastClearTime = currentTime;
// Calculate multiplier based on combo
comboMultiplier = comboCounter;
var uniqueBlocks = [];
var seen = {};
for (var i = 0; i < blockPositions.length; i++) {
var pos = blockPositions[i];
var key = pos.x + "," + pos.y;
if (!seen[key] && gameGrid[pos.y][pos.x] !== null) {
seen[key] = true;
uniqueBlocks.push(pos);
}
}
// Change block appearance before exploding - use last placed piece color
for (var i = 0; i < uniqueBlocks.length; i++) {
var pos = uniqueBlocks[i];
var block = gameGrid[pos.y][pos.x];
if (block && lastPlacedPieceColor) {
// Replace the block with last placed piece color asset before exploding
block.setColor(lastPlacedPieceColor);
}
}
// Delay explosion effects slightly to show the new appearance
LK.setTimeout(function () {
for (var i = 0; i < uniqueBlocks.length; i++) {
var pos = uniqueBlocks[i];
var block = gameGrid[pos.y][pos.x];
if (block) {
createExplosion(block.x, block.y, lastPlacedPieceColor);
block.destroy();
gameGrid[pos.y][pos.x] = null;
}
}
}, 200);
// Screen effects
var flashColor = comboCounter > 3 ? 0xff0000 : 0xffffff;
var flashDuration = Math.min(comboCounter * 100 + 200, 800);
LK.effects.flashScreen(flashColor, flashDuration);
// Update score with combo multiplier
var basePoints = uniqueBlocks.length * 10;
var finalPoints = basePoints * comboMultiplier;
LK.setScore(LK.getScore() + Math.floor(finalPoints));
// Track max combo for Level 2
if (isChallengeMode && currentChallengeLevel === 2) {
if (comboCounter > level2MaxCombo) {
level2MaxCombo = comboCounter;
}
scoreTxt.setText('Max Combo: ' + level2MaxCombo + ' / ' + level2ComboTarget);
} else if (isChallengeMode) {
scoreTxt.setText(LK.getScore() + ' / ' + challengeGoal);
} else {
scoreTxt.setText(LK.getScore());
}
// Update combo display
if (comboCounter > 1) {
if (comboCounter >= 4) {
comboTxt.setText("PERFECT COMBO x" + comboCounter);
} else {
comboTxt.setText("COMBO x" + comboCounter);
}
// Show center screen combo animation
showCenterCombo(comboCounter);
// Animate combo text
tween(comboTxt, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
easing: tween.easeOut
});
tween(comboTxt, {
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.easeInOut
});
// Add color flash to combo text
tween(comboTxt, {
tint: 0xFFFF00
}, {
duration: 100,
onFinish: function onFinish() {
tween(comboTxt, {
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
} else {
comboTxt.setText("");
// Show center screen combo for first clear too
showCenterCombo(comboCounter);
// Reset combo text color
comboTxt.tint = 0xFFFFFF;
}
// Play sound
if (comboCounter >= 4) {
LK.getSound('perfectCombo').play();
LK.getSound('lineClear').play();
} else if (comboCounter >= 2) {
LK.getSound('comboClear').play();
LK.getSound('lineClear').play();
} else {
LK.getSound('lineClear').play();
}
}
function showCenterCombo(combo) {
// Stop any existing combo animation
tween.stop(centerComboTxt);
// Set combo text
if (combo >= 4) {
centerComboTxt.setText("PERFECT COMBO!");
centerComboTxt.fill = 0xFF00FF; // Magenta for perfect combo
} else {
centerComboTxt.setText("COMBO x" + combo + "!");
centerComboTxt.fill = 0xFFFF00; // Yellow for regular combo
}
centerComboTxt.scaleX = 0.5;
centerComboTxt.scaleY = 0.5;
centerComboTxt.alpha = 0;
// Animate in
tween(centerComboTxt, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 1
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
// Hold for a moment then animate out
tween(centerComboTxt, {
scaleX: 0.8,
scaleY: 0.8,
alpha: 0
}, {
duration: 800,
easing: tween.easeIn
});
}
});
}
function createExplosion(x, y, color) {
var particleCount = Math.min(comboCounter * 2 + 5, 20);
for (var i = 0; i < particleCount; i++) {
var particle = new Particle();
particle.x = x;
particle.y = y;
particle.velocityX = (Math.random() - 0.5) * 10;
particle.velocityY = (Math.random() - 0.5) * 10 - 5;
particle.lifetime = 500 + Math.random() * 500;
particle.maxLifetime = particle.lifetime;
var particleGraphics = particle.children[0];
// Convert asset name to approximate color for particles
var colorMap = {
'blockPieceRed': 0xff6b6b,
'blockPieceCyan': 0x4ecdc4,
'blockPieceBlue': 0x45b7d1,
'blockPieceYellow': 0xf7dc6f,
'blockPiecePurple': 0xbb8fce,
'blockPieceGreen': 0x58d68d,
'blockPieceOrange': 0xf39c12
};
particleGraphics.tint = colorMap[color] || 0xffffff;
particles.push(particle);
game.addChild(particle);
}
}
function checkGridClean() {
if (!gridHadBlocks) return false; // Don't trigger clean if no blocks were ever placed
for (var i = 0; i < GRID_SIZE; i++) {
for (var j = 0; j < GRID_SIZE; j++) {
if (gameGrid[i][j] !== null) {
return false; // Grid is not clean
}
}
}
return true; // Grid is completely clean
}
function showCleanMessage() {
// Create and show "CLEAN!" message
cleanText = new Text2('CLEAN!', {
size: 150,
fill: 0x00FF00
});
cleanText.anchor.set(0.5, 0.5);
cleanText.x = 1024;
cleanText.y = 1366;
cleanText.alpha = 0;
cleanText.scaleX = 0.5;
cleanText.scaleY = 0.5;
game.addChild(cleanText);
// Animate the clean message
tween(cleanText, {
alpha: 1,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
// Hold message briefly then fade out
tween(cleanText, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 1000,
easing: tween.easeIn,
onFinish: function onFinish() {
if (cleanText) {
cleanText.destroy();
cleanText = null;
}
}
});
}
});
// Add bonus points
LK.setScore(LK.getScore() + 500);
// Update score display
if (isChallengeMode && currentChallengeLevel === 2) {
scoreTxt.setText('Max Combo: ' + level2MaxCombo + ' / ' + level2ComboTarget);
} else if (isChallengeMode) {
scoreTxt.setText(LK.getScore() + ' / ' + challengeGoal);
} else {
scoreTxt.setText(LK.getScore());
}
// Play clean sound
LK.getSound('clean').play();
// Screen flash effect
LK.effects.flashScreen(0x00FF00, 1000);
}
function checkGameOver() {
for (var i = 0; i < currentPieces.length; i++) {
var piece = currentPieces[i];
var canPlace = false;
for (var row = 0; row < GRID_SIZE; row++) {
for (var col = 0; col < GRID_SIZE; col++) {
if (canPlacePiece(piece, col, row)) {
canPlace = true;
break;
}
}
if (canPlace) break;
}
if (canPlace) return false;
}
return true;
}
game.move = function (x, y, obj) {
if (gameState === 'playing' && draggedPiece) {
draggedPiece.x = x;
draggedPiece.y = y;
var gridPos = getGridPosition(x, y);
showPreview(draggedPiece, gridPos.x, gridPos.y);
}
};
game.down = function (x, y, obj) {
// No longer trigger pause menu from empty space taps
};
game.up = function (x, y, obj) {
if (gameState === 'playing' && draggedPiece) {
var gridPos = getGridPosition(x, y);
if (canPlacePiece(draggedPiece, gridPos.x, gridPos.y)) {
placePiece(draggedPiece, gridPos.x, gridPos.y);
// Remove piece from current pieces
var index = currentPieces.indexOf(draggedPiece);
if (index > -1) {
currentPieces.splice(index, 1);
}
draggedPiece.destroy();
// Check for clears
var blocksWereCleared = checkLines();
// Check if grid is completely clean after clearing blocks
if (blocksWereCleared && checkGridClean()) {
showCleanMessage();
}
// Create new pieces if all are used
if (currentPieces.length === 0) {
createNewPieces();
}
} else {
// Return piece to original position
tween(draggedPiece, {
x: draggedPiece.originalX,
y: draggedPiece.originalY,
alpha: 1
}, {
duration: 300
});
draggedPiece.isDragging = false;
}
clearPreview();
draggedPiece = null;
} else if (gameState === 'playing') {
// Clear preview if user touches somewhere else while not dragging
clearPreview();
}
};
// Auto-play mode for testing
var autoPlay = false;
var autoPlayTimer = 0;
game.update = function () {
// Clean up particles
for (var i = particles.length - 1; i >= 0; i--) {
var particle = particles[i];
if (particle.lifetime <= 0) {
particles.splice(i, 1);
}
}
// Auto-play logic for testing
if (autoPlay && gameState === 'playing' && currentPieces.length > 0) {
autoPlayTimer += 16.67; // 60fps frame time
if (autoPlayTimer >= 1000) {
// Every 1 second
autoPlayTimer = 0;
// Try to place the first available piece
var piece = currentPieces[0];
var placed = false;
// Try to find a valid placement
for (var row = 0; row < GRID_SIZE && !placed; row++) {
for (var col = 0; col < GRID_SIZE && !placed; col++) {
if (canPlacePiece(piece, col, row)) {
placePiece(piece, col, row);
// Remove piece from current pieces
var index = currentPieces.indexOf(piece);
if (index > -1) {
currentPieces.splice(index, 1);
}
piece.destroy();
// Check for clears
var blocksWereCleared = checkLines();
// Check if grid is completely clean after clearing blocks
if (blocksWereCleared && checkGridClean()) {
showCleanMessage();
}
// Create new pieces if all are used
if (currentPieces.length === 0) {
createNewPieces();
}
placed = true;
}
}
}
}
}
// Handle Level 2 timer countdown
if (gameState === 'playing' && isChallengeMode && currentChallengeLevel === 2 && challengeTimeLimit > 0) {
challengeTimer -= 16.67; // Subtract frame time (60fps)
var remainingSeconds = Math.ceil(challengeTimer / 1000);
if (challengeTimerText) {
challengeTimerText.setText('Time: ' + Math.max(0, remainingSeconds) + 's');
// Change color as time runs low
if (remainingSeconds <= 10) {
challengeTimerText.fill = 0xFF0000; // Red
} else if (remainingSeconds <= 20) {
challengeTimerText.fill = 0xFFFF00; // Yellow
}
}
// Check if time ran out
if (challengeTimer <= 0 && level2MaxCombo < level2ComboTarget) {
// Time's up and didn't achieve 2x combo - game over
if (challengeTimerText) {
challengeTimerText.destroy();
challengeTimerText = null;
}
// Update high score if needed
var currentScore = LK.getScore();
if (currentScore > highScore) {
highScore = currentScore;
storage.highScore = highScore;
}
// Clear game elements
for (var i = 0; i < currentPieces.length; i++) {
currentPieces[i].destroy();
}
currentPieces = [];
// Hide game UI
scoreTxt.alpha = 0;
comboTxt.alpha = 0;
centerComboTxt.alpha = 0;
// Hide grid background and piece panel
gridBg.alpha = 0;
for (var i = 0; i < GRID_SIZE; i++) {
for (var j = 0; j < GRID_SIZE; j++) {
grid[i][j].alpha = 0;
}
}
piecePanel.alpha = 0;
// Reset challenge mode
isChallengeMode = false;
currentChallengeLevel = 0;
challengeGoal = 0;
challengeTimer = 0;
challengeTimeLimit = 0;
level2MaxCombo = 0;
gameState = 'menu';
createMainMenu();
return;
}
}
// Check challenge completion
var challengeCompleted = false;
if (gameState === 'playing' && isChallengeMode) {
if (currentChallengeLevel === 1 && LK.getScore() >= challengeGoal) {
challengeCompleted = true;
} else if (currentChallengeLevel === 2 && level2MaxCombo >= level2ComboTarget) {
challengeCompleted = true;
} else if (currentChallengeLevel > 2 && LK.getScore() >= challengeGoal) {
challengeCompleted = true;
}
}
if (challengeCompleted) {
// Challenge completed!
if (challengeTimerText) {
challengeTimerText.destroy();
challengeTimerText = null;
}
// Special handling for level 3 completion to unlock level 4
var wasLevel4Unlocked = false;
if (currentChallengeLevel === 3 && LK.getScore() >= 3000 && unlockedLevels < 4) {
unlockedLevels = 4;
storage.unlockedLevels = unlockedLevels;
wasLevel4Unlocked = true;
} else if (currentChallengeLevel >= unlockedLevels && currentChallengeLevel < 10) {
unlockedLevels = currentChallengeLevel + 1;
storage.unlockedLevels = unlockedLevels;
}
// Update high score if needed
var currentScore = LK.getScore();
if (currentScore > highScore) {
highScore = currentScore;
storage.highScore = highScore;
}
// Clear game elements
for (var i = 0; i < currentPieces.length; i++) {
currentPieces[i].destroy();
}
currentPieces = [];
// Hide game UI
scoreTxt.alpha = 0;
comboTxt.alpha = 0;
centerComboTxt.alpha = 0;
// Hide grid background and piece panel
gridBg.alpha = 0;
for (var i = 0; i < GRID_SIZE; i++) {
for (var j = 0; j < GRID_SIZE; j++) {
grid[i][j].alpha = 0;
}
}
piecePanel.alpha = 0;
// Reset challenge mode
isChallengeMode = false;
currentChallengeLevel = 0;
challengeGoal = 0;
challengeTimer = 0;
challengeTimeLimit = 0;
level2MaxCombo = 0;
gameState = 'menu';
showCustomWinScreen(wasLevel4Unlocked);
return;
}
// Check game over
if (gameState === 'playing' && currentPieces.length > 0 && checkGameOver()) {
// Update high score if needed
var currentScore = LK.getScore();
if (currentScore > highScore) {
highScore = currentScore;
storage.highScore = highScore;
}
// Clear game elements
for (var i = 0; i < currentPieces.length; i++) {
currentPieces[i].destroy();
}
currentPieces = [];
// Hide game UI
scoreTxt.alpha = 0;
comboTxt.alpha = 0;
centerComboTxt.alpha = 0;
// Hide grid background and piece panel
gridBg.alpha = 0;
for (var i = 0; i < GRID_SIZE; i++) {
for (var j = 0; j < GRID_SIZE; j++) {
grid[i][j].alpha = 0;
}
}
piecePanel.alpha = 0;
// Hide pause button
if (pauseButton) pauseButton.destroy();
if (pauseButtonText) pauseButtonText.destroy();
gameState = 'menu';
showGameOverScreen();
}
};
// Initialize game
scoreTxt.alpha = 0;
comboTxt.alpha = 0;
// Hide grid background and grid initially
gridBg.alpha = 0;
for (var i = 0; i < GRID_SIZE; i++) {
for (var j = 0; j < GRID_SIZE; j++) {
grid[i][j].alpha = 0;
}
}
piecePanel.alpha = 0;
createMainMenu();
LK.playMusic('bgmusic');
function showCustomWinScreen(unlockedLevel4) {
// Store the completed level for button logic
var completedLevel = currentChallengeLevel;
var nextLevel = completedLevel + 1;
var hasNextLevel = nextLevel <= 10 && unlockedLevels >= nextLevel;
// Create win overlay
var winOverlay = LK.getAsset('gridBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 2,
scaleY: 2.5,
alpha: 0.9
});
game.addChild(winOverlay);
// Win title
var winTitle = new Text2('YOU WIN!', {
size: 120,
fill: 0x00FF00
});
winTitle.anchor.set(0.5, 0.5);
winTitle.x = 1024;
winTitle.y = unlockedLevel4 ? 1100 : 1200;
game.addChild(winTitle);
// Level 4 unlock congratulations message
var congratsText = null;
if (unlockedLevel4) {
congratsText = new Text2('CONGRATULATIONS!\nLEVEL 4 UNLOCKED!', {
size: 80,
fill: 0xFFD700
});
congratsText.anchor.set(0.5, 0.5);
congratsText.x = 1024;
congratsText.y = 1250;
game.addChild(congratsText);
}
// Next Level Button (show if there's a next level available)
var nextButton = null;
var nextButtonText = null;
var buttonYOffset = unlockedLevel4 ? 200 : 0;
if (hasNextLevel) {
nextButton = LK.getAsset('piecePanel', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1400 + buttonYOffset,
scaleX: 0.4,
scaleY: 0.25
});
game.addChild(nextButton);
nextButtonText = new Text2('LEVEL ' + nextLevel, {
size: 60,
fill: 0xFFFFFF
});
nextButtonText.anchor.set(0.5, 0.5);
nextButtonText.x = 1024;
nextButtonText.y = 1400 + buttonYOffset;
game.addChild(nextButtonText);
nextButton.down = function () {
// Clean up win screen
winOverlay.destroy();
winTitle.destroy();
if (congratsText) congratsText.destroy();
nextButton.destroy();
nextButtonText.destroy();
mainMenuButton.destroy();
mainMenuButtonText.destroy();
// Start next level
startChallengeLevel(nextLevel);
};
}
// Main Menu Button
var mainMenuButton = LK.getAsset('piecePanel', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: (nextButton ? 1600 : 1400) + buttonYOffset,
scaleX: 0.4,
scaleY: 0.25
});
game.addChild(mainMenuButton);
var mainMenuButtonText = new Text2('MAIN MENU', {
size: 60,
fill: 0xFFFFFF
});
mainMenuButtonText.anchor.set(0.5, 0.5);
mainMenuButtonText.x = 1024;
mainMenuButtonText.y = (nextButton ? 1600 : 1400) + buttonYOffset;
game.addChild(mainMenuButtonText);
mainMenuButton.down = function () {
// Clean up win screen
winOverlay.destroy();
winTitle.destroy();
if (congratsText) congratsText.destroy();
if (nextButton) nextButton.destroy();
if (nextButtonText) nextButtonText.destroy();
mainMenuButton.destroy();
mainMenuButtonText.destroy();
// Return to main menu
createMainMenu();
};
}
function showLevelObjectives(levelNumber) {
// Create objectives overlay
var objectivesOverlay = LK.getAsset('gridBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 2,
scaleY: 2.5,
alpha: 0.9
});
game.addChild(objectivesOverlay);
// Level title
var levelTitle = new Text2('LEVEL ' + levelNumber, {
size: 120,
fill: 0xFFFFFF
});
levelTitle.anchor.set(0.5, 0.5);
levelTitle.x = 1024;
levelTitle.y = 1000;
game.addChild(levelTitle);
// Objective text based on level
var objectiveText = '';
if (levelNumber === 1) {
objectiveText = 'OBJECTIVE:\nScore 1000 points\nto complete this level';
} else if (levelNumber === 2) {
objectiveText = 'OBJECTIVE:\nAchieve a 2x combo\nwithin 45 seconds';
} else if (levelNumber === 3) {
objectiveText = 'OBJECTIVE:\nScore 3000 points\nto unlock Level 4';
} else if (levelNumber === 4) {
objectiveText = 'OBJECTIVE:\nScore 4000 points\nto complete this level';
} else {
objectiveText = 'OBJECTIVE:\nScore ' + levelNumber * 1000 + ' points\nto complete this level';
}
var objectiveDisplay = new Text2(objectiveText, {
size: 80,
fill: 0xFFFF00
});
objectiveDisplay.anchor.set(0.5, 0.5);
objectiveDisplay.x = 1024;
objectiveDisplay.y = 1300;
game.addChild(objectiveDisplay);
// Start button
var startButton = LK.getAsset('piecePanel', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1600,
scaleX: 0.4,
scaleY: 0.25
});
game.addChild(startButton);
var startButtonText = new Text2('START', {
size: 60,
fill: 0xFFFFFF
});
startButtonText.anchor.set(0.5, 0.5);
startButtonText.x = 1024;
startButtonText.y = 1600;
game.addChild(startButtonText);
startButton.down = function () {
// Clean up objectives screen
objectivesOverlay.destroy();
levelTitle.destroy();
objectiveDisplay.destroy();
startButton.destroy();
startButtonText.destroy();
// Set challenge mode variables
isChallengeMode = true;
currentChallengeLevel = levelNumber;
if (levelNumber === 1) {
challengeGoal = 1000; // Level 1 = 1000 points
challengeTimeLimit = 0; // No time limit for level 1
} else if (levelNumber === 2) {
challengeGoal = 0; // Level 2 is not about points
challengeTimeLimit = 45000; // 45 seconds in milliseconds
level2MaxCombo = 0; // Reset max combo for level 2
} else {
challengeGoal = levelNumber * 1000; // Other levels
challengeTimeLimit = 0;
}
challengeTimer = challengeTimeLimit; // Set initial timer
startGame();
};
}