/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Dragon = Container.expand(function () { var self = Container.call(this); self.dragonGraphics = self.attachAsset('dragon', { anchorX: 0.5, anchorY: 0.5 }); // Start idle wing flapping animation self.startIdleAnimation = function () { // Wing flapping - more pronounced scale and rotation changes tween(self.dragonGraphics, { scaleX: 1.3, scaleY: 0.8, rotation: 0.15 }, { duration: 600, easing: tween.easeInOut, onFinish: function onFinish() { tween(self.dragonGraphics, { scaleX: 1.0, scaleY: 1.0, rotation: -0.15 }, { duration: 600, easing: tween.easeInOut, onFinish: function onFinish() { // Continue the idle animation loop self.startIdleAnimation(); } }); } }); }; // Start the idle animation immediately self.startIdleAnimation(); self.throwPiece = function (piece) { // Stop idle animation temporarily tween.stop(self.dragonGraphics); // Add throwing animation - more dramatic bounce effect tween(self.dragonGraphics, { scaleY: 0.5, scaleX: 1.5, rotation: 0.25 }, { duration: 80, easing: tween.easeOut }); tween(self, { scaleY: 0.6 }, { duration: 120, easing: tween.easeOut }); tween(self, { scaleY: 1.0 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { // Reset dragon graphics and restart idle animation tween(self.dragonGraphics, { scaleY: 1.0, scaleX: 1.0, rotation: 0 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { self.startIdleAnimation(); } }); // After throw animation, animate the piece falling if (piece) { var startY = piece.y; piece.y = self.y + 50; // Start from dragon position tween(piece, { y: startY }, { duration: 800, easing: tween.easeIn }); } } }); }; return self; }); var GhostBlock = Container.expand(function (type) { var self = Container.call(this); self.type = type; self.blockGraphics = self.attachAsset('tetromino' + type, { anchorX: 0, anchorY: 0 }); // Make ghost block more transparent self.blockGraphics.alpha = 0.08; return self; }); var Tetromino = Container.expand(function (type) { var self = Container.call(this); self.type = type; self.blocks = []; self.currentRotation = 0; // Define tetromino shapes and rotations self.shapes = { 'I': [[[0, 0, 0, 0], [1, 1, 1, 1], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 1, 0], [0, 0, 1, 0], [0, 0, 1, 0], [0, 0, 1, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [1, 1, 1, 1], [0, 0, 0, 0]], [[0, 1, 0, 0], [0, 1, 0, 0], [0, 1, 0, 0], [0, 1, 0, 0]]], 'O': [[[1, 1], [1, 1]], [[1, 1], [1, 1]], [[1, 1], [1, 1]], [[1, 1], [1, 1]]], 'T': [[[0, 1, 0], [1, 1, 1], [0, 0, 0]], [[0, 1, 0], [0, 1, 1], [0, 1, 0]], [[0, 0, 0], [1, 1, 1], [0, 1, 0]], [[0, 1, 0], [1, 1, 0], [0, 1, 0]]], 'S': [[[0, 1, 1], [1, 1, 0], [0, 0, 0]], [[0, 1, 0], [0, 1, 1], [0, 0, 1]], [[0, 0, 0], [0, 1, 1], [1, 1, 0]], [[1, 0, 0], [1, 1, 0], [0, 1, 0]]], 'Z': [[[1, 1, 0], [0, 1, 1], [0, 0, 0]], [[0, 0, 1], [0, 1, 1], [0, 1, 0]], [[0, 0, 0], [1, 1, 0], [0, 1, 1]], [[0, 1, 0], [1, 1, 0], [1, 0, 0]]], 'J': [[[1, 0, 0], [1, 1, 1], [0, 0, 0]], [[0, 1, 1], [0, 1, 0], [0, 1, 0]], [[0, 0, 0], [1, 1, 1], [0, 0, 1]], [[0, 1, 0], [0, 1, 0], [1, 1, 0]]], 'L': [[[0, 0, 1], [1, 1, 1], [0, 0, 0]], [[0, 1, 0], [0, 1, 0], [0, 1, 1]], [[0, 0, 0], [1, 1, 1], [1, 0, 0]], [[1, 1, 0], [0, 1, 0], [0, 1, 0]]] }; self.createBlocks = function () { // Clear existing blocks for (var i = 0; i < self.blocks.length; i++) { self.blocks[i].destroy(); } self.blocks = []; var shape = self.shapes[self.type][self.currentRotation]; for (var row = 0; row < shape.length; row++) { for (var col = 0; col < shape[row].length; col++) { if (shape[row][col] === 1) { var block = new TetrominoBlock(self.type); block.x = col * 100; block.y = row * 100; self.blocks.push(block); self.addChild(block); } } } }; self.rotate = function () { var newRotation = (self.currentRotation + 1) % 4; var oldRotation = self.currentRotation; self.currentRotation = newRotation; self.createBlocks(); // Check if rotation is valid if (!self.isValidPosition()) { // Revert rotation if invalid self.currentRotation = oldRotation; self.createBlocks(); return false; } LK.getSound('rotate').play(); updateGhostPiece(); return true; }; self.isValidPosition = function () { var shape = self.shapes[self.type][self.currentRotation]; var boardX = Math.floor((self.x - boardOffsetX) / 100); var boardY = Math.floor((self.y - boardOffsetY) / 100); for (var row = 0; row < shape.length; row++) { for (var col = 0; col < shape[row].length; col++) { if (shape[row][col] === 1) { var checkX = boardX + col; var checkY = boardY + row; // Check boundaries if (checkX < 0 || checkX >= boardWidth || checkY >= boardHeight) { return false; } // Check collision with placed blocks if (checkY >= 0 && board[checkY] && board[checkY][checkX] !== null) { return false; } } } } return true; }; // Initialize with blocks self.createBlocks(); return self; }); var TetrominoBlock = Container.expand(function (type) { var self = Container.call(this); self.type = type; self.blockGraphics = self.attachAsset('tetromino' + type, { anchorX: 0, anchorY: 0 }); return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x111111 }); /**** * Game Code ****/ // Game constants // Tetromino blocks // Board elements // Sound effects var boardWidth = 10; var boardHeight = 20; var blockSize = 100; var boardOffsetX = 524; var boardOffsetY = 200; // Game state var board = []; var currentPiece = null; var nextPiece = null; var nextPieceType = null; var nextPiecePreview = null; var ghostPiece = null; var nextPieces = ['I', 'O', 'T', 'S', 'Z', 'J', 'L']; var fallTimer = 0; var fallSpeed = 60; // frames per fall var level = 1; var linesCleared = 0; var isGameOver = false; var dragon = null; // Initialize board function initializeBoard() { board = []; for (var row = 0; row < boardHeight; row++) { board[row] = []; for (var col = 0; col < boardWidth; col++) { board[row][col] = null; } } } // Create next piece preview area function createNextPiecePreview() { var previewX = boardOffsetX + boardWidth * blockSize + 50; var previewY = boardOffsetY + 50; var previewSize = 400; // Preview border var previewBorder = LK.getAsset('boardBorder', { anchorX: 0, anchorY: 0, width: 4, height: previewSize }); previewBorder.x = previewX - 4; previewBorder.y = previewY; game.addChild(previewBorder); var previewBorderRight = LK.getAsset('boardBorder', { anchorX: 0, anchorY: 0, width: 4, height: previewSize }); previewBorderRight.x = previewX + previewSize; previewBorderRight.y = previewY; game.addChild(previewBorderRight); var previewBorderTop = LK.getAsset('boardBorder', { anchorX: 0, anchorY: 0, width: previewSize + 8, height: 4 }); previewBorderTop.x = previewX - 4; previewBorderTop.y = previewY - 4; game.addChild(previewBorderTop); var previewBorderBottom = LK.getAsset('boardBorder', { anchorX: 0, anchorY: 0, width: previewSize + 8, height: 4 }); previewBorderBottom.x = previewX - 4; previewBorderBottom.y = previewY + previewSize; game.addChild(previewBorderBottom); // Next piece label var nextLabel = new Text2('NEXT', { size: 40, fill: 0xFFFFFF }); nextLabel.anchor.set(0.5, 1); nextLabel.x = previewX + previewSize / 2; nextLabel.y = previewY - 10; game.addChild(nextLabel); } // Create board borders function createBoardBorders() { // Left border var leftBorder = LK.getAsset('boardBorder', { anchorX: 0, anchorY: 0, width: 4, height: boardHeight * blockSize }); leftBorder.x = boardOffsetX - 4; leftBorder.y = boardOffsetY; game.addChild(leftBorder); // Right border var rightBorder = LK.getAsset('boardBorder', { anchorX: 0, anchorY: 0, width: 4, height: boardHeight * blockSize }); rightBorder.x = boardOffsetX + boardWidth * blockSize; rightBorder.y = boardOffsetY; game.addChild(rightBorder); // Bottom border var bottomBorder = LK.getAsset('boardBorder', { anchorX: 0, anchorY: 0, width: boardWidth * blockSize + 8, height: 4 }); bottomBorder.x = boardOffsetX - 4; bottomBorder.y = boardOffsetY + boardHeight * blockSize; game.addChild(bottomBorder); } // Generate random piece type function getRandomPieceType() { var types = ['I', 'O', 'T', 'S', 'Z', 'J', 'L']; return types[Math.floor(Math.random() * types.length)]; } // Update next piece preview function updateNextPiecePreview() { // Clear existing preview if (nextPiecePreview) { nextPiecePreview.destroy(); } if (nextPieceType) { var previewX = boardOffsetX + boardWidth * blockSize + 50; var previewY = boardOffsetY + 50; var previewSize = 400; nextPiecePreview = new Tetromino(nextPieceType); // Get piece dimensions to center it properly var shape = nextPiecePreview.shapes[nextPieceType][0]; var pieceWidth = 0; var pieceHeight = shape.length; for (var row = 0; row < shape.length; row++) { pieceWidth = Math.max(pieceWidth, shape[row].length); } // Calculate center position within preview area var piecePixelWidth = pieceWidth * 100; var piecePixelHeight = pieceHeight * 100; var centerX = previewX + (previewSize - piecePixelWidth) / 2; var centerY = previewY + (previewSize - piecePixelHeight) / 2; nextPiecePreview.x = centerX; nextPiecePreview.y = centerY; // Scale down the preview piece nextPiecePreview.scaleX = 1.0; nextPiecePreview.scaleY = 1.0; game.addChild(nextPiecePreview); } } // Update ghost piece preview function updateGhostPiece() { // Clear existing ghost piece if (ghostPiece) { ghostPiece.destroy(); ghostPiece = null; } if (!currentPiece) return; // Create ghost piece container ghostPiece = new Container(); // Get current piece shape var shape = currentPiece.shapes[currentPiece.type][currentPiece.currentRotation]; // Find the lowest valid position for the current piece var testY = currentPiece.y; var validY = currentPiece.y; // Move test position down until it's no longer valid while (true) { testY += blockSize; // Check if this position would be valid var boardX = Math.floor((currentPiece.x - boardOffsetX) / blockSize); var boardY = Math.floor((testY - boardOffsetY) / blockSize); var isValid = true; for (var row = 0; row < shape.length && isValid; row++) { for (var col = 0; col < shape[row].length && isValid; col++) { if (shape[row][col] === 1) { var checkX = boardX + col; var checkY = boardY + row; // Check boundaries and collisions if (checkX < 0 || checkX >= boardWidth || checkY >= boardHeight) { isValid = false; } else if (checkY >= 0 && board[checkY] && board[checkY][checkX] !== null) { isValid = false; } } } } if (!isValid) { break; } validY = testY; } // Only show ghost piece if it's below the current piece if (validY > currentPiece.y) { // Create ghost blocks at the valid position for (var row = 0; row < shape.length; row++) { for (var col = 0; col < shape[row].length; col++) { if (shape[row][col] === 1) { var ghostBlock = new GhostBlock(currentPiece.type); ghostBlock.x = currentPiece.x + col * 100; ghostBlock.y = validY + row * 100; ghostPiece.addChild(ghostBlock); } } } game.addChild(ghostPiece); } } // Spawn new piece function spawnNewPiece() { if (currentPiece) { currentPiece.destroy(); } // Clear ghost piece when spawning new piece if (ghostPiece) { ghostPiece.destroy(); ghostPiece = null; } // Use next piece or generate first piece var pieceType; if (nextPieceType) { pieceType = nextPieceType; } else { pieceType = getRandomPieceType(); } // Generate new next piece nextPieceType = getRandomPieceType(); updateNextPiecePreview(); currentPiece = new Tetromino(pieceType); currentPiece.x = boardOffsetX + Math.floor(boardWidth / 2) * blockSize - blockSize; currentPiece.y = boardOffsetY; game.addChild(currentPiece); // Dragon throws the piece if (dragon) { dragon.throwPiece(currentPiece); } // Update ghost piece for new piece updateGhostPiece(); // Check if game over if (!currentPiece.isValidPosition()) { isGameOver = true; LK.showGameOver(); } } // Move piece down function movePieceDown() { if (!currentPiece) return false; currentPiece.y += blockSize; if (!currentPiece.isValidPosition()) { currentPiece.y -= blockSize; placePiece(); return false; } updateGhostPiece(); return true; } // Move piece left function movePieceLeft() { if (!currentPiece) return; currentPiece.x -= blockSize; if (!currentPiece.isValidPosition()) { currentPiece.x += blockSize; } else { updateGhostPiece(); } } // Move piece right function movePieceRight() { if (!currentPiece) return; currentPiece.x += blockSize; if (!currentPiece.isValidPosition()) { currentPiece.x -= blockSize; } else { updateGhostPiece(); } } // Place piece on board function placePiece() { if (!currentPiece) return; var shape = currentPiece.shapes[currentPiece.type][currentPiece.currentRotation]; var boardX = Math.floor((currentPiece.x - boardOffsetX) / blockSize); var boardY = Math.floor((currentPiece.y - boardOffsetY) / blockSize); // Place blocks on board for (var row = 0; row < shape.length; row++) { for (var col = 0; col < shape[row].length; col++) { if (shape[row][col] === 1) { var placeX = boardX + col; var placeY = boardY + row; if (placeY >= 0 && placeY < boardHeight && placeX >= 0 && placeX < boardWidth) { // Create placed block var placedBlock = new TetrominoBlock(currentPiece.type); placedBlock.x = boardOffsetX + placeX * blockSize; placedBlock.y = boardOffsetY + placeY * blockSize; game.addChild(placedBlock); board[placeY][placeX] = placedBlock; } } } } LK.getSound('pieceLand').play(); // Check for completed lines checkCompletedLines(); // Spawn new piece spawnNewPiece(); } // Check for completed lines function checkCompletedLines() { var completedLines = []; // Find completed lines for (var row = 0; row < boardHeight; row++) { var isComplete = true; for (var col = 0; col < boardWidth; col++) { if (board[row][col] === null) { isComplete = false; break; } } if (isComplete) { completedLines.push(row); } } // Clear completed lines if (completedLines.length > 0) { // Remove blocks from completed lines for (var i = 0; i < completedLines.length; i++) { var row = completedLines[i]; for (var col = 0; col < boardWidth; col++) { if (board[row][col]) { board[row][col].destroy(); board[row][col] = null; } } } // Drop lines above for (var i = completedLines.length - 1; i >= 0; i--) { var clearedRow = completedLines[i]; // Move all rows above down for (var row = clearedRow; row > 0; row--) { for (var col = 0; col < boardWidth; col++) { board[row][col] = board[row - 1][col]; if (board[row][col]) { board[row][col].y += blockSize; } } } // Clear top row for (var col = 0; col < boardWidth; col++) { board[0][col] = null; } // Adjust subsequent cleared rows for (var j = i + 1; j < completedLines.length; j++) { completedLines[j]++; } } // Update score var points = completedLines.length * 100 * level; if (completedLines.length === 4) points *= 2; // Tetris bonus LK.setScore(LK.getScore() + points); scoreTxt.setText(LK.getScore()); linesCleared += completedLines.length; level = Math.floor(linesCleared / 10) + 1; fallSpeed = Math.max(10, 60 - (level - 1) * 5); levelTxt.setText('Level: ' + level); linesTxt.setText('Lines: ' + linesCleared); LK.getSound('lineClear').play(); } } // Touch controls var touchStartX = 0; var touchStartY = 0; var isTouching = false; game.down = function (x, y, obj) { touchStartX = x; touchStartY = y; isTouching = true; }; game.up = function (x, y, obj) { if (!isTouching) return; isTouching = false; var deltaX = x - touchStartX; var deltaY = y - touchStartY; // Swipe down for fast drop if (deltaY > 50 && Math.abs(deltaX) < 30) { while (movePieceDown()) { // Keep dropping until piece lands } return; } // Tap piece to rotate if (Math.abs(deltaX) < 30 && Math.abs(deltaY) < 30) { if (currentPiece) { currentPiece.rotate(); } return; } // Swipe left/right to move if (Math.abs(deltaX) > 30) { if (deltaX > 0) { movePieceRight(); } else { movePieceLeft(); } } }; // Initialize UI var scoreTxt = new Text2('0', { size: 60, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 1); scoreTxt.setText(LK.getScore()); LK.gui.bottom.addChild(scoreTxt); var levelTxt = new Text2('Level: 1', { size: 40, fill: 0xFFFFFF }); levelTxt.anchor.set(0, 0); levelTxt.x = 100; levelTxt.y = 100; LK.gui.topLeft.addChild(levelTxt); // Add flame next to level text var flameGraphics = LK.getAsset('flame', { anchorX: 0.5, anchorY: 0.5 }); flameGraphics.x = levelTxt.x + 200; // Position to the right of level text flameGraphics.y = levelTxt.y + 20; // Center vertically with text LK.gui.topLeft.addChild(flameGraphics); // Add flickering animation to flame function startFlameAnimation() { tween(flameGraphics, { scaleX: 1.2, scaleY: 0.8, alpha: 0.8 }, { duration: 300, easing: tween.easeInOut, onFinish: function onFinish() { tween(flameGraphics, { scaleX: 0.9, scaleY: 1.1, alpha: 1.0 }, { duration: 250, easing: tween.easeInOut, onFinish: function onFinish() { startFlameAnimation(); } }); } }); } startFlameAnimation(); var linesTxt = new Text2('Lines: 0', { size: 40, fill: 0xFFFFFF }); linesTxt.anchor.set(0, 0); linesTxt.x = 100; linesTxt.y = 150; LK.gui.topLeft.addChild(linesTxt); // Initialize dragon dragon = new Dragon(); dragon.x = boardOffsetX + boardWidth * blockSize / 2; dragon.y = boardOffsetY - 80; // Position above the game area (moved 120px down from -200 to -80) game.addChild(dragon); // Initialize game initializeBoard(); createBoardBorders(); createNextPiecePreview(); // Generate first next piece nextPieceType = getRandomPieceType(); updateNextPiecePreview(); spawnNewPiece(); // Game update loop game.update = function () { if (isGameOver) return; fallTimer++; if (fallTimer >= fallSpeed) { fallTimer = 0; movePieceDown(); } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Dragon = Container.expand(function () {
var self = Container.call(this);
self.dragonGraphics = self.attachAsset('dragon', {
anchorX: 0.5,
anchorY: 0.5
});
// Start idle wing flapping animation
self.startIdleAnimation = function () {
// Wing flapping - more pronounced scale and rotation changes
tween(self.dragonGraphics, {
scaleX: 1.3,
scaleY: 0.8,
rotation: 0.15
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self.dragonGraphics, {
scaleX: 1.0,
scaleY: 1.0,
rotation: -0.15
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Continue the idle animation loop
self.startIdleAnimation();
}
});
}
});
};
// Start the idle animation immediately
self.startIdleAnimation();
self.throwPiece = function (piece) {
// Stop idle animation temporarily
tween.stop(self.dragonGraphics);
// Add throwing animation - more dramatic bounce effect
tween(self.dragonGraphics, {
scaleY: 0.5,
scaleX: 1.5,
rotation: 0.25
}, {
duration: 80,
easing: tween.easeOut
});
tween(self, {
scaleY: 0.6
}, {
duration: 120,
easing: tween.easeOut
});
tween(self, {
scaleY: 1.0
}, {
duration: 120,
easing: tween.easeOut,
onFinish: function onFinish() {
// Reset dragon graphics and restart idle animation
tween(self.dragonGraphics, {
scaleY: 1.0,
scaleX: 1.0,
rotation: 0
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
self.startIdleAnimation();
}
});
// After throw animation, animate the piece falling
if (piece) {
var startY = piece.y;
piece.y = self.y + 50; // Start from dragon position
tween(piece, {
y: startY
}, {
duration: 800,
easing: tween.easeIn
});
}
}
});
};
return self;
});
var GhostBlock = Container.expand(function (type) {
var self = Container.call(this);
self.type = type;
self.blockGraphics = self.attachAsset('tetromino' + type, {
anchorX: 0,
anchorY: 0
});
// Make ghost block more transparent
self.blockGraphics.alpha = 0.08;
return self;
});
var Tetromino = Container.expand(function (type) {
var self = Container.call(this);
self.type = type;
self.blocks = [];
self.currentRotation = 0;
// Define tetromino shapes and rotations
self.shapes = {
'I': [[[0, 0, 0, 0], [1, 1, 1, 1], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 1, 0], [0, 0, 1, 0], [0, 0, 1, 0], [0, 0, 1, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [1, 1, 1, 1], [0, 0, 0, 0]], [[0, 1, 0, 0], [0, 1, 0, 0], [0, 1, 0, 0], [0, 1, 0, 0]]],
'O': [[[1, 1], [1, 1]], [[1, 1], [1, 1]], [[1, 1], [1, 1]], [[1, 1], [1, 1]]],
'T': [[[0, 1, 0], [1, 1, 1], [0, 0, 0]], [[0, 1, 0], [0, 1, 1], [0, 1, 0]], [[0, 0, 0], [1, 1, 1], [0, 1, 0]], [[0, 1, 0], [1, 1, 0], [0, 1, 0]]],
'S': [[[0, 1, 1], [1, 1, 0], [0, 0, 0]], [[0, 1, 0], [0, 1, 1], [0, 0, 1]], [[0, 0, 0], [0, 1, 1], [1, 1, 0]], [[1, 0, 0], [1, 1, 0], [0, 1, 0]]],
'Z': [[[1, 1, 0], [0, 1, 1], [0, 0, 0]], [[0, 0, 1], [0, 1, 1], [0, 1, 0]], [[0, 0, 0], [1, 1, 0], [0, 1, 1]], [[0, 1, 0], [1, 1, 0], [1, 0, 0]]],
'J': [[[1, 0, 0], [1, 1, 1], [0, 0, 0]], [[0, 1, 1], [0, 1, 0], [0, 1, 0]], [[0, 0, 0], [1, 1, 1], [0, 0, 1]], [[0, 1, 0], [0, 1, 0], [1, 1, 0]]],
'L': [[[0, 0, 1], [1, 1, 1], [0, 0, 0]], [[0, 1, 0], [0, 1, 0], [0, 1, 1]], [[0, 0, 0], [1, 1, 1], [1, 0, 0]], [[1, 1, 0], [0, 1, 0], [0, 1, 0]]]
};
self.createBlocks = function () {
// Clear existing blocks
for (var i = 0; i < self.blocks.length; i++) {
self.blocks[i].destroy();
}
self.blocks = [];
var shape = self.shapes[self.type][self.currentRotation];
for (var row = 0; row < shape.length; row++) {
for (var col = 0; col < shape[row].length; col++) {
if (shape[row][col] === 1) {
var block = new TetrominoBlock(self.type);
block.x = col * 100;
block.y = row * 100;
self.blocks.push(block);
self.addChild(block);
}
}
}
};
self.rotate = function () {
var newRotation = (self.currentRotation + 1) % 4;
var oldRotation = self.currentRotation;
self.currentRotation = newRotation;
self.createBlocks();
// Check if rotation is valid
if (!self.isValidPosition()) {
// Revert rotation if invalid
self.currentRotation = oldRotation;
self.createBlocks();
return false;
}
LK.getSound('rotate').play();
updateGhostPiece();
return true;
};
self.isValidPosition = function () {
var shape = self.shapes[self.type][self.currentRotation];
var boardX = Math.floor((self.x - boardOffsetX) / 100);
var boardY = Math.floor((self.y - boardOffsetY) / 100);
for (var row = 0; row < shape.length; row++) {
for (var col = 0; col < shape[row].length; col++) {
if (shape[row][col] === 1) {
var checkX = boardX + col;
var checkY = boardY + row;
// Check boundaries
if (checkX < 0 || checkX >= boardWidth || checkY >= boardHeight) {
return false;
}
// Check collision with placed blocks
if (checkY >= 0 && board[checkY] && board[checkY][checkX] !== null) {
return false;
}
}
}
}
return true;
};
// Initialize with blocks
self.createBlocks();
return self;
});
var TetrominoBlock = Container.expand(function (type) {
var self = Container.call(this);
self.type = type;
self.blockGraphics = self.attachAsset('tetromino' + type, {
anchorX: 0,
anchorY: 0
});
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x111111
});
/****
* Game Code
****/
// Game constants
// Tetromino blocks
// Board elements
// Sound effects
var boardWidth = 10;
var boardHeight = 20;
var blockSize = 100;
var boardOffsetX = 524;
var boardOffsetY = 200;
// Game state
var board = [];
var currentPiece = null;
var nextPiece = null;
var nextPieceType = null;
var nextPiecePreview = null;
var ghostPiece = null;
var nextPieces = ['I', 'O', 'T', 'S', 'Z', 'J', 'L'];
var fallTimer = 0;
var fallSpeed = 60; // frames per fall
var level = 1;
var linesCleared = 0;
var isGameOver = false;
var dragon = null;
// Initialize board
function initializeBoard() {
board = [];
for (var row = 0; row < boardHeight; row++) {
board[row] = [];
for (var col = 0; col < boardWidth; col++) {
board[row][col] = null;
}
}
}
// Create next piece preview area
function createNextPiecePreview() {
var previewX = boardOffsetX + boardWidth * blockSize + 50;
var previewY = boardOffsetY + 50;
var previewSize = 400;
// Preview border
var previewBorder = LK.getAsset('boardBorder', {
anchorX: 0,
anchorY: 0,
width: 4,
height: previewSize
});
previewBorder.x = previewX - 4;
previewBorder.y = previewY;
game.addChild(previewBorder);
var previewBorderRight = LK.getAsset('boardBorder', {
anchorX: 0,
anchorY: 0,
width: 4,
height: previewSize
});
previewBorderRight.x = previewX + previewSize;
previewBorderRight.y = previewY;
game.addChild(previewBorderRight);
var previewBorderTop = LK.getAsset('boardBorder', {
anchorX: 0,
anchorY: 0,
width: previewSize + 8,
height: 4
});
previewBorderTop.x = previewX - 4;
previewBorderTop.y = previewY - 4;
game.addChild(previewBorderTop);
var previewBorderBottom = LK.getAsset('boardBorder', {
anchorX: 0,
anchorY: 0,
width: previewSize + 8,
height: 4
});
previewBorderBottom.x = previewX - 4;
previewBorderBottom.y = previewY + previewSize;
game.addChild(previewBorderBottom);
// Next piece label
var nextLabel = new Text2('NEXT', {
size: 40,
fill: 0xFFFFFF
});
nextLabel.anchor.set(0.5, 1);
nextLabel.x = previewX + previewSize / 2;
nextLabel.y = previewY - 10;
game.addChild(nextLabel);
}
// Create board borders
function createBoardBorders() {
// Left border
var leftBorder = LK.getAsset('boardBorder', {
anchorX: 0,
anchorY: 0,
width: 4,
height: boardHeight * blockSize
});
leftBorder.x = boardOffsetX - 4;
leftBorder.y = boardOffsetY;
game.addChild(leftBorder);
// Right border
var rightBorder = LK.getAsset('boardBorder', {
anchorX: 0,
anchorY: 0,
width: 4,
height: boardHeight * blockSize
});
rightBorder.x = boardOffsetX + boardWidth * blockSize;
rightBorder.y = boardOffsetY;
game.addChild(rightBorder);
// Bottom border
var bottomBorder = LK.getAsset('boardBorder', {
anchorX: 0,
anchorY: 0,
width: boardWidth * blockSize + 8,
height: 4
});
bottomBorder.x = boardOffsetX - 4;
bottomBorder.y = boardOffsetY + boardHeight * blockSize;
game.addChild(bottomBorder);
}
// Generate random piece type
function getRandomPieceType() {
var types = ['I', 'O', 'T', 'S', 'Z', 'J', 'L'];
return types[Math.floor(Math.random() * types.length)];
}
// Update next piece preview
function updateNextPiecePreview() {
// Clear existing preview
if (nextPiecePreview) {
nextPiecePreview.destroy();
}
if (nextPieceType) {
var previewX = boardOffsetX + boardWidth * blockSize + 50;
var previewY = boardOffsetY + 50;
var previewSize = 400;
nextPiecePreview = new Tetromino(nextPieceType);
// Get piece dimensions to center it properly
var shape = nextPiecePreview.shapes[nextPieceType][0];
var pieceWidth = 0;
var pieceHeight = shape.length;
for (var row = 0; row < shape.length; row++) {
pieceWidth = Math.max(pieceWidth, shape[row].length);
}
// Calculate center position within preview area
var piecePixelWidth = pieceWidth * 100;
var piecePixelHeight = pieceHeight * 100;
var centerX = previewX + (previewSize - piecePixelWidth) / 2;
var centerY = previewY + (previewSize - piecePixelHeight) / 2;
nextPiecePreview.x = centerX;
nextPiecePreview.y = centerY;
// Scale down the preview piece
nextPiecePreview.scaleX = 1.0;
nextPiecePreview.scaleY = 1.0;
game.addChild(nextPiecePreview);
}
}
// Update ghost piece preview
function updateGhostPiece() {
// Clear existing ghost piece
if (ghostPiece) {
ghostPiece.destroy();
ghostPiece = null;
}
if (!currentPiece) return;
// Create ghost piece container
ghostPiece = new Container();
// Get current piece shape
var shape = currentPiece.shapes[currentPiece.type][currentPiece.currentRotation];
// Find the lowest valid position for the current piece
var testY = currentPiece.y;
var validY = currentPiece.y;
// Move test position down until it's no longer valid
while (true) {
testY += blockSize;
// Check if this position would be valid
var boardX = Math.floor((currentPiece.x - boardOffsetX) / blockSize);
var boardY = Math.floor((testY - boardOffsetY) / blockSize);
var isValid = true;
for (var row = 0; row < shape.length && isValid; row++) {
for (var col = 0; col < shape[row].length && isValid; col++) {
if (shape[row][col] === 1) {
var checkX = boardX + col;
var checkY = boardY + row;
// Check boundaries and collisions
if (checkX < 0 || checkX >= boardWidth || checkY >= boardHeight) {
isValid = false;
} else if (checkY >= 0 && board[checkY] && board[checkY][checkX] !== null) {
isValid = false;
}
}
}
}
if (!isValid) {
break;
}
validY = testY;
}
// Only show ghost piece if it's below the current piece
if (validY > currentPiece.y) {
// Create ghost blocks at the valid position
for (var row = 0; row < shape.length; row++) {
for (var col = 0; col < shape[row].length; col++) {
if (shape[row][col] === 1) {
var ghostBlock = new GhostBlock(currentPiece.type);
ghostBlock.x = currentPiece.x + col * 100;
ghostBlock.y = validY + row * 100;
ghostPiece.addChild(ghostBlock);
}
}
}
game.addChild(ghostPiece);
}
}
// Spawn new piece
function spawnNewPiece() {
if (currentPiece) {
currentPiece.destroy();
}
// Clear ghost piece when spawning new piece
if (ghostPiece) {
ghostPiece.destroy();
ghostPiece = null;
}
// Use next piece or generate first piece
var pieceType;
if (nextPieceType) {
pieceType = nextPieceType;
} else {
pieceType = getRandomPieceType();
}
// Generate new next piece
nextPieceType = getRandomPieceType();
updateNextPiecePreview();
currentPiece = new Tetromino(pieceType);
currentPiece.x = boardOffsetX + Math.floor(boardWidth / 2) * blockSize - blockSize;
currentPiece.y = boardOffsetY;
game.addChild(currentPiece);
// Dragon throws the piece
if (dragon) {
dragon.throwPiece(currentPiece);
}
// Update ghost piece for new piece
updateGhostPiece();
// Check if game over
if (!currentPiece.isValidPosition()) {
isGameOver = true;
LK.showGameOver();
}
}
// Move piece down
function movePieceDown() {
if (!currentPiece) return false;
currentPiece.y += blockSize;
if (!currentPiece.isValidPosition()) {
currentPiece.y -= blockSize;
placePiece();
return false;
}
updateGhostPiece();
return true;
}
// Move piece left
function movePieceLeft() {
if (!currentPiece) return;
currentPiece.x -= blockSize;
if (!currentPiece.isValidPosition()) {
currentPiece.x += blockSize;
} else {
updateGhostPiece();
}
}
// Move piece right
function movePieceRight() {
if (!currentPiece) return;
currentPiece.x += blockSize;
if (!currentPiece.isValidPosition()) {
currentPiece.x -= blockSize;
} else {
updateGhostPiece();
}
}
// Place piece on board
function placePiece() {
if (!currentPiece) return;
var shape = currentPiece.shapes[currentPiece.type][currentPiece.currentRotation];
var boardX = Math.floor((currentPiece.x - boardOffsetX) / blockSize);
var boardY = Math.floor((currentPiece.y - boardOffsetY) / blockSize);
// Place blocks on board
for (var row = 0; row < shape.length; row++) {
for (var col = 0; col < shape[row].length; col++) {
if (shape[row][col] === 1) {
var placeX = boardX + col;
var placeY = boardY + row;
if (placeY >= 0 && placeY < boardHeight && placeX >= 0 && placeX < boardWidth) {
// Create placed block
var placedBlock = new TetrominoBlock(currentPiece.type);
placedBlock.x = boardOffsetX + placeX * blockSize;
placedBlock.y = boardOffsetY + placeY * blockSize;
game.addChild(placedBlock);
board[placeY][placeX] = placedBlock;
}
}
}
}
LK.getSound('pieceLand').play();
// Check for completed lines
checkCompletedLines();
// Spawn new piece
spawnNewPiece();
}
// Check for completed lines
function checkCompletedLines() {
var completedLines = [];
// Find completed lines
for (var row = 0; row < boardHeight; row++) {
var isComplete = true;
for (var col = 0; col < boardWidth; col++) {
if (board[row][col] === null) {
isComplete = false;
break;
}
}
if (isComplete) {
completedLines.push(row);
}
}
// Clear completed lines
if (completedLines.length > 0) {
// Remove blocks from completed lines
for (var i = 0; i < completedLines.length; i++) {
var row = completedLines[i];
for (var col = 0; col < boardWidth; col++) {
if (board[row][col]) {
board[row][col].destroy();
board[row][col] = null;
}
}
}
// Drop lines above
for (var i = completedLines.length - 1; i >= 0; i--) {
var clearedRow = completedLines[i];
// Move all rows above down
for (var row = clearedRow; row > 0; row--) {
for (var col = 0; col < boardWidth; col++) {
board[row][col] = board[row - 1][col];
if (board[row][col]) {
board[row][col].y += blockSize;
}
}
}
// Clear top row
for (var col = 0; col < boardWidth; col++) {
board[0][col] = null;
}
// Adjust subsequent cleared rows
for (var j = i + 1; j < completedLines.length; j++) {
completedLines[j]++;
}
}
// Update score
var points = completedLines.length * 100 * level;
if (completedLines.length === 4) points *= 2; // Tetris bonus
LK.setScore(LK.getScore() + points);
scoreTxt.setText(LK.getScore());
linesCleared += completedLines.length;
level = Math.floor(linesCleared / 10) + 1;
fallSpeed = Math.max(10, 60 - (level - 1) * 5);
levelTxt.setText('Level: ' + level);
linesTxt.setText('Lines: ' + linesCleared);
LK.getSound('lineClear').play();
}
}
// Touch controls
var touchStartX = 0;
var touchStartY = 0;
var isTouching = false;
game.down = function (x, y, obj) {
touchStartX = x;
touchStartY = y;
isTouching = true;
};
game.up = function (x, y, obj) {
if (!isTouching) return;
isTouching = false;
var deltaX = x - touchStartX;
var deltaY = y - touchStartY;
// Swipe down for fast drop
if (deltaY > 50 && Math.abs(deltaX) < 30) {
while (movePieceDown()) {
// Keep dropping until piece lands
}
return;
}
// Tap piece to rotate
if (Math.abs(deltaX) < 30 && Math.abs(deltaY) < 30) {
if (currentPiece) {
currentPiece.rotate();
}
return;
}
// Swipe left/right to move
if (Math.abs(deltaX) > 30) {
if (deltaX > 0) {
movePieceRight();
} else {
movePieceLeft();
}
}
};
// Initialize UI
var scoreTxt = new Text2('0', {
size: 60,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 1);
scoreTxt.setText(LK.getScore());
LK.gui.bottom.addChild(scoreTxt);
var levelTxt = new Text2('Level: 1', {
size: 40,
fill: 0xFFFFFF
});
levelTxt.anchor.set(0, 0);
levelTxt.x = 100;
levelTxt.y = 100;
LK.gui.topLeft.addChild(levelTxt);
// Add flame next to level text
var flameGraphics = LK.getAsset('flame', {
anchorX: 0.5,
anchorY: 0.5
});
flameGraphics.x = levelTxt.x + 200; // Position to the right of level text
flameGraphics.y = levelTxt.y + 20; // Center vertically with text
LK.gui.topLeft.addChild(flameGraphics);
// Add flickering animation to flame
function startFlameAnimation() {
tween(flameGraphics, {
scaleX: 1.2,
scaleY: 0.8,
alpha: 0.8
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(flameGraphics, {
scaleX: 0.9,
scaleY: 1.1,
alpha: 1.0
}, {
duration: 250,
easing: tween.easeInOut,
onFinish: function onFinish() {
startFlameAnimation();
}
});
}
});
}
startFlameAnimation();
var linesTxt = new Text2('Lines: 0', {
size: 40,
fill: 0xFFFFFF
});
linesTxt.anchor.set(0, 0);
linesTxt.x = 100;
linesTxt.y = 150;
LK.gui.topLeft.addChild(linesTxt);
// Initialize dragon
dragon = new Dragon();
dragon.x = boardOffsetX + boardWidth * blockSize / 2;
dragon.y = boardOffsetY - 80; // Position above the game area (moved 120px down from -200 to -80)
game.addChild(dragon);
// Initialize game
initializeBoard();
createBoardBorders();
createNextPiecePreview();
// Generate first next piece
nextPieceType = getRandomPieceType();
updateNextPiecePreview();
spawnNewPiece();
// Game update loop
game.update = function () {
if (isGameOver) return;
fallTimer++;
if (fallTimer >= fallSpeed) {
fallTimer = 0;
movePieceDown();
}
};