/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Tetromino class var Tetromino = Container.expand(function () { var self = Container.call(this); // Properties self.type = null; self.shape = null; // array of [x,y] self.rotation = 0; self.blocks = []; self.x = 0; // board col self.y = 0; // board row self.isPowerup = false; self.powerupType = null; // Initialize tetromino self.init = function (type, isPowerup) { self.type = type; self.isPowerup = !!isPowerup; self.rotation = 0; self.shape = []; // Copy shape var base = TETROMINO_SHAPES[type][0]; for (var i = 0; i < base.length; i++) { self.shape.push([base[i][0], base[i][1]]); } self.blocks = []; self.removeChildren(); for (var i = 0; i < self.shape.length; i++) { var block; if (self.isPowerup && i === 0) { block = self.attachAsset('powerup', { anchorX: 0.5, anchorY: 0.5, width: CELL_SIZE, height: CELL_SIZE }); self.powerupType = 'clearLine'; // Only one powerup for MVP } else { block = self.attachAsset(self.type, { anchorX: 0.5, anchorY: 0.5, width: CELL_SIZE, height: CELL_SIZE }); } self.blocks.push(block); // Overlay grid lines for each block var gridOverlay = new Container(); var vlineL = LK.getAsset('O', { anchorX: 0.5, anchorY: 0, width: 4, height: CELL_SIZE, color: 0x222222 // match board grid color }); vlineL.x = -CELL_SIZE / 2; vlineL.y = -CELL_SIZE / 2; vlineL.alpha = 0.25; gridOverlay.addChild(vlineL); var vlineR = LK.getAsset('O', { anchorX: 0.5, anchorY: 0, width: 4, height: CELL_SIZE, color: 0x222222 // match board grid color }); vlineR.x = CELL_SIZE / 2; vlineR.y = -CELL_SIZE / 2; vlineR.alpha = 0.25; gridOverlay.addChild(vlineR); var hlineT = LK.getAsset('O', { anchorX: 0, anchorY: 0.5, width: CELL_SIZE, height: 4, color: 0x222222 // match board grid color }); hlineT.x = -CELL_SIZE / 2; hlineT.y = -CELL_SIZE / 2; hlineT.alpha = 0.25; gridOverlay.addChild(hlineT); var hlineB = LK.getAsset('O', { anchorX: 0, anchorY: 0.5, width: CELL_SIZE, height: 4, color: 0x222222 // match board grid color }); hlineB.x = -CELL_SIZE / 2; hlineB.y = CELL_SIZE / 2; hlineB.alpha = 0.25; gridOverlay.addChild(hlineB); block.addChild(gridOverlay); } self.x = Math.floor(BOARD_COLS / 2) - 2; self.y = 0; self.updateBlockPositions(); }; // Update block positions self.updateBlockPositions = function () { for (var i = 0; i < self.shape.length; i++) { var pos = self.shape[i]; self.blocks[i].x = (self.x + pos[0]) * CELL_SIZE + CELL_SIZE / 2; self.blocks[i].y = (self.y + pos[1]) * CELL_SIZE + CELL_SIZE / 2; } }; // Try move self.tryMove = function (dx, dy, board) { if (self.canMove(dx, dy, self.shape, board)) { self.x += dx; self.y += dy; self.updateBlockPositions(); return true; } return false; }; // Try rotate self.tryRotate = function (board) { var rotated = rotateShape(self.shape); if (self.canMove(0, 0, rotated, board)) { self.shape = rotated; self.updateBlockPositions(); return true; } return false; }; // Can move/rotate self.canMove = function (dx, dy, shape, board) { for (var i = 0; i < shape.length; i++) { var nx = self.x + shape[i][0] + dx; var ny = self.y + shape[i][1] + dy; if (nx < 0 || nx >= BOARD_COLS || ny < 0 || ny >= BOARD_ROWS) { return false; } if (board[ny][nx]) { return false; } } return true; }; // Lock tetromino into board self.lockToBoard = function (board, blockRefs) { for (var i = 0; i < self.shape.length; i++) { var nx = self.x + self.shape[i][0]; var ny = self.y + self.shape[i][1]; board[ny][nx] = self.isPowerup && i === 0 ? 'powerup' : self.type; blockRefs[ny][nx] = self.blocks[i]; } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ // No title, no description // Always backgroundColor is black backgroundColor: 0x000000 }); /**** * Game Code ****/ // Board state: 2D array [row][col], 0 = empty, else type string // Tetromino shapes (classic + new) // Colors for each tetromino type // New shapes // Power-up block // Power-up effect // Tetromino definitions (relative coordinates for each block in the tetromino) var TETROMINO_SHAPES = { I: [ // vertical (tall) [[1, 0], [1, 1], [1, 2], [1, 3]], // horizontal (flat) [[0, 1], [1, 1], [2, 1], [3, 1]]], O: [[[1, 0], [2, 0], [1, 1], [2, 1]]], T: [[[1, 0], [0, 1], [1, 1], [2, 1]]], S: [[[1, 0], [2, 0], [0, 1], [1, 1]]], Z: [[[0, 0], [1, 0], [1, 1], [2, 1]]], J: [[[0, 0], [0, 1], [1, 1], [2, 1]]], L: [[[2, 0], [0, 1], [1, 1], [2, 1]]], U: [[[0, 0], [2, 0], [0, 1], [1, 1], [2, 1]]], P: [[[0, 0], [1, 0], [0, 1], [1, 1], [0, 2]]], Dot: [[[1, 1]]] }; var TETROMINO_TYPES = ['I', 'O', 'T', 'S', 'Z', 'J', 'L', 'U', 'P']; var POWERUP_CHANCE = 0.08; // 8% chance for a powerup block // Board settings var BOARD_COLS = 8; // Reduced width for fewer columns, block size unchanged var BOARD_ROWS = 20; var CELL_SIZE = 90; // px, fits well on 2048x2732 var BOARD_OFFSET_X = Math.floor((2048 - BOARD_COLS * CELL_SIZE) / 2) - 80; // Shift left to make space for preview var BOARD_OFFSET_Y = 300; // Helper: rotate a shape (array of [x,y]) 90deg clockwise function rotateShape(shape) { var maxX = 0, maxY = 0; for (var i = 0; i < shape.length; i++) { if (shape[i][0] > maxX) { maxX = shape[i][0]; } if (shape[i][1] > maxY) { maxY = shape[i][1]; } } var rotated = []; for (var i = 0; i < shape.length; i++) { var x = shape[i][0], y = shape[i][1]; rotated.push([maxY - y, x]); } return rotated; } var board = []; var blockRefs = []; // 2D array of block display objects for (var r = 0; r < BOARD_ROWS; r++) { board[r] = []; blockRefs[r] = []; for (var c = 0; c < BOARD_COLS; c++) { board[r][c] = 0; blockRefs[r][c] = null; } } // Score and level var score = 0; var level = 1; var linesCleared = 0; var fallInterval = 1000; // ms, decreases with level // GUI var scoreTxt = new Text2('0', { size: 100, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); var levelTxt = new Text2('Lv.1', { size: 60, fill: "#fff" }); levelTxt.anchor.set(0.5, 0); LK.gui.top.addChild(levelTxt); levelTxt.y = 110; // Next tetromino preview var nextTetrominoType = null; var nextTetrominoPreview = new Container(); game.addChild(nextTetrominoPreview); // Current falling tetromino var currentTetromino = null; // Game state var isGameOver = false; var isDropping = false; var dropTimer = null; var moveTimer = null; var moveDir = 0; // -1 left, 1 right, 0 none // Helper: spawn new tetromino function spawnTetromino() { var type = nextTetrominoType; if (!type) { type = TETROMINO_TYPES[Math.floor(Math.random() * TETROMINO_TYPES.length)]; } var isPowerup = Math.random() < POWERUP_CHANCE; var tetro = new Tetromino(); tetro.init(type, isPowerup); game.addChild(tetro); currentTetromino = tetro; // Next nextTetrominoType = TETROMINO_TYPES[Math.floor(Math.random() * TETROMINO_TYPES.length)]; updateNextPreview(); // Flash effect on next preview if (nextTetrominoPreview.children && nextTetrominoPreview.children.length > 0) { for (var i = 0; i < nextTetrominoPreview.children.length; i++) { LK.effects.flashObject(nextTetrominoPreview.children[i], 0xffffff, 180); } } // If cannot place, game over if (!tetro.canMove(0, 0, tetro.shape, board)) { isGameOver = true; LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); } } // Helper: update next preview function updateNextPreview() { nextTetrominoPreview.removeChildren(); var type = nextTetrominoType; if (!type) { return; } var shape = TETROMINO_SHAPES[type][0]; // Find min/max for centering var minX = 99, maxX = -99, minY = 99, maxY = -99; for (var i = 0; i < shape.length; i++) { if (shape[i][0] < minX) { minX = shape[i][0]; } if (shape[i][0] > maxX) { maxX = shape[i][0]; } if (shape[i][1] < minY) { minY = shape[i][1]; } if (shape[i][1] > maxY) { maxY = shape[i][1]; } } var previewWidth = (maxX - minX + 1) * CELL_SIZE; var previewHeight = (maxY - minY + 1) * CELL_SIZE; for (var i = 0; i < shape.length; i++) { var block = LK.getAsset(type, { anchorX: 0.5, anchorY: 0.5, width: CELL_SIZE, height: CELL_SIZE }); block.x = (shape[i][0] - minX) * CELL_SIZE + CELL_SIZE / 2; block.y = (shape[i][1] - minY) * CELL_SIZE + CELL_SIZE / 2; nextTetrominoPreview.addChild(block); } // Place preview to the right of the board, fully visible and inside the screen nextTetrominoPreview.x = BOARD_OFFSET_X + BOARD_COLS * CELL_SIZE + 120; nextTetrominoPreview.y = BOARD_OFFSET_Y + 200; } // Helper: clear full lines function clearLines() { var lines = []; for (var r = 0; r < BOARD_ROWS; r++) { var full = true; for (var c = 0; c < BOARD_COLS; c++) { if (!board[r][c]) { full = false; break; } } if (full) { lines.push(r); } } if (lines.length === 0) { return 0; } // Animate and remove lines for (var i = 0; i < lines.length; i++) { var row = lines[i]; for (var c = 0; c < BOARD_COLS; c++) { if (blockRefs[row][c]) { tween(blockRefs[row][c], { alpha: 0 }, { duration: 200, onFinish: function (obj) { if (obj.parent) { obj.parent.removeChild(obj); } }.bind(null, blockRefs[row][c]) }); } } } // Remove from board for (var i = 0; i < lines.length; i++) { var row = lines[i]; for (var c = 0; c < BOARD_COLS; c++) { board[row][c] = 0; blockRefs[row][c] = null; } } // Drop above lines for (var i = lines.length - 1; i >= 0; i--) { var row = lines[i]; for (var r = row - 1; r >= 0; r--) { for (var c = 0; c < BOARD_COLS; c++) { board[r + 1][c] = board[r][c]; blockRefs[r + 1][c] = blockRefs[r][c]; if (blockRefs[r + 1][c]) { tween(blockRefs[r + 1][c], { y: blockRefs[r + 1][c].y + CELL_SIZE }, { duration: 100 }); } } } // Clear top row for (var c = 0; c < BOARD_COLS; c++) { board[0][c] = 0; blockRefs[0][c] = null; } } return lines.length; } // Helper: activate powerup function activatePowerup(row) { // For MVP: clear the row where powerup landed for (var c = 0; c < BOARD_COLS; c++) { if (blockRefs[row][c]) { tween(blockRefs[row][c], { alpha: 0 }, { duration: 200, onFinish: function (obj) { if (obj.parent) { obj.parent.removeChild(obj); } }.bind(null, blockRefs[row][c]) }); board[row][c] = 0; blockRefs[row][c] = null; } } // Drop above lines for (var r = row - 1; r >= 0; r--) { for (var c = 0; c < BOARD_COLS; c++) { board[r + 1][c] = board[r][c]; blockRefs[r + 1][c] = blockRefs[r][c]; if (blockRefs[r + 1][c]) { tween(blockRefs[r + 1][c], { y: blockRefs[r + 1][c].y + CELL_SIZE }, { duration: 100 }); } } } // Clear top row for (var c = 0; c < BOARD_COLS; c++) { board[0][c] = 0; blockRefs[0][c] = null; } } // Helper: update score/level function updateScore(lines) { var points = [0, 100, 300, 500, 800]; score += points[lines] || 0; linesCleared += lines; scoreTxt.setText(score); var newLevel = 1 + Math.floor(linesCleared / 10); if (newLevel !== level) { level = newLevel; levelTxt.setText('Lv.' + level); fallInterval = Math.max(150, 1000 - (level - 1) * 100); } } // Helper: draw board grid (for visual reference) var gridLines = new Container(); game.addChild(gridLines); // Draw faint cell backgrounds for subtle grid for (var r = 0; r < BOARD_ROWS; r++) { for (var c = 0; c < BOARD_COLS; c++) { var cell = LK.getAsset('O', { anchorX: 0.5, anchorY: 0.5, width: CELL_SIZE - 4, height: CELL_SIZE - 4, color: 0x222222 }); cell.x = c * CELL_SIZE + CELL_SIZE / 2; cell.y = r * CELL_SIZE + CELL_SIZE / 2; cell.alpha = 0.15; gridLines.addChild(cell); } } // Draw vertical grid lines for (var c = 0; c <= BOARD_COLS; c++) { var vline = LK.getAsset('O', { anchorX: 0.5, anchorY: 0, width: 4, height: CELL_SIZE * BOARD_ROWS, color: 0x222222 // darker gray for better contrast }); vline.x = c * CELL_SIZE; vline.y = 0; vline.alpha = 0.35; gridLines.addChild(vline); } // Draw horizontal grid lines for (var r = 0; r <= BOARD_ROWS; r++) { var hline = LK.getAsset('O', { anchorX: 0, anchorY: 0.5, width: CELL_SIZE * BOARD_COLS, height: 4, color: 0x222222 // darker gray for better contrast }); hline.x = 0; hline.y = r * CELL_SIZE; hline.alpha = 0.35; gridLines.addChild(hline); } gridLines.x = BOARD_OFFSET_X; gridLines.y = BOARD_OFFSET_Y; // Move all blocks/containers to board offset function updateBoardDisplayOffsets() { gridLines.x = BOARD_OFFSET_X; gridLines.y = BOARD_OFFSET_Y; for (var r = 0; r < BOARD_ROWS; r++) { for (var c = 0; c < BOARD_COLS; c++) { if (blockRefs[r][c]) { blockRefs[r][c].x = c * CELL_SIZE + CELL_SIZE / 2 + BOARD_OFFSET_X; blockRefs[r][c].y = r * CELL_SIZE + CELL_SIZE / 2 + BOARD_OFFSET_Y; } } } if (currentTetromino) { for (var i = 0; i < currentTetromino.blocks.length; i++) { currentTetromino.blocks[i].x += BOARD_OFFSET_X; currentTetromino.blocks[i].y += BOARD_OFFSET_Y; } } } // Remove board offset from tetromino before logic function removeTetrominoOffset() { if (currentTetromino) { for (var i = 0; i < currentTetromino.blocks.length; i++) { currentTetromino.blocks[i].x -= BOARD_OFFSET_X; currentTetromino.blocks[i].y -= BOARD_OFFSET_Y; } } } // Touch controls var dragStartX = null; var dragStartY = null; var dragTetrominoX = null; var dragTetrominoY = null; var lastTouchMove = 0; var touchDown = false; var hardDrop = false; // Convert screen x,y to board col,row function screenToBoard(x, y) { var bx = Math.floor((x - BOARD_OFFSET_X) / CELL_SIZE); var by = Math.floor((y - BOARD_OFFSET_Y) / CELL_SIZE); return { col: bx, row: by }; } // Touch/mouse events game.down = function (x, y, obj) { if (isGameOver || !currentTetromino) { return; } dragStartX = x; dragStartY = y; dragTetrominoX = currentTetromino.x; dragTetrominoY = currentTetromino.y; touchDown = true; hardDrop = false; lastTouchMove = Date.now(); }; game.move = function (x, y, obj) { if (isGameOver || !currentTetromino || !touchDown) { return; } var dx = x - dragStartX; var dy = y - dragStartY; var moved = false; // Horizontal drag: move left/right if (Math.abs(dx) > CELL_SIZE / 2) { var dir = dx > 0 ? 1 : -1; if (currentTetromino.tryMove(dir, 0, board)) { dragStartX = x; dragTetrominoX = currentTetromino.x; moved = true; } } // Vertical drag: hard drop if (dy > CELL_SIZE * 2 && !hardDrop) { // Hard drop while (currentTetromino.tryMove(0, 1, board)) {} hardDrop = true; moved = true; } // Tap: rotate (handled in up) if (moved) { currentTetromino.updateBlockPositions(); updateBoardDisplayOffsets(); } }; game.up = function (x, y, obj) { if (isGameOver || !currentTetromino) { return; } touchDown = false; var dt = Date.now() - lastTouchMove; var dx = x - dragStartX; var dy = y - dragStartY; // Tap: rotate if (Math.abs(dx) < 30 && Math.abs(dy) < 30 && dt < 400) { removeTetrominoOffset(); currentTetromino.tryRotate(board); currentTetromino.updateBlockPositions(); updateBoardDisplayOffsets(); } }; // Auto fall function scheduleDrop() { if (dropTimer) { LK.clearTimeout(dropTimer); } if (isGameOver) { return; } dropTimer = LK.setTimeout(function () { if (isGameOver) { return; } removeTetrominoOffset(); var moved = currentTetromino.tryMove(0, 1, board); currentTetromino.updateBlockPositions(); updateBoardDisplayOffsets(); if (!moved) { // Lock to board currentTetromino.lockToBoard(board, blockRefs); // Flash effect on lock for (var i = 0; i < currentTetromino.blocks.length; i++) { LK.effects.flashObject(currentTetromino.blocks[i], 0xffffff, 180); } // Powerup check var landedPowerup = false; for (var i = 0; i < currentTetromino.shape.length; i++) { var nx = currentTetromino.x + currentTetromino.shape[i][0]; var ny = currentTetromino.y + currentTetromino.shape[i][1]; if (board[ny][nx] === 'powerup') { activatePowerup(ny); landedPowerup = true; } } // Clear lines var lines = clearLines(); updateScore(lines); // Do not remove tetromino blocks here; they are now part of the board and will be removed when lines are cleared currentTetromino = null; // Spawn next spawnTetromino(); updateBoardDisplayOffsets(); } scheduleDrop(); }, fallInterval); } // Game update game.update = function () { // No per-frame logic needed for MVP }; // Start game function startGame() { // Reset board for (var r = 0; r < BOARD_ROWS; r++) { for (var c = 0; c < BOARD_COLS; c++) { board[r][c] = 0; if (blockRefs[r][c]) { if (blockRefs[r][c].parent) { blockRefs[r][c].parent.removeChild(blockRefs[r][c]); } blockRefs[r][c] = null; } } } score = 0; level = 1; linesCleared = 0; fallInterval = 1000; scoreTxt.setText(score); levelTxt.setText('Lv.1'); isGameOver = false; nextTetrominoType = TETROMINO_TYPES[Math.floor(Math.random() * TETROMINO_TYPES.length)]; updateNextPreview(); if (currentTetromino) { for (var i = 0; i < currentTetromino.blocks.length; i++) { if (currentTetromino.blocks[i].parent) { currentTetromino.blocks[i].parent.removeChild(currentTetromino.blocks[i]); } } currentTetromino = null; } spawnTetromino(); updateBoardDisplayOffsets(); scheduleDrop(); } // On game over, restart on new game LK.on('gameStart', function () { startGame(); }); // Initial start LK.playMusic('tetris_bgm'); startGame();
===================================================================
--- original.js
+++ change.js
@@ -24,183 +24,91 @@
self.isPowerup = !!isPowerup;
self.rotation = 0;
self.shape = [];
// Copy shape
- if (type === 'I') {
- // Use the first variant for I block
- var base = TETROMINO_SHAPES[type][0];
- for (var i = 0; i < base.length; i++) {
- self.shape.push([base[i][0], base[i][1]]);
- }
- } else {
- var base = TETROMINO_SHAPES[type][0];
- for (var i = 0; i < base.length; i++) {
- self.shape.push([base[i][0], base[i][1]]);
- }
+ var base = TETROMINO_SHAPES[type][0];
+ for (var i = 0; i < base.length; i++) {
+ self.shape.push([base[i][0], base[i][1]]);
}
self.blocks = [];
self.removeChildren();
- // Special handling for I block: use a single image asset for the whole tetromino
- if (self.type === 'I') {
- var block = self.attachAsset('I', {
- anchorX: 0.5,
- anchorY: 0.5,
- width: CELL_SIZE * 4,
- height: CELL_SIZE
- });
- self.blocks.push(block);
- // Overlay grid lines for I block
- var gridOverlay = new Container();
- for (var i = 0; i <= 4; i++) {
- var vline = LK.getAsset('O', {
+ for (var i = 0; i < self.shape.length; i++) {
+ var block;
+ if (self.isPowerup && i === 0) {
+ block = self.attachAsset('powerup', {
anchorX: 0.5,
- anchorY: 0,
- width: 4,
- height: CELL_SIZE,
- color: 0x222222 // match board grid color
+ anchorY: 0.5,
+ width: CELL_SIZE,
+ height: CELL_SIZE
});
- vline.x = -CELL_SIZE * 2 + i * CELL_SIZE;
- vline.y = -CELL_SIZE / 2;
- vline.alpha = 0.25;
- gridOverlay.addChild(vline);
+ self.powerupType = 'clearLine'; // Only one powerup for MVP
+ } else {
+ block = self.attachAsset(self.type, {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ width: CELL_SIZE,
+ height: CELL_SIZE
+ });
}
- var hline = LK.getAsset('O', {
+ self.blocks.push(block);
+ // Overlay grid lines for each block
+ var gridOverlay = new Container();
+ var vlineL = LK.getAsset('O', {
+ anchorX: 0.5,
+ anchorY: 0,
+ width: 4,
+ height: CELL_SIZE,
+ color: 0x222222 // match board grid color
+ });
+ vlineL.x = -CELL_SIZE / 2;
+ vlineL.y = -CELL_SIZE / 2;
+ vlineL.alpha = 0.25;
+ gridOverlay.addChild(vlineL);
+ var vlineR = LK.getAsset('O', {
+ anchorX: 0.5,
+ anchorY: 0,
+ width: 4,
+ height: CELL_SIZE,
+ color: 0x222222 // match board grid color
+ });
+ vlineR.x = CELL_SIZE / 2;
+ vlineR.y = -CELL_SIZE / 2;
+ vlineR.alpha = 0.25;
+ gridOverlay.addChild(vlineR);
+ var hlineT = LK.getAsset('O', {
anchorX: 0,
anchorY: 0.5,
- width: CELL_SIZE * 4,
+ width: CELL_SIZE,
height: 4,
color: 0x222222 // match board grid color
});
- hline.x = -CELL_SIZE * 2;
- hline.y = 0;
- hline.alpha = 0.25;
- gridOverlay.addChild(hline);
- var hline2 = LK.getAsset('O', {
+ hlineT.x = -CELL_SIZE / 2;
+ hlineT.y = -CELL_SIZE / 2;
+ hlineT.alpha = 0.25;
+ gridOverlay.addChild(hlineT);
+ var hlineB = LK.getAsset('O', {
anchorX: 0,
anchorY: 0.5,
- width: CELL_SIZE * 4,
+ width: CELL_SIZE,
height: 4,
color: 0x222222 // match board grid color
});
- hline2.x = -CELL_SIZE * 2;
- hline2.y = CELL_SIZE / 2;
- hline2.alpha = 0.25;
- gridOverlay.addChild(hline2);
- self.addChild(gridOverlay);
- } else {
- for (var i = 0; i < self.shape.length; i++) {
- var block;
- if (self.isPowerup && i === 0) {
- block = self.attachAsset('powerup', {
- anchorX: 0.5,
- anchorY: 0.5,
- width: CELL_SIZE,
- height: CELL_SIZE
- });
- self.powerupType = 'clearLine'; // Only one powerup for MVP
- } else {
- block = self.attachAsset(self.type, {
- anchorX: 0.5,
- anchorY: 0.5,
- width: CELL_SIZE,
- height: CELL_SIZE
- });
- }
- self.blocks.push(block);
- // Overlay grid lines for each block
- var gridOverlay = new Container();
- var vlineL = LK.getAsset('O', {
- anchorX: 0.5,
- anchorY: 0,
- width: 4,
- height: CELL_SIZE,
- color: 0x222222 // match board grid color
- });
- vlineL.x = -CELL_SIZE / 2;
- vlineL.y = -CELL_SIZE / 2;
- vlineL.alpha = 0.25;
- gridOverlay.addChild(vlineL);
- var vlineR = LK.getAsset('O', {
- anchorX: 0.5,
- anchorY: 0,
- width: 4,
- height: CELL_SIZE,
- color: 0x222222 // match board grid color
- });
- vlineR.x = CELL_SIZE / 2;
- vlineR.y = -CELL_SIZE / 2;
- vlineR.alpha = 0.25;
- gridOverlay.addChild(vlineR);
- var hlineT = LK.getAsset('O', {
- anchorX: 0,
- anchorY: 0.5,
- width: CELL_SIZE,
- height: 4,
- color: 0x222222 // match board grid color
- });
- hlineT.x = -CELL_SIZE / 2;
- hlineT.y = -CELL_SIZE / 2;
- hlineT.alpha = 0.25;
- gridOverlay.addChild(hlineT);
- var hlineB = LK.getAsset('O', {
- anchorX: 0,
- anchorY: 0.5,
- width: CELL_SIZE,
- height: 4,
- color: 0x222222 // match board grid color
- });
- hlineB.x = -CELL_SIZE / 2;
- hlineB.y = CELL_SIZE / 2;
- hlineB.alpha = 0.25;
- gridOverlay.addChild(hlineB);
- block.addChild(gridOverlay);
- }
+ hlineB.x = -CELL_SIZE / 2;
+ hlineB.y = CELL_SIZE / 2;
+ hlineB.alpha = 0.25;
+ gridOverlay.addChild(hlineB);
+ block.addChild(gridOverlay);
}
self.x = Math.floor(BOARD_COLS / 2) - 2;
self.y = 0;
self.updateBlockPositions();
};
// Update block positions
self.updateBlockPositions = function () {
- if (self.type === 'I') {
- // Center the I block image over the 4 cells
- // Find min/max x/y for the I shape
- var minX = 99,
- maxX = -99,
- minY = 99,
- maxY = -99;
- for (var i = 0; i < self.shape.length; i++) {
- if (self.shape[i][0] < minX) {
- minX = self.shape[i][0];
- }
- if (self.shape[i][0] > maxX) {
- maxX = self.shape[i][0];
- }
- if (self.shape[i][1] < minY) {
- minY = self.shape[i][1];
- }
- if (self.shape[i][1] > maxY) {
- maxY = self.shape[i][1];
- }
- }
- // The I block is always 4 wide, 1 tall
- // Place the image so its center matches the center of the 4 cells
- var centerX = 0,
- centerY = 0;
- for (var i = 0; i < self.shape.length; i++) {
- centerX += self.x + self.shape[i][0];
- centerY += self.y + self.shape[i][1];
- }
- centerX = centerX / self.shape.length;
- centerY = centerY / self.shape.length;
- self.blocks[0].x = centerX * CELL_SIZE + CELL_SIZE / 2;
- self.blocks[0].y = centerY * CELL_SIZE + CELL_SIZE / 2;
- } else {
- for (var i = 0; i < self.shape.length; i++) {
- var pos = self.shape[i];
- self.blocks[i].x = (self.x + pos[0]) * CELL_SIZE + CELL_SIZE / 2;
- self.blocks[i].y = (self.y + pos[1]) * CELL_SIZE + CELL_SIZE / 2;
- }
+ for (var i = 0; i < self.shape.length; i++) {
+ var pos = self.shape[i];
+ self.blocks[i].x = (self.x + pos[0]) * CELL_SIZE + CELL_SIZE / 2;
+ self.blocks[i].y = (self.y + pos[1]) * CELL_SIZE + CELL_SIZE / 2;
}
};
// Try move
self.tryMove = function (dx, dy, board) {
@@ -213,32 +121,15 @@
return false;
};
// Try rotate
self.tryRotate = function (board) {
- // For I block, use predefined shape variants for rotation
- if (self.type === 'I') {
- var shapeVariants = TETROMINO_SHAPES['I'];
- var nextRotation = (self.rotation + 1) % shapeVariants.length;
- var rotated = [];
- for (var i = 0; i < shapeVariants[nextRotation].length; i++) {
- rotated.push([shapeVariants[nextRotation][i][0], shapeVariants[nextRotation][i][1]]);
- }
- if (self.canMove(0, 0, rotated, board)) {
- self.shape = rotated;
- self.rotation = nextRotation;
- self.updateBlockPositions();
- return true;
- }
- return false;
- } else {
- var rotated = rotateShape(self.shape);
- if (self.canMove(0, 0, rotated, board)) {
- self.shape = rotated;
- self.updateBlockPositions();
- return true;
- }
- return false;
+ var rotated = rotateShape(self.shape);
+ if (self.canMove(0, 0, rotated, board)) {
+ self.shape = rotated;
+ self.updateBlockPositions();
+ return true;
}
+ return false;
};
// Can move/rotate
self.canMove = function (dx, dy, shape, board) {
for (var i = 0; i < shape.length; i++) {
@@ -254,22 +145,13 @@
return true;
};
// Lock tetromino into board
self.lockToBoard = function (board, blockRefs) {
- if (self.type === 'I') {
- for (var i = 0; i < self.shape.length; i++) {
- var nx = self.x + self.shape[i][0];
- var ny = self.y + self.shape[i][1];
- board[ny][nx] = self.type;
- blockRefs[ny][nx] = self.blocks[0];
- }
- } else {
- for (var i = 0; i < self.shape.length; i++) {
- var nx = self.x + self.shape[i][0];
- var ny = self.y + self.shape[i][1];
- board[ny][nx] = self.isPowerup && i === 0 ? 'powerup' : self.type;
- blockRefs[ny][nx] = self.blocks[i];
- }
+ for (var i = 0; i < self.shape.length; i++) {
+ var nx = self.x + self.shape[i][0];
+ var ny = self.y + self.shape[i][1];
+ board[ny][nx] = self.isPowerup && i === 0 ? 'powerup' : self.type;
+ blockRefs[ny][nx] = self.blocks[i];
}
};
return self;
});
@@ -293,12 +175,13 @@
// Power-up block
// Power-up effect
// Tetromino definitions (relative coordinates for each block in the tetromino)
var TETROMINO_SHAPES = {
- I: [[[1, 0], [1, 1], [1, 2], [1, 3]],
+ I: [
// vertical (tall)
- [[0, 1], [1, 1], [2, 1], [3, 1]] // horizontal
- ],
+ [[1, 0], [1, 1], [1, 2], [1, 3]],
+ // horizontal (flat)
+ [[0, 1], [1, 1], [2, 1], [3, 1]]],
O: [[[1, 0], [2, 0], [1, 1], [2, 1]]],
T: [[[1, 0], [0, 1], [1, 1], [2, 1]]],
S: [[[1, 0], [2, 0], [0, 1], [1, 1]]],
Z: [[[0, 0], [1, 0], [1, 1], [2, 1]]],
@@ -432,32 +315,18 @@
}
}
var previewWidth = (maxX - minX + 1) * CELL_SIZE;
var previewHeight = (maxY - minY + 1) * CELL_SIZE;
- if (type === 'I') {
- // Show as a single image asset
- var block = LK.getAsset('I', {
+ for (var i = 0; i < shape.length; i++) {
+ var block = LK.getAsset(type, {
anchorX: 0.5,
anchorY: 0.5,
- width: CELL_SIZE * 4,
+ width: CELL_SIZE,
height: CELL_SIZE
});
- // Center the image in the preview area
- block.x = (maxX - minX + 1) * CELL_SIZE / 2;
- block.y = (maxY - minY + 1) * CELL_SIZE / 2;
+ block.x = (shape[i][0] - minX) * CELL_SIZE + CELL_SIZE / 2;
+ block.y = (shape[i][1] - minY) * CELL_SIZE + CELL_SIZE / 2;
nextTetrominoPreview.addChild(block);
- } else {
- for (var i = 0; i < shape.length; i++) {
- var block = LK.getAsset(type, {
- anchorX: 0.5,
- anchorY: 0.5,
- width: CELL_SIZE,
- height: CELL_SIZE
- });
- block.x = (shape[i][0] - minX) * CELL_SIZE + CELL_SIZE / 2;
- block.y = (shape[i][1] - minY) * CELL_SIZE + CELL_SIZE / 2;
- nextTetrominoPreview.addChild(block);
- }
}
// Place preview to the right of the board, fully visible and inside the screen
nextTetrominoPreview.x = BOARD_OFFSET_X + BOARD_COLS * CELL_SIZE + 120;
nextTetrominoPreview.y = BOARD_OFFSET_Y + 200;