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();