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] ? self.rotationConfigs[shapeType][0] : config; var shapeColor = shapeColors[shapeType] || 0x4a90e2; // Create a single group container for all blocks self.blockGroup = new Container(); self.addChild(self.blockGroup); // Create individual blocks within the group for (var i = 0; i < config.length; i++) { var blockGraphic = self.blockGroup.attachAsset(shapeType, { anchorX: 0.5, anchorY: 0.5 }); // Position block relative to group center blockGraphic.x = config[i][0] * blockSize; blockGraphic.y = config[i][1] * blockSize; // Add subtle outline for better visibility blockGraphic.alpha = 0.9; // Completely disable interactivity on individual blocks to prevent structural changes blockGraphic.interactive = false; blockGraphic.buttonMode = false; // Prevent any touch events from reaching individual blocks blockGraphic.down = null; blockGraphic.up = null; blockGraphic.move = null; self.blocks.push({ graphic: blockGraphic, offsetX: config[i][0], offsetY: config[i][1] }); } // Calculate proper pivot point based on shape bounds for centered rotation var minX = 0, maxX = 0, minY = 0, maxY = 0; for (var i = 0; i < config.length; i++) { minX = Math.min(minX, config[i][0]); maxX = Math.max(maxX, config[i][0]); minY = Math.min(minY, config[i][1]); maxY = Math.max(maxY, config[i][1]); } var centerX = (minX + maxX) * blockSize / 2; var centerY = (minY + maxY) * blockSize / 2; self.blockGroup.pivot.set(centerX, centerY); // 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, configToCheck) { // Use provided configuration or current configuration var config = configToCheck || self.currentConfig; // Check collision for each block in the configuration for (var i = 0; i < config.length; i++) { var blockX = newGridX + config[i][0]; var blockY = newGridY + config[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 && blockY < gridHeight && 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; } // Clamp to grid boundaries based on current shape configuration var minX = 0; var maxX = 0; for (var i = 0; i < self.currentConfig.length; i++) { var blockX = self.currentConfig[i][0]; minX = Math.min(minX, blockX); maxX = Math.max(maxX, blockX); } // Clamp position to keep all blocks within grid bounds newGridX = Math.max(-minX, Math.min(gridWidth - 1 - maxX, newGridX)); // 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) { // Play light drop sound for step-down movement LK.getSound('drop').play(); self.gridY++; self.y = self.gridY * blockSize + blockSize / 2 + 200; } else { // Check if touching another shape (not ground) for contact sound var touchingShape = false; for (var j = 0; j < fallingShapes.length; j++) { var otherShape = fallingShapes[j]; if (otherShape !== self && self.checkCollision(self.gridX, self.gridY + 1)) { touchingShape = true; break; } } // Check if there are blocks below in grid var touchingGrid = false; 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] + 1; if (blockY < gridHeight && blockY >= 0 && grid[blockY] && grid[blockY][blockX]) { touchingGrid = true; break; } } // Play appropriate sound if (touchingShape || touchingGrid) { if (self.gridY + 1 >= gridHeight) { // Landing on ground LK.getSound('land').play(); } else { // Contact with another shape LK.getSound('contact').play(); } } // 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) { // Stop any existing tweens before destroying tween.stop(self); tween.stop(self.blockGroup); for (var i = 0; i < self.blocks.length; i++) { tween.stop(self.blocks[i].graphic); } // 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) { // Freeze the group before disassembly to maintain structural stability self.blockGroup.rotation = Math.round(self.blockGroup.rotation / (Math.PI / 2)) * (Math.PI / 2); // Place all blocks in grid - disassemble the group into static tiles 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 (static tile) 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; // Apply the same rotation as the group had placedBlock.rotation = self.blockGroup.rotation; 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); } } // Stop any existing tweens before destroying tween.stop(self); tween.stop(self.blockGroup); for (var i = 0; i < self.blocks.length; i++) { tween.stop(self.blocks[i].graphic); } // 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 () { // Only allow rotation if shape hasn't landed to maintain structural stability if (self.hasLanded) return; // Get rotation configurations for this shape type if (!self.rotationConfigs[self.shapeType]) return; // Calculate next rotation state var nextRotation = (self.rotation + 1) % 4; var nextConfig = self.rotationConfigs[self.shapeType][nextRotation]; // Check if rotation is valid (no collision) if (!self.checkCollision(self.gridX, self.gridY, nextConfig)) { // Update rotation state and configuration self.rotation = nextRotation; self.currentConfig = nextConfig; // Update pivot point for proper centered rotation var minX = 0, maxX = 0, minY = 0, maxY = 0; for (var i = 0; i < nextConfig.length; i++) { minX = Math.min(minX, nextConfig[i][0]); maxX = Math.max(maxX, nextConfig[i][0]); minY = Math.min(minY, nextConfig[i][1]); maxY = Math.max(maxY, nextConfig[i][1]); } var centerX = (minX + maxX) * blockSize / 2; var centerY = (minY + maxY) * blockSize / 2; self.blockGroup.pivot.set(centerX, centerY); // Update block positions based on new configuration for (var i = 0; i < self.blocks.length; i++) { if (nextConfig[i]) { self.blocks[i].graphic.x = nextConfig[i][0] * blockSize; self.blocks[i].graphic.y = nextConfig[i][1] * blockSize; self.blocks[i].offsetX = nextConfig[i][0]; self.blocks[i].offsetY = nextConfig[i][1]; } } // Play rotation sound effect LK.getSound('rotate').play(); // Animate rotation on the block group with responsive timing tween(self.blockGroup, { rotation: self.blockGroup.rotation + Math.PI / 2 }, { duration: 150, easing: tween.easeOut }); } }; self.rotateLeft = function () { // Only allow rotation if shape hasn't landed to maintain structural stability if (self.hasLanded) return; // Get rotation configurations for this shape type if (!self.rotationConfigs[self.shapeType]) return; // Calculate previous rotation state (counter-clockwise) var nextRotation = (self.rotation + 3) % 4; // +3 is same as -1 in mod 4 var nextConfig = self.rotationConfigs[self.shapeType][nextRotation]; // Check if rotation is valid (no collision) if (!self.checkCollision(self.gridX, self.gridY, nextConfig)) { // Update rotation state and configuration self.rotation = nextRotation; self.currentConfig = nextConfig; // Update pivot point for proper centered rotation var minX = 0, maxX = 0, minY = 0, maxY = 0; for (var i = 0; i < nextConfig.length; i++) { minX = Math.min(minX, nextConfig[i][0]); maxX = Math.max(maxX, nextConfig[i][0]); minY = Math.min(minY, nextConfig[i][1]); maxY = Math.max(maxY, nextConfig[i][1]); } var centerX = (minX + maxX) * blockSize / 2; var centerY = (minY + maxY) * blockSize / 2; self.blockGroup.pivot.set(centerX, centerY); // Update block positions based on new configuration for (var i = 0; i < self.blocks.length; i++) { if (nextConfig[i]) { self.blocks[i].graphic.x = nextConfig[i][0] * blockSize; self.blocks[i].graphic.y = nextConfig[i][1] * blockSize; self.blocks[i].offsetX = nextConfig[i][0]; self.blocks[i].offsetY = nextConfig[i][1]; } } // Play rotation sound effect LK.getSound('rotate').play(); // Animate rotation on the block group with responsive timing (counter-clockwise) tween(self.blockGroup, { rotation: self.blockGroup.rotation - Math.PI / 2 }, { duration: 150, easing: tween.easeOut }); } }; self.down = function (x, y, obj) { // Ignore touches on landed shapes to maintain structural stability until row completion if (self.hasLanded) return; // 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 on the group tween(self.blockGroup, { scaleX: 1.1, scaleY: 1.1, alpha: 0.8 }, { duration: 100, easing: tween.easeOut }); }; self.up = function (x, y, obj) { // Handle tap-to-rotate for falling shapes (maintain structural stability after landing) if (selectedShape === self && !self.hasLanded) { // Check if this was a tap (not a drag) and shape hasn't moved var touchDuration = LK.ticks - (self.touchStartTime || 0); if (!self.hasMoved && touchDuration < 30) { // Quick tap under 0.5 seconds self.rotate(); } selectedShape.isDragging = false; selectedShape = null; self.hasMoved = false; // Reset movement flag // Reset visual feedback on the group tween(self.blockGroup, { 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 faint grid lines for visual alignment var gridContainer = new Container(); game.addChild(gridContainer); var gameAreaLeft = (2048 - gridWidth * blockSize) / 2; var gameAreaTop = 200; // Create vertical grid lines for (var col = 0; col <= gridWidth; col++) { var vLine = LK.getAsset('gridLine', { anchorX: 0, anchorY: 0 }); vLine.x = gameAreaLeft + col * blockSize; vLine.y = gameAreaTop; vLine.width = 2; vLine.height = gridHeight * blockSize; vLine.tint = 0x444444; vLine.alpha = 0.3; gridContainer.addChild(vLine); } // Create horizontal grid lines for (var row = 0; row <= gridHeight; row++) { var hLine = LK.getAsset('gridLine', { anchorX: 0, anchorY: 0 }); hLine.x = gameAreaLeft; hLine.y = gameAreaTop + row * blockSize; hLine.width = gridWidth * blockSize; hLine.height = 2; hLine.tint = 0x444444; hLine.alpha = 0.3; gridContainer.addChild(hLine); } // 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); // Enable rotation for falling shapes while maintaining structural integrity after landing rotateLeftButton.down = function (x, y, obj) { // Find the first falling shape that can rotate for (var i = 0; i < fallingShapes.length; i++) { var shape = fallingShapes[i]; if (!shape.hasLanded) { shape.rotateLeft(); break; } } }; rotateRightButton.down = function (x, y, obj) { // Find the first falling shape that can rotate for (var i = 0; i < fallingShapes.length; i++) { var shape = fallingShapes[i]; if (!shape.hasLanded) { shape.rotate(); break; } } }; 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); // Play soft spawn sound when shape enters from top LK.getSound('spawn').play(); } 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) { // Stop any existing tween on this block first tween.stop(block); 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 full row completion (24 consecutive blocks from multiple shapes) for (var row = 0; row < gridHeight; row++) { var blocksInRow = 0; var shapesInRow = {}; // Count blocks and track unique shapes in this row for (var col = 0; col < gridWidth; col++) { if (grid[row][col]) { blocksInRow++; // Track which shapes contributed to this row for (var i = 0; i < placedShapes.length; i++) { var shape = placedShapes[i]; if (shape.gridY === row && shape.gridX === col) { // Use shape object reference as unique identifier var shapeId = shape.shapeType + '_' + shape.x + '_' + shape.y; shapesInRow[shapeId] = true; break; } } } } // Count unique shapes in this row var uniqueShapeCount = 0; for (var shapeId in shapesInRow) { uniqueShapeCount++; } // Only clear if entire row is filled (24 blocks) AND comes from multiple whole shapes if (blocksInRow === gridWidth && uniqueShapeCount >= 2) { linesToClear.push({ row: row, type: 'full_row', points: 100 }); } } // Clear matched lines if (linesToClear.length > 0) { clearLines(linesToClear); } } function clearLines(lines) { LK.getSound('lineClear').play(); var totalPoints = 0; // Calculate bonus for multiple rows cleared simultaneously var linesCount = lines.length; for (var i = 0; i < lines.length; i++) { totalPoints += lines[i].points || 100; } // Bonus scoring for multiple lines if (linesCount === 2) { totalPoints = totalPoints * 3; // Triple score for 2 lines } else if (linesCount === 3) { totalPoints = totalPoints * 5; // 5x score for 3 lines } else if (linesCount >= 4) { totalPoints = totalPoints * 8; // 8x score for 4+ lines (Tetris!) } score += totalPoints; linesCleared += linesCount; 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) { // Sort cleared lines from top to bottom to handle dropping correctly var sortedLines = []; for (var i = 0; i < clearedLines.length; i++) { sortedLines.push(clearedLines[i].row); } sortedLines.sort(function (a, b) { return a - b; }); // Process from bottom to top for (var row = gridHeight - 1; row >= 0; row--) { var dropDistance = 0; // Count how many cleared lines are below this row for (var i = 0; i < sortedLines.length; i++) { if (sortedLines[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 }); } }); } } // Trigger stability and height recalculation after special block effects calculateStability(); updateStability(); applyGravity(); checkForLines(); } 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 // Prevent movement of landed shapes to maintain structural stability until row completion if (selectedShape && selectedShape.isDragging && !selectedShape.hasLanded) { 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 - stack overflow (blocked spawn area) var spawnAreaBlocked = false; // Check if spawn area (top 3 rows) has any blocks that would prevent new shapes for (var row = 0; row < 3; row++) { for (var col = 0; col < gridWidth; col++) { if (grid[row][col]) { spawnAreaBlocked = true; break; } } if (spawnAreaBlocked) break; } // Game over if spawn area is blocked or stability is too low if (spawnAreaBlocked || stability < 10 && placedShapes.length > 0) { LK.showGameOver(); } }; // Initialize display updateScore(); updateStability(); updateInventoryDisplay(); updateHeight();
===================================================================
--- original.js
+++ change.js
@@ -321,11 +321,42 @@
self.lastMoveTime = currentTime;
// Check if can move down using collision detection
var canMoveDown = !self.checkCollision(self.gridX, self.gridY + 1);
if (canMoveDown) {
+ // Play light drop sound for step-down movement
+ LK.getSound('drop').play();
self.gridY++;
self.y = self.gridY * blockSize + blockSize / 2 + 200;
} else {
+ // Check if touching another shape (not ground) for contact sound
+ var touchingShape = false;
+ for (var j = 0; j < fallingShapes.length; j++) {
+ var otherShape = fallingShapes[j];
+ if (otherShape !== self && self.checkCollision(self.gridX, self.gridY + 1)) {
+ touchingShape = true;
+ break;
+ }
+ }
+ // Check if there are blocks below in grid
+ var touchingGrid = false;
+ 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] + 1;
+ if (blockY < gridHeight && blockY >= 0 && grid[blockY] && grid[blockY][blockX]) {
+ touchingGrid = true;
+ break;
+ }
+ }
+ // Play appropriate sound
+ if (touchingShape || touchingGrid) {
+ if (self.gridY + 1 >= gridHeight) {
+ // Landing on ground
+ LK.getSound('land').play();
+ } else {
+ // Contact with another shape
+ LK.getSound('contact').play();
+ }
+ }
// 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) {
@@ -450,8 +481,10 @@
self.blocks[i].offsetX = nextConfig[i][0];
self.blocks[i].offsetY = nextConfig[i][1];
}
}
+ // Play rotation sound effect
+ LK.getSound('rotate').play();
// Animate rotation on the block group with responsive timing
tween(self.blockGroup, {
rotation: self.blockGroup.rotation + Math.PI / 2
}, {
@@ -495,8 +528,10 @@
self.blocks[i].offsetX = nextConfig[i][0];
self.blocks[i].offsetY = nextConfig[i][1];
}
}
+ // Play rotation sound effect
+ LK.getSound('rotate').play();
// Animate rotation on the block group with responsive timing (counter-clockwise)
tween(self.blockGroup, {
rotation: self.blockGroup.rotation - Math.PI / 2
}, {