Code edit (13 edits merged)
Please save this source code
User prompt
Do it
Code edit (1 edits merged)
Please save this source code
User prompt
No! The other way around, don't destory boardBg! It should be shown still after starting the game
User prompt
Fix: by clickin on titleImage, the boardBg is destroyed?
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Uncaught TypeError: titleImage.Destroy is not a function' in or related to this line: 'titleImage.Destroy();' Line Number: 833
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (2 edits merged)
Please save this source code
User prompt
Please fix the bug: 'blackBg is not defined' in or related to this line: 'game.addChild(blackBg);' Line Number: 809
Code edit (9 edits merged)
Please save this source code
User prompt
Before starting the game, show the "title" image in the center. Once you click on it, it disappears and the game starts
Code edit (1 edits merged)
Please save this source code
User prompt
Do it
Code edit (1 edits merged)
Please save this source code
User prompt
Ok something weird happens - when freezing a column, it's like it alls again from the top! It should not, it's should just be stuck on the palce it is. Don't show it again falling from the top!
User prompt
Ok I have an issue - frozen columns (gems) are drawn behind boardBg. Can you fix it? Either put them on top of boardBg or make sure boardBg is always on the bottom after any other element is created.
Code edit (3 edits merged)
Please save this source code
User prompt
before a column moves, check the position below the bottom gem (that is same column but row+1). If it's in frozenGems, then don't move that column, stop it immediately, freeze it, store the position of the gems in frozen and spawn a new column.
User prompt
After a column freezes, store the 3 positions of it's geem in frozen gems.
User prompt
You are not storing the frozen gems
User prompt
Columns consist of three gems: top - middle - bottom. Before moving a column to the next row, you need to make sure there is no gem occupying the next row after the bottom gem. To do that, store which cell,rows are occupied by gems. And always check before moving a column. If a gem occupies the next row after bottom gem, freeze the column at it's current position.
User prompt
board_bg ... is not centered
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Column = Container.expand(function (gemTypes) {
var self = Container.call(this);
self.gems = [];
self.isActive = false;
// Create the three gems that form the column
for (var i = 0; i < 3; i++) {
var type = gemTypes && gemTypes[i] ? gemTypes[i] : null;
var gem = new Gem(type);
gem.y = -i * CELL_SIZE;
self.gems.push(gem);
self.addChild(gem);
}
self.rotate = function () {
if (!self.isActive) {
return;
}
// Move the last gem to a temporary position for animation
var lastGem = self.gems.pop();
var firstGem = self.gems[0];
var middleGem = self.gems[1];
// Animate the rotation
LK.getSound('rotate').play();
// Update the gems array with the new order
self.gems = [lastGem, firstGem, middleGem];
// Update positions to match new order
tween(lastGem, {
y: firstGem.y
}, {
duration: 150
});
tween(firstGem, {
y: middleGem.y
}, {
duration: 150
});
tween(middleGem, {
y: lastGem.y + 2 * CELL_SIZE
}, {
duration: 150,
onFinish: function onFinish() {
// Reset positions after animation
for (var i = 0; i < 3; i++) {
self.gems[i].y = -i * CELL_SIZE;
}
}
});
};
self.getTypes = function () {
return self.gems.map(function (gem) {
return gem.type;
});
};
return self;
});
var GameBoard = Container.expand(function () {
var self = Container.call(this);
self.grid = [];
self.cellContainer = self.addChild(new Container());
self.gemContainer = self.addChild(new Container());
// Create background
var boardBg = self.cellContainer.attachAsset('board_bg', {
anchorX: 0.5,
// Center horizontally
anchorY: 0.5 // Center vertically
});
boardBg.x = COLS * CELL_SIZE / 2; // Center the background on the board
boardBg.y = ROWS * CELL_SIZE / 2; // Center the background on the board
// Create grid cells
for (var row = 0; row < ROWS; row++) {
self.grid[row] = [];
for (var col = 0; col < COLS; col++) {
// Create cell background
var cell = LK.getAsset('board_cell', {
anchorX: 0.5,
anchorY: 0.5,
x: col * CELL_SIZE + CELL_SIZE / 2,
y: row * CELL_SIZE + CELL_SIZE / 2,
alpha: 0
});
self.cellContainer.addChild(cell);
self.grid[row][col] = null;
}
}
self.addGemToGrid = function (gem, row, col) {
if (row < 0 || row >= ROWS || col < 0 || col >= COLS) {
return false;
}
if (self.grid[row][col] !== null) {
return false;
}
gem.x = col * CELL_SIZE + CELL_SIZE / 2;
gem.y = row * CELL_SIZE + CELL_SIZE / 2;
self.gemContainer.addChild(gem);
self.grid[row][col] = gem;
return true;
};
self.removeGemFromGrid = function (row, col) {
if (row < 0 || row >= ROWS || col < 0 || col >= COLS) {
return null;
}
var gem = self.grid[row][col];
if (gem) {
self.grid[row][col] = null;
delete frozenGems[row + ',' + col]; // Remove gem from the dictionary
return gem;
}
return null;
};
self.isColumnFull = function (col) {
return self.grid[0][col] !== null;
};
self.canPlaceColumn = function (col) {
// Check if any of the top 3 rows in the column are occupied
for (var row = 0; row < 3; row++) {
if (col < 0 || col >= COLS) {
return false;
}
if (row < ROWS && self.grid[row][col] !== null) {
return false;
}
}
return true;
};
self.findFirstEmptyRow = function (col) {
for (var row = ROWS - 1; row >= 0; row--) {
if (self.grid[row][col] === null) {
return row;
}
}
return -1; // Column is full
};
self.placeColumn = function (column, col, callback) {
var gems = column.gems;
var placedGems = [];
var firstEmptyRow = self.findFirstEmptyRow(col);
if (firstEmptyRow < 0 || firstEmptyRow < gems.length - 1) {
if (callback) {
callback(false);
}
return;
}
// Place gems from bottom to top
for (var i = 0; i < gems.length; i++) {
var row = firstEmptyRow - i;
if (row >= 0) {
// Use the existing gem instead of creating a new one
var gem = gems[i];
placedGems.push({
gem: gem,
row: row,
col: col
});
// Update the gem's position
gem.x = col * CELL_SIZE + CELL_SIZE / 2;
gem.y = row * CELL_SIZE + CELL_SIZE / 2;
// Add the gem to the grid
self.gemContainer.addChild(gem);
self.grid[row][col] = gem;
frozenGems[row + ',' + col] = gem; // Store gem in the dictionary
// If it's the last gem, notify callback
if (i === gems.length - 1) {
LK.getSound('drop').play();
if (callback) {
callback(true, placedGems);
}
}
}
}
};
self.checkMatches = function () {
var matchedCells = [];
// Check horizontal matches
for (var row = 0; row < ROWS; row++) {
var count = 1;
var type = null;
for (var col = 0; col < COLS; col++) {
var gem = self.grid[row][col];
if (gem && type === gem.type) {
count++;
} else {
if (count >= 3) {
for (var i = 0; i < count; i++) {
matchedCells.push({
row: row,
col: col - 1 - i
});
}
}
count = 1;
type = gem ? gem.type : null;
}
}
if (count >= 3) {
for (var i = 0; i < count; i++) {
matchedCells.push({
row: row,
col: COLS - 1 - i
});
}
}
}
// Check vertical matches
for (var col = 0; col < COLS; col++) {
var count = 1;
var type = null;
for (var row = 0; row < ROWS; row++) {
var gem = self.grid[row][col];
if (gem && type === gem.type) {
count++;
} else {
if (count >= 3) {
for (var i = 0; i < count; i++) {
matchedCells.push({
row: row - 1 - i,
col: col
});
}
}
count = 1;
type = gem ? gem.type : null;
}
}
if (count >= 3) {
for (var i = 0; i < count; i++) {
matchedCells.push({
row: ROWS - 1 - i,
col: col
});
}
}
}
// Remove duplicates from matchedCells
var uniqueMatches = [];
var matchMap = {};
for (var i = 0; i < matchedCells.length; i++) {
var cell = matchedCells[i];
var key = cell.row + "," + cell.col;
if (!matchMap[key]) {
matchMap[key] = true;
uniqueMatches.push(cell);
}
}
return uniqueMatches;
};
self.removeMatches = function (matches, callback) {
if (matches.length === 0) {
if (callback) {
callback(0);
}
return;
}
var remainingAnimations = matches.length;
var points = matches.length * 10;
// Always play match sound and flash effect when matches are found
// Force sound to play even if it's already playing
LK.getSound('match').stop();
LK.getSound('match').play();
// Flash for any match (3 or more gems)
LK.effects.flashScreen(0xFFFFFF, 300);
matches.forEach(function (match) {
var gem = self.grid[match.row][match.col];
if (gem) {
self.grid[match.row][match.col] = null;
delete frozenGems[match.row + ',' + match.col]; // Remove gem from the dictionary
gem.highlight();
LK.setTimeout(function () {
gem.animateMatch(function () {
self.gemContainer.removeChild(gem);
remainingAnimations--;
if (remainingAnimations === 0) {
if (callback) {
callback(points);
}
}
});
}, 100);
} else {
remainingAnimations--;
if (remainingAnimations === 0 && callback) {
callback(points);
}
}
});
};
self.applyGravity = function (callback) {
var movedGems = false;
var animationsRunning = 0;
// Iterate bottom to top, right to left
for (var col = 0; col < COLS; col++) {
for (var row = ROWS - 1; row > 0; row--) {
if (self.grid[row][col] === null) {
// Find the first non-null gem above this position
for (var checkRow = row - 1; checkRow >= 0; checkRow--) {
if (self.grid[checkRow][col] !== null) {
var gem = self.grid[checkRow][col];
self.grid[checkRow][col] = null;
self.grid[row][col] = gem;
// Update the frozenGems dictionary
delete frozenGems[checkRow + ',' + col];
frozenGems[row + ',' + col] = gem;
movedGems = true;
animationsRunning++;
gem.animateDrop(row * CELL_SIZE + CELL_SIZE / 2, 0, function () {
animationsRunning--;
if (animationsRunning === 0 && callback) {
callback(movedGems);
}
});
break;
}
}
}
}
}
if (animationsRunning === 0 && callback) {
callback(movedGems);
}
};
return self;
});
var Gem = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || getRandomGemType();
var gemSprite = self.attachAsset('gem_' + self.type, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.95,
scaleY: 0.95
});
self.setType = function (newType) {
self.type = newType;
self.removeChildren();
gemSprite = self.attachAsset('gem_' + newType, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.95,
scaleY: 0.95
});
};
self.highlight = function () {
tween(gemSprite, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 200,
easing: tween.easeOut
});
};
self.unhighlight = function () {
tween(gemSprite, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 200,
easing: tween.easeOut
});
};
self.animateMatch = function (callback) {
tween(gemSprite, {
alpha: 0,
scaleX: 0.2,
scaleY: 0.2
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
if (callback) {
callback();
}
}
});
};
self.animateDrop = function (targetY, delay, callback) {
tween(self, {
y: targetY
}, {
duration: 300 + delay,
easing: tween.bounceOut,
onFinish: function onFinish() {
if (callback) {
callback();
}
}
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x111122
});
/****
* Game Code
****/
// Constants
var CELL_SIZE = 120;
var COLS = 25;
var ROWS = Math.floor(2732 / CELL_SIZE);
//var ROWS = 25;
var GEM_TYPES = ['red', 'blue', 'green', 'yellow', 'purple'];
var DROP_INTERVAL_START = 40; // ms
var DROP_INTERVAL_MIN = 40; // ms
var LEVEL_THRESHOLD = 50; // Points needed to level up
// Game state
var board;
var activeColumn;
var nextColumn;
var dropInterval;
var dropTimer = 0;
var fallSpeed = DROP_INTERVAL_START;
var gameActive = false;
var score = 0;
var level = 1;
var nextColumnPreview;
var columnPositionX = 0;
// Dictionary to track frozen gems
var frozenGems = {};
// UI elements
var scoreText;
var levelText;
var highScoreText;
function getRandomGemType() {
var index = Math.floor(Math.random() * GEM_TYPES.length);
return GEM_TYPES[index];
}
function setupUI() {
// Score text
scoreText = new Text2('SCORE: 0', {
size: 50,
fill: 0x000000 // Changed to black
});
scoreText.anchor.set(0.5, 0);
scoreText.y = 50;
LK.gui.top.addChild(scoreText);
// Level text
levelText = new Text2('LEVEL: 1', {
size: 50,
fill: 0x000000 // Changed to black
});
levelText.anchor.set(0.5, 0);
levelText.y = 100;
LK.gui.top.addChild(levelText);
// High score text
var highScore = storage.highScore || 0;
highScoreText = new Text2('BEST: ' + highScore, {
size: 40,
fill: 0x800080 // Changed to purple
});
highScoreText.anchor.set(0.5, 0);
highScoreText.y = 150;
LK.gui.top.addChild(highScoreText);
}
function updateScore(points) {
score += points;
scoreText.setText('SCORE: ' + score);
// Check if player leveled up
var newLevel = Math.floor(score / LEVEL_THRESHOLD) + 1;
if (newLevel > level) {
level = newLevel;
levelText.setText('LEVEL: ' + level);
// Increase game speed
fallSpeed = Math.max(DROP_INTERVAL_MIN, DROP_INTERVAL_START - (level - 1) * 10); // Adjust fallSpeed based on the current level
// Play level up sound
LK.getSound('levelup').play();
// Flash effect for level up
LK.effects.flashScreen(0x00FFFF, 500);
}
// Update high score if needed
var highScore = storage.highScore || 0;
if (score > highScore) {
storage.highScore = score;
highScoreText.setText('BEST: ' + score);
}
}
function createBoard() {
board = new GameBoard();
board.x = (2048 - COLS * CELL_SIZE) / 2;
board.y = (2732 - ROWS * CELL_SIZE) / 2;
game.addChild(board);
}
function createNextColumnPreview() {
nextColumnPreview = new Container();
nextColumnPreview.x = 2048 - 200;
nextColumnPreview.y = 400;
var previewLabel = new Text2('NEXT', {
size: 40,
fill: 0xFFFFFF
});
previewLabel.anchor.set(0.5, 0);
nextColumnPreview.addChild(previewLabel);
game.addChild(nextColumnPreview);
}
function updateNextColumnPreview() {
// Clear previous preview
for (var i = nextColumnPreview.children.length - 1; i > 0; i--) {
nextColumnPreview.removeChild(nextColumnPreview.children[i]);
}
// Add new gems to preview in the correct order
if (nextColumn) {
// Loop through gems in reverse order to match the visual appearance in the game
for (var i = nextColumn.gems.length - 1; i >= 0; i--) {
var previewGem = new Gem(nextColumn.gems[i].type);
previewGem.x = 0;
// Position from top to bottom in the preview
previewGem.y = 80 + (nextColumn.gems.length - 1 - i) * CELL_SIZE;
previewGem.scale.set(0.8);
nextColumnPreview.addChild(previewGem);
}
}
}
function createNewColumn() {
var column = new Column();
column.isActive = true;
return column;
}
function startGame() {
// Reset game state
score = 0;
level = 1;
fallSpeed = DROP_INTERVAL_START; // Initialize fallSpeed with the starting drop interval
gameActive = true;
dropTimer = 0;
// Clear frozen gems dictionary
frozenGems = {};
// Initialize the board
if (board) {
game.removeChild(board);
}
createBoard();
// Create the preview for the next column
if (nextColumnPreview) {
game.removeChild(nextColumnPreview);
}
createNextColumnPreview();
// Set up UI
setupUI();
// Create initial columns
activeColumn = createNewColumn();
nextColumn = createNewColumn();
updateNextColumnPreview();
// Position active column
columnPositionX = Math.floor(COLS / 2);
activeColumn.x = board.x + columnPositionX * CELL_SIZE + CELL_SIZE / 2;
activeColumn.y = 0;
game.addChild(activeColumn);
// Start music
LK.playMusic('bgmusic', {
fade: {
start: 0,
end: 0.4,
duration: 1000
}
});
}
function processMatches(callback) {
var matches = board.checkMatches();
if (matches.length > 0) {
board.removeMatches(matches, function (points) {
updateScore(points);
board.applyGravity(function (movedGems) {
// Check for chain reactions
if (movedGems) {
processMatches(callback);
} else {
if (callback) {
callback();
}
}
});
});
} else {
if (callback) {
callback();
}
}
}
function spawnNewColumn() {
activeColumn = nextColumn;
nextColumn = createNewColumn();
updateNextColumnPreview();
columnPositionX = Math.floor(COLS / 2);
activeColumn.x = board.x + columnPositionX * CELL_SIZE + CELL_SIZE / 2;
activeColumn.y = 0;
game.addChild(activeColumn);
// Check if game over
if (!board.canPlaceColumn(columnPositionX)) {
gameActive = false;
LK.showGameOver();
} else {
activeColumn.isActive = true; // Reactivate the new column
}
}
function moveColumnLeft() {
if (!gameActive || !activeColumn || !activeColumn.isActive) {
return;
}
if (columnPositionX > 0) {
// Check if there's a collision in the new position
var canMove = true;
var currentRow = Math.floor(activeColumn.y / CELL_SIZE);
for (var i = 0; i < activeColumn.gems.length; i++) {
var gemRow = currentRow - i;
if (gemRow >= 0 && gemRow < ROWS) {
var gemKey = gemRow + ',' + (columnPositionX - 1);
if (frozenGems[gemKey]) {
canMove = false;
break;
}
}
}
if (canMove) {
columnPositionX--;
activeColumn.x = board.x + columnPositionX * CELL_SIZE + CELL_SIZE / 2;
}
}
}
function moveColumnRight() {
if (!gameActive || !activeColumn || !activeColumn.isActive) {
return;
}
if (columnPositionX < COLS - 1) {
// Check if there's a collision in the new position
var canMove = true;
var currentRow = Math.floor(activeColumn.y / CELL_SIZE);
for (var i = 0; i < activeColumn.gems.length; i++) {
var gemRow = currentRow - i;
if (gemRow >= 0 && gemRow < ROWS) {
var gemKey = gemRow + ',' + (columnPositionX + 1);
if (frozenGems[gemKey]) {
canMove = false;
break;
}
}
}
if (canMove) {
columnPositionX++;
activeColumn.x = board.x + columnPositionX * CELL_SIZE + CELL_SIZE / 2;
}
}
}
function rotateColumn() {
if (!gameActive || !activeColumn) {
return;
}
activeColumn.rotate();
}
function dropColumn() {
if (!gameActive || !activeColumn || !activeColumn.isActive) {
return;
}
activeColumn.x = board.x + columnPositionX * CELL_SIZE + CELL_SIZE / 2;
activeColumn.isActive = false;
board.placeColumn(activeColumn, columnPositionX, function (success, placedGems) {
if (success) {
// Store the positions of the frozen gems
placedGems.forEach(function (placedGem) {
frozenGems[placedGem.row + ',' + placedGem.col] = placedGem.gem;
});
game.removeChild(activeColumn);
processMatches(function () {
spawnNewColumn(); // Spawn a new column after processing matches
});
} else {
// Failed to place column, game over
gameActive = false;
LK.showGameOver();
}
});
}
// Input handlers
game.down = function (x, y, obj) {
if (!gameActive) {
startGame();
return;
}
// Determine which action to take based on touch position
var boardX = x - board.x;
// If touch is on the board
if (boardX >= 0 && boardX < COLS * CELL_SIZE) {
var touchCol = Math.floor(boardX / CELL_SIZE);
// If touch is on the left side of the active column, move left
if (touchCol < columnPositionX) {
moveColumnLeft();
}
// If touch is on the right side of the active column, move right
else if (touchCol > columnPositionX) {
moveColumnRight();
}
// If touch is on the active column, rotate it
else {
rotateColumn();
}
}
};
game.move = function (x, y, obj) {
// Optional: handle drag for more fluid control
};
game.up = function (x, y, obj) {
// Optional: implement swipe detection for quick drop
if (gameActive && y > game.down.y + 100) {
dropColumn();
}
};
game.update = function () {
if (!gameActive || !activeColumn || !activeColumn.isActive) {
return;
}
// Update column position using rows
var currentRow = Math.floor(activeColumn.y / CELL_SIZE);
// Calculate the next row position based on the current fall speed
dropTimer += fallSpeed; // Increment dropTimer by fallSpeed
if (dropTimer >= 1000) {
// Check if dropTimer has reached the threshold for a drop
// 1000ms is the base interval for a row drop
dropTimer = 0;
// Check if the column can move down
var canMoveDown = true;
var nextRow = currentRow + 1;
// Check if any gem in the column would collide with a frozen gem
for (var i = 0; i < activeColumn.gems.length; i++) {
var gemRow = nextRow - i;
if (gemRow >= 0 && gemRow < ROWS) {
var gemKey = gemRow + ',' + columnPositionX;
if (frozenGems[gemKey]) {
canMoveDown = false;
break;
}
}
}
// Check if column reached the bottom
// The bottom-most gem in the column is at position nextRow
// So we need to check if this position is beyond the board
if (nextRow >= ROWS) {
canMoveDown = false;
}
if (canMoveDown) {
activeColumn.y = nextRow * CELL_SIZE; // Move down
} else {
// Column has hit something or reached the bottom
dropColumn();
return;
}
}
// Check for immediate collisions (for fast drops or manual movements)
var checkRow = Math.floor(activeColumn.y / CELL_SIZE);
for (var i = 0; i < activeColumn.gems.length; i++) {
var gemRow = checkRow - i;
if (gemRow >= 0 && gemRow < ROWS) {
var gemKey = gemRow + ',' + columnPositionX;
if (frozenGems[gemKey]) {
// Move column back up to avoid overlap
activeColumn.y = (gemRow + i) * CELL_SIZE;
dropColumn();
return;
}
}
}
// Check if column reached the bottom
// Only freeze the column if the bottom-most gem has reached the bottom row
if (checkRow >= ROWS) {
activeColumn.y = (ROWS - 1) * CELL_SIZE;
dropColumn();
return;
}
};
// Initialize the board
if (board) {
game.removeChild(board);
}
createBoard();
// Create and display the title image in the center of the screen
var titleImage = LK.getAsset('title', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
var blackBg = LK.getAsset('blackBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
alpha: 0.75
});
game.addChild(blackBg);
game.addChild(titleImage);
startGameText = new Text2('Press any button to start...', {
size: 150,
fill: 0xFFFFFF // Changed to black,
});
startGameText.y = 2732 / 2 + 500;
startGameText.x = 2048 / 2 - 1000;
game.addChild(startGameText);
// Function to start the game
// Start the game when the title image is clicked
titleImage.down = function (x, y, obj) {
startGame();
}; ===================================================================
--- original.js
+++ change.js
@@ -792,12 +792,19 @@
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
- alpha: 0.5
+ alpha: 0.75
});
game.addChild(blackBg);
game.addChild(titleImage);
+startGameText = new Text2('Press any button to start...', {
+ size: 150,
+ fill: 0xFFFFFF // Changed to black,
+});
+startGameText.y = 2732 / 2 + 500;
+startGameText.x = 2048 / 2 - 1000;
+game.addChild(startGameText);
// Function to start the game
// Start the game when the title image is clicked
titleImage.down = function (x, y, obj) {
startGame();