/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0,
level: 1
});
/****
* Classes
****/
var Block = Container.expand(function () {
var self = Container.call(this);
self.blockType = '';
self.init = function (type) {
self.blockType = type;
var color = 'block_' + type;
self.graphics = self.attachAsset(color, {
anchorX: 0,
anchorY: 0,
alpha: 0.9
});
return self;
};
return self;
});
var Button = Container.expand(function () {
var self = Container.call(this);
self.init = function (label, iconType) {
var buttonGraphics = self.attachAsset('button', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 2.0,
shape: 'ellipse' // Change shape to ellipse to make it circular
});
var text = new Text2(label, {
size: 100,
fill: 0xFFFFFF // White color for icon
});
text.anchor.set(0.5, 0.5);
self.addChild(text);
self.down = function (x, y, obj) {
buttonGraphics.alpha = 0.7;
};
self.up = function (x, y, obj) {
buttonGraphics.alpha = 1.0;
};
return self;
};
return self;
});
var Tetromino = Container.expand(function () {
var self = Container.call(this);
self.blocks = [];
self.shape = [];
self.type = '';
self.rotation = 0;
self.gridX = 0;
self.gridY = 0;
self.init = function (type) {
self.type = type;
self.createShape(type);
return self;
};
self.createShape = function (type) {
// Clear existing blocks
for (var i = 0; i < self.blocks.length; i++) {
self.removeChild(self.blocks[i]);
}
self.blocks = [];
// Create new shape based on type
switch (type) {
case 'i':
self.shape = [[0, 0, 0, 0], [1, 1, 1, 1], [0, 0, 0, 0], [0, 0, 0, 0]];
break;
case 'j':
self.shape = [[1, 0, 0], [1, 1, 1], [0, 0, 0]];
break;
case 'l':
self.shape = [[0, 0, 1], [1, 1, 1], [0, 0, 0]];
break;
case 'o':
self.shape = [[1, 1], [1, 1]];
break;
case 's':
self.shape = [[0, 1, 1], [1, 1, 0], [0, 0, 0]];
break;
case 't':
self.shape = [[0, 1, 0], [1, 1, 1], [0, 0, 0]];
break;
case 'z':
self.shape = [[1, 1, 0], [0, 1, 1], [0, 0, 0]];
break;
}
// Create blocks based on shape
for (var y = 0; y < self.shape.length; y++) {
for (var x = 0; x < self.shape[y].length; x++) {
if (self.shape[y][x]) {
var block = new Block().init(type);
block.x = x * BLOCK_SIZE;
block.y = y * BLOCK_SIZE;
self.addChild(block);
self.blocks.push(block);
}
}
}
};
self.rotate = function () {
if (self.type === 'o') {
return;
} // O pieces don't rotate
// Create a new rotated matrix
var newShape = [];
var size = self.shape.length;
for (var y = 0; y < size; y++) {
newShape[y] = [];
for (var x = 0; x < size; x++) {
newShape[y][x] = self.shape[size - 1 - x][y];
}
}
// Save original shape in case rotation is invalid
var originalShape = self.shape;
self.shape = newShape;
// Update block positions
self.updateBlocks();
// Check if the rotation is valid
if (!isValidPosition(self)) {
// Revert to original shape
self.shape = originalShape;
self.updateBlocks();
return false;
}
if (LK.getSound('tetromino_rotate')) {
LK.getSound('tetromino_rotate').play();
} else {
console.error("Sound 'tetromino_rotate' not found");
}
return true;
};
self.updateBlocks = function () {
var blockIndex = 0;
for (var y = 0; y < self.shape.length; y++) {
for (var x = 0; x < self.shape[y].length; x++) {
if (self.shape[y][x]) {
self.blocks[blockIndex].x = x * BLOCK_SIZE;
self.blocks[blockIndex].y = y * BLOCK_SIZE;
blockIndex++;
}
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x9bbc0f
});
/****
* Game Code
****/
// Constants
var BLOCK_SIZE = 100; // Block size in pixels
var GRID_WIDTH = 10; // Grid width in blocks
var GRID_HEIGHT = 20; // Grid height in blocks
var GRID_OFFSET_X = (2048 - GRID_WIDTH * BLOCK_SIZE) / 2; // Center grid x position
var GRID_OFFSET_Y = 100; // Grid y position
var DROP_INTERVAL = 1000; // Initial interval between block drops in ms
var DROP_SPEED_INCREASE = 0.8; // Speed multiplier per level
// Game variables
var grid = []; // Grid state
var currentTetromino = null; // Current active tetromino
var nextTetromino = null; // Next tetromino to drop
var score = 0;
var level = storage.level || 1;
var lines = 0;
var isGameOver = false;
var dropTimer = 0;
var lastDropTime = 0;
var inputDelay = 0; // Delay for input controls to prevent too fast movement
// Initialize grid
for (var y = 0; y < GRID_HEIGHT; y++) {
grid[y] = [];
for (var x = 0; x < GRID_WIDTH; x++) {
grid[y][x] = null;
}
}
// Create grid background with lines
var gridBackground = new Container();
for (var y = 0; y < GRID_HEIGHT; y++) {
for (var x = 0; x < GRID_WIDTH; x++) {
var cell = LK.getAsset('grid_cell', {
anchorX: 0,
anchorY: 0,
alpha: 0.5
});
cell.x = x * BLOCK_SIZE;
cell.y = y * BLOCK_SIZE;
gridBackground.addChild(cell);
// Add vertical line
if (x < GRID_WIDTH - 1) {
var verticalLine = LK.getAsset('block', {
anchorX: 0,
anchorY: 0,
alpha: 0.2,
width: 20,
// Increased thickness
height: BLOCK_SIZE,
color: 0x000000 // Black color
});
verticalLine.x = (x + 1) * BLOCK_SIZE - 1;
verticalLine.y = y * BLOCK_SIZE;
gridBackground.addChild(verticalLine);
}
// Add horizontal line
if (y < GRID_HEIGHT - 1) {
var horizontalLine = LK.getAsset('block', {
anchorX: 0,
anchorY: 0,
alpha: 0.2,
width: BLOCK_SIZE,
height: 20,
// Increased thickness
color: 0x000000 // Black color
});
horizontalLine.x = x * BLOCK_SIZE;
horizontalLine.y = (y + 1) * BLOCK_SIZE - 1;
gridBackground.addChild(horizontalLine);
}
}
}
gridBackground.x = GRID_OFFSET_X;
gridBackground.y = GRID_OFFSET_Y;
game.addChild(gridBackground);
// Create game board container
var gameBoard = new Container();
gameBoard.x = GRID_OFFSET_X;
gameBoard.y = GRID_OFFSET_Y;
game.addChild(gameBoard);
// Next piece preview
var nextPieceContainer = new Container();
nextPieceContainer.x = GRID_OFFSET_X - 200;
nextPieceContainer.y = GRID_OFFSET_Y + 100;
game.addChild(nextPieceContainer);
// Create UI text elements
var scoreText = new Text2('Score: 0', {
size: 80,
fill: 0x000000
});
scoreText.anchor.set(0, 0);
scoreText.x = GRID_OFFSET_X + GRID_WIDTH * BLOCK_SIZE + 50;
scoreText.y = GRID_OFFSET_Y;
game.addChild(scoreText);
var levelText = new Text2('Level: 1', {
size: 80,
fill: 0x000000
});
levelText.anchor.set(0, 0);
levelText.x = GRID_OFFSET_X + GRID_WIDTH * BLOCK_SIZE + 50;
levelText.y = GRID_OFFSET_Y + 120;
game.addChild(levelText);
var linesText = new Text2('Lines: 0', {
size: 80,
fill: 0x000000
});
linesText.anchor.set(0, 0);
linesText.x = GRID_OFFSET_X + GRID_WIDTH * BLOCK_SIZE + 50;
linesText.y = GRID_OFFSET_Y + 250;
game.addChild(linesText);
// Create control buttons
var leftButton = new Button().init('←');
leftButton.x = 300;
leftButton.y = 2432; // Move up by 200 pixels
leftButton.down = function () {
moveTetrominoLeft();
this.alpha = 0.7;
};
leftButton.up = function () {
this.alpha = 1.0;
};
game.addChild(leftButton);
var rightButton = new Button().init('→');
rightButton.x = 750;
rightButton.y = 2432; // Move up by 200 pixels
rightButton.down = function () {
moveTetrominoRight();
this.alpha = 0.7;
};
rightButton.up = function () {
this.alpha = 1.0;
};
game.addChild(rightButton);
var rotateButton = new Button().init('↻');
rotateButton.x = 1300;
rotateButton.y = 2432; // Move up by 200 pixels
rotateButton.down = function () {
rotateTetromino();
this.alpha = 0.7;
};
rotateButton.up = function () {
this.alpha = 1.0;
};
game.addChild(rotateButton);
var dropButton = new Button().init('↓');
dropButton.x = 1750;
dropButton.y = 2432; // Move up by 200 pixels
dropButton.down = function () {
dropTetromino();
this.alpha = 0.7;
};
dropButton.up = function () {
this.alpha = 1.0;
};
game.addChild(dropButton);
// Tetromino types
var tetrominoTypes = ['i', 'j', 'l', 'o', 's', 't', 'z'];
// Helper functions
function createRandomTetromino() {
var type = tetrominoTypes[Math.floor(Math.random() * tetrominoTypes.length)];
return new Tetromino().init(type);
}
function spawnTetromino() {
if (nextTetromino === null) {
nextTetromino = createRandomTetromino();
}
currentTetromino = nextTetromino;
nextTetromino = createRandomTetromino();
// Position the tetromino at the top center of the grid
currentTetromino.gridX = Math.floor(GRID_WIDTH / 2) - Math.floor(currentTetromino.shape.length / 2);
currentTetromino.gridY = 0;
// Update tetromino position
currentTetromino.x = currentTetromino.gridX * BLOCK_SIZE;
currentTetromino.y = currentTetromino.gridY * BLOCK_SIZE;
// Add to game board
gameBoard.addChild(currentTetromino);
// Update next piece preview
updateNextPiecePreview();
// Check if game is over
if (!isValidPosition(currentTetromino)) {
gameOver();
}
}
function updateNextPiecePreview() {
// Clear previous preview
while (nextPieceContainer.children.length > 0) {
nextPieceContainer.removeChild(nextPieceContainer.children[0]);
}
// Create a copy of the next tetromino for preview
var previewTetromino = new Tetromino().init(nextTetromino.type);
// Center it in the preview area
previewTetromino.x = 75 - previewTetromino.shape.length * BLOCK_SIZE / 4;
previewTetromino.y = 75 - previewTetromino.shape.length * BLOCK_SIZE / 4;
previewTetromino.scale.set(0.5, 0.5);
nextPieceContainer.addChild(previewTetromino);
}
function isValidPosition(tetromino) {
for (var y = 0; y < tetromino.shape.length; y++) {
for (var x = 0; x < tetromino.shape[y].length; x++) {
if (tetromino.shape[y][x]) {
var gridX = tetromino.gridX + x;
var gridY = tetromino.gridY + y;
// Check if out of bounds
if (gridX < 0 || gridX >= GRID_WIDTH || gridY >= GRID_HEIGHT) {
return false;
}
// Check if overlapping with locked blocks (and not above the grid)
if (gridY >= 0 && grid[gridY][gridX] !== null) {
return false;
}
}
}
}
return true;
}
function moveTetromino(dx, dy) {
if (currentTetromino === null || isGameOver) {
return false;
}
// Save original position
var originalX = currentTetromino.gridX;
var originalY = currentTetromino.gridY;
// Try to move
currentTetromino.gridX += dx;
currentTetromino.gridY += dy;
// Check if the new position is valid
if (!isValidPosition(currentTetromino)) {
// Revert position
currentTetromino.gridX = originalX;
currentTetromino.gridY = originalY;
// If we tried to move down and failed, lock the tetromino
if (dy > 0) {
lockTetromino();
}
return false;
}
// Update position
currentTetromino.x = currentTetromino.gridX * BLOCK_SIZE;
currentTetromino.y = currentTetromino.gridY * BLOCK_SIZE;
return true;
}
function moveTetrominoLeft() {
if (LK.ticks - inputDelay < 5) {
return;
} // Prevent too fast movement
inputDelay = LK.ticks;
moveTetromino(-1, 0);
}
function moveTetrominoRight() {
if (LK.ticks - inputDelay < 5) {
return;
} // Prevent too fast movement
inputDelay = LK.ticks;
moveTetromino(1, 0);
}
function moveTetrominoDown() {
return moveTetromino(0, 1);
}
function rotateTetromino() {
if (currentTetromino === null || isGameOver) {
return;
}
if (LK.ticks - inputDelay < 5) {
return;
} // Prevent too fast rotation
inputDelay = LK.ticks;
currentTetromino.rotate();
}
function dropTetromino() {
if (currentTetromino === null || isGameOver) {
return;
}
if (LK.ticks - inputDelay < 5) {
return;
} // Prevent too fast dropping
inputDelay = LK.ticks;
// Move tetromino down until it can't move anymore
while (moveTetrominoDown()) {
// Continue moving down
}
}
function lockTetromino() {
if (currentTetromino === null) {
return;
}
// Add blocks to grid
for (var y = 0; y < currentTetromino.shape.length; y++) {
for (var x = 0; x < currentTetromino.shape[y].length; x++) {
if (currentTetromino.shape[y][x]) {
var gridX = currentTetromino.gridX + x;
var gridY = currentTetromino.gridY + y;
// Skip if above the grid
if (gridY < 0) {
continue;
}
// Create a new block at this position
var block = new Block().init(currentTetromino.type);
block.x = gridX * BLOCK_SIZE;
block.y = gridY * BLOCK_SIZE;
// Add to grid and game board
grid[gridY][gridX] = block;
gameBoard.addChild(block);
}
}
}
// Remove the tetromino
gameBoard.removeChild(currentTetromino);
currentTetromino = null;
// Play sound
if (LK.getSound('tetromino_land')) {
LK.getSound('tetromino_land').play();
} else {
console.error("Sound 'tetromino_land' not found");
}
// Check for completed lines
checkLines();
// Spawn new tetromino
spawnTetromino();
}
function checkLines() {
var completedLines = 0;
var linesToClear = [];
// Check for completed lines
for (var y = 0; y < GRID_HEIGHT; y++) {
var isLineComplete = true;
for (var x = 0; x < GRID_WIDTH; x++) {
if (grid[y][x] === null) {
isLineComplete = false;
break;
}
}
if (isLineComplete) {
completedLines++;
linesToClear.push(y);
}
}
// If no lines were completed, return
if (completedLines === 0) {
return;
}
// Add score based on number of lines cleared
var points = 0;
switch (completedLines) {
case 1:
points = 100 * level;
break;
case 2:
points = 300 * level;
break;
case 3:
points = 500 * level;
break;
case 4:
points = 800 * level;
break;
}
score = Math.min(score + points, 9999);
lines += completedLines;
// Update level
var newLevel = Math.floor(lines / 10) + 1;
if (newLevel > level) {
level = newLevel;
if (LK.getSound('level_up')) {
LK.getSound('level_up').play();
} else {
console.error("Sound 'level_up' not found");
}
}
// Play sound
if (LK.getSound('line_clear')) {
LK.getSound('line_clear').play();
} else {
console.error("Sound 'line_clear' not found");
}
if (LK.getSound('hacked_speech')) {
LK.getSound('hacked_speech').play();
} else {
console.error("Sound 'hacked_speech' not found");
}
// Clear the lines
clearLines(linesToClear);
// Update UI
updateUI();
// Save high score
if (score > storage.highScore) {
storage.highScore = score;
}
}
function clearLines(linesToClear) {
// Create and add 'HACKED' text
var hackedText = new Text2('HACKED', {
size: 200,
fill: 0xffffff // White color
});
hackedText.anchor.set(0.5, 0.5);
hackedText.x = GRID_OFFSET_X + GRID_WIDTH * BLOCK_SIZE / 2;
hackedText.y = GRID_OFFSET_Y + GRID_HEIGHT * BLOCK_SIZE / 2;
game.addChild(hackedText);
// Play digitized speech saying 'hacked'
if (LK.getSound('hacked_speech')) {
LK.getSound('hacked_speech').play();
} else {
console.error("Sound 'hacked_speech' not found");
}
// Flash the 'HACKED' text
tween(hackedText, {
alpha: 0
}, {
duration: 1600,
repeat: 10,
yoyo: true,
onFinish: function onFinish() {
game.removeChild(hackedText);
}
});
// Flash the lines to be cleared
for (var i = 0; i < linesToClear.length; i++) {
var y = linesToClear[i];
for (var x = 0; x < GRID_WIDTH; x++) {
if (grid[y][x] !== null) {
tween(grid[y][x], {
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
tween(this, {
alpha: 1
}, {
duration: 200
});
}
});
}
}
}
// Schedule line clearing after the flash
LK.setTimeout(function () {
// Remove blocks from cleared lines with shattering effect
for (var i = 0; i < linesToClear.length; i++) {
var y = linesToClear[i];
for (var x = 0; x < GRID_WIDTH; x++) {
if (grid[y][x] !== null) {
// Create shattering effect
createShatterEffect(grid[y][x]);
gameBoard.removeChild(grid[y][x]);
grid[y][x] = null;
}
}
}
// Function to create shattering effect
function createShatterEffect(block) {
var particles = [];
var numParticles = 20; // Increased number of particles per block
for (var i = 0; i < numParticles; i++) {
var particle = LK.getAsset('block', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8,
width: BLOCK_SIZE / 3,
// Increased particle size
height: BLOCK_SIZE / 3,
// Increased particle size
color: block.graphics.color
});
particle.x = block.x + (Math.random() - 0.5) * BLOCK_SIZE;
particle.y = block.y + (Math.random() - 0.5) * BLOCK_SIZE;
particles.push(particle);
gameBoard.addChild(particle);
// Animate particle
tween(particle, {
x: particle.x + (Math.random() - 0.5) * 300,
// Increased movement range
y: particle.y + (Math.random() - 0.5) * 300,
// Increased movement range
alpha: 0
}, {
duration: 1500,
// Increased duration
onFinish: function onFinish() {
gameBoard.removeChild(this);
}
});
}
}
// Move down blocks above cleared lines
for (var i = 0; i < linesToClear.length; i++) {
var clearedY = linesToClear[i];
// Move all blocks above the cleared line down
for (var y = clearedY; y > 0; y--) {
for (var x = 0; x < GRID_WIDTH; x++) {
grid[y][x] = grid[y - 1][x];
if (grid[y][x] !== null) {
grid[y][x].y = y * BLOCK_SIZE;
}
}
}
// Clear the top line
for (var x = 0; x < GRID_WIDTH; x++) {
grid[0][x] = null;
}
}
}, 400);
}
function updateUI() {
scoreText.setText('Score: ' + score);
levelText.setText('Level: ' + level);
linesText.setText('Lines: ' + lines);
// Set score to LK score system
LK.setScore(score);
}
function gameOver() {
isGameOver = true;
// Flash the grid
tween(gameBoard, {
alpha: 0.5
}, {
duration: 500,
onFinish: function onFinish() {
tween(gameBoard, {
alpha: 1
}, {
duration: 500
});
}
});
// Show game over dialog
LK.setTimeout(function () {
LK.showGameOver();
}, 1000);
}
function resetGame() {
// Clear the grid
for (var y = 0; y < GRID_HEIGHT; y++) {
for (var x = 0; x < GRID_WIDTH; x++) {
if (grid[y][x] !== null) {
gameBoard.removeChild(grid[y][x]);
grid[y][x] = null;
}
}
}
// Reset game variables
score = 0;
lines = 0;
level = storage.level || 1;
isGameOver = false;
// Clear any active tetromino
if (currentTetromino !== null) {
gameBoard.removeChild(currentTetromino);
currentTetromino = null;
}
// Clear next tetromino
nextTetromino = null;
// Update UI
updateUI();
// Spawn new tetromino
spawnTetromino();
}
// Game update logic
game.update = function () {
if (isGameOver) {
return;
}
// Initialize game if needed
if (currentTetromino === null && nextTetromino === null) {
spawnTetromino();
}
// Update drop timer
var currentTime = LK.ticks;
var dropInterval = DROP_INTERVAL * Math.pow(DROP_SPEED_INCREASE, level - 1);
if (currentTime - lastDropTime >= dropInterval / 16.67) {
// Convert ms to ticks (60 FPS)
moveTetrominoDown();
lastDropTime = currentTime;
}
};
// Start background music
LK.playMusic('bgmusic', {
loop: true
});
// Initialize game
resetGame(); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0,
level: 1
});
/****
* Classes
****/
var Block = Container.expand(function () {
var self = Container.call(this);
self.blockType = '';
self.init = function (type) {
self.blockType = type;
var color = 'block_' + type;
self.graphics = self.attachAsset(color, {
anchorX: 0,
anchorY: 0,
alpha: 0.9
});
return self;
};
return self;
});
var Button = Container.expand(function () {
var self = Container.call(this);
self.init = function (label, iconType) {
var buttonGraphics = self.attachAsset('button', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 2.0,
shape: 'ellipse' // Change shape to ellipse to make it circular
});
var text = new Text2(label, {
size: 100,
fill: 0xFFFFFF // White color for icon
});
text.anchor.set(0.5, 0.5);
self.addChild(text);
self.down = function (x, y, obj) {
buttonGraphics.alpha = 0.7;
};
self.up = function (x, y, obj) {
buttonGraphics.alpha = 1.0;
};
return self;
};
return self;
});
var Tetromino = Container.expand(function () {
var self = Container.call(this);
self.blocks = [];
self.shape = [];
self.type = '';
self.rotation = 0;
self.gridX = 0;
self.gridY = 0;
self.init = function (type) {
self.type = type;
self.createShape(type);
return self;
};
self.createShape = function (type) {
// Clear existing blocks
for (var i = 0; i < self.blocks.length; i++) {
self.removeChild(self.blocks[i]);
}
self.blocks = [];
// Create new shape based on type
switch (type) {
case 'i':
self.shape = [[0, 0, 0, 0], [1, 1, 1, 1], [0, 0, 0, 0], [0, 0, 0, 0]];
break;
case 'j':
self.shape = [[1, 0, 0], [1, 1, 1], [0, 0, 0]];
break;
case 'l':
self.shape = [[0, 0, 1], [1, 1, 1], [0, 0, 0]];
break;
case 'o':
self.shape = [[1, 1], [1, 1]];
break;
case 's':
self.shape = [[0, 1, 1], [1, 1, 0], [0, 0, 0]];
break;
case 't':
self.shape = [[0, 1, 0], [1, 1, 1], [0, 0, 0]];
break;
case 'z':
self.shape = [[1, 1, 0], [0, 1, 1], [0, 0, 0]];
break;
}
// Create blocks based on shape
for (var y = 0; y < self.shape.length; y++) {
for (var x = 0; x < self.shape[y].length; x++) {
if (self.shape[y][x]) {
var block = new Block().init(type);
block.x = x * BLOCK_SIZE;
block.y = y * BLOCK_SIZE;
self.addChild(block);
self.blocks.push(block);
}
}
}
};
self.rotate = function () {
if (self.type === 'o') {
return;
} // O pieces don't rotate
// Create a new rotated matrix
var newShape = [];
var size = self.shape.length;
for (var y = 0; y < size; y++) {
newShape[y] = [];
for (var x = 0; x < size; x++) {
newShape[y][x] = self.shape[size - 1 - x][y];
}
}
// Save original shape in case rotation is invalid
var originalShape = self.shape;
self.shape = newShape;
// Update block positions
self.updateBlocks();
// Check if the rotation is valid
if (!isValidPosition(self)) {
// Revert to original shape
self.shape = originalShape;
self.updateBlocks();
return false;
}
if (LK.getSound('tetromino_rotate')) {
LK.getSound('tetromino_rotate').play();
} else {
console.error("Sound 'tetromino_rotate' not found");
}
return true;
};
self.updateBlocks = function () {
var blockIndex = 0;
for (var y = 0; y < self.shape.length; y++) {
for (var x = 0; x < self.shape[y].length; x++) {
if (self.shape[y][x]) {
self.blocks[blockIndex].x = x * BLOCK_SIZE;
self.blocks[blockIndex].y = y * BLOCK_SIZE;
blockIndex++;
}
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x9bbc0f
});
/****
* Game Code
****/
// Constants
var BLOCK_SIZE = 100; // Block size in pixels
var GRID_WIDTH = 10; // Grid width in blocks
var GRID_HEIGHT = 20; // Grid height in blocks
var GRID_OFFSET_X = (2048 - GRID_WIDTH * BLOCK_SIZE) / 2; // Center grid x position
var GRID_OFFSET_Y = 100; // Grid y position
var DROP_INTERVAL = 1000; // Initial interval between block drops in ms
var DROP_SPEED_INCREASE = 0.8; // Speed multiplier per level
// Game variables
var grid = []; // Grid state
var currentTetromino = null; // Current active tetromino
var nextTetromino = null; // Next tetromino to drop
var score = 0;
var level = storage.level || 1;
var lines = 0;
var isGameOver = false;
var dropTimer = 0;
var lastDropTime = 0;
var inputDelay = 0; // Delay for input controls to prevent too fast movement
// Initialize grid
for (var y = 0; y < GRID_HEIGHT; y++) {
grid[y] = [];
for (var x = 0; x < GRID_WIDTH; x++) {
grid[y][x] = null;
}
}
// Create grid background with lines
var gridBackground = new Container();
for (var y = 0; y < GRID_HEIGHT; y++) {
for (var x = 0; x < GRID_WIDTH; x++) {
var cell = LK.getAsset('grid_cell', {
anchorX: 0,
anchorY: 0,
alpha: 0.5
});
cell.x = x * BLOCK_SIZE;
cell.y = y * BLOCK_SIZE;
gridBackground.addChild(cell);
// Add vertical line
if (x < GRID_WIDTH - 1) {
var verticalLine = LK.getAsset('block', {
anchorX: 0,
anchorY: 0,
alpha: 0.2,
width: 20,
// Increased thickness
height: BLOCK_SIZE,
color: 0x000000 // Black color
});
verticalLine.x = (x + 1) * BLOCK_SIZE - 1;
verticalLine.y = y * BLOCK_SIZE;
gridBackground.addChild(verticalLine);
}
// Add horizontal line
if (y < GRID_HEIGHT - 1) {
var horizontalLine = LK.getAsset('block', {
anchorX: 0,
anchorY: 0,
alpha: 0.2,
width: BLOCK_SIZE,
height: 20,
// Increased thickness
color: 0x000000 // Black color
});
horizontalLine.x = x * BLOCK_SIZE;
horizontalLine.y = (y + 1) * BLOCK_SIZE - 1;
gridBackground.addChild(horizontalLine);
}
}
}
gridBackground.x = GRID_OFFSET_X;
gridBackground.y = GRID_OFFSET_Y;
game.addChild(gridBackground);
// Create game board container
var gameBoard = new Container();
gameBoard.x = GRID_OFFSET_X;
gameBoard.y = GRID_OFFSET_Y;
game.addChild(gameBoard);
// Next piece preview
var nextPieceContainer = new Container();
nextPieceContainer.x = GRID_OFFSET_X - 200;
nextPieceContainer.y = GRID_OFFSET_Y + 100;
game.addChild(nextPieceContainer);
// Create UI text elements
var scoreText = new Text2('Score: 0', {
size: 80,
fill: 0x000000
});
scoreText.anchor.set(0, 0);
scoreText.x = GRID_OFFSET_X + GRID_WIDTH * BLOCK_SIZE + 50;
scoreText.y = GRID_OFFSET_Y;
game.addChild(scoreText);
var levelText = new Text2('Level: 1', {
size: 80,
fill: 0x000000
});
levelText.anchor.set(0, 0);
levelText.x = GRID_OFFSET_X + GRID_WIDTH * BLOCK_SIZE + 50;
levelText.y = GRID_OFFSET_Y + 120;
game.addChild(levelText);
var linesText = new Text2('Lines: 0', {
size: 80,
fill: 0x000000
});
linesText.anchor.set(0, 0);
linesText.x = GRID_OFFSET_X + GRID_WIDTH * BLOCK_SIZE + 50;
linesText.y = GRID_OFFSET_Y + 250;
game.addChild(linesText);
// Create control buttons
var leftButton = new Button().init('←');
leftButton.x = 300;
leftButton.y = 2432; // Move up by 200 pixels
leftButton.down = function () {
moveTetrominoLeft();
this.alpha = 0.7;
};
leftButton.up = function () {
this.alpha = 1.0;
};
game.addChild(leftButton);
var rightButton = new Button().init('→');
rightButton.x = 750;
rightButton.y = 2432; // Move up by 200 pixels
rightButton.down = function () {
moveTetrominoRight();
this.alpha = 0.7;
};
rightButton.up = function () {
this.alpha = 1.0;
};
game.addChild(rightButton);
var rotateButton = new Button().init('↻');
rotateButton.x = 1300;
rotateButton.y = 2432; // Move up by 200 pixels
rotateButton.down = function () {
rotateTetromino();
this.alpha = 0.7;
};
rotateButton.up = function () {
this.alpha = 1.0;
};
game.addChild(rotateButton);
var dropButton = new Button().init('↓');
dropButton.x = 1750;
dropButton.y = 2432; // Move up by 200 pixels
dropButton.down = function () {
dropTetromino();
this.alpha = 0.7;
};
dropButton.up = function () {
this.alpha = 1.0;
};
game.addChild(dropButton);
// Tetromino types
var tetrominoTypes = ['i', 'j', 'l', 'o', 's', 't', 'z'];
// Helper functions
function createRandomTetromino() {
var type = tetrominoTypes[Math.floor(Math.random() * tetrominoTypes.length)];
return new Tetromino().init(type);
}
function spawnTetromino() {
if (nextTetromino === null) {
nextTetromino = createRandomTetromino();
}
currentTetromino = nextTetromino;
nextTetromino = createRandomTetromino();
// Position the tetromino at the top center of the grid
currentTetromino.gridX = Math.floor(GRID_WIDTH / 2) - Math.floor(currentTetromino.shape.length / 2);
currentTetromino.gridY = 0;
// Update tetromino position
currentTetromino.x = currentTetromino.gridX * BLOCK_SIZE;
currentTetromino.y = currentTetromino.gridY * BLOCK_SIZE;
// Add to game board
gameBoard.addChild(currentTetromino);
// Update next piece preview
updateNextPiecePreview();
// Check if game is over
if (!isValidPosition(currentTetromino)) {
gameOver();
}
}
function updateNextPiecePreview() {
// Clear previous preview
while (nextPieceContainer.children.length > 0) {
nextPieceContainer.removeChild(nextPieceContainer.children[0]);
}
// Create a copy of the next tetromino for preview
var previewTetromino = new Tetromino().init(nextTetromino.type);
// Center it in the preview area
previewTetromino.x = 75 - previewTetromino.shape.length * BLOCK_SIZE / 4;
previewTetromino.y = 75 - previewTetromino.shape.length * BLOCK_SIZE / 4;
previewTetromino.scale.set(0.5, 0.5);
nextPieceContainer.addChild(previewTetromino);
}
function isValidPosition(tetromino) {
for (var y = 0; y < tetromino.shape.length; y++) {
for (var x = 0; x < tetromino.shape[y].length; x++) {
if (tetromino.shape[y][x]) {
var gridX = tetromino.gridX + x;
var gridY = tetromino.gridY + y;
// Check if out of bounds
if (gridX < 0 || gridX >= GRID_WIDTH || gridY >= GRID_HEIGHT) {
return false;
}
// Check if overlapping with locked blocks (and not above the grid)
if (gridY >= 0 && grid[gridY][gridX] !== null) {
return false;
}
}
}
}
return true;
}
function moveTetromino(dx, dy) {
if (currentTetromino === null || isGameOver) {
return false;
}
// Save original position
var originalX = currentTetromino.gridX;
var originalY = currentTetromino.gridY;
// Try to move
currentTetromino.gridX += dx;
currentTetromino.gridY += dy;
// Check if the new position is valid
if (!isValidPosition(currentTetromino)) {
// Revert position
currentTetromino.gridX = originalX;
currentTetromino.gridY = originalY;
// If we tried to move down and failed, lock the tetromino
if (dy > 0) {
lockTetromino();
}
return false;
}
// Update position
currentTetromino.x = currentTetromino.gridX * BLOCK_SIZE;
currentTetromino.y = currentTetromino.gridY * BLOCK_SIZE;
return true;
}
function moveTetrominoLeft() {
if (LK.ticks - inputDelay < 5) {
return;
} // Prevent too fast movement
inputDelay = LK.ticks;
moveTetromino(-1, 0);
}
function moveTetrominoRight() {
if (LK.ticks - inputDelay < 5) {
return;
} // Prevent too fast movement
inputDelay = LK.ticks;
moveTetromino(1, 0);
}
function moveTetrominoDown() {
return moveTetromino(0, 1);
}
function rotateTetromino() {
if (currentTetromino === null || isGameOver) {
return;
}
if (LK.ticks - inputDelay < 5) {
return;
} // Prevent too fast rotation
inputDelay = LK.ticks;
currentTetromino.rotate();
}
function dropTetromino() {
if (currentTetromino === null || isGameOver) {
return;
}
if (LK.ticks - inputDelay < 5) {
return;
} // Prevent too fast dropping
inputDelay = LK.ticks;
// Move tetromino down until it can't move anymore
while (moveTetrominoDown()) {
// Continue moving down
}
}
function lockTetromino() {
if (currentTetromino === null) {
return;
}
// Add blocks to grid
for (var y = 0; y < currentTetromino.shape.length; y++) {
for (var x = 0; x < currentTetromino.shape[y].length; x++) {
if (currentTetromino.shape[y][x]) {
var gridX = currentTetromino.gridX + x;
var gridY = currentTetromino.gridY + y;
// Skip if above the grid
if (gridY < 0) {
continue;
}
// Create a new block at this position
var block = new Block().init(currentTetromino.type);
block.x = gridX * BLOCK_SIZE;
block.y = gridY * BLOCK_SIZE;
// Add to grid and game board
grid[gridY][gridX] = block;
gameBoard.addChild(block);
}
}
}
// Remove the tetromino
gameBoard.removeChild(currentTetromino);
currentTetromino = null;
// Play sound
if (LK.getSound('tetromino_land')) {
LK.getSound('tetromino_land').play();
} else {
console.error("Sound 'tetromino_land' not found");
}
// Check for completed lines
checkLines();
// Spawn new tetromino
spawnTetromino();
}
function checkLines() {
var completedLines = 0;
var linesToClear = [];
// Check for completed lines
for (var y = 0; y < GRID_HEIGHT; y++) {
var isLineComplete = true;
for (var x = 0; x < GRID_WIDTH; x++) {
if (grid[y][x] === null) {
isLineComplete = false;
break;
}
}
if (isLineComplete) {
completedLines++;
linesToClear.push(y);
}
}
// If no lines were completed, return
if (completedLines === 0) {
return;
}
// Add score based on number of lines cleared
var points = 0;
switch (completedLines) {
case 1:
points = 100 * level;
break;
case 2:
points = 300 * level;
break;
case 3:
points = 500 * level;
break;
case 4:
points = 800 * level;
break;
}
score = Math.min(score + points, 9999);
lines += completedLines;
// Update level
var newLevel = Math.floor(lines / 10) + 1;
if (newLevel > level) {
level = newLevel;
if (LK.getSound('level_up')) {
LK.getSound('level_up').play();
} else {
console.error("Sound 'level_up' not found");
}
}
// Play sound
if (LK.getSound('line_clear')) {
LK.getSound('line_clear').play();
} else {
console.error("Sound 'line_clear' not found");
}
if (LK.getSound('hacked_speech')) {
LK.getSound('hacked_speech').play();
} else {
console.error("Sound 'hacked_speech' not found");
}
// Clear the lines
clearLines(linesToClear);
// Update UI
updateUI();
// Save high score
if (score > storage.highScore) {
storage.highScore = score;
}
}
function clearLines(linesToClear) {
// Create and add 'HACKED' text
var hackedText = new Text2('HACKED', {
size: 200,
fill: 0xffffff // White color
});
hackedText.anchor.set(0.5, 0.5);
hackedText.x = GRID_OFFSET_X + GRID_WIDTH * BLOCK_SIZE / 2;
hackedText.y = GRID_OFFSET_Y + GRID_HEIGHT * BLOCK_SIZE / 2;
game.addChild(hackedText);
// Play digitized speech saying 'hacked'
if (LK.getSound('hacked_speech')) {
LK.getSound('hacked_speech').play();
} else {
console.error("Sound 'hacked_speech' not found");
}
// Flash the 'HACKED' text
tween(hackedText, {
alpha: 0
}, {
duration: 1600,
repeat: 10,
yoyo: true,
onFinish: function onFinish() {
game.removeChild(hackedText);
}
});
// Flash the lines to be cleared
for (var i = 0; i < linesToClear.length; i++) {
var y = linesToClear[i];
for (var x = 0; x < GRID_WIDTH; x++) {
if (grid[y][x] !== null) {
tween(grid[y][x], {
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
tween(this, {
alpha: 1
}, {
duration: 200
});
}
});
}
}
}
// Schedule line clearing after the flash
LK.setTimeout(function () {
// Remove blocks from cleared lines with shattering effect
for (var i = 0; i < linesToClear.length; i++) {
var y = linesToClear[i];
for (var x = 0; x < GRID_WIDTH; x++) {
if (grid[y][x] !== null) {
// Create shattering effect
createShatterEffect(grid[y][x]);
gameBoard.removeChild(grid[y][x]);
grid[y][x] = null;
}
}
}
// Function to create shattering effect
function createShatterEffect(block) {
var particles = [];
var numParticles = 20; // Increased number of particles per block
for (var i = 0; i < numParticles; i++) {
var particle = LK.getAsset('block', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8,
width: BLOCK_SIZE / 3,
// Increased particle size
height: BLOCK_SIZE / 3,
// Increased particle size
color: block.graphics.color
});
particle.x = block.x + (Math.random() - 0.5) * BLOCK_SIZE;
particle.y = block.y + (Math.random() - 0.5) * BLOCK_SIZE;
particles.push(particle);
gameBoard.addChild(particle);
// Animate particle
tween(particle, {
x: particle.x + (Math.random() - 0.5) * 300,
// Increased movement range
y: particle.y + (Math.random() - 0.5) * 300,
// Increased movement range
alpha: 0
}, {
duration: 1500,
// Increased duration
onFinish: function onFinish() {
gameBoard.removeChild(this);
}
});
}
}
// Move down blocks above cleared lines
for (var i = 0; i < linesToClear.length; i++) {
var clearedY = linesToClear[i];
// Move all blocks above the cleared line down
for (var y = clearedY; y > 0; y--) {
for (var x = 0; x < GRID_WIDTH; x++) {
grid[y][x] = grid[y - 1][x];
if (grid[y][x] !== null) {
grid[y][x].y = y * BLOCK_SIZE;
}
}
}
// Clear the top line
for (var x = 0; x < GRID_WIDTH; x++) {
grid[0][x] = null;
}
}
}, 400);
}
function updateUI() {
scoreText.setText('Score: ' + score);
levelText.setText('Level: ' + level);
linesText.setText('Lines: ' + lines);
// Set score to LK score system
LK.setScore(score);
}
function gameOver() {
isGameOver = true;
// Flash the grid
tween(gameBoard, {
alpha: 0.5
}, {
duration: 500,
onFinish: function onFinish() {
tween(gameBoard, {
alpha: 1
}, {
duration: 500
});
}
});
// Show game over dialog
LK.setTimeout(function () {
LK.showGameOver();
}, 1000);
}
function resetGame() {
// Clear the grid
for (var y = 0; y < GRID_HEIGHT; y++) {
for (var x = 0; x < GRID_WIDTH; x++) {
if (grid[y][x] !== null) {
gameBoard.removeChild(grid[y][x]);
grid[y][x] = null;
}
}
}
// Reset game variables
score = 0;
lines = 0;
level = storage.level || 1;
isGameOver = false;
// Clear any active tetromino
if (currentTetromino !== null) {
gameBoard.removeChild(currentTetromino);
currentTetromino = null;
}
// Clear next tetromino
nextTetromino = null;
// Update UI
updateUI();
// Spawn new tetromino
spawnTetromino();
}
// Game update logic
game.update = function () {
if (isGameOver) {
return;
}
// Initialize game if needed
if (currentTetromino === null && nextTetromino === null) {
spawnTetromino();
}
// Update drop timer
var currentTime = LK.ticks;
var dropInterval = DROP_INTERVAL * Math.pow(DROP_SPEED_INCREASE, level - 1);
if (currentTime - lastDropTime >= dropInterval / 16.67) {
// Convert ms to ticks (60 FPS)
moveTetrominoDown();
lastDropTime = currentTime;
}
};
// Start background music
LK.playMusic('bgmusic', {
loop: true
});
// Initialize game
resetGame();