/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Block = Container.expand(function (blockType) {
var self = Container.call(this);
self.blockType = blockType || 'block_I';
self.gridX = 0;
self.gridY = 0;
var blockGraphics = self.attachAsset(self.blockType, {
anchorX: 0,
anchorY: 0
});
self.setGridPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
self.x = GRID_OFFSET_X + gridX * BLOCK_SIZE;
self.y = GRID_OFFSET_Y + gridY * BLOCK_SIZE;
};
return self;
});
var Tetromino = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'I';
self.rotation = 0;
self.gridX = 7; // Start in middle of board
self.gridY = 0;
self.blocks = [];
self.fallTimer = 0;
self.fallSpeed = 40; // Frames until next fall (faster falling speed)
self.isPlaced = false; // Prevent multiple placement calls
// Tetromino shapes and rotations
self.shapes = {
'I': [[[1, 1, 1, 1]], [[1], [1], [1], [1]], [[1, 1, 1, 1]], [[1], [1], [1], [1]]],
'O': [[[1, 1], [1, 1]], [[1, 1], [1, 1]], [[1, 1], [1, 1]], [[1, 1], [1, 1]]],
'T': [[[0, 1, 0], [1, 1, 1]], [[1, 0], [1, 1], [1, 0]], [[1, 1, 1], [0, 1, 0]], [[0, 1], [1, 1], [0, 1]]],
'S': [[[0, 1, 1], [1, 1, 0]], [[1, 0], [1, 1], [0, 1]], [[0, 1, 1], [1, 1, 0]], [[1, 0], [1, 1], [0, 1]]],
'Z': [[[1, 1, 0], [0, 1, 1]], [[0, 1], [1, 1], [1, 0]], [[1, 1, 0], [0, 1, 1]], [[0, 1], [1, 1], [1, 0]]],
'J': [[[1, 0, 0], [1, 1, 1]], [[1, 1], [1, 0], [1, 0]], [[1, 1, 1], [0, 0, 1]], [[0, 1], [0, 1], [1, 1]]],
'L': [[[0, 0, 1], [1, 1, 1]], [[1, 0], [1, 0], [1, 1]], [[1, 1, 1], [1, 0, 0]], [[1, 1], [0, 1], [0, 1]]]
};
self.getBlockType = function () {
// Map each tetromino type to its corresponding block asset
var blockTypeMap = {
'I': 'block_I',
'O': 'block_O',
'T': 'block_T',
'S': 'block_S',
'Z': 'block_Z',
'J': 'block_J',
'L': 'block_L'
};
return blockTypeMap[self.type] || 'block_I';
};
self.createBlocks = function () {
// Clear existing blocks
for (var i = 0; i < self.blocks.length; i++) {
self.blocks[i].destroy();
}
self.blocks = [];
// Ensure we have a valid shape for this type
if (!self.shapes[self.type]) {
console.error('Invalid tetromino type:', self.type);
return;
}
var shape = self.shapes[self.type][self.rotation];
var blockType = self.getBlockType();
for (var y = 0; y < shape.length; y++) {
for (var x = 0; x < shape[y].length; x++) {
if (shape[y][x] === 1) {
var block = new Block(blockType);
block.setGridPosition(self.gridX + x, self.gridY + y);
self.blocks.push(block);
game.addChild(block);
}
}
}
};
self.updatePosition = function () {
var shape = self.shapes[self.type][self.rotation];
var blockIndex = 0;
for (var y = 0; y < shape.length; y++) {
for (var x = 0; x < shape[y].length; x++) {
if (shape[y][x] === 1) {
if (blockIndex < self.blocks.length) {
self.blocks[blockIndex].setGridPosition(self.gridX + x, self.gridY + y);
}
blockIndex++;
}
}
}
};
self.canMoveTo = function (newX, newY, newRotation) {
var testRotation = newRotation !== undefined ? newRotation : self.rotation;
var shape = self.shapes[self.type][testRotation];
for (var y = 0; y < shape.length; y++) {
for (var x = 0; x < shape[y].length; x++) {
if (shape[y][x] === 1) {
var checkX = newX + x;
var checkY = newY + y;
// Check bounds
if (checkX < 0 || checkX >= GRID_WIDTH || checkY >= GRID_HEIGHT) {
return false;
}
// Check collision with placed blocks
if (checkY >= 0 && gameGrid[checkY] && gameGrid[checkY][checkX]) {
return false;
}
}
}
}
return true;
};
self.move = function (deltaX, deltaY) {
if (self.canMoveTo(self.gridX + deltaX, self.gridY + deltaY)) {
self.gridX += deltaX;
self.gridY += deltaY;
self.updatePosition();
return true;
}
return false;
};
self.rotate = function () {
var newRotation = (self.rotation + 1) % 4;
if (self.canMoveTo(self.gridX, self.gridY, newRotation)) {
self.rotation = newRotation;
self.updatePosition();
// Create sparkle effect for rotation
for (var i = 0; i < self.blocks.length; i++) {
var block = self.blocks[i];
var sparkleCount = 8;
for (var j = 0; j < sparkleCount; j++) {
var sparkle = LK.getAsset('grid_border', {
x: block.x + BLOCK_SIZE / 2,
y: block.y + BLOCK_SIZE / 2,
scaleX: 5,
scaleY: 5,
alpha: 1.5,
tint: 0xFFFFFF
});
game.addChild(sparkle);
// Random sparkle position around block
var angle = j / sparkleCount * Math.PI * 2;
var distance = BLOCK_SIZE * 1.0;
var targetX = sparkle.x + Math.cos(angle) * distance;
var targetY = sparkle.y + Math.sin(angle) * distance;
// Animate sparkle outward and fade
tween(sparkle, {
x: targetX,
y: targetY,
alpha: 0.3,
scaleX: 0.3,
scaleY: 0.3
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
if (sparkle && sparkle.destroy) {
sparkle.destroy();
}
}
});
}
}
LK.getSound('sparkle_effect').play();
LK.getSound('piece_rotate').play();
return true;
}
return false;
};
self.update = function () {
self.fallTimer++;
if (self.fallTimer >= self.fallSpeed) {
self.fallTimer = 0;
if (!self.move(0, 1)) {
// Piece has landed
self.placePiece();
}
}
};
self.placePiece = function () {
// Prevent multiple calls
if (self.isPlaced) return;
self.isPlaced = true;
// Add blocks to grid and create sparkle effects
for (var i = 0; i < self.blocks.length; i++) {
var block = self.blocks[i];
if (block.gridY >= 0) {
if (!gameGrid[block.gridY]) gameGrid[block.gridY] = [];
gameGrid[block.gridY][block.gridX] = block;
// Create sparkle effect around the block
var sparkleCount = 8;
for (var j = 0; j < sparkleCount; j++) {
var sparkle = LK.getAsset('grid_border', {
x: block.x + BLOCK_SIZE / 2,
y: block.y + BLOCK_SIZE / 2,
scaleX: 8,
scaleY: 8,
alpha: 1.5,
tint: 0xFFFFFF
});
game.addChild(sparkle);
// Random sparkle position around block
var angle = j / sparkleCount * Math.PI * 2;
var distance = BLOCK_SIZE * 1.0;
var targetX = sparkle.x + Math.cos(angle) * distance;
var targetY = sparkle.y + Math.sin(angle) * distance;
// Animate sparkle outward and fade
tween(sparkle, {
x: targetX,
y: targetY,
alpha: 0.3,
scaleX: 0.3,
scaleY: 0.3
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
if (sparkle && sparkle.destroy) {
sparkle.destroy();
}
}
});
}
}
}
// Remove blocks from this tetromino since they're now part of the grid
self.blocks = [];
LK.getSound('sparkle_effect').play();
LK.getSound('piece_drop').play();
checkCompleteLines();
// Clear current piece reference first
currentPiece = null;
// Spawn new piece with a small delay to ensure proper sequencing
LK.setTimeout(function () {
spawnNewPiece();
}, 50);
};
self.destroy = function () {
// Clean up all blocks when tetromino is destroyed
for (var i = 0; i < self.blocks.length; i++) {
if (self.blocks[i] && self.blocks[i].destroy) {
self.blocks[i].destroy();
}
}
self.blocks = [];
// Call parent destroy
Container.prototype.destroy.call(self);
};
return self;
});
/****
* Initialize Game
****/
// Game constants
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Orange
// Blue
// Red
// Green
// Purple
// Yellow
// Cyan
// Tetromino blocks - using different colors for each piece type
// Game constants
var GRID_WIDTH = 14;
var GRID_HEIGHT = 24;
var BLOCK_SIZE = 100;
var GRID_OFFSET_X = (2048 - GRID_WIDTH * BLOCK_SIZE) / 2;
var GRID_OFFSET_Y = 100;
// Game variables
var gameGrid = [];
var currentPiece = null;
var nextPieceType = null;
var gameLevel = 1;
var linesCleared = 0;
var lastMoveTime = 0;
var moveDelay = 100; // Milliseconds between moves
// Initialize grid
for (var y = 0; y < GRID_HEIGHT; y++) {
gameGrid[y] = [];
for (var x = 0; x < GRID_WIDTH; x++) {
gameGrid[y][x] = null;
}
}
// Tetromino types
var pieceTypes = ['I', 'O', 'T', 'S', 'Z', 'J', 'L'];
function getRandomPieceType() {
return pieceTypes[Math.floor(Math.random() * pieceTypes.length)];
}
function spawnNewPiece() {
// Don't spawn if there's already an active piece or if one is being placed
if (currentPiece && !currentPiece.isPlaced) {
return;
}
if (!nextPieceType) {
nextPieceType = getRandomPieceType();
}
// Create new piece and set as current immediately to prevent double spawning
var newPiece = new Tetromino(nextPieceType);
currentPiece = newPiece;
game.addChild(currentPiece);
currentPiece.createBlocks();
nextPieceType = getRandomPieceType();
// Check game over
if (!currentPiece.canMoveTo(currentPiece.gridX, currentPiece.gridY)) {
LK.showGameOver();
return;
}
}
function checkCompleteLines() {
var completedLines = [];
// Find completed lines
for (var y = 0; y < GRID_HEIGHT; y++) {
var lineComplete = true;
for (var x = 0; x < GRID_WIDTH; x++) {
if (!gameGrid[y][x]) {
lineComplete = false;
break;
}
}
if (lineComplete) {
completedLines.push(y);
}
}
if (completedLines.length > 0) {
// Sort completed lines from bottom to top (highest Y to lowest Y)
// This ensures we process lines from bottom up, keeping indices valid
completedLines.sort(function (a, b) {
return b - a;
});
// Remove completed lines
for (var i = 0; i < completedLines.length; i++) {
var lineY = completedLines[i];
// Create particle explosion effect for this line
for (var x = 0; x < GRID_WIDTH; x++) {
if (gameGrid[lineY][x]) {
var block = gameGrid[lineY][x];
// Create sparkle effect for cleared line blocks
var sparkleCount = 8; //{2X_new}
for (var j = 0; j < sparkleCount; j++) {
//{2X_new2}
var sparkle = LK.getAsset('grid_border', {
//{2X_new3}
x: block.x + BLOCK_SIZE / 2,
//{2X_new4}
y: block.y + BLOCK_SIZE / 2,
//{2X_new5}
scaleX: 5,
//{2X_new6}
scaleY: 5,
//{2X_new7}
alpha: 1.5,
//{2X_new8}
tint: 0xFFFFFF //{2X_new9}
}); //{2X_new10}
game.addChild(sparkle); //{2X_new11}
// Random sparkle position around block//{2X_new12}
var angle = j / sparkleCount * Math.PI * 2; //{2X_new13}
var distance = BLOCK_SIZE * 1.0; //{2X_new14}
var targetX = sparkle.x + Math.cos(angle) * distance; //{2X_new15}
var targetY = sparkle.y + Math.sin(angle) * distance; //{2X_new16}
// Animate sparkle outward and fade//{2X_new17}
tween(sparkle, {
//{2X_new18}
x: targetX,
//{2X_new19}
y: targetY,
//{2X_new20}
alpha: 0.3,
//{2X_new21}
scaleX: 0.3,
//{2X_new22}
scaleY: 0.3 //{2X_new23}
}, {
//{2X_new24}
duration: 200,
//{2X_new25}
easing: tween.easeOut,
//{2X_new26}
onFinish: function onFinish() {
//{2X_new27}
if (sparkle && sparkle.destroy) {
//{2X_new28}
sparkle.destroy(); //{2X_new29}
} //{2X_new30}
} //{2X_new31}
}); //{2X_new32}
} //{2X_new33}
// Create explosion effect before destroying
var originalX = block.x;
var originalY = block.y;
var originalScaleX = block.scaleX;
var originalScaleY = block.scaleY;
var originalAlpha = block.alpha;
// Random direction for explosion
var randomX = originalX + (Math.random() - 0.5) * 400;
var randomY = originalY + (Math.random() - 0.5) * 200;
// Animate block flying away and fading
tween(block, {
x: randomX,
y: randomY,
scaleX: originalScaleX * 0.2,
scaleY: originalScaleY * 0.2,
alpha: 0,
rotation: (Math.random() - 0.5) * Math.PI * 2
}, {
duration: 30,
easing: tween.easeOut,
onFinish: function onFinish() {
if (block && block.destroy) {
block.destroy();
}
}
});
gameGrid[lineY][x] = null;
}
}
// Move lines down
for (var y = lineY; y > 0; y--) {
for (var x = 0; x < GRID_WIDTH; x++) {
gameGrid[y][x] = gameGrid[y - 1][x];
if (gameGrid[y][x]) {
gameGrid[y][x].setGridPosition(x, y);
}
}
}
// Clear top line
for (var x = 0; x < GRID_WIDTH; x++) {
gameGrid[0][x] = null;
}
}
// Update score
linesCleared += completedLines.length;
var points = 0;
switch (completedLines.length) {
case 1:
points = 100;
break;
case 2:
points = 300;
break;
case 3:
points = 500;
break;
case 4:
points = 800;
break;
}
LK.setScore(LK.getScore() + points * gameLevel);
// Level up every 10 lines
var newLevel = Math.floor(linesCleared / 10) + 1;
if (newLevel > gameLevel) {
gameLevel = newLevel;
}
// Create border sparkle effect using line clear sparkles when lines are cleared
var borderSparkles = [];
// Left border sparkles
for (var i = 0; i < 10; i++) {
var sparkle = LK.getAsset('grid_border', {
x: GRID_OFFSET_X - 20,
y: GRID_OFFSET_Y + i * (GRID_HEIGHT * BLOCK_SIZE / 10),
scaleX: 5,
scaleY: 5,
alpha: 1.5,
tint: 0xFFFFFF
});
game.addChild(sparkle);
borderSparkles.push(sparkle);
// Random sparkle position for border effect//{3K_new}
var angle = i / 10 * Math.PI * 2; //{3K_new2}
var distance = 50; //{3K_new3}
var targetX = sparkle.x + Math.cos(angle) * distance; //{3K_new4}
var targetY = sparkle.y + Math.sin(angle) * distance; //{3K_new5}
// Animate sparkle outward and fade//{3K_new6}
tween(sparkle, {
//{3K_new7}
x: targetX,
//{3K_new8}
y: targetY,
//{3K_new9}
alpha: 0.3,
//{3K_new10}
scaleX: 0.3,
//{3K_new11}
scaleY: 0.3 //{3K_new12}
}, {
//{3K_new13}
duration: 200,
//{3K_new14}
easing: tween.easeOut,
//{3K_new15}
onFinish: function onFinish() {
//{3K_new16}
if (sparkle && sparkle.destroy) {
//{3K_new17}
sparkle.destroy(); //{3K_new18}
} //{3K_new19}
} //{3K_new20}
}); //{3K_new21}
}
// Right border sparkles
for (var i = 0; i < 10; i++) {
var sparkle = LK.getAsset('grid_border', {
x: GRID_OFFSET_X + GRID_WIDTH * BLOCK_SIZE + 20,
y: GRID_OFFSET_Y + i * (GRID_HEIGHT * BLOCK_SIZE / 10),
scaleX: 5,
scaleY: 5,
alpha: 1.5,
tint: 0xFFFFFF
});
game.addChild(sparkle);
borderSparkles.push(sparkle);
// Random sparkle position for border effect//{3V_new}
var angle = i / 10 * Math.PI * 2; //{3V_new2}
var distance = 50; //{3V_new3}
var targetX = sparkle.x + Math.cos(angle) * distance; //{3V_new4}
var targetY = sparkle.y + Math.sin(angle) * distance; //{3V_new5}
// Animate sparkle outward and fade//{3V_new6}
tween(sparkle, {
//{3V_new7}
x: targetX,
//{3V_new8}
y: targetY,
//{3V_new9}
alpha: 0.3,
//{3V_new10}
scaleX: 0.3,
//{3V_new11}
scaleY: 0.3 //{3V_new12}
}, {
//{3V_new13}
duration: 200,
//{3V_new14}
easing: tween.easeOut,
//{3V_new15}
onFinish: function onFinish() {
//{3V_new16}
if (sparkle && sparkle.destroy) {
//{3V_new17}
sparkle.destroy(); //{3V_new18}
} //{3V_new19}
} //{3V_new20}
}); //{3V_new21}
}
LK.getSound('sparkle_effect').play(); //{3j_new}
LK.getSound('line_clear').play();
}
}
// Create score display
var scoreText = new Text2('Score: 0', {
size: 40,
fill: 0xFFFFFF
});
scoreText.anchor.set(1, 0);
scoreText.x = -50;
LK.gui.topRight.addChild(scoreText);
var levelText = new Text2('Level: 1', {
size: 40,
fill: 0xFFFFFF
});
levelText.anchor.set(1, 0);
levelText.x = -50;
levelText.y = 60;
LK.gui.topRight.addChild(levelText);
var linesText = new Text2('Lines: 0', {
size: 40,
fill: 0xFFFFFF
});
linesText.anchor.set(1, 0);
linesText.x = -50;
linesText.y = 120;
LK.gui.topRight.addChild(linesText);
// Add background color asset
var backgroundColorAsset = LK.getAsset('background_color', {
x: 0,
y: 0
});
game.addChild(backgroundColorAsset);
// Draw grid borders - vertical lines only for game area height
for (var x = 0; x <= GRID_WIDTH; x++) {
if (x === 0 || x === GRID_WIDTH) {
var border = LK.getAsset('grid_border', {
x: GRID_OFFSET_X + x * BLOCK_SIZE - 4,
y: GRID_OFFSET_Y,
scaleX: 1,
scaleY: GRID_HEIGHT * BLOCK_SIZE / 8
});
game.addChild(border);
}
}
for (var y = GRID_HEIGHT; y <= GRID_HEIGHT; y++) {
for (var x = 0; x < GRID_WIDTH; x++) {
var border = LK.getAsset('grid_border', {
x: GRID_OFFSET_X + x * BLOCK_SIZE,
y: GRID_OFFSET_Y + y * BLOCK_SIZE,
scaleX: BLOCK_SIZE / 8,
scaleY: 1
});
game.addChild(border);
}
}
// Spawn first piece
spawnNewPiece();
// Start background music
LK.playMusic('tetris_theme');
game.down = function (x, y, obj) {
if (!currentPiece) return;
var now = Date.now();
if (now - lastMoveTime < moveDelay) return;
lastMoveTime = now;
// Divide screen into zones for controls
var leftZone = 2048 * 0.25;
var rightZone = 2048 * 0.75;
var rotateZone = 2732 * 0.7;
if (x < leftZone) {
// Left movement
currentPiece.move(-1, 0);
} else if (x > rightZone) {
// Right movement
currentPiece.move(1, 0);
} else if (y > rotateZone) {
// Drop piece faster
currentPiece.move(0, 1);
} else {
// Rotate piece
currentPiece.rotate();
}
};
game.update = function () {
if (currentPiece) {
// Update fall speed based on level
currentPiece.fallSpeed = Math.max(10, 40 - (gameLevel - 1) * 5);
}
// Update UI
scoreText.setText('Score: ' + LK.getScore());
levelText.setText('Level: ' + gameLevel);
linesText.setText('Lines: ' + linesCleared);
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Block = Container.expand(function (blockType) {
var self = Container.call(this);
self.blockType = blockType || 'block_I';
self.gridX = 0;
self.gridY = 0;
var blockGraphics = self.attachAsset(self.blockType, {
anchorX: 0,
anchorY: 0
});
self.setGridPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
self.x = GRID_OFFSET_X + gridX * BLOCK_SIZE;
self.y = GRID_OFFSET_Y + gridY * BLOCK_SIZE;
};
return self;
});
var Tetromino = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'I';
self.rotation = 0;
self.gridX = 7; // Start in middle of board
self.gridY = 0;
self.blocks = [];
self.fallTimer = 0;
self.fallSpeed = 40; // Frames until next fall (faster falling speed)
self.isPlaced = false; // Prevent multiple placement calls
// Tetromino shapes and rotations
self.shapes = {
'I': [[[1, 1, 1, 1]], [[1], [1], [1], [1]], [[1, 1, 1, 1]], [[1], [1], [1], [1]]],
'O': [[[1, 1], [1, 1]], [[1, 1], [1, 1]], [[1, 1], [1, 1]], [[1, 1], [1, 1]]],
'T': [[[0, 1, 0], [1, 1, 1]], [[1, 0], [1, 1], [1, 0]], [[1, 1, 1], [0, 1, 0]], [[0, 1], [1, 1], [0, 1]]],
'S': [[[0, 1, 1], [1, 1, 0]], [[1, 0], [1, 1], [0, 1]], [[0, 1, 1], [1, 1, 0]], [[1, 0], [1, 1], [0, 1]]],
'Z': [[[1, 1, 0], [0, 1, 1]], [[0, 1], [1, 1], [1, 0]], [[1, 1, 0], [0, 1, 1]], [[0, 1], [1, 1], [1, 0]]],
'J': [[[1, 0, 0], [1, 1, 1]], [[1, 1], [1, 0], [1, 0]], [[1, 1, 1], [0, 0, 1]], [[0, 1], [0, 1], [1, 1]]],
'L': [[[0, 0, 1], [1, 1, 1]], [[1, 0], [1, 0], [1, 1]], [[1, 1, 1], [1, 0, 0]], [[1, 1], [0, 1], [0, 1]]]
};
self.getBlockType = function () {
// Map each tetromino type to its corresponding block asset
var blockTypeMap = {
'I': 'block_I',
'O': 'block_O',
'T': 'block_T',
'S': 'block_S',
'Z': 'block_Z',
'J': 'block_J',
'L': 'block_L'
};
return blockTypeMap[self.type] || 'block_I';
};
self.createBlocks = function () {
// Clear existing blocks
for (var i = 0; i < self.blocks.length; i++) {
self.blocks[i].destroy();
}
self.blocks = [];
// Ensure we have a valid shape for this type
if (!self.shapes[self.type]) {
console.error('Invalid tetromino type:', self.type);
return;
}
var shape = self.shapes[self.type][self.rotation];
var blockType = self.getBlockType();
for (var y = 0; y < shape.length; y++) {
for (var x = 0; x < shape[y].length; x++) {
if (shape[y][x] === 1) {
var block = new Block(blockType);
block.setGridPosition(self.gridX + x, self.gridY + y);
self.blocks.push(block);
game.addChild(block);
}
}
}
};
self.updatePosition = function () {
var shape = self.shapes[self.type][self.rotation];
var blockIndex = 0;
for (var y = 0; y < shape.length; y++) {
for (var x = 0; x < shape[y].length; x++) {
if (shape[y][x] === 1) {
if (blockIndex < self.blocks.length) {
self.blocks[blockIndex].setGridPosition(self.gridX + x, self.gridY + y);
}
blockIndex++;
}
}
}
};
self.canMoveTo = function (newX, newY, newRotation) {
var testRotation = newRotation !== undefined ? newRotation : self.rotation;
var shape = self.shapes[self.type][testRotation];
for (var y = 0; y < shape.length; y++) {
for (var x = 0; x < shape[y].length; x++) {
if (shape[y][x] === 1) {
var checkX = newX + x;
var checkY = newY + y;
// Check bounds
if (checkX < 0 || checkX >= GRID_WIDTH || checkY >= GRID_HEIGHT) {
return false;
}
// Check collision with placed blocks
if (checkY >= 0 && gameGrid[checkY] && gameGrid[checkY][checkX]) {
return false;
}
}
}
}
return true;
};
self.move = function (deltaX, deltaY) {
if (self.canMoveTo(self.gridX + deltaX, self.gridY + deltaY)) {
self.gridX += deltaX;
self.gridY += deltaY;
self.updatePosition();
return true;
}
return false;
};
self.rotate = function () {
var newRotation = (self.rotation + 1) % 4;
if (self.canMoveTo(self.gridX, self.gridY, newRotation)) {
self.rotation = newRotation;
self.updatePosition();
// Create sparkle effect for rotation
for (var i = 0; i < self.blocks.length; i++) {
var block = self.blocks[i];
var sparkleCount = 8;
for (var j = 0; j < sparkleCount; j++) {
var sparkle = LK.getAsset('grid_border', {
x: block.x + BLOCK_SIZE / 2,
y: block.y + BLOCK_SIZE / 2,
scaleX: 5,
scaleY: 5,
alpha: 1.5,
tint: 0xFFFFFF
});
game.addChild(sparkle);
// Random sparkle position around block
var angle = j / sparkleCount * Math.PI * 2;
var distance = BLOCK_SIZE * 1.0;
var targetX = sparkle.x + Math.cos(angle) * distance;
var targetY = sparkle.y + Math.sin(angle) * distance;
// Animate sparkle outward and fade
tween(sparkle, {
x: targetX,
y: targetY,
alpha: 0.3,
scaleX: 0.3,
scaleY: 0.3
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
if (sparkle && sparkle.destroy) {
sparkle.destroy();
}
}
});
}
}
LK.getSound('sparkle_effect').play();
LK.getSound('piece_rotate').play();
return true;
}
return false;
};
self.update = function () {
self.fallTimer++;
if (self.fallTimer >= self.fallSpeed) {
self.fallTimer = 0;
if (!self.move(0, 1)) {
// Piece has landed
self.placePiece();
}
}
};
self.placePiece = function () {
// Prevent multiple calls
if (self.isPlaced) return;
self.isPlaced = true;
// Add blocks to grid and create sparkle effects
for (var i = 0; i < self.blocks.length; i++) {
var block = self.blocks[i];
if (block.gridY >= 0) {
if (!gameGrid[block.gridY]) gameGrid[block.gridY] = [];
gameGrid[block.gridY][block.gridX] = block;
// Create sparkle effect around the block
var sparkleCount = 8;
for (var j = 0; j < sparkleCount; j++) {
var sparkle = LK.getAsset('grid_border', {
x: block.x + BLOCK_SIZE / 2,
y: block.y + BLOCK_SIZE / 2,
scaleX: 8,
scaleY: 8,
alpha: 1.5,
tint: 0xFFFFFF
});
game.addChild(sparkle);
// Random sparkle position around block
var angle = j / sparkleCount * Math.PI * 2;
var distance = BLOCK_SIZE * 1.0;
var targetX = sparkle.x + Math.cos(angle) * distance;
var targetY = sparkle.y + Math.sin(angle) * distance;
// Animate sparkle outward and fade
tween(sparkle, {
x: targetX,
y: targetY,
alpha: 0.3,
scaleX: 0.3,
scaleY: 0.3
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
if (sparkle && sparkle.destroy) {
sparkle.destroy();
}
}
});
}
}
}
// Remove blocks from this tetromino since they're now part of the grid
self.blocks = [];
LK.getSound('sparkle_effect').play();
LK.getSound('piece_drop').play();
checkCompleteLines();
// Clear current piece reference first
currentPiece = null;
// Spawn new piece with a small delay to ensure proper sequencing
LK.setTimeout(function () {
spawnNewPiece();
}, 50);
};
self.destroy = function () {
// Clean up all blocks when tetromino is destroyed
for (var i = 0; i < self.blocks.length; i++) {
if (self.blocks[i] && self.blocks[i].destroy) {
self.blocks[i].destroy();
}
}
self.blocks = [];
// Call parent destroy
Container.prototype.destroy.call(self);
};
return self;
});
/****
* Initialize Game
****/
// Game constants
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Orange
// Blue
// Red
// Green
// Purple
// Yellow
// Cyan
// Tetromino blocks - using different colors for each piece type
// Game constants
var GRID_WIDTH = 14;
var GRID_HEIGHT = 24;
var BLOCK_SIZE = 100;
var GRID_OFFSET_X = (2048 - GRID_WIDTH * BLOCK_SIZE) / 2;
var GRID_OFFSET_Y = 100;
// Game variables
var gameGrid = [];
var currentPiece = null;
var nextPieceType = null;
var gameLevel = 1;
var linesCleared = 0;
var lastMoveTime = 0;
var moveDelay = 100; // Milliseconds between moves
// Initialize grid
for (var y = 0; y < GRID_HEIGHT; y++) {
gameGrid[y] = [];
for (var x = 0; x < GRID_WIDTH; x++) {
gameGrid[y][x] = null;
}
}
// Tetromino types
var pieceTypes = ['I', 'O', 'T', 'S', 'Z', 'J', 'L'];
function getRandomPieceType() {
return pieceTypes[Math.floor(Math.random() * pieceTypes.length)];
}
function spawnNewPiece() {
// Don't spawn if there's already an active piece or if one is being placed
if (currentPiece && !currentPiece.isPlaced) {
return;
}
if (!nextPieceType) {
nextPieceType = getRandomPieceType();
}
// Create new piece and set as current immediately to prevent double spawning
var newPiece = new Tetromino(nextPieceType);
currentPiece = newPiece;
game.addChild(currentPiece);
currentPiece.createBlocks();
nextPieceType = getRandomPieceType();
// Check game over
if (!currentPiece.canMoveTo(currentPiece.gridX, currentPiece.gridY)) {
LK.showGameOver();
return;
}
}
function checkCompleteLines() {
var completedLines = [];
// Find completed lines
for (var y = 0; y < GRID_HEIGHT; y++) {
var lineComplete = true;
for (var x = 0; x < GRID_WIDTH; x++) {
if (!gameGrid[y][x]) {
lineComplete = false;
break;
}
}
if (lineComplete) {
completedLines.push(y);
}
}
if (completedLines.length > 0) {
// Sort completed lines from bottom to top (highest Y to lowest Y)
// This ensures we process lines from bottom up, keeping indices valid
completedLines.sort(function (a, b) {
return b - a;
});
// Remove completed lines
for (var i = 0; i < completedLines.length; i++) {
var lineY = completedLines[i];
// Create particle explosion effect for this line
for (var x = 0; x < GRID_WIDTH; x++) {
if (gameGrid[lineY][x]) {
var block = gameGrid[lineY][x];
// Create sparkle effect for cleared line blocks
var sparkleCount = 8; //{2X_new}
for (var j = 0; j < sparkleCount; j++) {
//{2X_new2}
var sparkle = LK.getAsset('grid_border', {
//{2X_new3}
x: block.x + BLOCK_SIZE / 2,
//{2X_new4}
y: block.y + BLOCK_SIZE / 2,
//{2X_new5}
scaleX: 5,
//{2X_new6}
scaleY: 5,
//{2X_new7}
alpha: 1.5,
//{2X_new8}
tint: 0xFFFFFF //{2X_new9}
}); //{2X_new10}
game.addChild(sparkle); //{2X_new11}
// Random sparkle position around block//{2X_new12}
var angle = j / sparkleCount * Math.PI * 2; //{2X_new13}
var distance = BLOCK_SIZE * 1.0; //{2X_new14}
var targetX = sparkle.x + Math.cos(angle) * distance; //{2X_new15}
var targetY = sparkle.y + Math.sin(angle) * distance; //{2X_new16}
// Animate sparkle outward and fade//{2X_new17}
tween(sparkle, {
//{2X_new18}
x: targetX,
//{2X_new19}
y: targetY,
//{2X_new20}
alpha: 0.3,
//{2X_new21}
scaleX: 0.3,
//{2X_new22}
scaleY: 0.3 //{2X_new23}
}, {
//{2X_new24}
duration: 200,
//{2X_new25}
easing: tween.easeOut,
//{2X_new26}
onFinish: function onFinish() {
//{2X_new27}
if (sparkle && sparkle.destroy) {
//{2X_new28}
sparkle.destroy(); //{2X_new29}
} //{2X_new30}
} //{2X_new31}
}); //{2X_new32}
} //{2X_new33}
// Create explosion effect before destroying
var originalX = block.x;
var originalY = block.y;
var originalScaleX = block.scaleX;
var originalScaleY = block.scaleY;
var originalAlpha = block.alpha;
// Random direction for explosion
var randomX = originalX + (Math.random() - 0.5) * 400;
var randomY = originalY + (Math.random() - 0.5) * 200;
// Animate block flying away and fading
tween(block, {
x: randomX,
y: randomY,
scaleX: originalScaleX * 0.2,
scaleY: originalScaleY * 0.2,
alpha: 0,
rotation: (Math.random() - 0.5) * Math.PI * 2
}, {
duration: 30,
easing: tween.easeOut,
onFinish: function onFinish() {
if (block && block.destroy) {
block.destroy();
}
}
});
gameGrid[lineY][x] = null;
}
}
// Move lines down
for (var y = lineY; y > 0; y--) {
for (var x = 0; x < GRID_WIDTH; x++) {
gameGrid[y][x] = gameGrid[y - 1][x];
if (gameGrid[y][x]) {
gameGrid[y][x].setGridPosition(x, y);
}
}
}
// Clear top line
for (var x = 0; x < GRID_WIDTH; x++) {
gameGrid[0][x] = null;
}
}
// Update score
linesCleared += completedLines.length;
var points = 0;
switch (completedLines.length) {
case 1:
points = 100;
break;
case 2:
points = 300;
break;
case 3:
points = 500;
break;
case 4:
points = 800;
break;
}
LK.setScore(LK.getScore() + points * gameLevel);
// Level up every 10 lines
var newLevel = Math.floor(linesCleared / 10) + 1;
if (newLevel > gameLevel) {
gameLevel = newLevel;
}
// Create border sparkle effect using line clear sparkles when lines are cleared
var borderSparkles = [];
// Left border sparkles
for (var i = 0; i < 10; i++) {
var sparkle = LK.getAsset('grid_border', {
x: GRID_OFFSET_X - 20,
y: GRID_OFFSET_Y + i * (GRID_HEIGHT * BLOCK_SIZE / 10),
scaleX: 5,
scaleY: 5,
alpha: 1.5,
tint: 0xFFFFFF
});
game.addChild(sparkle);
borderSparkles.push(sparkle);
// Random sparkle position for border effect//{3K_new}
var angle = i / 10 * Math.PI * 2; //{3K_new2}
var distance = 50; //{3K_new3}
var targetX = sparkle.x + Math.cos(angle) * distance; //{3K_new4}
var targetY = sparkle.y + Math.sin(angle) * distance; //{3K_new5}
// Animate sparkle outward and fade//{3K_new6}
tween(sparkle, {
//{3K_new7}
x: targetX,
//{3K_new8}
y: targetY,
//{3K_new9}
alpha: 0.3,
//{3K_new10}
scaleX: 0.3,
//{3K_new11}
scaleY: 0.3 //{3K_new12}
}, {
//{3K_new13}
duration: 200,
//{3K_new14}
easing: tween.easeOut,
//{3K_new15}
onFinish: function onFinish() {
//{3K_new16}
if (sparkle && sparkle.destroy) {
//{3K_new17}
sparkle.destroy(); //{3K_new18}
} //{3K_new19}
} //{3K_new20}
}); //{3K_new21}
}
// Right border sparkles
for (var i = 0; i < 10; i++) {
var sparkle = LK.getAsset('grid_border', {
x: GRID_OFFSET_X + GRID_WIDTH * BLOCK_SIZE + 20,
y: GRID_OFFSET_Y + i * (GRID_HEIGHT * BLOCK_SIZE / 10),
scaleX: 5,
scaleY: 5,
alpha: 1.5,
tint: 0xFFFFFF
});
game.addChild(sparkle);
borderSparkles.push(sparkle);
// Random sparkle position for border effect//{3V_new}
var angle = i / 10 * Math.PI * 2; //{3V_new2}
var distance = 50; //{3V_new3}
var targetX = sparkle.x + Math.cos(angle) * distance; //{3V_new4}
var targetY = sparkle.y + Math.sin(angle) * distance; //{3V_new5}
// Animate sparkle outward and fade//{3V_new6}
tween(sparkle, {
//{3V_new7}
x: targetX,
//{3V_new8}
y: targetY,
//{3V_new9}
alpha: 0.3,
//{3V_new10}
scaleX: 0.3,
//{3V_new11}
scaleY: 0.3 //{3V_new12}
}, {
//{3V_new13}
duration: 200,
//{3V_new14}
easing: tween.easeOut,
//{3V_new15}
onFinish: function onFinish() {
//{3V_new16}
if (sparkle && sparkle.destroy) {
//{3V_new17}
sparkle.destroy(); //{3V_new18}
} //{3V_new19}
} //{3V_new20}
}); //{3V_new21}
}
LK.getSound('sparkle_effect').play(); //{3j_new}
LK.getSound('line_clear').play();
}
}
// Create score display
var scoreText = new Text2('Score: 0', {
size: 40,
fill: 0xFFFFFF
});
scoreText.anchor.set(1, 0);
scoreText.x = -50;
LK.gui.topRight.addChild(scoreText);
var levelText = new Text2('Level: 1', {
size: 40,
fill: 0xFFFFFF
});
levelText.anchor.set(1, 0);
levelText.x = -50;
levelText.y = 60;
LK.gui.topRight.addChild(levelText);
var linesText = new Text2('Lines: 0', {
size: 40,
fill: 0xFFFFFF
});
linesText.anchor.set(1, 0);
linesText.x = -50;
linesText.y = 120;
LK.gui.topRight.addChild(linesText);
// Add background color asset
var backgroundColorAsset = LK.getAsset('background_color', {
x: 0,
y: 0
});
game.addChild(backgroundColorAsset);
// Draw grid borders - vertical lines only for game area height
for (var x = 0; x <= GRID_WIDTH; x++) {
if (x === 0 || x === GRID_WIDTH) {
var border = LK.getAsset('grid_border', {
x: GRID_OFFSET_X + x * BLOCK_SIZE - 4,
y: GRID_OFFSET_Y,
scaleX: 1,
scaleY: GRID_HEIGHT * BLOCK_SIZE / 8
});
game.addChild(border);
}
}
for (var y = GRID_HEIGHT; y <= GRID_HEIGHT; y++) {
for (var x = 0; x < GRID_WIDTH; x++) {
var border = LK.getAsset('grid_border', {
x: GRID_OFFSET_X + x * BLOCK_SIZE,
y: GRID_OFFSET_Y + y * BLOCK_SIZE,
scaleX: BLOCK_SIZE / 8,
scaleY: 1
});
game.addChild(border);
}
}
// Spawn first piece
spawnNewPiece();
// Start background music
LK.playMusic('tetris_theme');
game.down = function (x, y, obj) {
if (!currentPiece) return;
var now = Date.now();
if (now - lastMoveTime < moveDelay) return;
lastMoveTime = now;
// Divide screen into zones for controls
var leftZone = 2048 * 0.25;
var rightZone = 2048 * 0.75;
var rotateZone = 2732 * 0.7;
if (x < leftZone) {
// Left movement
currentPiece.move(-1, 0);
} else if (x > rightZone) {
// Right movement
currentPiece.move(1, 0);
} else if (y > rotateZone) {
// Drop piece faster
currentPiece.move(0, 1);
} else {
// Rotate piece
currentPiece.rotate();
}
};
game.update = function () {
if (currentPiece) {
// Update fall speed based on level
currentPiece.fallSpeed = Math.max(10, 40 - (gameLevel - 1) * 5);
}
// Update UI
scoreText.setText('Score: ' + LK.getScore());
levelText.setText('Level: ' + gameLevel);
linesText.setText('Lines: ' + linesCleared);
};