User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of undefined (reading 'length')' in or related to this line: 'var placedBlock = gameArea.attachAsset(blockColor, {' Line Number: 419
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of undefined (reading 'length')' in or related to this line: 'var placedBlock = gameArea.attachAsset(blockColor, {' Line Number: 419
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of undefined (reading 'length')' in or related to this line: 'var placedBlock = gameArea.attachAsset(blockColor, {' Line Number: 414
Code edit (1 edits merged)
Please save this source code
User prompt
Block Tower Challenge
Initial prompt
I want a good game like tetris, but in that game you have 10 levels max. In level five the level is harder then level 1, 2, 3 and four. Level ten is the hardest.
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { highScore: 0, level: 1 }); /**** * Classes ****/ var Block = Container.expand(function () { var self = Container.call(this); self.init = function (type, size) { self.blockType = type; self.blockSize = size || 80; self.blocks = []; // Create block shapes based on type // Each block type has a different pattern var color; var pattern; switch (type) { case 'I': // I block (4 blocks in a line) color = 'blockCyan'; pattern = [[0, 0], [0, 1], [0, 2], [0, 3]]; break; case 'J': // J block color = 'blockBlue'; pattern = [[0, 0], [0, 1], [0, 2], [-1, 2]]; break; case 'L': // L block color = 'blockOrange'; pattern = [[0, 0], [0, 1], [0, 2], [1, 2]]; break; case 'O': // O block (2x2 square) color = 'blockYellow'; pattern = [[0, 0], [1, 0], [0, 1], [1, 1]]; break; case 'S': // S block color = 'blockGreen'; pattern = [[0, 0], [1, 0], [0, 1], [-1, 1]]; break; case 'Z': // Z block color = 'blockRed'; pattern = [[0, 0], [-1, 0], [0, 1], [1, 1]]; break; case 'T': // T block color = 'blockPurple'; pattern = [[0, 0], [-1, 1], [0, 1], [1, 1]]; break; default: color = 'blockBlue'; pattern = [[0, 0]]; } // Create block parts based on pattern for (var i = 0; i < pattern.length; i++) { var blockPart = self.attachAsset(color, { anchorX: 0, anchorY: 0, x: pattern[i][0] * self.blockSize, y: pattern[i][1] * self.blockSize, width: self.blockSize, height: self.blockSize }); // Add a small border to each block by reducing its size a bit blockPart.scale.set(0.95); blockPart.position.x += self.blockSize * 0.025; blockPart.position.y += self.blockSize * 0.025; self.blocks.push({ sprite: blockPart, relX: pattern[i][0], relY: pattern[i][1] }); } return self; }; self.rotate = function (direction) { // Rotate the block (1 = clockwise, -1 = counter-clockwise) if (self.blockType === 'O') { return; } // O block doesn't need rotation for (var i = 0; i < self.blocks.length; i++) { var block = self.blocks[i]; var newRelX, newRelY; if (direction === 1) { // Clockwise rotation newRelX = -block.relY; newRelY = block.relX; } else { // Counter-clockwise rotation newRelX = block.relY; newRelY = -block.relX; } block.relX = newRelX; block.relY = newRelY; block.sprite.x = newRelX * self.blockSize; block.sprite.y = newRelY * self.blockSize; } // Play rotation sound LK.getSound('blockRotate').play(); }; self.getBlockPositions = function (offsetX, offsetY) { // Get the global positions of each block part var positions = []; for (var i = 0; i < self.blocks.length; i++) { var block = self.blocks[i]; positions.push({ x: Math.floor((self.x + block.relX * self.blockSize) / self.blockSize) + offsetX, y: Math.floor((self.y + block.relY * self.blockSize) / self.blockSize) + offsetY }); } return positions; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222222 }); /**** * Game Code ****/ // Game constants var BLOCK_SIZE = 80; // Block size in pixels var GRID_WIDTH = 10; // Width of the game grid var GRID_HEIGHT = 20; // Height of the game grid var GAME_AREA_WIDTH = GRID_WIDTH * BLOCK_SIZE; var GAME_AREA_HEIGHT = GRID_HEIGHT * BLOCK_SIZE; var BLOCK_TYPES = ['I', 'J', 'L', 'O', 'S', 'Z', 'T']; var LEVEL_SPEEDS = [800, 700, 600, 500, 400, 300, 250, 200, 150, 100, 50]; // Fall speed in ms per level // Game state variables var gameGrid = []; // 2D grid to track placed blocks var currentBlock = null; // Current falling block var nextBlock = null; // Next block to fall var gameTimer = null; // Timer for block movement var gameOver = false; var score = 0; var level = storage.level || 1; var linesCleared = 0; var linesToNextLevel = 10; // Lines needed to clear to advance a level var fallSpeed = LEVEL_SPEEDS[level - 1] || LEVEL_SPEEDS[0]; var touchStartX = 0; var touchStartY = 0; var isSwipeDown = false; // Create UI elements var scoreText = new Text2('Score: 0', { size: 50, fill: 0xFFFFFF }); scoreText.anchor.set(0, 0); LK.gui.topRight.addChild(scoreText); scoreText.position.x = -scoreText.width - 20; scoreText.position.y = 20; var levelText = new Text2('Level: ' + level, { size: 50, fill: 0xFFFFFF }); levelText.anchor.set(0, 0); LK.gui.topRight.addChild(levelText); levelText.position.x = -levelText.width - 20; levelText.position.y = 80; var nextBlockText = new Text2('Next:', { size: 40, fill: 0xFFFFFF }); nextBlockText.anchor.set(0, 0); LK.gui.topRight.addChild(nextBlockText); nextBlockText.position.x = -nextBlockText.width - 160; nextBlockText.position.y = 140; // Create game area container var gameArea = new Container(); game.addChild(gameArea); // Center the game area in the screen gameArea.x = (2048 - GAME_AREA_WIDTH) / 2; gameArea.y = (2732 - GAME_AREA_HEIGHT) / 2; // Create game area border var gameBorder = gameArea.attachAsset('gameAreaBorder', { anchorX: 0, anchorY: 0, width: GAME_AREA_WIDTH + 10, height: GAME_AREA_HEIGHT + 10, x: -5, y: -5 }); // Create next block display area var nextBlockArea = new Container(); game.addChild(nextBlockArea); nextBlockArea.x = gameArea.x + GAME_AREA_WIDTH + 100; nextBlockArea.y = gameArea.y + 200; var nextBlockBorder = nextBlockArea.attachAsset('nextBlockArea', { anchorX: 0, anchorY: 0, width: 200, height: 200, x: -5, y: -5 }); // Initialize game grid function initializeGrid() { gameGrid = []; for (var y = 0; y < GRID_HEIGHT; y++) { gameGrid[y] = []; for (var x = 0; x < GRID_WIDTH; x++) { gameGrid[y][x] = null; } } } // Create a random block function createRandomBlock() { var type = BLOCK_TYPES[Math.floor(Math.random() * BLOCK_TYPES.length)]; return new Block().init(type, BLOCK_SIZE); } // Start the game function startGame() { initializeGrid(); score = 0; linesCleared = 0; linesToNextLevel = 10; gameOver = false; // Update UI updateScoreDisplay(); updateLevelDisplay(); // Create initial blocks currentBlock = createRandomBlock(); nextBlock = createRandomBlock(); // Position the current block currentBlock.x = GAME_AREA_WIDTH / 2; currentBlock.y = 0; gameArea.addChild(currentBlock); // Display next block displayNextBlock(); // Start game timer if (gameTimer) { LK.clearInterval(gameTimer); } gameTimer = LK.setInterval(moveBlockDown, fallSpeed); // Play game music LK.playMusic('gameplayMusic'); } // Display the next block in the next block area function displayNextBlock() { // Clear previous next block display while (nextBlockArea.children.length > 1) { // Keep the border nextBlockArea.removeChild(nextBlockArea.children[1]); } // Add next block to the display area var displayBlock = createRandomBlock(); displayBlock.init(nextBlock.blockType, BLOCK_SIZE); // Center the block in the display area displayBlock.x = 100; displayBlock.y = 100; nextBlockArea.addChild(displayBlock); } // Check if a position is valid for the current block function isPositionValid(offsetX, offsetY) { var positions = currentBlock.getBlockPositions(offsetX, offsetY); for (var i = 0; i < positions.length; i++) { var pos = positions[i]; // Check if out of bounds if (pos.x < 0 || pos.x >= GRID_WIDTH || pos.y < 0 || pos.y >= GRID_HEIGHT) { return false; } // Check if position is already occupied if (pos.y >= 0 && gameGrid[pos.y][pos.x] !== null) { return false; } } return true; } // Move the current block down one position function moveBlockDown() { if (gameOver) { return; } if (isPositionValid(0, 1)) { currentBlock.y += BLOCK_SIZE; } else { // Block has reached the bottom or hit another block placeBlock(); // Check for completed lines checkLines(); // Create new block currentBlock = nextBlock; nextBlock = createRandomBlock(); // Position the new block currentBlock.x = GAME_AREA_WIDTH / 2; currentBlock.y = 0; gameArea.addChild(currentBlock); // Display next block displayNextBlock(); // Check if game over if (!isPositionValid(0, 0)) { handleGameOver(); } // Play drop sound LK.getSound('blockDrop').play(); } } // Move the block horizontally function moveBlockHorizontal(direction) { if (gameOver) { return; } if (isPositionValid(direction, 0)) { currentBlock.x += BLOCK_SIZE * direction; // Play move sound LK.getSound('blockMove').play(); } } // Rotate the current block function rotateBlock() { if (gameOver) { return; } // Backup current positions var originalPositions = currentBlock.getBlockPositions(0, 0); // Try to rotate currentBlock.rotate(1); // Check if rotation is valid if (!isPositionValid(0, 0)) { // If not valid, try with wall kicks (adjustments) var validPosition = false; // Try different offsets for wall kicks var wallKickOffsets = [{ x: -1, y: 0 }, { x: 1, y: 0 }, { x: -2, y: 0 }, { x: 2, y: 0 }, { x: 0, y: -1 }, { x: 0, y: -2 }]; for (var i = 0; i < wallKickOffsets.length; i++) { var offset = wallKickOffsets[i]; if (isPositionValid(offset.x, offset.y)) { currentBlock.x += offset.x * BLOCK_SIZE; currentBlock.y += offset.y * BLOCK_SIZE; validPosition = true; break; } } // If still not valid, revert the rotation if (!validPosition) { currentBlock.rotate(-1); // Rotate back } } } // Drop the block instantly to the bottom function dropBlock() { if (gameOver) { return; } // Find how far the block can drop var dropDistance = 0; while (isPositionValid(0, dropDistance + 1)) { dropDistance++; } if (dropDistance > 0) { currentBlock.y += dropDistance * BLOCK_SIZE; // Force placement moveBlockDown(); // Add bonus points for hard drop score += dropDistance * 2; updateScoreDisplay(); } } // Place the current block on the grid function placeBlock() { var positions = currentBlock.getBlockPositions(0, 0); for (var i = 0; i < positions.length; i++) { var pos = positions[i]; if (pos.y >= 0) { // Create a static block on the grid var blockColor = currentBlock.blocks[i].sprite.color || 'blockBlue'; gameGrid[pos.y][pos.x] = blockColor; // Create visual representation of placed block var placedBlock = gameArea.attachAsset(blockColor, { anchorX: 0, anchorY: 0, x: pos.x * BLOCK_SIZE, y: pos.y * BLOCK_SIZE, width: BLOCK_SIZE, height: BLOCK_SIZE }); // Add a small border to each block placedBlock.scale.set(0.95); placedBlock.position.x += BLOCK_SIZE * 0.025; placedBlock.position.y += BLOCK_SIZE * 0.025; // Tag it for identification placedBlock.gridX = pos.x; placedBlock.gridY = pos.y; } } // Remove the falling block gameArea.removeChild(currentBlock); } // Check for completed lines function checkLines() { var linesCompleted = 0; var linesToClear = []; for (var y = 0; y < GRID_HEIGHT; y++) { var lineComplete = true; for (var x = 0; x < GRID_WIDTH; x++) { if (gameGrid[y][x] === null) { lineComplete = false; break; } } if (lineComplete) { linesCompleted++; linesToClear.push(y); } } if (linesCompleted > 0) { // Clear the completed lines clearLines(linesToClear); // Update score var linePoints = 0; switch (linesCompleted) { case 1: linePoints = 100; break; case 2: linePoints = 300; break; case 3: linePoints = 500; break; case 4: linePoints = 800; break; // Tetris! } // Apply level multiplier score += linePoints * level; linesCleared += linesCompleted; linesToNextLevel -= linesCompleted; // Check for level up if (linesToNextLevel <= 0) { levelUp(); } updateScoreDisplay(); // Play line clear sound LK.getSound('lineClear').play(); } } // Clear the completed lines function clearLines(lines) { // First, remove blocks from the display for completed lines for (var i = 0; i < lines.length; i++) { var lineY = lines[i]; // Remove blocks in this line from display for (var j = gameArea.children.length - 1; j >= 0; j--) { var child = gameArea.children[j]; if (child.gridY !== undefined && child.gridY === lineY) { gameArea.removeChild(child); } } } // Update the grid and shift blocks down for (var i = 0; i < lines.length; i++) { var lineY = lines[i]; // Shift grid down for (var y = lineY; y > 0; y--) { for (var x = 0; x < GRID_WIDTH; x++) { gameGrid[y][x] = gameGrid[y - 1][x]; } } // Clear top line for (var x = 0; x < GRID_WIDTH; x++) { gameGrid[0][x] = null; } // Update visual blocks for (var j = 0; j < gameArea.children.length; j++) { var child = gameArea.children[j]; if (child.gridY !== undefined) { if (child.gridY < lineY) { child.gridY++; child.y += BLOCK_SIZE; } } } // Adjust remaining lines to clear for (var j = i + 1; j < lines.length; j++) { if (lines[j] < lineY) { lines[j]++; } } } } // Level up function levelUp() { level++; if (level > 10) { level = 10; } // Cap at level 10 // Update level display updateLevelDisplay(); // Reset lines to next level linesToNextLevel = 10; // Update speed fallSpeed = LEVEL_SPEEDS[level - 1] || LEVEL_SPEEDS[0]; if (gameTimer) { LK.clearInterval(gameTimer); } gameTimer = LK.setInterval(moveBlockDown, fallSpeed); // Play level up sound LK.getSound('levelUp').play(); // Flash screen for level up LK.effects.flashScreen(0x33cc33, 500); } // Update the score display function updateScoreDisplay() { scoreText.setText('Score: ' + score); scoreText.position.x = -scoreText.width - 20; } // Update the level display function updateLevelDisplay() { levelText.setText('Level: ' + level + ' (' + linesToNextLevel + ' lines)'); levelText.position.x = -levelText.width - 20; } // Handle game over function handleGameOver() { gameOver = true; // Clear game timer if (gameTimer) { LK.clearInterval(gameTimer); } // Update high score if needed if (score > storage.highScore) { storage.highScore = score; } // Save current level storage.level = level; // Show game over screen LK.showGameOver(); } // Touch handling for the game game.down = function (x, y, obj) { if (gameOver) { return; } touchStartX = x; touchStartY = y; isSwipeDown = false; }; game.move = function (x, y, obj) { if (gameOver) { return; } if (touchStartX !== null) { var deltaX = x - touchStartX; var deltaY = y - touchStartY; // Detect horizontal swipe if (Math.abs(deltaX) > 50 && Math.abs(deltaY) < 50) { if (deltaX < 0) { moveBlockHorizontal(-1); // Move left } else { moveBlockHorizontal(1); // Move right } touchStartX = x; touchStartY = y; } // Detect downward swipe if (deltaY > 100 && !isSwipeDown) { dropBlock(); // Hard drop isSwipeDown = true; } } }; game.up = function (x, y, obj) { if (gameOver) { return; } var deltaX = x - touchStartX; var deltaY = y - touchStartY; // Tap detection (little movement) if (Math.abs(deltaX) < 30 && Math.abs(deltaY) < 30) { rotateBlock(); } touchStartX = null; touchStartY = null; }; game.update = function () { // This function is called every frame // Not needed for core gameplay as we use timers for block movement }; // Start the game startGame();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0,
level: 1
});
/****
* Classes
****/
var Block = Container.expand(function () {
var self = Container.call(this);
self.init = function (type, size) {
self.blockType = type;
self.blockSize = size || 80;
self.blocks = [];
// Create block shapes based on type
// Each block type has a different pattern
var color;
var pattern;
switch (type) {
case 'I':
// I block (4 blocks in a line)
color = 'blockCyan';
pattern = [[0, 0], [0, 1], [0, 2], [0, 3]];
break;
case 'J':
// J block
color = 'blockBlue';
pattern = [[0, 0], [0, 1], [0, 2], [-1, 2]];
break;
case 'L':
// L block
color = 'blockOrange';
pattern = [[0, 0], [0, 1], [0, 2], [1, 2]];
break;
case 'O':
// O block (2x2 square)
color = 'blockYellow';
pattern = [[0, 0], [1, 0], [0, 1], [1, 1]];
break;
case 'S':
// S block
color = 'blockGreen';
pattern = [[0, 0], [1, 0], [0, 1], [-1, 1]];
break;
case 'Z':
// Z block
color = 'blockRed';
pattern = [[0, 0], [-1, 0], [0, 1], [1, 1]];
break;
case 'T':
// T block
color = 'blockPurple';
pattern = [[0, 0], [-1, 1], [0, 1], [1, 1]];
break;
default:
color = 'blockBlue';
pattern = [[0, 0]];
}
// Create block parts based on pattern
for (var i = 0; i < pattern.length; i++) {
var blockPart = self.attachAsset(color, {
anchorX: 0,
anchorY: 0,
x: pattern[i][0] * self.blockSize,
y: pattern[i][1] * self.blockSize,
width: self.blockSize,
height: self.blockSize
});
// Add a small border to each block by reducing its size a bit
blockPart.scale.set(0.95);
blockPart.position.x += self.blockSize * 0.025;
blockPart.position.y += self.blockSize * 0.025;
self.blocks.push({
sprite: blockPart,
relX: pattern[i][0],
relY: pattern[i][1]
});
}
return self;
};
self.rotate = function (direction) {
// Rotate the block (1 = clockwise, -1 = counter-clockwise)
if (self.blockType === 'O') {
return;
} // O block doesn't need rotation
for (var i = 0; i < self.blocks.length; i++) {
var block = self.blocks[i];
var newRelX, newRelY;
if (direction === 1) {
// Clockwise rotation
newRelX = -block.relY;
newRelY = block.relX;
} else {
// Counter-clockwise rotation
newRelX = block.relY;
newRelY = -block.relX;
}
block.relX = newRelX;
block.relY = newRelY;
block.sprite.x = newRelX * self.blockSize;
block.sprite.y = newRelY * self.blockSize;
}
// Play rotation sound
LK.getSound('blockRotate').play();
};
self.getBlockPositions = function (offsetX, offsetY) {
// Get the global positions of each block part
var positions = [];
for (var i = 0; i < self.blocks.length; i++) {
var block = self.blocks[i];
positions.push({
x: Math.floor((self.x + block.relX * self.blockSize) / self.blockSize) + offsetX,
y: Math.floor((self.y + block.relY * self.blockSize) / self.blockSize) + offsetY
});
}
return positions;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// Game constants
var BLOCK_SIZE = 80; // Block size in pixels
var GRID_WIDTH = 10; // Width of the game grid
var GRID_HEIGHT = 20; // Height of the game grid
var GAME_AREA_WIDTH = GRID_WIDTH * BLOCK_SIZE;
var GAME_AREA_HEIGHT = GRID_HEIGHT * BLOCK_SIZE;
var BLOCK_TYPES = ['I', 'J', 'L', 'O', 'S', 'Z', 'T'];
var LEVEL_SPEEDS = [800, 700, 600, 500, 400, 300, 250, 200, 150, 100, 50]; // Fall speed in ms per level
// Game state variables
var gameGrid = []; // 2D grid to track placed blocks
var currentBlock = null; // Current falling block
var nextBlock = null; // Next block to fall
var gameTimer = null; // Timer for block movement
var gameOver = false;
var score = 0;
var level = storage.level || 1;
var linesCleared = 0;
var linesToNextLevel = 10; // Lines needed to clear to advance a level
var fallSpeed = LEVEL_SPEEDS[level - 1] || LEVEL_SPEEDS[0];
var touchStartX = 0;
var touchStartY = 0;
var isSwipeDown = false;
// Create UI elements
var scoreText = new Text2('Score: 0', {
size: 50,
fill: 0xFFFFFF
});
scoreText.anchor.set(0, 0);
LK.gui.topRight.addChild(scoreText);
scoreText.position.x = -scoreText.width - 20;
scoreText.position.y = 20;
var levelText = new Text2('Level: ' + level, {
size: 50,
fill: 0xFFFFFF
});
levelText.anchor.set(0, 0);
LK.gui.topRight.addChild(levelText);
levelText.position.x = -levelText.width - 20;
levelText.position.y = 80;
var nextBlockText = new Text2('Next:', {
size: 40,
fill: 0xFFFFFF
});
nextBlockText.anchor.set(0, 0);
LK.gui.topRight.addChild(nextBlockText);
nextBlockText.position.x = -nextBlockText.width - 160;
nextBlockText.position.y = 140;
// Create game area container
var gameArea = new Container();
game.addChild(gameArea);
// Center the game area in the screen
gameArea.x = (2048 - GAME_AREA_WIDTH) / 2;
gameArea.y = (2732 - GAME_AREA_HEIGHT) / 2;
// Create game area border
var gameBorder = gameArea.attachAsset('gameAreaBorder', {
anchorX: 0,
anchorY: 0,
width: GAME_AREA_WIDTH + 10,
height: GAME_AREA_HEIGHT + 10,
x: -5,
y: -5
});
// Create next block display area
var nextBlockArea = new Container();
game.addChild(nextBlockArea);
nextBlockArea.x = gameArea.x + GAME_AREA_WIDTH + 100;
nextBlockArea.y = gameArea.y + 200;
var nextBlockBorder = nextBlockArea.attachAsset('nextBlockArea', {
anchorX: 0,
anchorY: 0,
width: 200,
height: 200,
x: -5,
y: -5
});
// Initialize game grid
function initializeGrid() {
gameGrid = [];
for (var y = 0; y < GRID_HEIGHT; y++) {
gameGrid[y] = [];
for (var x = 0; x < GRID_WIDTH; x++) {
gameGrid[y][x] = null;
}
}
}
// Create a random block
function createRandomBlock() {
var type = BLOCK_TYPES[Math.floor(Math.random() * BLOCK_TYPES.length)];
return new Block().init(type, BLOCK_SIZE);
}
// Start the game
function startGame() {
initializeGrid();
score = 0;
linesCleared = 0;
linesToNextLevel = 10;
gameOver = false;
// Update UI
updateScoreDisplay();
updateLevelDisplay();
// Create initial blocks
currentBlock = createRandomBlock();
nextBlock = createRandomBlock();
// Position the current block
currentBlock.x = GAME_AREA_WIDTH / 2;
currentBlock.y = 0;
gameArea.addChild(currentBlock);
// Display next block
displayNextBlock();
// Start game timer
if (gameTimer) {
LK.clearInterval(gameTimer);
}
gameTimer = LK.setInterval(moveBlockDown, fallSpeed);
// Play game music
LK.playMusic('gameplayMusic');
}
// Display the next block in the next block area
function displayNextBlock() {
// Clear previous next block display
while (nextBlockArea.children.length > 1) {
// Keep the border
nextBlockArea.removeChild(nextBlockArea.children[1]);
}
// Add next block to the display area
var displayBlock = createRandomBlock();
displayBlock.init(nextBlock.blockType, BLOCK_SIZE);
// Center the block in the display area
displayBlock.x = 100;
displayBlock.y = 100;
nextBlockArea.addChild(displayBlock);
}
// Check if a position is valid for the current block
function isPositionValid(offsetX, offsetY) {
var positions = currentBlock.getBlockPositions(offsetX, offsetY);
for (var i = 0; i < positions.length; i++) {
var pos = positions[i];
// Check if out of bounds
if (pos.x < 0 || pos.x >= GRID_WIDTH || pos.y < 0 || pos.y >= GRID_HEIGHT) {
return false;
}
// Check if position is already occupied
if (pos.y >= 0 && gameGrid[pos.y][pos.x] !== null) {
return false;
}
}
return true;
}
// Move the current block down one position
function moveBlockDown() {
if (gameOver) {
return;
}
if (isPositionValid(0, 1)) {
currentBlock.y += BLOCK_SIZE;
} else {
// Block has reached the bottom or hit another block
placeBlock();
// Check for completed lines
checkLines();
// Create new block
currentBlock = nextBlock;
nextBlock = createRandomBlock();
// Position the new block
currentBlock.x = GAME_AREA_WIDTH / 2;
currentBlock.y = 0;
gameArea.addChild(currentBlock);
// Display next block
displayNextBlock();
// Check if game over
if (!isPositionValid(0, 0)) {
handleGameOver();
}
// Play drop sound
LK.getSound('blockDrop').play();
}
}
// Move the block horizontally
function moveBlockHorizontal(direction) {
if (gameOver) {
return;
}
if (isPositionValid(direction, 0)) {
currentBlock.x += BLOCK_SIZE * direction;
// Play move sound
LK.getSound('blockMove').play();
}
}
// Rotate the current block
function rotateBlock() {
if (gameOver) {
return;
}
// Backup current positions
var originalPositions = currentBlock.getBlockPositions(0, 0);
// Try to rotate
currentBlock.rotate(1);
// Check if rotation is valid
if (!isPositionValid(0, 0)) {
// If not valid, try with wall kicks (adjustments)
var validPosition = false;
// Try different offsets for wall kicks
var wallKickOffsets = [{
x: -1,
y: 0
}, {
x: 1,
y: 0
}, {
x: -2,
y: 0
}, {
x: 2,
y: 0
}, {
x: 0,
y: -1
}, {
x: 0,
y: -2
}];
for (var i = 0; i < wallKickOffsets.length; i++) {
var offset = wallKickOffsets[i];
if (isPositionValid(offset.x, offset.y)) {
currentBlock.x += offset.x * BLOCK_SIZE;
currentBlock.y += offset.y * BLOCK_SIZE;
validPosition = true;
break;
}
}
// If still not valid, revert the rotation
if (!validPosition) {
currentBlock.rotate(-1); // Rotate back
}
}
}
// Drop the block instantly to the bottom
function dropBlock() {
if (gameOver) {
return;
}
// Find how far the block can drop
var dropDistance = 0;
while (isPositionValid(0, dropDistance + 1)) {
dropDistance++;
}
if (dropDistance > 0) {
currentBlock.y += dropDistance * BLOCK_SIZE;
// Force placement
moveBlockDown();
// Add bonus points for hard drop
score += dropDistance * 2;
updateScoreDisplay();
}
}
// Place the current block on the grid
function placeBlock() {
var positions = currentBlock.getBlockPositions(0, 0);
for (var i = 0; i < positions.length; i++) {
var pos = positions[i];
if (pos.y >= 0) {
// Create a static block on the grid
var blockColor = currentBlock.blocks[i].sprite.color || 'blockBlue';
gameGrid[pos.y][pos.x] = blockColor;
// Create visual representation of placed block
var placedBlock = gameArea.attachAsset(blockColor, {
anchorX: 0,
anchorY: 0,
x: pos.x * BLOCK_SIZE,
y: pos.y * BLOCK_SIZE,
width: BLOCK_SIZE,
height: BLOCK_SIZE
});
// Add a small border to each block
placedBlock.scale.set(0.95);
placedBlock.position.x += BLOCK_SIZE * 0.025;
placedBlock.position.y += BLOCK_SIZE * 0.025;
// Tag it for identification
placedBlock.gridX = pos.x;
placedBlock.gridY = pos.y;
}
}
// Remove the falling block
gameArea.removeChild(currentBlock);
}
// Check for completed lines
function checkLines() {
var linesCompleted = 0;
var linesToClear = [];
for (var y = 0; y < GRID_HEIGHT; y++) {
var lineComplete = true;
for (var x = 0; x < GRID_WIDTH; x++) {
if (gameGrid[y][x] === null) {
lineComplete = false;
break;
}
}
if (lineComplete) {
linesCompleted++;
linesToClear.push(y);
}
}
if (linesCompleted > 0) {
// Clear the completed lines
clearLines(linesToClear);
// Update score
var linePoints = 0;
switch (linesCompleted) {
case 1:
linePoints = 100;
break;
case 2:
linePoints = 300;
break;
case 3:
linePoints = 500;
break;
case 4:
linePoints = 800;
break;
// Tetris!
}
// Apply level multiplier
score += linePoints * level;
linesCleared += linesCompleted;
linesToNextLevel -= linesCompleted;
// Check for level up
if (linesToNextLevel <= 0) {
levelUp();
}
updateScoreDisplay();
// Play line clear sound
LK.getSound('lineClear').play();
}
}
// Clear the completed lines
function clearLines(lines) {
// First, remove blocks from the display for completed lines
for (var i = 0; i < lines.length; i++) {
var lineY = lines[i];
// Remove blocks in this line from display
for (var j = gameArea.children.length - 1; j >= 0; j--) {
var child = gameArea.children[j];
if (child.gridY !== undefined && child.gridY === lineY) {
gameArea.removeChild(child);
}
}
}
// Update the grid and shift blocks down
for (var i = 0; i < lines.length; i++) {
var lineY = lines[i];
// Shift grid down
for (var y = lineY; y > 0; y--) {
for (var x = 0; x < GRID_WIDTH; x++) {
gameGrid[y][x] = gameGrid[y - 1][x];
}
}
// Clear top line
for (var x = 0; x < GRID_WIDTH; x++) {
gameGrid[0][x] = null;
}
// Update visual blocks
for (var j = 0; j < gameArea.children.length; j++) {
var child = gameArea.children[j];
if (child.gridY !== undefined) {
if (child.gridY < lineY) {
child.gridY++;
child.y += BLOCK_SIZE;
}
}
}
// Adjust remaining lines to clear
for (var j = i + 1; j < lines.length; j++) {
if (lines[j] < lineY) {
lines[j]++;
}
}
}
}
// Level up
function levelUp() {
level++;
if (level > 10) {
level = 10;
} // Cap at level 10
// Update level display
updateLevelDisplay();
// Reset lines to next level
linesToNextLevel = 10;
// Update speed
fallSpeed = LEVEL_SPEEDS[level - 1] || LEVEL_SPEEDS[0];
if (gameTimer) {
LK.clearInterval(gameTimer);
}
gameTimer = LK.setInterval(moveBlockDown, fallSpeed);
// Play level up sound
LK.getSound('levelUp').play();
// Flash screen for level up
LK.effects.flashScreen(0x33cc33, 500);
}
// Update the score display
function updateScoreDisplay() {
scoreText.setText('Score: ' + score);
scoreText.position.x = -scoreText.width - 20;
}
// Update the level display
function updateLevelDisplay() {
levelText.setText('Level: ' + level + ' (' + linesToNextLevel + ' lines)');
levelText.position.x = -levelText.width - 20;
}
// Handle game over
function handleGameOver() {
gameOver = true;
// Clear game timer
if (gameTimer) {
LK.clearInterval(gameTimer);
}
// Update high score if needed
if (score > storage.highScore) {
storage.highScore = score;
}
// Save current level
storage.level = level;
// Show game over screen
LK.showGameOver();
}
// Touch handling for the game
game.down = function (x, y, obj) {
if (gameOver) {
return;
}
touchStartX = x;
touchStartY = y;
isSwipeDown = false;
};
game.move = function (x, y, obj) {
if (gameOver) {
return;
}
if (touchStartX !== null) {
var deltaX = x - touchStartX;
var deltaY = y - touchStartY;
// Detect horizontal swipe
if (Math.abs(deltaX) > 50 && Math.abs(deltaY) < 50) {
if (deltaX < 0) {
moveBlockHorizontal(-1); // Move left
} else {
moveBlockHorizontal(1); // Move right
}
touchStartX = x;
touchStartY = y;
}
// Detect downward swipe
if (deltaY > 100 && !isSwipeDown) {
dropBlock(); // Hard drop
isSwipeDown = true;
}
}
};
game.up = function (x, y, obj) {
if (gameOver) {
return;
}
var deltaX = x - touchStartX;
var deltaY = y - touchStartY;
// Tap detection (little movement)
if (Math.abs(deltaX) < 30 && Math.abs(deltaY) < 30) {
rotateBlock();
}
touchStartX = null;
touchStartY = null;
};
game.update = function () {
// This function is called every frame
// Not needed for core gameplay as we use timers for block movement
};
// Start the game
startGame();