/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // BlockShape: A draggable group of blocks (the "piece" the player places) var BlockShape = Container.expand(function () { var self = Container.call(this); // Subtle idle animation: gentle up-down and scale for cuteness // (Moved to after blocks are created, see below) self.init = function (shapeData) { self.shapeData = shapeData; self.blocks = []; // Remove previous children if any while (self.children.length) self.removeChild(self.children[0]); // Draw blocks for (var r = 0; r < shapeData.matrix.length; r++) { for (var c = 0; c < shapeData.matrix[r].length; c++) { if (shapeData.matrix[r][c]) { var block = self.attachAsset(shapeData.color, { anchorX: 0.5, anchorY: 0.5, x: c * 200, y: r * 200 }); self.blocks.push(block); // Subtle idle animation for block (function (block) { var baseY = block.y; var baseScale = block.scale ? block.scale.x : 1; function animateBlock() { // Animate up tween(block, { y: baseY - 8, scaleX: baseScale * 1.04, scaleY: baseScale * 0.96 }, { duration: 700 + Math.floor(Math.random() * 200), easing: tween.easeInOut, onFinish: function onFinish() { // Animate down tween(block, { y: baseY + 8, scaleX: baseScale * 0.96, scaleY: baseScale * 1.04 }, { duration: 700 + Math.floor(Math.random() * 200), easing: tween.easeInOut, onFinish: function onFinish() { // Return to base and repeat tween(block, { y: baseY, scaleX: baseScale, scaleY: baseScale }, { duration: 700 + Math.floor(Math.random() * 200), easing: tween.easeInOut, onFinish: animateBlock }); } }); } }); } animateBlock(); })(block); } } } // Center anchor var w = shapeData.matrix[0].length * 200; var h = shapeData.matrix.length * 200; self.pieceWidth = w; self.pieceHeight = h; self.pivot.x = w / 2 - 100; self.pivot.y = h / 2 - 100; }; // Used for drag highlight self.setAlpha = function (a) { for (var i = 0; i < self.blocks.length; i++) { self.blocks[i].alpha = a; } }; return self; }); // BoardCell: A single cell on the board var BoardCell = Container.expand(function () { var self = Container.call(this); self.bg = self.attachAsset('cell_bg', { anchorX: 0.5, anchorY: 0.5 }); self.block = null; // If occupied, this is the block asset self.occupied = false; self.setBlock = function (color) { if (self.block) self.removeChild(self.block); self.block = self.attachAsset(color, { anchorX: 0.5, anchorY: 0.5 }); self.occupied = true; // Subtle idle animation for placed block if (self.block) { var _animateBlock = function animateBlock() { tween(block, { y: baseY - 6, scaleX: baseScale * 1.03, scaleY: baseScale * 0.97 }, { duration: 800 + Math.floor(Math.random() * 200), easing: tween.easeInOut, onFinish: function onFinish() { tween(block, { y: baseY + 6, scaleX: baseScale * 0.97, scaleY: baseScale * 1.03 }, { duration: 800 + Math.floor(Math.random() * 200), easing: tween.easeInOut, onFinish: function onFinish() { tween(block, { y: baseY, scaleX: baseScale, scaleY: baseScale }, { duration: 800 + Math.floor(Math.random() * 200), easing: tween.easeInOut, onFinish: _animateBlock }); } }); } }); }; var block = self.block; var baseY = block.y; var baseScale = block.scale ? block.scale.x : 1; _animateBlock(); } }; self.clearBlock = function () { if (self.block) { // Stop any tweens on this block tween.stop(self.block); self.removeChild(self.block); } self.block = null; self.occupied = false; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x181818 }); /**** * Game Code ****/ // Block shapes/colors // --- Game Constants --- var GRID_SIZE = 8; // 8x8 grid var CELL_SIZE = 200; // px var BOARD_SIZE = GRID_SIZE * CELL_SIZE; // 1600px var BOARD_OFFSET_X = (2048 - BOARD_SIZE) / 2; var BOARD_OFFSET_Y = 300; // leave space for GUI at top // --- Block Shape Definitions --- var SHAPES = [ // Single { matrix: [[1]], color: 'block_blue' }, // 2x1 { matrix: [[1, 1]], color: 'block_red' }, // 1x2 { matrix: [[1], [1]], color: 'block_green' }, // 3x1 { matrix: [[1, 1, 1]], color: 'block_yellow' }, // 1x3 { matrix: [[1], [1], [1]], color: 'block_purple' }, // Square { matrix: [[1, 1], [1, 1]], color: 'block_orange' }, // L { matrix: [[1, 0], [1, 0], [1, 1]], color: 'block_blue' }, // Reverse L { matrix: [[0, 1], [0, 1], [1, 1]], color: 'block_red' }, // T { matrix: [[1, 1, 1], [0, 1, 0]], color: 'block_green' }, // S { matrix: [[0, 1, 1], [1, 1, 0]], color: 'block_yellow' }, // Z { matrix: [[1, 1, 0], [0, 1, 1]], color: 'block_purple' }, // 4x1 { matrix: [[1, 1, 1, 1]], color: 'block_gray' }, // 1x4 { matrix: [[1], [1], [1], [1]], color: 'block_orange' }]; // --- Board State --- var board = []; for (var r = 0; r < GRID_SIZE; r++) { board[r] = []; for (var c = 0; c < GRID_SIZE; c++) { var cell = new BoardCell(); cell.x = BOARD_OFFSET_X + c * CELL_SIZE + CELL_SIZE / 2; cell.y = BOARD_OFFSET_Y + r * CELL_SIZE + CELL_SIZE / 2; board[r][c] = cell; game.addChild(cell); } } // --- Score Display --- var score = 0; var scoreTxt = new Text2('0', { size: 120, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); scoreTxt.setText(score); // --- Next Pieces --- var nextPieces = [null, null, null]; var nextPieceContainers = [null, null, null]; var PIECE_AREA_Y = BOARD_OFFSET_Y + BOARD_SIZE + 80; var PIECE_AREA_X = (2048 - (3 * 400 + 2 * 60)) / 2; // 3 pieces, 60px gap function randomShape() { var idx = Math.floor(Math.random() * SHAPES.length); // Deep copy matrix var m = []; for (var i = 0; i < SHAPES[idx].matrix.length; i++) { m[i] = []; for (var j = 0; j < SHAPES[idx].matrix[i].length; j++) { m[i][j] = SHAPES[idx].matrix[i][j]; } } return { matrix: m, color: SHAPES[idx].color }; } function refillNextPieces() { for (var i = 0; i < 3; i++) { if (nextPieceContainers[i]) { nextPieceContainers[i].destroy(); nextPieceContainers[i] = null; } var shape = randomShape(); var piece = new BlockShape(); piece.init(shape); piece.x = PIECE_AREA_X + i * (400 + 60) + 200; piece.y = PIECE_AREA_Y + 200; piece.interactive = true; nextPieces[i] = { shape: shape, piece: piece, used: false }; nextPieceContainers[i] = piece; game.addChild(piece); } // Ensure all next pieces are on the topmost layer for (var j = 0; j < 3; j++) { if (nextPieceContainers[j]) { game.removeChild(nextPieceContainers[j]); game.addChild(nextPieceContainers[j]); } } } // --- Drag State --- var draggingPiece = null; var draggingIndex = -1; var dragOffsetX = 0; var dragOffsetY = 0; var dragValid = false; var dragTargetRow = -1; var dragTargetCol = -1; // --- Undo State --- var undoStack = []; function saveUndoState() { // Save a deep copy of board state, nextPieces, score var boardState = []; for (var r = 0; r < GRID_SIZE; r++) { boardState[r] = []; for (var c = 0; c < GRID_SIZE; c++) { boardState[r][c] = board[r][c].occupied ? board[r][c].block ? board[r][c].block.assetId : null : null; } } var nextState = []; for (var i = 0; i < 3; i++) { if (nextPieces[i]) { // Deep copy shape matrix var m = []; for (var mi = 0; mi < nextPieces[i].shape.matrix.length; mi++) { m[mi] = []; for (var mj = 0; mj < nextPieces[i].shape.matrix[mi].length; mj++) { m[mi][mj] = nextPieces[i].shape.matrix[mi][mj]; } } nextState[i] = { shape: { matrix: m, color: nextPieces[i].shape.color }, used: nextPieces[i].used }; } else { nextState[i] = null; } } undoStack.push({ board: boardState, next: nextState, score: score }); // Limit undo stack size if (undoStack.length > 20) undoStack.shift(); } function restoreUndoState() { if (undoStack.length === 0) return; var state = undoStack.pop(); // Restore board for (var r = 0; r < GRID_SIZE; r++) { for (var c = 0; c < GRID_SIZE; c++) { if (state.board[r][c]) { board[r][c].setBlock(state.board[r][c]); } else { board[r][c].clearBlock(); } } } // Restore next pieces for (var i = 0; i < 3; i++) { if (nextPieceContainers[i]) { nextPieceContainers[i].destroy(); nextPieceContainers[i] = null; } if (state.next[i]) { var piece = new BlockShape(); piece.init({ matrix: state.next[i].shape.matrix, color: state.next[i].shape.color }); piece.x = PIECE_AREA_X + i * (400 + 60) + 200; piece.y = PIECE_AREA_Y + 200; piece.interactive = true; nextPieces[i] = { shape: { matrix: state.next[i].shape.matrix, color: state.next[i].shape.color }, piece: piece, used: state.next[i].used }; nextPieceContainers[i] = piece; game.addChild(piece); piece.visible = !state.next[i].used; } else { nextPieces[i] = null; nextPieceContainers[i] = null; } } // Ensure all next pieces are on the topmost layer for (var j = 0; j < 3; j++) { if (nextPieceContainers[j]) { game.removeChild(nextPieceContainers[j]); game.addChild(nextPieceContainers[j]); } } // Restore score score = state.score; scoreTxt.setText(score); } function showUndoButton() { if (typeof undoBtn !== "undefined" && undoBtn) { return; } undoBtn = new Text2("↩️ Geri Al", { size: 90, fill: 0xffffff, font: "Impact, Arial Black, Tahoma" }); undoBtn.anchor.set(1, 0); undoBtn.x = 2048 - 40; undoBtn.y = 120; undoBtn.interactive = true; undoBtn.buttonMode = true; undoBtn.on('down', function () { restoreUndoState(); }); LK.gui.top.addChild(undoBtn); } var undoBtn; showUndoButton(); // --- Helper: Check if a piece can be placed at (row, col) --- function canPlace(shapeData, row, col) { for (var r = 0; r < shapeData.matrix.length; r++) { for (var c = 0; c < shapeData.matrix[r].length; c++) { if (shapeData.matrix[r][c]) { var br = row + r; var bc = col + c; if (br < 0 || br >= GRID_SIZE || bc < 0 || bc >= GRID_SIZE) return false; if (board[br][bc].occupied) return false; } } } return true; } // --- Helper: Place a piece at (row, col) --- function placePiece(shapeData, row, col) { for (var r = 0; r < shapeData.matrix.length; r++) { var _loop = function _loop() { if (shapeData.matrix[r][c]) { br = row + r; bc = col + c; board[br][bc].setBlock(shapeData.color); // No animation for placed block; static placement placedBlock = board[br][bc].block; } }, br, bc, placedBlock, baseX; for (var c = 0; c < shapeData.matrix[r].length; c++) { _loop(); } } } // --- Helper: Clear full lines, return number of lines cleared --- function clearLines() { var fullRows = []; var fullCols = []; // Check rows for (var r = 0; r < GRID_SIZE; r++) { var full = true; for (var c = 0; c < GRID_SIZE; c++) { if (!board[r][c].occupied) { full = false; break; } } if (full) fullRows.push(r); } // Check cols for (var c = 0; c < GRID_SIZE; c++) { var full = true; for (var r = 0; r < GRID_SIZE; r++) { if (!board[r][c].occupied) { full = false; break; } } if (full) fullCols.push(c); } // Clear for (var i = 0; i < fullRows.length; i++) { var r = fullRows[i]; for (var c = 0; c < GRID_SIZE; c++) { board[r][c].clearBlock(); } } for (var i = 0; i < fullCols.length; i++) { var c = fullCols[i]; for (var r = 0; r < GRID_SIZE; r++) { board[r][c].clearBlock(); } } return fullRows.length + fullCols.length; } // --- Helper: Check if any next piece can be placed --- function anyMovePossible() { for (var i = 0; i < 3; i++) { if (nextPieces[i] && !nextPieces[i].used) { var shapeData = nextPieces[i].shape; for (var r = 0; r <= GRID_SIZE - shapeData.matrix.length; r++) { for (var c = 0; c <= GRID_SIZE - shapeData.matrix[0].length; c++) { if (canPlace(shapeData, r, c)) return true; } } } } return false; } // --- Drag and Drop Handlers --- function getBoardCellAt(x, y) { // Convert global x,y to board row,col var bx = x - BOARD_OFFSET_X; var by = y - BOARD_OFFSET_Y; var col = Math.floor(bx / CELL_SIZE); var row = Math.floor(by / CELL_SIZE); if (col < 0 || col >= GRID_SIZE || row < 0 || row >= GRID_SIZE) return null; return { row: row, col: col }; } function handleMove(x, y, obj) { if (draggingPiece) { // Move piece draggingPiece.x = x - dragOffsetX; draggingPiece.y = y - dragOffsetY; // Check if over board var cell = getBoardCellAt(x, y); dragValid = false; dragTargetRow = -1; dragTargetCol = -1; if (cell) { var shapeData = nextPieces[draggingIndex].shape; // Snap to top-left of shape var shapeRows = shapeData.matrix.length; var shapeCols = shapeData.matrix[0].length; var baseRow = cell.row - Math.floor(shapeRows / 2); var baseCol = cell.col - Math.floor(shapeCols / 2); if (canPlace(shapeData, baseRow, baseCol)) { dragValid = true; dragTargetRow = baseRow; dragTargetCol = baseCol; // Snap piece visually draggingPiece.x = BOARD_OFFSET_X + (baseCol + shapeCols / 2) * CELL_SIZE; draggingPiece.y = BOARD_OFFSET_Y + (baseRow + shapeRows / 2) * CELL_SIZE; } } // Visual feedback draggingPiece.setAlpha(dragValid ? 0.8 : 0.4); } } game.move = handleMove; game.down = function (x, y, obj) { // Check if touching a next piece for (var i = 0; i < 3; i++) { if (nextPieceContainers[i] && !nextPieces[i].used) { var piece = nextPieceContainers[i]; // Bounding box var px = piece.x; var py = piece.y; var w = piece.pieceWidth; var h = piece.pieceHeight; if (x >= px - w / 2 && x <= px + w / 2 && y >= py - h / 2 && y <= py + h / 2) { draggingPiece = piece; draggingIndex = i; dragOffsetX = x - piece.x; dragOffsetY = y - piece.y; piece.setAlpha(0.4); // Bring to front game.removeChild(piece); game.addChild(piece); handleMove(x, y, obj); break; } } } }; game.up = function (x, y, obj) { if (draggingPiece) { if (dragValid) { // Save undo state before making changes saveUndoState(); // Place piece var shapeData = nextPieces[draggingIndex].shape; placePiece(shapeData, dragTargetRow, dragTargetCol); nextPieces[draggingIndex].used = true; draggingPiece.visible = false; LK.getSound('place').play(); // Clear lines var cleared = clearLines(); if (cleared > 0) { score += cleared * 10; LK.getSound('clear').play(); // Flash effect LK.effects.flashObject(scoreTxt, 0xffff00, 400); // Show 'ULTRA AWESOME!' for combos (cleared > 1), otherwise 'AWESOME!' or 'SUPER!' var awesomeWord; if (cleared > 1) { awesomeWord = 'ULTRA AWESOME!'; } else { var awesomeWords = ['AWESOME!', 'SUPER!']; awesomeWord = awesomeWords[Math.floor(Math.random() * awesomeWords.length)]; } var awesomeTxt = new Text2(awesomeWord, { size: 180, fill: 0xffd700, font: "Impact, Arial Black, Tahoma" }); awesomeTxt.anchor.set(0.5, 0.5); awesomeTxt.x = 2048 / 2; awesomeTxt.y = BOARD_OFFSET_Y + BOARD_SIZE / 2; game.addChild(awesomeTxt); tween(awesomeTxt, { alpha: 0, y: awesomeTxt.y - 120 }, { duration: 900, onComplete: function onComplete() { awesomeTxt.destroy(); } }); } else { score += shapeData.matrix.flat().reduce(function (a, b) { return a + b; }, 0); } scoreTxt.setText(score); // If all 3 used, refill var allUsed = true; for (var i = 0; i < 3; i++) { if (!nextPieces[i].used) allUsed = false; } if (allUsed) { refillNextPieces(); } // Check for game over if (!anyMovePossible()) { LK.getSound('fail').play(); LK.effects.flashScreen(0xff0000, 800); LK.showGameOver(); } } else { // Snap back tween(draggingPiece, { x: PIECE_AREA_X + draggingIndex * (400 + 60) + 200, y: PIECE_AREA_Y + 200 }, { duration: 200, easing: tween.easeOut }); draggingPiece.setAlpha(1); } draggingPiece = null; draggingIndex = -1; dragValid = false; dragTargetRow = -1; dragTargetCol = -1; } }; // --- Game Update --- game.update = function () { // No per-frame logic needed for MVP }; // --- Start Game --- refillNextPieces();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// BlockShape: A draggable group of blocks (the "piece" the player places)
var BlockShape = Container.expand(function () {
var self = Container.call(this);
// Subtle idle animation: gentle up-down and scale for cuteness
// (Moved to after blocks are created, see below)
self.init = function (shapeData) {
self.shapeData = shapeData;
self.blocks = [];
// Remove previous children if any
while (self.children.length) self.removeChild(self.children[0]);
// Draw blocks
for (var r = 0; r < shapeData.matrix.length; r++) {
for (var c = 0; c < shapeData.matrix[r].length; c++) {
if (shapeData.matrix[r][c]) {
var block = self.attachAsset(shapeData.color, {
anchorX: 0.5,
anchorY: 0.5,
x: c * 200,
y: r * 200
});
self.blocks.push(block);
// Subtle idle animation for block
(function (block) {
var baseY = block.y;
var baseScale = block.scale ? block.scale.x : 1;
function animateBlock() {
// Animate up
tween(block, {
y: baseY - 8,
scaleX: baseScale * 1.04,
scaleY: baseScale * 0.96
}, {
duration: 700 + Math.floor(Math.random() * 200),
easing: tween.easeInOut,
onFinish: function onFinish() {
// Animate down
tween(block, {
y: baseY + 8,
scaleX: baseScale * 0.96,
scaleY: baseScale * 1.04
}, {
duration: 700 + Math.floor(Math.random() * 200),
easing: tween.easeInOut,
onFinish: function onFinish() {
// Return to base and repeat
tween(block, {
y: baseY,
scaleX: baseScale,
scaleY: baseScale
}, {
duration: 700 + Math.floor(Math.random() * 200),
easing: tween.easeInOut,
onFinish: animateBlock
});
}
});
}
});
}
animateBlock();
})(block);
}
}
}
// Center anchor
var w = shapeData.matrix[0].length * 200;
var h = shapeData.matrix.length * 200;
self.pieceWidth = w;
self.pieceHeight = h;
self.pivot.x = w / 2 - 100;
self.pivot.y = h / 2 - 100;
};
// Used for drag highlight
self.setAlpha = function (a) {
for (var i = 0; i < self.blocks.length; i++) {
self.blocks[i].alpha = a;
}
};
return self;
});
// BoardCell: A single cell on the board
var BoardCell = Container.expand(function () {
var self = Container.call(this);
self.bg = self.attachAsset('cell_bg', {
anchorX: 0.5,
anchorY: 0.5
});
self.block = null; // If occupied, this is the block asset
self.occupied = false;
self.setBlock = function (color) {
if (self.block) self.removeChild(self.block);
self.block = self.attachAsset(color, {
anchorX: 0.5,
anchorY: 0.5
});
self.occupied = true;
// Subtle idle animation for placed block
if (self.block) {
var _animateBlock = function animateBlock() {
tween(block, {
y: baseY - 6,
scaleX: baseScale * 1.03,
scaleY: baseScale * 0.97
}, {
duration: 800 + Math.floor(Math.random() * 200),
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(block, {
y: baseY + 6,
scaleX: baseScale * 0.97,
scaleY: baseScale * 1.03
}, {
duration: 800 + Math.floor(Math.random() * 200),
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(block, {
y: baseY,
scaleX: baseScale,
scaleY: baseScale
}, {
duration: 800 + Math.floor(Math.random() * 200),
easing: tween.easeInOut,
onFinish: _animateBlock
});
}
});
}
});
};
var block = self.block;
var baseY = block.y;
var baseScale = block.scale ? block.scale.x : 1;
_animateBlock();
}
};
self.clearBlock = function () {
if (self.block) {
// Stop any tweens on this block
tween.stop(self.block);
self.removeChild(self.block);
}
self.block = null;
self.occupied = false;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181818
});
/****
* Game Code
****/
// Block shapes/colors
// --- Game Constants ---
var GRID_SIZE = 8; // 8x8 grid
var CELL_SIZE = 200; // px
var BOARD_SIZE = GRID_SIZE * CELL_SIZE; // 1600px
var BOARD_OFFSET_X = (2048 - BOARD_SIZE) / 2;
var BOARD_OFFSET_Y = 300; // leave space for GUI at top
// --- Block Shape Definitions ---
var SHAPES = [
// Single
{
matrix: [[1]],
color: 'block_blue'
},
// 2x1
{
matrix: [[1, 1]],
color: 'block_red'
},
// 1x2
{
matrix: [[1], [1]],
color: 'block_green'
},
// 3x1
{
matrix: [[1, 1, 1]],
color: 'block_yellow'
},
// 1x3
{
matrix: [[1], [1], [1]],
color: 'block_purple'
},
// Square
{
matrix: [[1, 1], [1, 1]],
color: 'block_orange'
},
// L
{
matrix: [[1, 0], [1, 0], [1, 1]],
color: 'block_blue'
},
// Reverse L
{
matrix: [[0, 1], [0, 1], [1, 1]],
color: 'block_red'
},
// T
{
matrix: [[1, 1, 1], [0, 1, 0]],
color: 'block_green'
},
// S
{
matrix: [[0, 1, 1], [1, 1, 0]],
color: 'block_yellow'
},
// Z
{
matrix: [[1, 1, 0], [0, 1, 1]],
color: 'block_purple'
},
// 4x1
{
matrix: [[1, 1, 1, 1]],
color: 'block_gray'
},
// 1x4
{
matrix: [[1], [1], [1], [1]],
color: 'block_orange'
}];
// --- Board State ---
var board = [];
for (var r = 0; r < GRID_SIZE; r++) {
board[r] = [];
for (var c = 0; c < GRID_SIZE; c++) {
var cell = new BoardCell();
cell.x = BOARD_OFFSET_X + c * CELL_SIZE + CELL_SIZE / 2;
cell.y = BOARD_OFFSET_Y + r * CELL_SIZE + CELL_SIZE / 2;
board[r][c] = cell;
game.addChild(cell);
}
}
// --- Score Display ---
var score = 0;
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
scoreTxt.setText(score);
// --- Next Pieces ---
var nextPieces = [null, null, null];
var nextPieceContainers = [null, null, null];
var PIECE_AREA_Y = BOARD_OFFSET_Y + BOARD_SIZE + 80;
var PIECE_AREA_X = (2048 - (3 * 400 + 2 * 60)) / 2; // 3 pieces, 60px gap
function randomShape() {
var idx = Math.floor(Math.random() * SHAPES.length);
// Deep copy matrix
var m = [];
for (var i = 0; i < SHAPES[idx].matrix.length; i++) {
m[i] = [];
for (var j = 0; j < SHAPES[idx].matrix[i].length; j++) {
m[i][j] = SHAPES[idx].matrix[i][j];
}
}
return {
matrix: m,
color: SHAPES[idx].color
};
}
function refillNextPieces() {
for (var i = 0; i < 3; i++) {
if (nextPieceContainers[i]) {
nextPieceContainers[i].destroy();
nextPieceContainers[i] = null;
}
var shape = randomShape();
var piece = new BlockShape();
piece.init(shape);
piece.x = PIECE_AREA_X + i * (400 + 60) + 200;
piece.y = PIECE_AREA_Y + 200;
piece.interactive = true;
nextPieces[i] = {
shape: shape,
piece: piece,
used: false
};
nextPieceContainers[i] = piece;
game.addChild(piece);
}
// Ensure all next pieces are on the topmost layer
for (var j = 0; j < 3; j++) {
if (nextPieceContainers[j]) {
game.removeChild(nextPieceContainers[j]);
game.addChild(nextPieceContainers[j]);
}
}
}
// --- Drag State ---
var draggingPiece = null;
var draggingIndex = -1;
var dragOffsetX = 0;
var dragOffsetY = 0;
var dragValid = false;
var dragTargetRow = -1;
var dragTargetCol = -1;
// --- Undo State ---
var undoStack = [];
function saveUndoState() {
// Save a deep copy of board state, nextPieces, score
var boardState = [];
for (var r = 0; r < GRID_SIZE; r++) {
boardState[r] = [];
for (var c = 0; c < GRID_SIZE; c++) {
boardState[r][c] = board[r][c].occupied ? board[r][c].block ? board[r][c].block.assetId : null : null;
}
}
var nextState = [];
for (var i = 0; i < 3; i++) {
if (nextPieces[i]) {
// Deep copy shape matrix
var m = [];
for (var mi = 0; mi < nextPieces[i].shape.matrix.length; mi++) {
m[mi] = [];
for (var mj = 0; mj < nextPieces[i].shape.matrix[mi].length; mj++) {
m[mi][mj] = nextPieces[i].shape.matrix[mi][mj];
}
}
nextState[i] = {
shape: {
matrix: m,
color: nextPieces[i].shape.color
},
used: nextPieces[i].used
};
} else {
nextState[i] = null;
}
}
undoStack.push({
board: boardState,
next: nextState,
score: score
});
// Limit undo stack size
if (undoStack.length > 20) undoStack.shift();
}
function restoreUndoState() {
if (undoStack.length === 0) return;
var state = undoStack.pop();
// Restore board
for (var r = 0; r < GRID_SIZE; r++) {
for (var c = 0; c < GRID_SIZE; c++) {
if (state.board[r][c]) {
board[r][c].setBlock(state.board[r][c]);
} else {
board[r][c].clearBlock();
}
}
}
// Restore next pieces
for (var i = 0; i < 3; i++) {
if (nextPieceContainers[i]) {
nextPieceContainers[i].destroy();
nextPieceContainers[i] = null;
}
if (state.next[i]) {
var piece = new BlockShape();
piece.init({
matrix: state.next[i].shape.matrix,
color: state.next[i].shape.color
});
piece.x = PIECE_AREA_X + i * (400 + 60) + 200;
piece.y = PIECE_AREA_Y + 200;
piece.interactive = true;
nextPieces[i] = {
shape: {
matrix: state.next[i].shape.matrix,
color: state.next[i].shape.color
},
piece: piece,
used: state.next[i].used
};
nextPieceContainers[i] = piece;
game.addChild(piece);
piece.visible = !state.next[i].used;
} else {
nextPieces[i] = null;
nextPieceContainers[i] = null;
}
}
// Ensure all next pieces are on the topmost layer
for (var j = 0; j < 3; j++) {
if (nextPieceContainers[j]) {
game.removeChild(nextPieceContainers[j]);
game.addChild(nextPieceContainers[j]);
}
}
// Restore score
score = state.score;
scoreTxt.setText(score);
}
function showUndoButton() {
if (typeof undoBtn !== "undefined" && undoBtn) {
return;
}
undoBtn = new Text2("↩️ Geri Al", {
size: 90,
fill: 0xffffff,
font: "Impact, Arial Black, Tahoma"
});
undoBtn.anchor.set(1, 0);
undoBtn.x = 2048 - 40;
undoBtn.y = 120;
undoBtn.interactive = true;
undoBtn.buttonMode = true;
undoBtn.on('down', function () {
restoreUndoState();
});
LK.gui.top.addChild(undoBtn);
}
var undoBtn;
showUndoButton();
// --- Helper: Check if a piece can be placed at (row, col) ---
function canPlace(shapeData, row, col) {
for (var r = 0; r < shapeData.matrix.length; r++) {
for (var c = 0; c < shapeData.matrix[r].length; c++) {
if (shapeData.matrix[r][c]) {
var br = row + r;
var bc = col + c;
if (br < 0 || br >= GRID_SIZE || bc < 0 || bc >= GRID_SIZE) return false;
if (board[br][bc].occupied) return false;
}
}
}
return true;
}
// --- Helper: Place a piece at (row, col) ---
function placePiece(shapeData, row, col) {
for (var r = 0; r < shapeData.matrix.length; r++) {
var _loop = function _loop() {
if (shapeData.matrix[r][c]) {
br = row + r;
bc = col + c;
board[br][bc].setBlock(shapeData.color);
// No animation for placed block; static placement
placedBlock = board[br][bc].block;
}
},
br,
bc,
placedBlock,
baseX;
for (var c = 0; c < shapeData.matrix[r].length; c++) {
_loop();
}
}
}
// --- Helper: Clear full lines, return number of lines cleared ---
function clearLines() {
var fullRows = [];
var fullCols = [];
// Check rows
for (var r = 0; r < GRID_SIZE; r++) {
var full = true;
for (var c = 0; c < GRID_SIZE; c++) {
if (!board[r][c].occupied) {
full = false;
break;
}
}
if (full) fullRows.push(r);
}
// Check cols
for (var c = 0; c < GRID_SIZE; c++) {
var full = true;
for (var r = 0; r < GRID_SIZE; r++) {
if (!board[r][c].occupied) {
full = false;
break;
}
}
if (full) fullCols.push(c);
}
// Clear
for (var i = 0; i < fullRows.length; i++) {
var r = fullRows[i];
for (var c = 0; c < GRID_SIZE; c++) {
board[r][c].clearBlock();
}
}
for (var i = 0; i < fullCols.length; i++) {
var c = fullCols[i];
for (var r = 0; r < GRID_SIZE; r++) {
board[r][c].clearBlock();
}
}
return fullRows.length + fullCols.length;
}
// --- Helper: Check if any next piece can be placed ---
function anyMovePossible() {
for (var i = 0; i < 3; i++) {
if (nextPieces[i] && !nextPieces[i].used) {
var shapeData = nextPieces[i].shape;
for (var r = 0; r <= GRID_SIZE - shapeData.matrix.length; r++) {
for (var c = 0; c <= GRID_SIZE - shapeData.matrix[0].length; c++) {
if (canPlace(shapeData, r, c)) return true;
}
}
}
}
return false;
}
// --- Drag and Drop Handlers ---
function getBoardCellAt(x, y) {
// Convert global x,y to board row,col
var bx = x - BOARD_OFFSET_X;
var by = y - BOARD_OFFSET_Y;
var col = Math.floor(bx / CELL_SIZE);
var row = Math.floor(by / CELL_SIZE);
if (col < 0 || col >= GRID_SIZE || row < 0 || row >= GRID_SIZE) return null;
return {
row: row,
col: col
};
}
function handleMove(x, y, obj) {
if (draggingPiece) {
// Move piece
draggingPiece.x = x - dragOffsetX;
draggingPiece.y = y - dragOffsetY;
// Check if over board
var cell = getBoardCellAt(x, y);
dragValid = false;
dragTargetRow = -1;
dragTargetCol = -1;
if (cell) {
var shapeData = nextPieces[draggingIndex].shape;
// Snap to top-left of shape
var shapeRows = shapeData.matrix.length;
var shapeCols = shapeData.matrix[0].length;
var baseRow = cell.row - Math.floor(shapeRows / 2);
var baseCol = cell.col - Math.floor(shapeCols / 2);
if (canPlace(shapeData, baseRow, baseCol)) {
dragValid = true;
dragTargetRow = baseRow;
dragTargetCol = baseCol;
// Snap piece visually
draggingPiece.x = BOARD_OFFSET_X + (baseCol + shapeCols / 2) * CELL_SIZE;
draggingPiece.y = BOARD_OFFSET_Y + (baseRow + shapeRows / 2) * CELL_SIZE;
}
}
// Visual feedback
draggingPiece.setAlpha(dragValid ? 0.8 : 0.4);
}
}
game.move = handleMove;
game.down = function (x, y, obj) {
// Check if touching a next piece
for (var i = 0; i < 3; i++) {
if (nextPieceContainers[i] && !nextPieces[i].used) {
var piece = nextPieceContainers[i];
// Bounding box
var px = piece.x;
var py = piece.y;
var w = piece.pieceWidth;
var h = piece.pieceHeight;
if (x >= px - w / 2 && x <= px + w / 2 && y >= py - h / 2 && y <= py + h / 2) {
draggingPiece = piece;
draggingIndex = i;
dragOffsetX = x - piece.x;
dragOffsetY = y - piece.y;
piece.setAlpha(0.4);
// Bring to front
game.removeChild(piece);
game.addChild(piece);
handleMove(x, y, obj);
break;
}
}
}
};
game.up = function (x, y, obj) {
if (draggingPiece) {
if (dragValid) {
// Save undo state before making changes
saveUndoState();
// Place piece
var shapeData = nextPieces[draggingIndex].shape;
placePiece(shapeData, dragTargetRow, dragTargetCol);
nextPieces[draggingIndex].used = true;
draggingPiece.visible = false;
LK.getSound('place').play();
// Clear lines
var cleared = clearLines();
if (cleared > 0) {
score += cleared * 10;
LK.getSound('clear').play();
// Flash effect
LK.effects.flashObject(scoreTxt, 0xffff00, 400);
// Show 'ULTRA AWESOME!' for combos (cleared > 1), otherwise 'AWESOME!' or 'SUPER!'
var awesomeWord;
if (cleared > 1) {
awesomeWord = 'ULTRA AWESOME!';
} else {
var awesomeWords = ['AWESOME!', 'SUPER!'];
awesomeWord = awesomeWords[Math.floor(Math.random() * awesomeWords.length)];
}
var awesomeTxt = new Text2(awesomeWord, {
size: 180,
fill: 0xffd700,
font: "Impact, Arial Black, Tahoma"
});
awesomeTxt.anchor.set(0.5, 0.5);
awesomeTxt.x = 2048 / 2;
awesomeTxt.y = BOARD_OFFSET_Y + BOARD_SIZE / 2;
game.addChild(awesomeTxt);
tween(awesomeTxt, {
alpha: 0,
y: awesomeTxt.y - 120
}, {
duration: 900,
onComplete: function onComplete() {
awesomeTxt.destroy();
}
});
} else {
score += shapeData.matrix.flat().reduce(function (a, b) {
return a + b;
}, 0);
}
scoreTxt.setText(score);
// If all 3 used, refill
var allUsed = true;
for (var i = 0; i < 3; i++) {
if (!nextPieces[i].used) allUsed = false;
}
if (allUsed) {
refillNextPieces();
}
// Check for game over
if (!anyMovePossible()) {
LK.getSound('fail').play();
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
}
} else {
// Snap back
tween(draggingPiece, {
x: PIECE_AREA_X + draggingIndex * (400 + 60) + 200,
y: PIECE_AREA_Y + 200
}, {
duration: 200,
easing: tween.easeOut
});
draggingPiece.setAlpha(1);
}
draggingPiece = null;
draggingIndex = -1;
dragValid = false;
dragTargetRow = -1;
dragTargetCol = -1;
}
};
// --- Game Update ---
game.update = function () {
// No per-frame logic needed for MVP
};
// --- Start Game ---
refillNextPieces();
Happy blue block. In-Game asset. 2d. High contrast. No shadows
Happy gray block. In-Game asset. 2d. High contrast. No shadows
Happy red block. In-Game asset. 2d. High contrast. No shadows
Happy green block. In-Game asset. 2d. High contrast. No shadows
Happy purple block. In-Game asset. 2d. High contrast. No shadows
Happy yellow block. In-Game asset. 2d. High contrast. No shadows
Happy orange block. In-Game asset. 2d. High contrast. No shadows
Krnarları siyah içi koyu gri bir kare. In-Game asset. 2d. High contrast. No shadows