/****
* 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();
}
};