/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Board class
var Board = Container.expand(function () {
var self = Container.call(this);
self.grid = [];
self.cells = [];
self.bg = null;
self.init = function () {
self.grid = [];
for (var y = 0; y < BOARD_ROWS; ++y) {
var row = [];
for (var x = 0; x < BOARD_COLS; ++x) {
row.push(null);
}
self.grid.push(row);
}
self.bg = self.attachAsset('boardbg', {
anchorX: 0,
anchorY: 0,
width: BOARD_COLS * CELL_SIZE,
height: BOARD_ROWS * CELL_SIZE,
x: 0,
y: 0
});
};
self.isInside = function (x, y) {
return x >= 0 && x < BOARD_COLS && y >= 0 && y < BOARD_ROWS;
};
self.isCellEmpty = function (x, y) {
if (!self.isInside(x, y)) return false;
return self.grid[y][x] === null;
};
self.canPlace = function (type, xCell, yCell, rotation) {
var shape = TETROMINOS[type][rotation];
for (var i = 0; i < shape.length; ++i) {
var x = xCell + shape[i][0];
var y = yCell + shape[i][1];
if (!self.isInside(x, y) || self.grid[y][x] !== null) {
return false;
}
}
return true;
};
self.placeTetrimino = function (type, xCell, yCell, rotation) {
var shape = TETROMINOS[type][rotation];
for (var i = 0; i < shape.length; ++i) {
var x = xCell + shape[i][0];
var y = yCell + shape[i][1];
self.grid[y][x] = type;
}
};
self.clearLines = function () {
var lines = [];
for (var y = 0; y < BOARD_ROWS; ++y) {
var full = true;
for (var x = 0; x < BOARD_COLS; ++x) {
if (self.grid[y][x] === null) {
full = false;
break;
}
}
if (full) lines.push(y);
}
for (var i = 0; i < lines.length; ++i) {
var y = lines[i];
for (var yy = y; yy > 0; --yy) {
for (var x = 0; x < BOARD_COLS; ++x) {
self.grid[yy][x] = self.grid[yy - 1][x];
}
}
for (var x = 0; x < BOARD_COLS; ++x) {
self.grid[0][x] = null;
}
}
return lines.length;
};
self.renderBlocks = function () {
// Remove old
for (var i = 0; i < self.cells.length; ++i) {
self.cells[i].destroy();
}
self.cells = [];
// Draw new
for (var y = 0; y < BOARD_ROWS; ++y) {
for (var x = 0; x < BOARD_COLS; ++x) {
var type = self.grid[y][x];
if (type) {
var block = self.attachAsset(getTetrominoAsset(type), {
anchorX: 0.5,
anchorY: 0.5,
x: x * CELL_SIZE + CELL_SIZE / 2,
y: y * CELL_SIZE + CELL_SIZE / 2
});
self.cells.push(block);
}
}
}
};
return self;
});
// Button class
var GameButton = Container.expand(function () {
var self = Container.call(this);
self.bg = null;
self.icon = null;
self.label = null;
self.action = null;
self.init = function (iconType, label, action) {
self.bg = self.attachAsset('btn', {
anchorX: 0.5,
anchorY: 0.5
});
if (iconType === 'left') {
self.icon = self.attachAsset('btnArrow', {
anchorX: 0.5,
anchorY: 0.5,
rotation: Math.PI
});
} else if (iconType === 'right') {
self.icon = self.attachAsset('btnArrow', {
anchorX: 0.5,
anchorY: 0.5,
rotation: 0
});
} else if (iconType === 'down') {
self.icon = self.attachAsset('btnArrow', {
anchorX: 0.5,
anchorY: 0.5,
rotation: Math.PI / 2
});
} else if (iconType === 'up') {
self.icon = self.attachAsset('btnArrow', {
anchorX: 0.5,
anchorY: 0.5,
rotation: -Math.PI / 2
});
} else if (iconType === 'rotate') {
self.label = new Text2('⟳', {
size: 90,
fill: "#fff"
});
self.label.anchor.set(0.5, 0.5);
self.addChild(self.label);
} else if (iconType === 'drop') {
self.label = new Text2('⇩', {
size: 90,
fill: "#fff"
});
self.label.anchor.set(0.5, 0.5);
self.addChild(self.label);
}
self.action = action;
};
self.down = function (x, y, obj) {
if (self.action) self.action();
};
return self;
});
// Tetrimino class
var Tetrimino = Container.expand(function () {
var self = Container.call(this);
self.type = null;
self.rotation = 0;
self.xCell = 0;
self.yCell = 0;
self.blocks = [];
self.ghostBlocks = [];
self.isGhost = false;
self.init = function (type, rotation, xCell, yCell, isGhost) {
self.type = type;
self.rotation = rotation;
self.xCell = xCell;
self.yCell = yCell;
self.isGhost = !!isGhost;
self.clearBlocks();
self.createBlocks();
};
self.clearBlocks = function () {
for (var i = 0; i < self.blocks.length; ++i) {
self.blocks[i].destroy();
}
self.blocks = [];
for (var i = 0; i < self.ghostBlocks.length; ++i) {
self.ghostBlocks[i].destroy();
}
self.ghostBlocks = [];
};
self.createBlocks = function () {
var shape = self.isGhost ? getGhostAsset() : getTetrominoAsset(self.type);
var positions = TETROMINOS[self.type][self.rotation];
for (var i = 0; i < positions.length; ++i) {
var block = self.attachAsset(shape, {
anchorX: 0.5,
anchorY: 0.5,
x: positions[i][0] * CELL_SIZE + CELL_SIZE / 2,
y: positions[i][1] * CELL_SIZE + CELL_SIZE / 2,
alpha: self.isGhost ? 0.3 : 1
});
self.blocks.push(block);
}
};
self.setPosition = function (xCell, yCell) {
self.xCell = xCell;
self.yCell = yCell;
self.x = BOARD_X + xCell * CELL_SIZE;
self.y = BOARD_Y + yCell * CELL_SIZE;
};
self.setRotation = function (rotation) {
self.rotation = rotation;
self.clearBlocks();
self.createBlocks();
self.setPosition(self.xCell, self.yCell);
};
self.getBlockPositions = function (xCell, yCell, rotation) {
var pos = [];
var shape = TETROMINOS[self.type][rotation];
for (var i = 0; i < shape.length; ++i) {
pos.push([xCell + shape[i][0], yCell + shape[i][1]]);
}
return pos;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181828
});
/****
* Game Code
****/
// Tetrimino definitions (relative block positions for each rotation)
// Button shapes
// Board background
// Ghost piece (semi-transparent)
// Tetrimino colors (7 types)
// Responsive scaling
var TETROMINOS = {
I: [[[0, 1], [1, 1], [2, 1], [3, 1]],
// 0
[[2, 0], [2, 1], [2, 2], [2, 3]],
// 1
[[0, 2], [1, 2], [2, 2], [3, 2]],
// 2
[[1, 0], [1, 1], [1, 2], [1, 3]] // 3
],
O: [[[1, 0], [2, 0], [1, 1], [2, 1]],
// 0
[[1, 0], [2, 0], [1, 1], [2, 1]],
// 1
[[1, 0], [2, 0], [1, 1], [2, 1]],
// 2
[[1, 0], [2, 0], [1, 1], [2, 1]] // 3
],
T: [[[1, 0], [0, 1], [1, 1], [2, 1]],
// 0
[[1, 0], [1, 1], [2, 1], [1, 2]],
// 1
[[0, 1], [1, 1], [2, 1], [1, 2]],
// 2
[[1, 0], [0, 1], [1, 1], [1, 2]] // 3
],
S: [[[1, 0], [2, 0], [0, 1], [1, 1]],
// 0
[[1, 0], [1, 1], [2, 1], [2, 2]],
// 1
[[1, 1], [2, 1], [0, 2], [1, 2]],
// 2
[[0, 0], [0, 1], [1, 1], [1, 2]] // 3
],
Z: [[[0, 0], [1, 0], [1, 1], [2, 1]],
// 0
[[2, 0], [1, 1], [2, 1], [1, 2]],
// 1
[[0, 1], [1, 1], [1, 2], [2, 2]],
// 2
[[1, 0], [0, 1], [1, 1], [0, 2]] // 3
],
J: [[[0, 0], [0, 1], [1, 1], [2, 1]],
// 0
[[1, 0], [2, 0], [1, 1], [1, 2]],
// 1
[[0, 1], [1, 1], [2, 1], [2, 2]],
// 2
[[1, 0], [1, 1], [0, 2], [1, 2]] // 3
],
L: [[[2, 0], [0, 1], [1, 1], [2, 1]],
// 0
[[1, 0], [1, 1], [1, 2], [2, 2]],
// 1
[[0, 1], [1, 1], [2, 1], [0, 2]],
// 2
[[0, 0], [1, 0], [1, 1], [1, 2]] // 3
]
};
var TETROMINO_TYPES = ['I', 'O', 'T', 'S', 'Z', 'J', 'L'];
// Board constants
var BOARD_COLS = 10;
var BOARD_ROWS = 20;
var CELL_SIZE = 80; // px, will be scaled to fit
var BOARD_X = 0;
var BOARD_Y = 0;
// Helper: get color asset id for a tetromino type
function getTetrominoAsset(type) {
return type;
}
// Helper: get ghost asset
function getGhostAsset() {
return 'ghost';
}
function updateBoardLayout() {
// Center board
BOARD_X = Math.floor((2048 - BOARD_COLS * CELL_SIZE) / 2);
BOARD_Y = Math.floor((2732 - BOARD_ROWS * CELL_SIZE) / 2);
if (BOARD_X < 120) BOARD_X = 120; // Avoid top left menu
if (BOARD_Y < 0) BOARD_Y = 0;
}
// Game state
var board = null;
var current = null;
var nextType = null;
var ghost = null;
var dropTimer = 0;
var dropInterval = 36; // 36 ticks = 0.6s at 60fps
var moveDelay = 0;
var moveRepeat = 0;
var gameOver = false;
var score = 0;
var linesCleared = 0;
var level = 1;
var softDrop = false;
var hardDrop = false;
var controlsLocked = false;
// UI
var scoreTxt = null;
var linesTxt = null;
var levelTxt = null;
var btnLeft = null;
var btnRight = null;
var btnDown = null;
var btnRotate = null;
var btnDrop = null;
var buttons = [];
// Helper: random tetromino
function randomTetromino() {
return TETROMINO_TYPES[Math.floor(Math.random() * TETROMINO_TYPES.length)];
}
// Helper: spawn new tetrimino
function spawnTetrimino() {
var type = nextType || randomTetromino();
nextType = randomTetromino();
var t = new Tetrimino();
t.init(type, 0, Math.floor(BOARD_COLS / 2) - 2, 0, false);
return t;
}
// Helper: check game over
function checkGameOver() {
if (!board.canPlace(current.type, current.xCell, current.yCell, current.rotation)) {
gameOver = true;
LK.showGameOver();
}
}
// Helper: lock current piece
function lockCurrent() {
board.placeTetrimino(current.type, current.xCell, current.yCell, current.rotation);
var cleared = board.clearLines();
if (cleared > 0) {
score += [0, 40, 100, 300, 1200][cleared] * level;
linesCleared += cleared;
if (linesCleared >= level * 10) {
level += 1;
if (dropInterval > 6) dropInterval -= 3;
}
}
board.renderBlocks();
updateScore();
current.destroy();
current = spawnTetrimino();
game.addChild(current);
updateGhost();
checkGameOver();
}
// Helper: update ghost piece
function updateGhost() {
if (ghost) ghost.destroy();
ghost = new Tetrimino();
ghost.init(current.type, current.rotation, current.xCell, current.yCell, true);
var y = current.yCell;
while (board.canPlace(current.type, current.xCell, y + 1, current.rotation)) {
y += 1;
}
ghost.setPosition(current.xCell, y);
game.addChild(ghost);
ghost.zIndex = -1;
}
// Helper: update score UI
function updateScore() {
scoreTxt.setText(score + "");
linesTxt.setText("Lines: " + linesCleared);
levelTxt.setText("Level: " + level);
}
// Helper: move current piece
function tryMove(dx, dy, rot) {
if (controlsLocked) return;
var nx = current.xCell + (dx || 0);
var ny = current.yCell + (dy || 0);
var nr = (current.rotation + (rot || 0) + 4) % 4;
if (board.canPlace(current.type, nx, ny, nr)) {
current.setRotation(nr);
current.setPosition(nx, ny);
updateGhost();
return true;
}
return false;
}
// Helper: hard drop
function doHardDrop() {
if (controlsLocked) return;
var y = current.yCell;
while (board.canPlace(current.type, current.xCell, y + 1, current.rotation)) {
y += 1;
}
current.setPosition(current.xCell, y);
lockCurrent();
}
// Helper: soft drop
function doSoftDrop() {
if (controlsLocked) return;
if (!tryMove(0, 1, 0)) {
lockCurrent();
}
}
// Helper: tick drop
function tickDrop() {
if (controlsLocked) return;
if (!tryMove(0, 1, 0)) {
lockCurrent();
}
}
// UI: create buttons
function createButtons() {
var yBase = 2732 - 220;
var xBase = 2048 / 2;
btnLeft = new GameButton();
btnLeft.init('left', '', function () {
tryMove(-1, 0, 0);
});
btnLeft.x = xBase - 320;
btnLeft.y = yBase;
btnRight = new GameButton();
btnRight.init('right', '', function () {
tryMove(1, 0, 0);
});
btnRight.x = xBase - 80;
btnRight.y = yBase;
btnDown = new GameButton();
btnDown.init('down', '', function () {
doSoftDrop();
});
btnDown.x = xBase + 160;
btnDown.y = yBase;
btnRotate = new GameButton();
btnRotate.init('rotate', '', function () {
tryMove(0, 0, 1);
});
btnRotate.x = xBase + 400;
btnRotate.y = yBase;
btnDrop = new GameButton();
btnDrop.init('drop', '', function () {
doHardDrop();
});
btnDrop.x = xBase + 640;
btnDrop.y = yBase;
buttons = [btnLeft, btnRight, btnDown, btnRotate, btnDrop];
for (var i = 0; i < buttons.length; ++i) {
LK.gui.bottom.addChild(buttons[i]);
}
}
// UI: create score
function createScoreUI() {
scoreTxt = new Text2("0", {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
scoreTxt.x = 2048 / 2;
scoreTxt.y = 40;
linesTxt = new Text2("Lines: 0", {
size: 60,
fill: "#fff"
});
linesTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(linesTxt);
linesTxt.x = 2048 / 2 - 300;
linesTxt.y = 180;
levelTxt = new Text2("Level: 1", {
size: 60,
fill: "#fff"
});
levelTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(levelTxt);
levelTxt.x = 2048 / 2 + 300;
levelTxt.y = 180;
}
// Touch drag controls (swipe left/right/down/up)
var dragStartX = null,
dragStartY = null,
dragActive = false;
game.down = function (x, y, obj) {
dragStartX = x;
dragStartY = y;
dragActive = true;
};
game.up = function (x, y, obj) {
dragActive = false;
};
game.move = function (x, y, obj) {
if (!dragActive) return;
var dx = x - dragStartX;
var dy = y - dragStartY;
if (Math.abs(dx) > 80) {
if (dx > 0) {
tryMove(1, 0, 0);
} else {
tryMove(-1, 0, 0);
}
dragStartX = x;
dragStartY = y;
}
if (Math.abs(dy) > 80) {
if (dy > 0) {
doSoftDrop();
} else {
tryMove(0, 0, 1);
}
dragStartX = x;
dragStartY = y;
}
};
// Main game setup
function startGame() {
updateBoardLayout();
board = new Board();
board.init();
board.x = BOARD_X;
board.y = BOARD_Y;
game.addChild(board);
board.renderBlocks();
current = spawnTetrimino();
game.addChild(current);
updateGhost();
score = 0;
linesCleared = 0;
level = 1;
dropInterval = 36;
gameOver = false;
updateScore();
}
// UI setup
createScoreUI();
createButtons();
startGame();
// Main game loop
game.update = function () {
if (gameOver) return;
dropTimer++;
if (dropTimer >= (softDrop ? 3 : dropInterval)) {
tickDrop();
dropTimer = 0;
}
};
// Reset on game over
LK.on('gameover', function () {
controlsLocked = true;
LK.setTimeout(function () {
controlsLocked = false;
startGame();
}, 1200);
}); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Board class
var Board = Container.expand(function () {
var self = Container.call(this);
self.grid = [];
self.cells = [];
self.bg = null;
self.init = function () {
self.grid = [];
for (var y = 0; y < BOARD_ROWS; ++y) {
var row = [];
for (var x = 0; x < BOARD_COLS; ++x) {
row.push(null);
}
self.grid.push(row);
}
self.bg = self.attachAsset('boardbg', {
anchorX: 0,
anchorY: 0,
width: BOARD_COLS * CELL_SIZE,
height: BOARD_ROWS * CELL_SIZE,
x: 0,
y: 0
});
};
self.isInside = function (x, y) {
return x >= 0 && x < BOARD_COLS && y >= 0 && y < BOARD_ROWS;
};
self.isCellEmpty = function (x, y) {
if (!self.isInside(x, y)) return false;
return self.grid[y][x] === null;
};
self.canPlace = function (type, xCell, yCell, rotation) {
var shape = TETROMINOS[type][rotation];
for (var i = 0; i < shape.length; ++i) {
var x = xCell + shape[i][0];
var y = yCell + shape[i][1];
if (!self.isInside(x, y) || self.grid[y][x] !== null) {
return false;
}
}
return true;
};
self.placeTetrimino = function (type, xCell, yCell, rotation) {
var shape = TETROMINOS[type][rotation];
for (var i = 0; i < shape.length; ++i) {
var x = xCell + shape[i][0];
var y = yCell + shape[i][1];
self.grid[y][x] = type;
}
};
self.clearLines = function () {
var lines = [];
for (var y = 0; y < BOARD_ROWS; ++y) {
var full = true;
for (var x = 0; x < BOARD_COLS; ++x) {
if (self.grid[y][x] === null) {
full = false;
break;
}
}
if (full) lines.push(y);
}
for (var i = 0; i < lines.length; ++i) {
var y = lines[i];
for (var yy = y; yy > 0; --yy) {
for (var x = 0; x < BOARD_COLS; ++x) {
self.grid[yy][x] = self.grid[yy - 1][x];
}
}
for (var x = 0; x < BOARD_COLS; ++x) {
self.grid[0][x] = null;
}
}
return lines.length;
};
self.renderBlocks = function () {
// Remove old
for (var i = 0; i < self.cells.length; ++i) {
self.cells[i].destroy();
}
self.cells = [];
// Draw new
for (var y = 0; y < BOARD_ROWS; ++y) {
for (var x = 0; x < BOARD_COLS; ++x) {
var type = self.grid[y][x];
if (type) {
var block = self.attachAsset(getTetrominoAsset(type), {
anchorX: 0.5,
anchorY: 0.5,
x: x * CELL_SIZE + CELL_SIZE / 2,
y: y * CELL_SIZE + CELL_SIZE / 2
});
self.cells.push(block);
}
}
}
};
return self;
});
// Button class
var GameButton = Container.expand(function () {
var self = Container.call(this);
self.bg = null;
self.icon = null;
self.label = null;
self.action = null;
self.init = function (iconType, label, action) {
self.bg = self.attachAsset('btn', {
anchorX: 0.5,
anchorY: 0.5
});
if (iconType === 'left') {
self.icon = self.attachAsset('btnArrow', {
anchorX: 0.5,
anchorY: 0.5,
rotation: Math.PI
});
} else if (iconType === 'right') {
self.icon = self.attachAsset('btnArrow', {
anchorX: 0.5,
anchorY: 0.5,
rotation: 0
});
} else if (iconType === 'down') {
self.icon = self.attachAsset('btnArrow', {
anchorX: 0.5,
anchorY: 0.5,
rotation: Math.PI / 2
});
} else if (iconType === 'up') {
self.icon = self.attachAsset('btnArrow', {
anchorX: 0.5,
anchorY: 0.5,
rotation: -Math.PI / 2
});
} else if (iconType === 'rotate') {
self.label = new Text2('⟳', {
size: 90,
fill: "#fff"
});
self.label.anchor.set(0.5, 0.5);
self.addChild(self.label);
} else if (iconType === 'drop') {
self.label = new Text2('⇩', {
size: 90,
fill: "#fff"
});
self.label.anchor.set(0.5, 0.5);
self.addChild(self.label);
}
self.action = action;
};
self.down = function (x, y, obj) {
if (self.action) self.action();
};
return self;
});
// Tetrimino class
var Tetrimino = Container.expand(function () {
var self = Container.call(this);
self.type = null;
self.rotation = 0;
self.xCell = 0;
self.yCell = 0;
self.blocks = [];
self.ghostBlocks = [];
self.isGhost = false;
self.init = function (type, rotation, xCell, yCell, isGhost) {
self.type = type;
self.rotation = rotation;
self.xCell = xCell;
self.yCell = yCell;
self.isGhost = !!isGhost;
self.clearBlocks();
self.createBlocks();
};
self.clearBlocks = function () {
for (var i = 0; i < self.blocks.length; ++i) {
self.blocks[i].destroy();
}
self.blocks = [];
for (var i = 0; i < self.ghostBlocks.length; ++i) {
self.ghostBlocks[i].destroy();
}
self.ghostBlocks = [];
};
self.createBlocks = function () {
var shape = self.isGhost ? getGhostAsset() : getTetrominoAsset(self.type);
var positions = TETROMINOS[self.type][self.rotation];
for (var i = 0; i < positions.length; ++i) {
var block = self.attachAsset(shape, {
anchorX: 0.5,
anchorY: 0.5,
x: positions[i][0] * CELL_SIZE + CELL_SIZE / 2,
y: positions[i][1] * CELL_SIZE + CELL_SIZE / 2,
alpha: self.isGhost ? 0.3 : 1
});
self.blocks.push(block);
}
};
self.setPosition = function (xCell, yCell) {
self.xCell = xCell;
self.yCell = yCell;
self.x = BOARD_X + xCell * CELL_SIZE;
self.y = BOARD_Y + yCell * CELL_SIZE;
};
self.setRotation = function (rotation) {
self.rotation = rotation;
self.clearBlocks();
self.createBlocks();
self.setPosition(self.xCell, self.yCell);
};
self.getBlockPositions = function (xCell, yCell, rotation) {
var pos = [];
var shape = TETROMINOS[self.type][rotation];
for (var i = 0; i < shape.length; ++i) {
pos.push([xCell + shape[i][0], yCell + shape[i][1]]);
}
return pos;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181828
});
/****
* Game Code
****/
// Tetrimino definitions (relative block positions for each rotation)
// Button shapes
// Board background
// Ghost piece (semi-transparent)
// Tetrimino colors (7 types)
// Responsive scaling
var TETROMINOS = {
I: [[[0, 1], [1, 1], [2, 1], [3, 1]],
// 0
[[2, 0], [2, 1], [2, 2], [2, 3]],
// 1
[[0, 2], [1, 2], [2, 2], [3, 2]],
// 2
[[1, 0], [1, 1], [1, 2], [1, 3]] // 3
],
O: [[[1, 0], [2, 0], [1, 1], [2, 1]],
// 0
[[1, 0], [2, 0], [1, 1], [2, 1]],
// 1
[[1, 0], [2, 0], [1, 1], [2, 1]],
// 2
[[1, 0], [2, 0], [1, 1], [2, 1]] // 3
],
T: [[[1, 0], [0, 1], [1, 1], [2, 1]],
// 0
[[1, 0], [1, 1], [2, 1], [1, 2]],
// 1
[[0, 1], [1, 1], [2, 1], [1, 2]],
// 2
[[1, 0], [0, 1], [1, 1], [1, 2]] // 3
],
S: [[[1, 0], [2, 0], [0, 1], [1, 1]],
// 0
[[1, 0], [1, 1], [2, 1], [2, 2]],
// 1
[[1, 1], [2, 1], [0, 2], [1, 2]],
// 2
[[0, 0], [0, 1], [1, 1], [1, 2]] // 3
],
Z: [[[0, 0], [1, 0], [1, 1], [2, 1]],
// 0
[[2, 0], [1, 1], [2, 1], [1, 2]],
// 1
[[0, 1], [1, 1], [1, 2], [2, 2]],
// 2
[[1, 0], [0, 1], [1, 1], [0, 2]] // 3
],
J: [[[0, 0], [0, 1], [1, 1], [2, 1]],
// 0
[[1, 0], [2, 0], [1, 1], [1, 2]],
// 1
[[0, 1], [1, 1], [2, 1], [2, 2]],
// 2
[[1, 0], [1, 1], [0, 2], [1, 2]] // 3
],
L: [[[2, 0], [0, 1], [1, 1], [2, 1]],
// 0
[[1, 0], [1, 1], [1, 2], [2, 2]],
// 1
[[0, 1], [1, 1], [2, 1], [0, 2]],
// 2
[[0, 0], [1, 0], [1, 1], [1, 2]] // 3
]
};
var TETROMINO_TYPES = ['I', 'O', 'T', 'S', 'Z', 'J', 'L'];
// Board constants
var BOARD_COLS = 10;
var BOARD_ROWS = 20;
var CELL_SIZE = 80; // px, will be scaled to fit
var BOARD_X = 0;
var BOARD_Y = 0;
// Helper: get color asset id for a tetromino type
function getTetrominoAsset(type) {
return type;
}
// Helper: get ghost asset
function getGhostAsset() {
return 'ghost';
}
function updateBoardLayout() {
// Center board
BOARD_X = Math.floor((2048 - BOARD_COLS * CELL_SIZE) / 2);
BOARD_Y = Math.floor((2732 - BOARD_ROWS * CELL_SIZE) / 2);
if (BOARD_X < 120) BOARD_X = 120; // Avoid top left menu
if (BOARD_Y < 0) BOARD_Y = 0;
}
// Game state
var board = null;
var current = null;
var nextType = null;
var ghost = null;
var dropTimer = 0;
var dropInterval = 36; // 36 ticks = 0.6s at 60fps
var moveDelay = 0;
var moveRepeat = 0;
var gameOver = false;
var score = 0;
var linesCleared = 0;
var level = 1;
var softDrop = false;
var hardDrop = false;
var controlsLocked = false;
// UI
var scoreTxt = null;
var linesTxt = null;
var levelTxt = null;
var btnLeft = null;
var btnRight = null;
var btnDown = null;
var btnRotate = null;
var btnDrop = null;
var buttons = [];
// Helper: random tetromino
function randomTetromino() {
return TETROMINO_TYPES[Math.floor(Math.random() * TETROMINO_TYPES.length)];
}
// Helper: spawn new tetrimino
function spawnTetrimino() {
var type = nextType || randomTetromino();
nextType = randomTetromino();
var t = new Tetrimino();
t.init(type, 0, Math.floor(BOARD_COLS / 2) - 2, 0, false);
return t;
}
// Helper: check game over
function checkGameOver() {
if (!board.canPlace(current.type, current.xCell, current.yCell, current.rotation)) {
gameOver = true;
LK.showGameOver();
}
}
// Helper: lock current piece
function lockCurrent() {
board.placeTetrimino(current.type, current.xCell, current.yCell, current.rotation);
var cleared = board.clearLines();
if (cleared > 0) {
score += [0, 40, 100, 300, 1200][cleared] * level;
linesCleared += cleared;
if (linesCleared >= level * 10) {
level += 1;
if (dropInterval > 6) dropInterval -= 3;
}
}
board.renderBlocks();
updateScore();
current.destroy();
current = spawnTetrimino();
game.addChild(current);
updateGhost();
checkGameOver();
}
// Helper: update ghost piece
function updateGhost() {
if (ghost) ghost.destroy();
ghost = new Tetrimino();
ghost.init(current.type, current.rotation, current.xCell, current.yCell, true);
var y = current.yCell;
while (board.canPlace(current.type, current.xCell, y + 1, current.rotation)) {
y += 1;
}
ghost.setPosition(current.xCell, y);
game.addChild(ghost);
ghost.zIndex = -1;
}
// Helper: update score UI
function updateScore() {
scoreTxt.setText(score + "");
linesTxt.setText("Lines: " + linesCleared);
levelTxt.setText("Level: " + level);
}
// Helper: move current piece
function tryMove(dx, dy, rot) {
if (controlsLocked) return;
var nx = current.xCell + (dx || 0);
var ny = current.yCell + (dy || 0);
var nr = (current.rotation + (rot || 0) + 4) % 4;
if (board.canPlace(current.type, nx, ny, nr)) {
current.setRotation(nr);
current.setPosition(nx, ny);
updateGhost();
return true;
}
return false;
}
// Helper: hard drop
function doHardDrop() {
if (controlsLocked) return;
var y = current.yCell;
while (board.canPlace(current.type, current.xCell, y + 1, current.rotation)) {
y += 1;
}
current.setPosition(current.xCell, y);
lockCurrent();
}
// Helper: soft drop
function doSoftDrop() {
if (controlsLocked) return;
if (!tryMove(0, 1, 0)) {
lockCurrent();
}
}
// Helper: tick drop
function tickDrop() {
if (controlsLocked) return;
if (!tryMove(0, 1, 0)) {
lockCurrent();
}
}
// UI: create buttons
function createButtons() {
var yBase = 2732 - 220;
var xBase = 2048 / 2;
btnLeft = new GameButton();
btnLeft.init('left', '', function () {
tryMove(-1, 0, 0);
});
btnLeft.x = xBase - 320;
btnLeft.y = yBase;
btnRight = new GameButton();
btnRight.init('right', '', function () {
tryMove(1, 0, 0);
});
btnRight.x = xBase - 80;
btnRight.y = yBase;
btnDown = new GameButton();
btnDown.init('down', '', function () {
doSoftDrop();
});
btnDown.x = xBase + 160;
btnDown.y = yBase;
btnRotate = new GameButton();
btnRotate.init('rotate', '', function () {
tryMove(0, 0, 1);
});
btnRotate.x = xBase + 400;
btnRotate.y = yBase;
btnDrop = new GameButton();
btnDrop.init('drop', '', function () {
doHardDrop();
});
btnDrop.x = xBase + 640;
btnDrop.y = yBase;
buttons = [btnLeft, btnRight, btnDown, btnRotate, btnDrop];
for (var i = 0; i < buttons.length; ++i) {
LK.gui.bottom.addChild(buttons[i]);
}
}
// UI: create score
function createScoreUI() {
scoreTxt = new Text2("0", {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
scoreTxt.x = 2048 / 2;
scoreTxt.y = 40;
linesTxt = new Text2("Lines: 0", {
size: 60,
fill: "#fff"
});
linesTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(linesTxt);
linesTxt.x = 2048 / 2 - 300;
linesTxt.y = 180;
levelTxt = new Text2("Level: 1", {
size: 60,
fill: "#fff"
});
levelTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(levelTxt);
levelTxt.x = 2048 / 2 + 300;
levelTxt.y = 180;
}
// Touch drag controls (swipe left/right/down/up)
var dragStartX = null,
dragStartY = null,
dragActive = false;
game.down = function (x, y, obj) {
dragStartX = x;
dragStartY = y;
dragActive = true;
};
game.up = function (x, y, obj) {
dragActive = false;
};
game.move = function (x, y, obj) {
if (!dragActive) return;
var dx = x - dragStartX;
var dy = y - dragStartY;
if (Math.abs(dx) > 80) {
if (dx > 0) {
tryMove(1, 0, 0);
} else {
tryMove(-1, 0, 0);
}
dragStartX = x;
dragStartY = y;
}
if (Math.abs(dy) > 80) {
if (dy > 0) {
doSoftDrop();
} else {
tryMove(0, 0, 1);
}
dragStartX = x;
dragStartY = y;
}
};
// Main game setup
function startGame() {
updateBoardLayout();
board = new Board();
board.init();
board.x = BOARD_X;
board.y = BOARD_Y;
game.addChild(board);
board.renderBlocks();
current = spawnTetrimino();
game.addChild(current);
updateGhost();
score = 0;
linesCleared = 0;
level = 1;
dropInterval = 36;
gameOver = false;
updateScore();
}
// UI setup
createScoreUI();
createButtons();
startGame();
// Main game loop
game.update = function () {
if (gameOver) return;
dropTimer++;
if (dropTimer >= (softDrop ? 3 : dropInterval)) {
tickDrop();
dropTimer = 0;
}
};
// Reset on game over
LK.on('gameover', function () {
controlsLocked = true;
LK.setTimeout(function () {
controlsLocked = false;
startGame();
}, 1200);
});