User prompt
🔊 Sound Effects – Command List 🎵 1. Rotation Sound Command: > Play a subtle "rotation" sound effect every time the player rotates the active shape (left or right). onShapeRotate(): playSound("rotate.wav") --- 🎵 2. Block-to-Block Contact Sound Command: > When a falling shape touches another shape, play a soft contact sound to indicate collision but not final landing yet. onShapeTouchAnotherShape(): playSound("contact.wav") --- 🎵 3. Landing on Ground Sound Command: > When a shape touches the bottom surface or settles in place, play a distinct “landing” sound (e.g., a deep thud). onShapeLand(): playSound("land.wav") --- 🎵 4. Step-Down Movement Sound Command: > Play a light “drop” or “slide” sound each time the active shape moves one row downward during free fall.
User prompt
🔊 1. Sound on Shape Spawn Command: > Play a soft “spawn” sound effect each time a new shape enters from the top of the screen. onShapeSpawn(): playSound("spawn.wav") --- 🔊 2. Sound on Shape Landing Command: > Play a different, solid “landing” sound effect when the shape touches the surface or another shape and locks into place. onShapeLand(): playSound("land.wav") --- 🎧 Sound Design Notes: "spawn.wav" – gentle tone (e.g., pop or slide-in sound) "land.wav" – firmer sound (e.g., soft thud, click, or drop impact)
User prompt
Activate rotation buttons
User prompt
Command: > Allow each falling shape to be rotated left (⟲) or right (⟳) in 90-degree steps as many times as desired. Rotation is only permitted until the shape touches the surface or another shape. Once landed, rotation is disabled permanently for that shape. --- 🧩 Functional Notes Rotation must always occur around the shape's pivot center. Each rotation must snap cleanly to the grid. Rotation input should be responsive and repeatable with each button tap. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
🔁 Restore Rotation & Define Shape Integrity Command: > Re-enable rotation for all falling shapes. Each shape is composed of multiple visual blocks, but it is internally treated as one single object. Therefore, the shape’s form cannot break or deform at any time. --- 🔒 Shape Unity Rule Command: > Treat every falling shape as a single, indivisible unit. It may contain multiple square tiles visually, but functionally it is one whole piece. Rotation is allowed, but splitting or collapsing is never permitted. --- ✅ Line Clear Rule Command: > A row is only cleared when exactly 24 consecutive tiles (left to right) are filled by multiple whole shapes positioned side by side. Once cleared, remove that row and add score accordingly.
User prompt
Give back rotation capability
User prompt
Each shape must retain its original structure and form from the moment it spawns until it lands. The player may rotate the shape (90° steps), but cannot reshape, split, deform, or collapse any part of it. After landing, the shape becomes fully static and immutable.
User prompt
❌ Game Over Condition – Stack Overflow Command: > Trigger Game Over when the falling shape can no longer spawn due to blocked space at the top of the grid. > The condition is met if: Any new shape overlaps existing blocks immediately upon spawn, or The topmost rows become too full or uneven, preventing clean placement of new shapes.
User prompt
Fix them
User prompt
🧱 Shape Stability Until Full Row Completion Command: > Each falling shape must remain structurally stable and unchanged unless it helps complete a full row of 24 blocks horizontally. Until that row is completed and cleared: The shape must not break apart or deform. It can only be rotated in fixed 90° increments, in-place. No structural transformation (splitting, bending, reshaping) is allowed.
User prompt
🎯 Rotation Clamping Inside Grid Lines Command: > When a shape rotates, it must remain fully within the visible grid lines. The rotation should be constrained so that no part of the shape overlaps or exits the grid area. --- 🧩 Rotation Snap Logic (for grid-based games) Command: > Apply rotation only if the resulting position of the shape fits entirely within the grid bounds. If not, adjust the X position (left/right) slightly to make it fit inside before applying rotation.
User prompt
❗️Ongoing Bugs and Fix Commands 🛑 1. Bug: Shapes break apart or deform on touch Fix Command: > Prevent individual tiles from being interactable. The entire shape should behave as a single, unified object until it lands. Disable splitting or deformation on touch. --- 🔄 2. Bug: Shapes rotate incorrectly or unpredictably Fix Command: > Ensure rotation occurs around the central pivot point of the entire shape. Apply a strict 90° rotation, snapping to the grid. Align shapes with visible horizontal and vertical guide lines for visual accuracy. rotate(shape, 90 degrees) snapToNearestGrid(shape) --- ⛔️ 3. Bug: Shapes exit the screen from left/right Fix Command: > Clamp all falling shapes within the left and right game boundaries. If shape exceeds screen bounds on rotation or drag, shift it inward automatically. if (shape.position.x < 0) { shape.position.x = 0; } if (shape.position.x + shape.width > grid.width) { shape.position.x = grid.width - shape.width; } --- ✏️ 4. Visual Aid (Optional Suggestion) Command: > Add faint grid lines to the entire game board. These should help both rotation snapping and block alignment without distracting the player.
User prompt
✅ Replace Old Line Clear Logic with Grid-Based Full Row Rule ❌ Remove Old Rule > Remove the logic where individual Tetris shapes (like 4-square lines) disappear and award points just by touching the surface. --- ✅ New Command – Full Row Clear Command: > Only clear a row when all horizontal tiles in that row are filled (e.g., 24 consecutive blocks from left to right with no empty space). When such a full row is detected: Remove that row visually and logically Shift down all blocks above it Add score based on how many rows are cleared simultaneously
User prompt
🧱 1. Group Blocks into a Single Object Command: > Treat each Tetris shape as a single rigid body object (not separate squares). All sub-blocks must move and rotate as one unit until collision. 🔄 2. Proper Group Rotation Logic Command: > When rotating, apply a 90° rotation around the center of the shape, not around individual blocks. Use the group’s pivot point for transformation. 🧲 3. Lock Rotation After Landing Command: > After the group collides with the surface or another block: Freeze position Freeze rotation Disassemble into static tiles if needed ⚠️ 4. Prevent Independent Block Physics Command: > Disable individual physics for child blocks of the group. Only the parent shape container should be affected by gravity, collision, and rotation.
User prompt
🛠️ Rotation After Contact Bug – Fix Command Command: > Once a falling shape collides with the ground or any other block, it must become locked in its current position and shape. Disable all further rotation, movement, or transformation for that shape. --- ✅ Stability Lock After Landing Command: > Implement a "landed" state: When a shape touches a surface or another shape, mark it as isLanded = true. If isLanded == true, ignore all rotation/movement inputs for that shape.
User prompt
🎮 Rotation & Placement Rules (Finalized Logic) 1. Rotation Condition > A falling shape can be rotated (90° only) only while it has not yet touched the surface or another block. Once it lands, rotation is disabled. 2. No Auto-Adjustment After Landing > After touching the ground or stacking, the shape must retain its last rotated orientation permanently. It must not auto-adjust or shift after placement. 3. Screen Boundary Restriction > A shape must never leave the game grid boundaries, even after rotation. Apply wall kick or horizontal shift to keep it inside the playable area. 4. No Post-Landing Transformation > Once a shape is settled (landed), it becomes static and locked. The player cannot rotate or move it anymore.
User prompt
🔄 Rotation Logic & Boundary Handling 1. Fixed 90° Rotation > Each time the player taps a rotation button, the falling shape should rotate exactly 90 degrees (clockwise or counter-clockwise). 2. Boundary Collision Check > When a shape is rotated and touches the left or right edge of the game area, it must not go outside the visible grid. 3. Wall Kick / Clamp to Edge > If a rotated shape would overlap the screen boundary, automatically shift it inward (left or right) so it remains fully visible and playable.
User prompt
🎯 Spawn Control & Rotation UI – Finalized Commands 1. One-at-a-Time Spawn Rule > Only spawn a new falling shape after the current shape touches the surface or another block. 2. Rotation Buttons UI > Place two buttons below the gameplay area, outside the active grid: Left Rotation (⟲) button Right Rotation (⟳) button 3. Shape-Specific Rotation > When pressing either button, only the currently falling shape rotates. Rotation does not affect settled blocks. 4. Slight Speed Increase > Increase the falling speed slightly above the current speed to make the game more dynamic. (Optional: define baseFallSpeed = currentSpeed + 10%)
User prompt
🐞 Bug Fix – Floating Blocks Command: > Prevent blocks from remaining in the air if there is no support beneath. A block must fall until it touches another block or the ground. Disable "floating block" bug by applying continuous gravity checks. --- 🎮 Add Control Buttons (Left/Right Rotation) Command: > Add two on-screen buttons below the game area: "Rotate Left" ⟲ "Rotate Right" ⟳ These buttons rotate the currently falling Tetris block clockwise or counter-clockwise. --- ⏱ Limit Spawn Rate Command: > Decrease the number of shapes spawning at the same time from the top. Limit to 1–2 falling shapes at a time in early levels. As the player score increases, gradually increase spawn speed and difficulty.
User prompt
Please fix the bug: 'TypeError: Cannot read properties of undefined (reading 'length')' in or related to this line: 'for (var i = 0; i < self.currentConfig.length; i++) {' Line Number: 252
User prompt
Super.
User prompt
Replace simple square blocks with classic Tetris shapes (I, O, T, L, J, S, Z). Use different colors and outlines for each shape.
User prompt
📐 Drag Sensitivity & Movement Ratio Fix Problem: When dragging a finger 2–3 tiles left or right, most shapes jump 8–10 tiles or more. The drag distance is disproportionate to finger movement, making control inaccurate. Fix Instruction: Adjust the drag sensitivity and movement mapping ratio so that: 1 tile of screen drag ≈ 1 tile of board movement. The movement of shapes must be proportional and consistent with the finger movement. Eliminate any excess multipliers or acceleration applied to horizontal movement. Use pixel-to-grid calibration or track finger delta position precisely and clamp movement per unit. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
👉 Touch-Drag Movement Fix (Precise Control) Enable precise touch dragging: The player must be able to place a finger directly on a shape and, without lifting the finger, drag it left or right across the screen smoothly to any desired tile. Fix lost functionality: This feature previously worked but is currently broken or disabled. It must be restored exactly as before. Finger stays in contact: Movement should continue only while the finger is held down. As soon as the finger is lifted, movement stops and the block drops naturally. Drag must feel responsive and direct, with no delay or incorrect shape selection. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Fix that: allow player to drag any figure buy putting a finger on that figure and drag anywhere a player want. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var GridBlock = Container.expand(function (shapeType) { var self = Container.call(this); self.shapeType = shapeType; self.color = shapeColors[shapeType] || 0xffffff; self.gridX = 0; self.gridY = 0; var shapeGraphic = self.attachAsset(shapeType, { anchorX: 0.5, anchorY: 0.5 }); // Add bomb fuse for bomb blocks if (shapeType === 'bomb') { var fuseGraphic = self.attachAsset('bombFuse', { anchorX: 0.5, anchorY: 1, x: 0, y: -25 }); // Animate fuse flickering var _fuseFlicker = function fuseFlicker() { tween(fuseGraphic, { alpha: 0.3 }, { duration: 200, onFinish: function onFinish() { tween(fuseGraphic, { alpha: 1 }, { duration: 200, onFinish: _fuseFlicker }); } }); }; _fuseFlicker(); } return self; }); var PlacedShape = Container.expand(function (shapeType) { var self = Container.call(this); self.shapeType = shapeType; self.isStable = true; self.isDragging = false; var shapeGraphic = self.attachAsset(shapeType, { anchorX: 0.5, anchorY: 0.5 }); self.rotate = function () { // Smooth rotation animation tween(shapeGraphic, { rotation: shapeGraphic.rotation + Math.PI / 2 }, { duration: 300, easing: tween.easeInOut, onFinish: function onFinish() { calculateStability(); updateStability(); } }); }; self.down = function (x, y, obj) { // Only set as selected if no other shape is already being dragged if (!selectedShape || !selectedShape.isDragging) { self.isDragging = true; selectedShape = self; } }; self.up = function (x, y, obj) { self.isDragging = false; if (selectedShape === self) { selectedShape = null; } calculateStability(); updateStability(); }; return self; }); var TetrisBlock = Container.expand(function (shapeType) { var self = Container.call(this); self.shapeType = shapeType; self.fallSpeed = Math.random() < 0.3 ? 3.5 : 2.5; // Increased by ~66% for more dynamic gameplay self.isSpecial = specialBlocks.indexOf(shapeType) !== -1; self.gridX = Math.floor(Math.random() * (gridWidth - 3)); // Ensure space for shape self.gridY = 0; self.lastMoveTime = 0; self.targetX = 0; // For smooth movement self.isMoving = false; // Track if block is currently moving self.rotation = 0; // Track current rotation state (0, 1, 2, 3) self.blocks = []; // Array to hold individual block graphics self.hasLanded = false; // Track if shape has touched ground or another block // Rotation configurations for each shape self.rotationConfigs = { 'iblock': [[[-1, 0], [0, 0], [1, 0], [2, 0]], // horizontal [[0, -1], [0, 0], [0, 1], [0, 2]], // vertical [[-1, 0], [0, 0], [1, 0], [2, 0]], // horizontal [[0, -1], [0, 0], [0, 1], [0, 2]] // vertical ], 'oblock': [[[0, 0], [1, 0], [0, 1], [1, 1]], // same for all rotations [[0, 0], [1, 0], [0, 1], [1, 1]], [[0, 0], [1, 0], [0, 1], [1, 1]], [[0, 0], [1, 0], [0, 1], [1, 1]]], 'tblock': [[[-1, 0], [0, 0], [1, 0], [0, 1]], // T pointing down [[0, -1], [0, 0], [1, 0], [0, 1]], // T pointing left [[-1, 0], [0, 0], [1, 0], [0, -1]], // T pointing up [[0, -1], [-1, 0], [0, 0], [0, 1]] // T pointing right ], 'lblock': [[[-1, 0], [0, 0], [1, 0], [1, 1]], // L standard [[0, -1], [0, 0], [0, 1], [-1, 1]], // L rotated 90 [[-1, -1], [-1, 0], [0, 0], [1, 0]], // L rotated 180 [[0, -1], [0, 0], [0, 1], [1, -1]] // L rotated 270 ], 'jblock': [[[-1, 0], [0, 0], [1, 0], [-1, 1]], // J standard [[0, -1], [0, 0], [0, 1], [1, 1]], // J rotated 90 [[-1, 0], [0, 0], [1, 0], [1, -1]], // J rotated 180 [[0, -1], [-1, -1], [0, 0], [0, 1]] // J rotated 270 ], 'sblock': [[[-1, 1], [0, 1], [0, 0], [1, 0]], // S horizontal [[0, -1], [0, 0], [1, 0], [1, 1]], // S vertical [[-1, 1], [0, 1], [0, 0], [1, 0]], // S horizontal [[0, -1], [0, 0], [1, 0], [1, 1]] // S vertical ], 'zblock': [[[-1, 0], [0, 0], [0, 1], [1, 1]], // Z horizontal [[1, -1], [1, 0], [0, 0], [0, 1]], // Z vertical [[-1, 0], [0, 0], [0, 1], [1, 1]], // Z horizontal [[1, -1], [1, 0], [0, 0], [0, 1]] // Z vertical ] }; // Define classic Tetris shape configurations with proper proportions var shapeConfigs = { 'iblock': [[-1, 0], [0, 0], [1, 0], [2, 0] // I-piece: centered horizontal line ], 'oblock': [[0, 0], [1, 0], [0, 1], [1, 1] // O-piece: 2x2 square ], 'tblock': [[-1, 0], [0, 0], [1, 0], [0, 1] // T-piece: centered with stem down ], 'lblock': [[-1, 0], [0, 0], [1, 0], [1, 1] // L-piece: centered with corner ], 'jblock': [[-1, 0], [0, 0], [1, 0], [-1, 1] // J-piece: centered with corner ], 'sblock': [[-1, 1], [0, 1], [0, 0], [1, 0] // S-piece: classic S shape ], 'zblock': [[-1, 0], [0, 0], [0, 1], [1, 1] // Z-piece: classic Z shape ] }; // Get shape configuration or default to single block var config = shapeConfigs[shapeType] || [[0, 0]]; // Store current shape configuration after config is defined self.currentConfig = self.rotationConfigs[shapeType] ? self.rotationConfigs[shapeType][0] : config; var shapeColor = shapeColors[shapeType] || 0x4a90e2; // Create individual blocks for the shape for (var i = 0; i < config.length; i++) { var blockGraphic = self.attachAsset(shapeType, { anchorX: 0.5, anchorY: 0.5 }); // Position block relative to shape center blockGraphic.x = config[i][0] * blockSize; blockGraphic.y = config[i][1] * blockSize; // Add subtle outline for better visibility blockGraphic.alpha = 0.9; self.blocks.push({ graphic: blockGraphic, offsetX: config[i][0], offsetY: config[i][1] }); } // Add bomb fuse for bomb blocks if (shapeType === 'bomb') { var fuseGraphic = self.attachAsset('bombFuse', { anchorX: 0.5, anchorY: 1, x: 0, y: -25 }); // Animate fuse flickering var _fuseFlicker2 = function fuseFlicker() { tween(fuseGraphic, { alpha: 0.3 }, { duration: 200, onFinish: function onFinish() { tween(fuseGraphic, { alpha: 1 }, { duration: 200, onFinish: _fuseFlicker2 }); } }); }; _fuseFlicker2(); } // Position on grid self.x = self.gridX * blockSize + blockSize / 2 + (2048 - gridWidth * blockSize) / 2; self.y = self.gridY * blockSize + blockSize / 2 + 200; self.targetX = self.x; // Initialize target position self.checkCollision = function (newGridX, newGridY) { // Check collision for each block in the current configuration for (var i = 0; i < self.currentConfig.length; i++) { var blockX = newGridX + self.currentConfig[i][0]; var blockY = newGridY + self.currentConfig[i][1]; // Check bounds if (blockX < 0 || blockX >= gridWidth || blockY < 0 || blockY >= gridHeight) { return true; } // Check if position is occupied in grid if (blockY >= 0 && grid[blockY] && grid[blockY][blockX]) { return true; } // Check collision with other falling shapes for (var j = 0; j < fallingShapes.length; j++) { var otherShape = fallingShapes[j]; if (otherShape !== self) { for (var k = 0; k < otherShape.blocks.length; k++) { var otherBlock = otherShape.blocks[k]; var otherBlockX = otherShape.gridX + otherBlock.offsetX; var otherBlockY = otherShape.gridY + otherBlock.offsetY; if (otherBlockX === blockX && otherBlockY === blockY) { return true; } } } } } return false; }; self.moveToPosition = function (newGridX) { // Track that the shape has moved (for distinguishing tap from drag) if (Math.abs(newGridX - self.gridX) > 0) { self.hasMoved = true; } // Allow movement even if there might be collision - let player move freely while dragging self.gridX = newGridX; self.targetX = self.gridX * blockSize + blockSize / 2 + (2048 - gridWidth * blockSize) / 2; self.isMoving = true; // Stop any existing movement tween tween.stop(self, { x: true }); // Instant movement for maximum responsiveness when dragging if (self.isDragging) { self.x = self.targetX; self.isMoving = false; } else { // Ultra-fast responsive movement for non-dragging cases tween(self, { x: self.targetX }, { duration: 50, easing: tween.easeOut, onFinish: function onFinish() { self.isMoving = false; } }); } }; self.update = function () { var currentTime = LK.ticks; if (currentTime - self.lastMoveTime > 80 / self.fallSpeed) { self.lastMoveTime = currentTime; // Check if can move down using collision detection var canMoveDown = !self.checkCollision(self.gridX, self.gridY + 1); if (canMoveDown) { self.gridY++; self.y = self.gridY * blockSize + blockSize / 2 + 200; } else { // Mark as landed when it can't move down self.hasLanded = true; // Only place if we're in a valid position and can't move down if (self.gridY >= 0) { self.placeInGrid(); } else { // If stuck above grid, try to find a valid position var foundValidPosition = false; for (var testY = 0; testY < gridHeight; testY++) { if (!self.checkCollision(self.gridX, testY)) { self.gridY = testY; self.y = self.gridY * blockSize + blockSize / 2 + 200; foundValidPosition = true; break; } } if (!foundValidPosition) { // Remove stuck shape var index = fallingShapes.indexOf(self); if (index > -1) { fallingShapes.splice(index, 1); } self.destroy(); } } } } }; self.placeInGrid = function () { var allBlocksValid = true; // Check if all blocks can be placed for (var i = 0; i < self.currentConfig.length; i++) { var blockX = self.gridX + self.currentConfig[i][0]; var blockY = self.gridY + self.currentConfig[i][1]; if (blockY < 0 || blockY >= gridHeight || blockX < 0 || blockX >= gridWidth) { allBlocksValid = false; break; } } if (allBlocksValid) { // Place all blocks in grid for (var i = 0; i < self.currentConfig.length; i++) { var blockX = self.gridX + self.currentConfig[i][0]; var blockY = self.gridY + self.currentConfig[i][1]; grid[blockY][blockX] = self.shapeType; // Create visual block in grid var placedBlock = new GridBlock(self.shapeType); placedBlock.gridX = blockX; placedBlock.gridY = blockY; placedBlock.x = blockX * blockSize + blockSize / 2 + (2048 - gridWidth * blockSize) / 2; placedBlock.y = blockY * blockSize + blockSize / 2 + 200; placedShapes.push(placedBlock); game.addChild(placedBlock); } LK.getSound('place').play(); score += 10 * self.blocks.length; updateScore(); checkForLines(); // Handle special blocks if (self.isSpecial) { handleSpecialBlock(self.shapeType, self.gridX, self.gridY); } } // Remove from falling shapes var index = fallingShapes.indexOf(self); if (index > -1) { fallingShapes.splice(index, 1); } self.destroy(); // Check game over if (self.gridY <= 1) { LK.showGameOver(); } }; self.rotate = function () { if (!self.rotationConfigs[self.shapeType]) return; // No rotation for non-Tetris shapes if (self.hasLanded) return; // Cannot rotate after landing var newRotation = (self.rotation + 1) % 4; var newConfig = self.rotationConfigs[self.shapeType][newRotation]; // Try rotation at current position first var testGridX = self.gridX; var canRotate = true; var blockedByGrid = false; // Check if rotation is valid at current position for (var i = 0; i < newConfig.length; i++) { var blockX = testGridX + newConfig[i][0]; var blockY = self.gridY + newConfig[i][1]; // Check bounds and collisions if (blockX < 0 || blockX >= gridWidth || blockY < 0 || blockY >= gridHeight) { canRotate = false; break; } if (blockY >= 0 && grid[blockY] && grid[blockY][blockX]) { canRotate = false; blockedByGrid = true; break; } } // If rotation failed due to boundary, try wall kick/clamp if (!canRotate && !blockedByGrid) { // Calculate how far left or right we need to shift var minX = 0; var maxX = 0; for (var i = 0; i < newConfig.length; i++) { var blockX = newConfig[i][0]; minX = Math.min(minX, blockX); maxX = Math.max(maxX, blockX); } // Try shifting left (wall kick left) var shiftLeft = -(testGridX + minX); if (shiftLeft > 0) { var testLeftX = testGridX + shiftLeft; var canShiftLeft = true; for (var i = 0; i < newConfig.length; i++) { var blockX = testLeftX + newConfig[i][0]; var blockY = self.gridY + newConfig[i][1]; if (blockX < 0 || blockX >= gridWidth || blockY < 0 || blockY >= gridHeight) { canShiftLeft = false; break; } if (blockY >= 0 && grid[blockY] && grid[blockY][blockX]) { canShiftLeft = false; break; } } if (canShiftLeft) { testGridX = testLeftX; canRotate = true; } } // Try shifting right (wall kick right) if left shift didn't work if (!canRotate) { var shiftRight = gridWidth - 1 - (testGridX + maxX); if (shiftRight < 0) { var testRightX = testGridX + shiftRight; var canShiftRight = true; for (var i = 0; i < newConfig.length; i++) { var blockX = testRightX + newConfig[i][0]; var blockY = self.gridY + newConfig[i][1]; if (blockX < 0 || blockX >= gridWidth || blockY < 0 || blockY >= gridHeight) { canShiftRight = false; break; } if (blockY >= 0 && grid[blockY] && grid[blockY][blockX]) { canShiftRight = false; break; } } if (canShiftRight) { testGridX = testRightX; canRotate = true; } } } } if (canRotate) { self.rotation = newRotation; self.currentConfig = newConfig; // Update grid position if we had to shift if (testGridX !== self.gridX) { self.gridX = testGridX; self.x = self.gridX * blockSize + blockSize / 2 + (2048 - gridWidth * blockSize) / 2; } // Update visual blocks positions for (var i = 0; i < self.blocks.length; i++) { if (newConfig[i]) { self.blocks[i].offsetX = newConfig[i][0]; self.blocks[i].offsetY = newConfig[i][1]; self.blocks[i].graphic.x = newConfig[i][0] * blockSize; self.blocks[i].graphic.y = newConfig[i][1] * blockSize; } } // Smooth rotation animation for (var i = 0; i < self.blocks.length; i++) { tween(self.blocks[i].graphic, { rotation: self.blocks[i].graphic.rotation + Math.PI / 2 }, { duration: 150, easing: tween.easeOut }); } } }; self.down = function (x, y, obj) { // Store touch start time for tap detection self.touchStartTime = LK.ticks; // Clear any existing selection first if (selectedShape && selectedShape !== self) { // Reset previous shape tween(selectedShape, { scaleX: 1, scaleY: 1, alpha: 1 }, { duration: 100 }); selectedShape.isDragging = false; } // Immediately select this shape when touched selectedShape = self; selectedShape.isDragging = true; // No touch offset - use direct positioning for 1:1 movement // Immediate visual feedback tween(self, { scaleX: 1.1, scaleY: 1.1, alpha: 0.8 }, { duration: 100, easing: tween.easeOut }); }; self.up = function (x, y, obj) { // Check if this was a quick tap (for rotation) vs a drag var touchDuration = LK.ticks - (self.touchStartTime || 0); var wasQuickTap = touchDuration < 10; // Less than ~166ms at 60fps // Release the shape when touch ends if (selectedShape === self) { // If it was a quick tap and not much movement, rotate the piece (only if not landed) if (wasQuickTap && !self.hasMoved && !self.hasLanded) { self.rotate(); } selectedShape.isDragging = false; selectedShape = null; self.hasMoved = false; // Reset movement flag // Reset visual feedback tween(self, { scaleX: 1, scaleY: 1, alpha: 1 }, { duration: 150, easing: tween.easeOut }); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x87CEEB }); /**** * Game Code ****/ var fallingShapes = []; var placedShapes = []; var score = 0; var linesCleared = 0; var level = 1; var groundY = 2400; var shapeTypes = ['lblock', 'tblock', 'zblock', 'iblock', 'jblock', 'sblock', 'oblock']; var specialBlocks = ['gift', 'bomb', 'wildcard']; var shapeColors = { 'lblock': 0xFF6600, // Orange - L piece 'tblock': 0x9900FF, // Purple - T piece 'zblock': 0xFF0000, // Red - Z piece 'iblock': 0x00FFFF, // Cyan - I piece 'jblock': 0x0000FF, // Blue - J piece 'sblock': 0x00FF00, // Green - S piece 'oblock': 0xFFFF00, // Yellow - O piece 'cube': 0x4a90e2, 'pyramid': 0xf5a623, 'triangle': 0x00b894, 'diamond': 0xfd79a8, 'star': 0xff6b35, 'cross': 0x1b998b, 'hexagon': 0x6c5ce7 }; var gridWidth = 24; var gridHeight = 24; var blockSize = 80; var grid = []; var stability = 100; var maxHeight = 0; var selectedShape = null; var inventory = []; // Initialize empty grid for (var row = 0; row < gridHeight; row++) { grid[row] = []; for (var col = 0; col < gridWidth; col++) { grid[row][col] = null; } } var nextShapeTimer = 0; var shapeSpawnDelay = 180; // 3 seconds at 60fps - slower initial spawn var maxFallingShapes = 1; // Start with only 1 falling shape // Create ground var ground = game.addChild(LK.getAsset('ground', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: groundY })); // Create UI elements var scoreText = new Text2('Score: 0', { size: 40, fill: 0xFFFFFF }); scoreText.anchor.set(0, 0); scoreText.x = 120; scoreText.y = 120; LK.gui.topLeft.addChild(scoreText); var stabilityText = new Text2('Stability: 100%', { size: 40, fill: 0xFFFFFF }); stabilityText.anchor.set(0, 0); stabilityText.x = 120; stabilityText.y = 180; LK.gui.topLeft.addChild(stabilityText); // Stability bar background var stabilityBarBg = LK.getAsset('stabilityBarBg', { anchorX: 0, anchorY: 0, x: 120, y: 220 }); LK.gui.topLeft.addChild(stabilityBarBg); // Stability bar var stabilityBar = LK.getAsset('stabilityBar', { anchorX: 0, anchorY: 0, x: 120, y: 220 }); LK.gui.topLeft.addChild(stabilityBar); var inventoryText = new Text2('Inventory: 0', { size: 40, fill: 0xFFFFFF }); inventoryText.anchor.set(1, 0); LK.gui.topRight.addChild(inventoryText); // Create rotation buttons var rotateLeftButton = new Text2('⟲', { size: 80, fill: 0xFFFFFF }); rotateLeftButton.anchor.set(0.5, 1); rotateLeftButton.x = -150; rotateLeftButton.y = -50; LK.gui.bottom.addChild(rotateLeftButton); var rotateRightButton = new Text2('⟳', { size: 80, fill: 0xFFFFFF }); rotateRightButton.anchor.set(0.5, 1); rotateRightButton.x = 150; rotateRightButton.y = -50; LK.gui.bottom.addChild(rotateRightButton); // Add button functionality - only rotate currently falling shapes rotateLeftButton.down = function (x, y, obj) { // Find currently falling shape (not settled) for (var i = 0; i < fallingShapes.length; i++) { var shape = fallingShapes[i]; if (shape.rotate && !shape.hasLanded) { // Rotate counterclockwise (3 times clockwise = 1 counterclockwise) shape.rotate(); shape.rotate(); shape.rotate(); break; // Only rotate the first falling shape } } }; rotateRightButton.down = function (x, y, obj) { // Find currently falling shape (not settled) for (var i = 0; i < fallingShapes.length; i++) { var shape = fallingShapes[i]; if (shape.rotate && !shape.hasLanded) { shape.rotate(); break; // Only rotate the first falling shape } } }; var heightText = new Text2('Height: 0', { size: 40, fill: 0xFFFFFF }); heightText.anchor.set(0, 0); heightText.x = 120; heightText.y = 260; LK.gui.topLeft.addChild(heightText); function spawnShape() { var shapeType; // 10% chance for special blocks if (Math.random() < 0.1) { shapeType = specialBlocks[Math.floor(Math.random() * specialBlocks.length)]; } else { shapeType = shapeTypes[Math.floor(Math.random() * shapeTypes.length)]; } var shape = new TetrisBlock(shapeType); fallingShapes.push(shape); game.addChild(shape); } function updateInventoryDisplay() { inventoryText.setText('Inventory: ' + inventory.length); } function updateScore() { scoreText.setText('Score: ' + score); } function calculateStability() { // Calculate stability based on grid density and structure var totalBlocks = 0; var connectedBlocks = 0; var baseSupport = 0; // Count total blocks and base support for (var row = 0; row < gridHeight; row++) { for (var col = 0; col < gridWidth; col++) { if (grid[row][col]) { totalBlocks++; // Check if block has support below or is on ground if (row === gridHeight - 1 || grid[row + 1][col]) { baseSupport++; } // Check connections (adjacent blocks) var connections = 0; if (row > 0 && grid[row - 1][col]) connections++; if (row < gridHeight - 1 && grid[row + 1][col]) connections++; if (col > 0 && grid[row][col - 1]) connections++; if (col < gridWidth - 1 && grid[row][col + 1]) connections++; if (connections > 0) connectedBlocks++; } } } if (totalBlocks === 0) { stability = 100; } else { // Calculate stability percentage var supportRatio = baseSupport / totalBlocks; var connectionRatio = connectedBlocks / totalBlocks; stability = Math.max(10, Math.min(100, (supportRatio * 60 + connectionRatio * 40) * 100)); } } function updateStability() { stabilityText.setText('Stability: ' + Math.round(stability) + '%'); var stabilityPercent = Math.max(0, Math.min(1, stability / 100)); stabilityBar.width = 300 * stabilityPercent; // Color based on stability if (stability > 70) { stabilityBar.tint = 0x00FF00; // Green } else if (stability > 30) { stabilityBar.tint = 0xFFFF00; // Yellow } else { stabilityBar.tint = 0xFF0000; // Red } } function updateHeight() { var currentHeight = 0; for (var i = 0; i < placedShapes.length; i++) { var shapeBottom = placedShapes[i].y + 40; var heightFromGround = groundY - shapeBottom; if (heightFromGround > currentHeight) { currentHeight = heightFromGround; } } maxHeight = Math.max(maxHeight, currentHeight); heightText.setText('Height: ' + Math.round(maxHeight / 80)); } function applyGravity() { var hasFloatingBlocks = true; while (hasFloatingBlocks) { hasFloatingBlocks = false; // Check each grid position from bottom to top for (var row = gridHeight - 2; row >= 0; row--) { for (var col = 0; col < gridWidth; col++) { if (grid[row][col] && !grid[row + 1][col]) { // Block is floating - make it fall grid[row + 1][col] = grid[row][col]; grid[row][col] = null; hasFloatingBlocks = true; // Find and move corresponding visual block for (var i = 0; i < placedShapes.length; i++) { var block = placedShapes[i]; if (block.gridX === col && block.gridY === row) { block.gridY = row + 1; tween(block, { y: block.gridY * blockSize + blockSize / 2 + 200 }, { duration: 150, easing: tween.easeOut }); break; } } } } } } } function checkForLines() { var linesToClear = []; // Check each row for matches for (var row = 0; row < gridHeight; row++) { var rowBlocks = []; var colors = {}; var colorCount = 0; // Collect all blocks in this row for (var col = 0; col < gridWidth; col++) { if (grid[row][col]) { rowBlocks.push({ col: col, type: grid[row][col] }); var color = shapeColors[grid[row][col]] || 0xffffff; if (!colors[color]) { colors[color] = 0; colorCount++; } colors[color]++; } } // Rule 1: 4+ blocks of same color horizontally for (var color in colors) { if (colors[color] >= 4) { linesToClear.push({ row: row, type: 'same_color', points: colors[color] * 25 }); break; } } // Rule 2: 8+ blocks with exactly 2 colors if (linesToClear.length === 0 && colorCount === 2 && rowBlocks.length >= 8) { linesToClear.push({ row: row, type: 'two_color', points: 200 }); } // Rule 3: Full row (24 blocks) with 3+ colors if (linesToClear.length === 0 && colorCount >= 3 && rowBlocks.length === 24) { linesToClear.push({ row: row, type: 'full_multicolor', points: 500 }); } } // Clear matched lines if (linesToClear.length > 0) { clearLines(linesToClear); } } function clearLines(lines) { LK.getSound('lineClear').play(); var totalPoints = 0; for (var i = 0; i < lines.length; i++) { totalPoints += lines[i].points || 100; } score += totalPoints; updateScore(); // Remove visual blocks and clear grid for (var i = 0; i < lines.length; i++) { var row = lines[i].row; // Remove visual blocks for (var j = placedShapes.length - 1; j >= 0; j--) { var block = placedShapes[j]; if (block.gridY === row) { tween(block, { alpha: 0, scaleX: 0, scaleY: 0 }, { duration: 300, onFinish: function onFinish() { block.destroy(); } }); placedShapes.splice(j, 1); } } // Clear grid row for (var col = 0; col < gridWidth; col++) { grid[row][col] = null; } } // Drop remaining blocks down LK.setTimeout(function () { dropBlocks(lines); }, 400); } function dropBlocks(clearedLines) { for (var row = gridHeight - 1; row >= 0; row--) { var dropDistance = 0; for (var i = 0; i < clearedLines.length; i++) { if (clearedLines[i] > row) { dropDistance++; } } if (dropDistance > 0) { // Move grid data for (var col = 0; col < gridWidth; col++) { if (grid[row][col]) { grid[row + dropDistance][col] = grid[row][col]; grid[row][col] = null; } } // Move visual blocks for (var j = 0; j < placedShapes.length; j++) { var block = placedShapes[j]; if (block.gridY === row) { block.gridY += dropDistance; tween(block, { y: block.gridY * blockSize + blockSize / 2 + 200 }, { duration: 200, easing: tween.easeOut }); } } } } } function handleSpecialBlock(type, gridX, gridY) { if (type === 'gift') { score += 50; updateScore(); LK.effects.flashObject(game, 0xffd700, 500); } else if (type === 'bomb') { LK.getSound('bomb').play(); // Destroy blocks in 3x3 area for (var row = Math.max(0, gridY - 1); row <= Math.min(gridHeight - 1, gridY + 1); row++) { for (var col = Math.max(0, gridX - 1); col <= Math.min(gridWidth - 1, gridX + 1); col++) { if (grid[row][col]) { grid[row][col] = null; // Remove visual block for (var i = placedShapes.length - 1; i >= 0; i--) { var block = placedShapes[i]; if (block.gridX === col && block.gridY === row) { tween(block, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 300, onFinish: function onFinish() { block.destroy(); } }); placedShapes.splice(i, 1); break; } } } } } } else if (type === 'wildcard') { // Fill nearest gap var nearestGap = findNearestGap(gridX, gridY); if (nearestGap) { grid[nearestGap.row][nearestGap.col] = 'cube'; var wildBlock = new GridBlock('cube'); wildBlock.gridX = nearestGap.col; wildBlock.gridY = nearestGap.row; wildBlock.x = nearestGap.col * blockSize + blockSize / 2 + (2048 - gridWidth * blockSize) / 2; wildBlock.y = nearestGap.row * blockSize + blockSize / 2 + 200; placedShapes.push(wildBlock); game.addChild(wildBlock); tween(wildBlock, { scaleX: 1.5, scaleY: 1.5 }, { duration: 200, easing: tween.bounceOut, onFinish: function onFinish() { tween(wildBlock, { scaleX: 1, scaleY: 1 }, { duration: 100 }); } }); } } } function findNearestGap(startX, startY) { var searchRadius = 1; while (searchRadius < Math.max(gridWidth, gridHeight)) { for (var row = Math.max(0, startY - searchRadius); row <= Math.min(gridHeight - 1, startY + searchRadius); row++) { for (var col = Math.max(0, startX - searchRadius); col <= Math.min(gridWidth - 1, startX + searchRadius); col++) { if (!grid[row][col]) { return { row: row, col: col }; } } } searchRadius++; } return null; } function triggerCollapse() { LK.getSound('collapse').play(); // Remove unstable shapes for (var i = placedShapes.length - 1; i >= 0; i--) { if (Math.random() < 0.3) { // 30% chance each shape collapses var shape = placedShapes[i]; tween(shape, { alpha: 0, y: shape.y + 100 }, { duration: 500, onFinish: function onFinish() { shape.destroy(); } }); placedShapes.splice(i, 1); } } score = Math.max(0, score - 50); updateScore(); } function placeShape(shape, x, y) { if (inventory.length === 0) return; var inventoryShape = inventory.shift(); var placedShape = new PlacedShape(inventoryShape.shapeType); placedShape.x = x; placedShape.y = y; // Start with smaller scale for placement animation placedShape.scaleX = 0.5; placedShape.scaleY = 0.5; placedShape.alpha = 0.7; placedShapes.push(placedShape); game.addChild(placedShape); // Animate placement with smooth scaling tween(placedShape, { scaleX: 1, scaleY: 1, alpha: 1 }, { duration: 300, easing: tween.elasticOut }); LK.getSound('place').play(); score += 10; // Bonus for good placement var centerX = 1024; var distanceFromCenter = Math.abs(x - centerX); if (distanceFromCenter < 100) { score += 5; // Bonus for center placement // Add bonus glow effect for center placement tween(placedShape, { tint: 0xffff00 }, { duration: 500, onFinish: function onFinish() { tween(placedShape, { tint: 0xffffff }, { duration: 500 }); } }); } updateScore(); updateInventoryDisplay(); calculateStability(); updateHeight(); inventoryShape.destroy(); } // Event handlers game.down = function (x, y, obj) { // Touch handling is now done directly by TetrisBlock shapes // This ensures only the touched shape responds }; game.move = function (x, y, obj) { // Handle dragging of selected shape with precise 1:1 movement mapping if (selectedShape && selectedShape.isDragging) { var gameAreaLeft = (2048 - gridWidth * blockSize) / 2; var gameAreaRight = gameAreaLeft + gridWidth * blockSize; // Use direct finger position for movement - no offset calculation // This creates 1:1 mapping between finger movement and shape movement var clampedX = Math.max(gameAreaLeft + blockSize / 2, Math.min(gameAreaRight - blockSize / 2, x)); var targetGridX = Math.floor((clampedX - gameAreaLeft) / blockSize); targetGridX = Math.max(0, Math.min(gridWidth - 1, targetGridX)); // Move to new position only if it's actually different if (targetGridX !== selectedShape.gridX) { selectedShape.moveToPosition(targetGridX); } } }; game.up = function (x, y, obj) { // Touch release is now handled directly by TetrisBlock shapes // This ensures proper cleanup when touch ends }; game.update = function () { // One-at-a-time spawn rule: only spawn when no shapes are falling nextShapeTimer++; if (nextShapeTimer >= shapeSpawnDelay && fallingShapes.length === 0) { spawnShape(); nextShapeTimer = 0; } // Apply gravity to prevent floating blocks if (LK.ticks % 20 === 0) { applyGravity(); } // Update stability periodically if (LK.ticks % 30 === 0) { calculateStability(); updateStability(); } // Check win condition if (maxHeight > 800) { // About 10 blocks high LK.showYouWin(); } // Check game over condition if (stability < 10 && placedShapes.length > 0) { LK.showGameOver(); } }; // Initialize display updateScore(); updateStability(); updateInventoryDisplay(); updateHeight();
===================================================================
--- original.js
+++ change.js
@@ -94,8 +94,9 @@
self.targetX = 0; // For smooth movement
self.isMoving = false; // Track if block is currently moving
self.rotation = 0; // Track current rotation state (0, 1, 2, 3)
self.blocks = []; // Array to hold individual block graphics
+ self.hasLanded = false; // Track if shape has touched ground or another block
// Rotation configurations for each shape
self.rotationConfigs = {
'iblock': [[[-1, 0], [0, 0], [1, 0], [2, 0]],
// horizontal
@@ -287,8 +288,10 @@
if (canMoveDown) {
self.gridY++;
self.y = self.gridY * blockSize + blockSize / 2 + 200;
} else {
+ // Mark as landed when it can't move down
+ self.hasLanded = true;
// Only place if we're in a valid position and can't move down
if (self.gridY >= 0) {
self.placeInGrid();
} else {
@@ -361,8 +364,9 @@
}
};
self.rotate = function () {
if (!self.rotationConfigs[self.shapeType]) return; // No rotation for non-Tetris shapes
+ if (self.hasLanded) return; // Cannot rotate after landing
var newRotation = (self.rotation + 1) % 4;
var newConfig = self.rotationConfigs[self.shapeType][newRotation];
// Try rotation at current position first
var testGridX = self.gridX;
@@ -502,10 +506,10 @@
var touchDuration = LK.ticks - (self.touchStartTime || 0);
var wasQuickTap = touchDuration < 10; // Less than ~166ms at 60fps
// Release the shape when touch ends
if (selectedShape === self) {
- // If it was a quick tap and not much movement, rotate the piece
- if (wasQuickTap && !self.hasMoved) {
+ // If it was a quick tap and not much movement, rotate the piece (only if not landed)
+ if (wasQuickTap && !self.hasMoved && !self.hasLanded) {
self.rotate();
}
selectedShape.isDragging = false;
selectedShape = null;
@@ -650,9 +654,9 @@
rotateLeftButton.down = function (x, y, obj) {
// Find currently falling shape (not settled)
for (var i = 0; i < fallingShapes.length; i++) {
var shape = fallingShapes[i];
- if (shape.rotate) {
+ if (shape.rotate && !shape.hasLanded) {
// Rotate counterclockwise (3 times clockwise = 1 counterclockwise)
shape.rotate();
shape.rotate();
shape.rotate();
@@ -663,9 +667,9 @@
rotateRightButton.down = function (x, y, obj) {
// Find currently falling shape (not settled)
for (var i = 0; i < fallingShapes.length; i++) {
var shape = fallingShapes[i];
- if (shape.rotate) {
+ if (shape.rotate && !shape.hasLanded) {
shape.rotate();
break; // Only rotate the first falling shape
}
}