User prompt
I still can't
User prompt
Please fix the bug: 'Timeout.tick error: Cannot set properties of null (setting 'gridX')' in or related to this line: 'selectedCandy.gridX = oldGrid1.x;' Line Number: 441
Code edit (1 edits merged)
Please save this source code
User prompt
Sweet Cascades
Initial prompt
import pygame import random import sys from pygame.locals import * # Initialize pygame pygame.init() # Constants SCREEN_WIDTH = 800 SCREEN_HEIGHT = 600 GRID_SIZE = 8 CELL_SIZE = 64 GRID_OFFSET_X = (SCREEN_WIDTH - GRID_SIZE * CELL_SIZE) // 2 GRID_OFFSET_Y = 150 FPS = 60 # Colors WHITE = (255, 255, 255) BLACK = (0, 0, 0) BACKGROUND_COLOR = (253, 245, 230) # Creamy background # Candy colors CANDY_COLORS = [ (255, 50, 50), # Red (50, 255, 50), # Green (50, 50, 255), # Blue (255, 255, 50), # Yellow (255, 50, 255), # Purple (50, 255, 255), # Cyan ] class Candy: def __init__(self, row, col, color_idx): self.row = row self.col = col self.color_idx = color_idx self.x = 0 self.y = 0 self.target_y = 0 self.target_x = 0 self.is_moving = False self.is_selected = False self.special_type = None # 'bomb', 'rainbow', 'lollipop' def update_position(self): if self.x < self.target_x: self.x += 5 self.is_moving = True elif self.x > self.target_x: self.x -= 5 self.is_moving = True else: self.x = self.target_x if self.y < self.target_y: self.y += 5 self.is_moving = True elif self.y > self.target_y: self.y -= 5 self.is_moving = True else: self.y = self.target_y self.is_moving = False def draw(self, surface): color = CANDY_COLORS[self.color_idx] rect = pygame.Rect( GRID_OFFSET_X + self.col * CELL_SIZE + self.x + 5, GRID_OFFSET_Y + self.row * CELL_SIZE + self.y + 5, CELL_SIZE - 10, CELL_SIZE - 10 ) # Draw candy base pygame.draw.rect(surface, color, rect, border_radius=10) # Add candy highlights highlight = pygame.Surface((CELL_SIZE-10, CELL_SIZE-10), pygame.SRCALPHA) pygame.draw.rect(highlight, (*color[:3], 50), highlight.get_rect(), border_radius=10) surface.blit(highlight, rect) # Draw selection border if self.is_selected: pygame.draw.rect(surface, WHITE, rect, 3, border_radius=10) # Draw special candy indicators if self.special_type == 'bomb': pygame.draw.circle(surface, BLACK, rect.center, 10) elif self.special_type == 'rainbow': for i in range(6): pygame.draw.arc(surface, CANDY_COLORS[i], rect, i * 0.8, (i+1) * 0.8, 5) elif self.special_type == 'lollipop': pygame.draw.circle(surface, WHITE, rect.center, 15, 3) class Game: def __init__(self): self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) pygame.display.set_caption("Sweet Cascades") self.clock = pygame.time.Clock() self.font = pygame.font.SysFont('Arial', 24) self.big_font = pygame.font.SysFont('Arial', 36) self.grid = [] self.selected_candy = None self.score = 0 self.moves_left = 30 self.level = 1 self.target_score = 1000 self.initialize_grid() def initialize_grid(self): self.grid = [] for row in range(GRID_SIZE): grid_row = [] for col in range(GRID_SIZE): # Make sure we don't create matches at the start while True: color_idx = random.randint(0, len(CANDY_COLORS) - 1) # Check horizontal matches if col >= 2: if (grid_row[col-1].color_idx == color_idx and grid_row[col-2].color_idx == color_idx): continue # Check vertical matches if row >= 2: if (self.grid[row-1][col].color_idx == color_idx and self.grid[row-2][col].color_idx == color_idx): continue break candy = Candy(row, col, color_idx) candy.target_x = 0 candy.target_y = 0 grid_row.append(candy) self.grid.append(grid_row) def draw(self): # Draw background self.screen.fill(BACKGROUND_COLOR) # Draw title title = self.big_font.render("Sweet Cascades", True, (200, 50, 100)) self.screen.blit(title, (SCREEN_WIDTH // 2 - title.get_width() // 2, 20)) # Draw score and moves score_text = self.font.render(f"Score: {self.score}", True, BLACK) moves_text = self.font.render(f"Moves: {self.moves_left}", True, BLACK) level_text = self.font.render(f"Level: {self.level}", True, BLACK) target_text = self.font.render(f"Target: {self.target_score}", True, BLACK) self.screen.blit(score_text, (50, 80)) self.screen.blit(moves_text, (200, 80)) self.screen.blit(level_text, (350, 80)) self.screen.blit(target_text, (500, 80)) # Draw grid background grid_bg = pygame.Rect( GRID_OFFSET_X - 5, GRID_OFFSET_Y - 5, GRID_SIZE * CELL_SIZE + 10, GRID_SIZE * CELL_SIZE + 10 ) pygame.draw.rect(self.screen, (220, 220, 220), grid_bg, border_radius=15) # Draw candies for row in self.grid: for candy in row: candy.draw(self.screen) # Draw game over or level complete message if self.moves_left <= 0: if self.score >= self.target_score: message = self.big_font.render("Level Complete!", True, (0, 200, 0)) else: message = self.big_font.render("Game Over!", True, (200, 0, 0)) self.screen.blit(message, (SCREEN_WIDTH // 2 - message.get_width() // 2, SCREEN_HEIGHT - 100)) pygame.display.flip() def handle_click(self, pos): if self.moves_left <= 0: return x, y = pos # Check if click is within grid if (GRID_OFFSET_X <= x < GRID_OFFSET_X + GRID_SIZE * CELL_SIZE and GRID_OFFSET_Y <= y < GRID_OFFSET_Y + GRID_SIZE * CELL_SIZE): col = (x - GRID_OFFSET_X) // CELL_SIZE row = (y - GRID_OFFSET_Y) // CELL_SIZE if 0 <= row < GRID_SIZE and 0 <= col < GRID_SIZE: clicked_candy = self.grid[row][col] if self.selected_candy is None: # First selection self.selected_candy = clicked_candy clicked_candy.is_selected = True else: # Second selection - check if adjacent if (abs(self.selected_candy.row - clicked_candy.row) == 1 and self.selected_candy.col == clicked_candy.col) or ( abs(self.selected_candy.col - clicked_candy.col) == 1 and self.selected_candy.row == clicked_candy.row): # Swap candies self.swap_candies(self.selected_candy, clicked_candy) self.moves_left -= 1 # Check for matches after swap matches = self.find_matches() if not matches: # No matches, swap back self.swap_candies(self.selected_candy, clicked_candy) self.moves_left += 1 else: # Process matches self.process_matches(matches) # Deselect self.selected_candy.is_selected = False self.selected_candy = None clicked_candy.is_selected = False def swap_candies(self, candy1, candy2): # Swap positions in grid self.grid[candy1.row][candy1.col], self.grid[candy2.row][candy2.col] = ( self.grid[candy2.row][candy2.col], self.grid[candy1.row][candy1.col] ) # Swap row and col attributes candy1.row, candy2.row = candy2.row, candy1.row candy1.col, candy2.col = candy2.col, candy1.col # Set target positions for animation candy1.target_x = 0 candy1.target_y = 0 candy2.target_x = 0 candy2.target_y = 0 def find_matches(self): matches = [] # Check horizontal matches for row in range(GRID_SIZE): for col in range(GRID_SIZE - 2): if (self.grid[row][col].color_idx == self.grid[row][col+1].color_idx == self.grid[row][col+2].color_idx): match = [] # Find all consecutive matching candies for c in range(col, GRID_SIZE): if self.grid[row][c].color_idx == self.grid[row][col].color_idx: match.append(self.grid[row][c]) else: break if len(match) >= 3: matches.append(match) # Check vertical matches for col in range(GRID_SIZE): for row in range(GRID_SIZE - 2): if (self.grid[row][col].color_idx == self.grid[row+1][col].color_idx == self.grid[row+2][col].color_idx): match = [] # Find all consecutive matching candies for r in range(row, GRID_SIZE): if self.grid[r][col].color_idx == self.grid[row][col].color_idx: match.append(self.grid[r][col]) else: break if len(match) >= 3: matches.append(match) return matches def process_matches(self, matches): # Create special candies for larger matches for match in matches: if len(match) == 4: # Create a bomb (random candy in the match) candy = random.choice(match) candy.special_type = 'bomb' elif len(match) >= 5: # Create a rainbow candy (first candy in the match) match[0].special_type = 'rainbow' # Lollipop could be created for L or T shapes (not implemented here) # Remove matched candies and calculate score matched_positions = set() for match in matches: for candy in match: if (candy.row, candy.col) not in matched_positions: matched_positions.add((candy.row, candy.col)) self.score += 10 * len(match) # Special candy effects if candy.special_type == 'bomb': self.score += 50 # Clear row and column for r in range(GRID_SIZE): matched_positions.add((r, candy.col)) for c in range(GRID_SIZE): matched_positions.add((candy.row, c)) elif candy.special_type == 'rainbow': self.score += 100 # Clear all candies of same color color = candy.color_idx for row in self.grid: for c in row: if c.color_idx == color: matched_positions.add((c.row, c.col)) # Remove matched candies for row, col in matched_positions: self.grid[row][col] = None # Drop candies down self.drop_candies() # Fill empty spaces self.fill_empty_spaces() # Check for new matches after filling new_matches = self.find_matches() if new_matches: self.process_matches(new_matches) def drop_candies(self): for col in range(GRID_SIZE): # Count empty spaces in column empty_rows = [] for row in range(GRID_SIZE-1, -1, -1): if self.grid[row][col] is None: empty_rows.append(row) elif empty_rows: # Move candy down to the lowest empty space lowest_empty = empty_rows.pop(0) self.grid[lowest_empty][col] = self.grid[row][col] self.grid[row][col] = None self.grid[lowest_empty][col].row = lowest_empty self.grid[lowest_empty][col].target_y = 0 empty_rows.append(row) def fill_empty_spaces(self): for row in range(GRID_SIZE): for col in range(GRID_SIZE): if self.grid[row][col] is None: color_idx = random.randint(0, len(CANDY_COLORS) - 1) candy = Candy(row, col, color_idx) candy.target_y = -CELL_SIZE # Start above the grid for falling animation candy.y = -CELL_SIZE self.grid[row][col] = candy def update(self): # Update candy positions for animations for row in self.grid: for candy in row: if candy: candy.update_position() # Check level completion if self.score >= self.target_score and self.moves_left > 0: self.level += 1 self.target_score += self.level * 500 self.moves_left = 30 self.initialize_grid() def run(self): running = True while running: for event in pygame.event.get(): if event.type == QUIT: running = False elif event.type == MOUSEBUTTONDOWN: self.handle_click(event.pos) self.update() self.draw() self.clock.tick(FPS) pygame.quit() sys.exit() if __name__ == "__main__": game = Game() game.run()
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Candy = Container.expand(function (type) {
var self = Container.call(this);
self.candyType = type || 0;
self.isSpecial = false;
self.specialType = 'normal'; // 'normal', 'bomb', 'rainbow'
self.gridX = 0;
self.gridY = 0;
self.isAnimating = false;
self.isSelected = false;
self.interactive = true;
var candyTypes = ['candy_red', 'candy_blue', 'candy_green', 'candy_yellow', 'candy_purple', 'candy_orange'];
var assetId = candyTypes[self.candyType] || candyTypes[0];
self.candyGraphics = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Set hit area for better touch detection
self.hitArea = new Rectangle(-60, -60, 120, 120);
self.makeSpecial = function (specialType) {
self.isSpecial = true;
self.specialType = specialType;
if (specialType === 'bomb') {
self.removeChild(self.candyGraphics);
self.candyGraphics = self.attachAsset('bomb_candy', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (specialType === 'rainbow') {
self.removeChild(self.candyGraphics);
self.candyGraphics = self.attachAsset('rainbow_candy', {
anchorX: 0.5,
anchorY: 0.5
});
}
};
self.setSelected = function (selected) {
self.isSelected = selected;
if (selected) {
self.candyGraphics.scaleX = 1.2;
self.candyGraphics.scaleY = 1.2;
} else {
self.candyGraphics.scaleX = 1.0;
self.candyGraphics.scaleY = 1.0;
}
};
self.down = function (x, y, obj) {
if (!self.isAnimating) {
handleCandyClick(self);
}
};
return self;
});
var GameGrid = Container.expand(function () {
var self = Container.call(this);
self.gridSize = 8;
self.cellSize = 120;
self.grid = [];
self.backgroundCells = [];
self.init = function () {
// Create background grid
for (var row = 0; row < self.gridSize; row++) {
for (var col = 0; col < self.gridSize; col++) {
var bgCell = self.attachAsset('grid_bg', {
anchorX: 0.5,
anchorY: 0.5
});
bgCell.x = col * self.cellSize;
bgCell.y = row * self.cellSize;
bgCell.alpha = 0.3;
self.backgroundCells.push(bgCell);
}
}
// Initialize grid array
for (var row = 0; row < self.gridSize; row++) {
self.grid[row] = [];
for (var col = 0; col < self.gridSize; col++) {
self.grid[row][col] = null;
}
}
// Fill grid with candies ensuring no initial matches
self.fillGrid();
};
self.fillGrid = function () {
for (var row = 0; row < self.gridSize; row++) {
for (var col = 0; col < self.gridSize; col++) {
if (!self.grid[row][col]) {
var candyType = self.getValidCandyType(row, col);
var candy = new Candy(candyType);
candy.gridX = col;
candy.gridY = row;
candy.x = col * self.cellSize;
candy.y = row * self.cellSize;
self.grid[row][col] = candy;
self.addChild(candy);
}
}
}
};
self.getValidCandyType = function (row, col) {
var invalidTypes = [];
// Check horizontal matches
if (col >= 2) {
var type1 = self.grid[row][col - 1] ? self.grid[row][col - 1].candyType : -1;
var type2 = self.grid[row][col - 2] ? self.grid[row][col - 2].candyType : -1;
if (type1 === type2 && type1 !== -1) {
invalidTypes.push(type1);
}
}
// Check vertical matches
if (row >= 2) {
var type1 = self.grid[row - 1][col] ? self.grid[row - 1][col].candyType : -1;
var type2 = self.grid[row - 2][col] ? self.grid[row - 2][col].candyType : -1;
if (type1 === type2 && type1 !== -1) {
invalidTypes.push(type1);
}
}
var validTypes = [];
for (var i = 0; i < 6; i++) {
if (invalidTypes.indexOf(i) === -1) {
validTypes.push(i);
}
}
return validTypes[Math.floor(Math.random() * validTypes.length)];
};
self.swapCandies = function (candy1, candy2) {
if (!candy1 || !candy2) return false;
var tempX = candy1.gridX;
var tempY = candy1.gridY;
candy1.gridX = candy2.gridX;
candy1.gridY = candy2.gridY;
candy2.gridX = tempX;
candy2.gridY = tempY;
self.grid[candy1.gridY][candy1.gridX] = candy1;
self.grid[candy2.gridY][candy2.gridX] = candy2;
// Animate swap
candy1.isAnimating = true;
candy2.isAnimating = true;
tween(candy1, {
x: candy1.gridX * self.cellSize,
y: candy1.gridY * self.cellSize
}, {
duration: 200,
onFinish: function onFinish() {
candy1.isAnimating = false;
}
});
tween(candy2, {
x: candy2.gridX * self.cellSize,
y: candy2.gridY * self.cellSize
}, {
duration: 200,
onFinish: function onFinish() {
candy2.isAnimating = false;
self.checkMatches();
}
});
return true;
};
self.checkMatches = function () {
var matches = [];
var visited = [];
// Initialize visited array
for (var row = 0; row < self.gridSize; row++) {
visited[row] = [];
for (var col = 0; col < self.gridSize; col++) {
visited[row][col] = false;
}
}
// Check horizontal matches
for (var row = 0; row < self.gridSize; row++) {
var count = 1;
var currentType = self.grid[row][0] ? self.grid[row][0].candyType : -1;
for (var col = 1; col < self.gridSize; col++) {
var cellType = self.grid[row][col] ? self.grid[row][col].candyType : -1;
if (cellType === currentType && currentType !== -1) {
count++;
} else {
if (count >= 3) {
for (var i = col - count; i < col; i++) {
if (!visited[row][i]) {
matches.push({
row: row,
col: i
});
visited[row][i] = true;
}
}
}
count = 1;
currentType = cellType;
}
}
if (count >= 3) {
for (var i = self.gridSize - count; i < self.gridSize; i++) {
if (!visited[row][i]) {
matches.push({
row: row,
col: i
});
visited[row][i] = true;
}
}
}
}
// Check vertical matches
for (var col = 0; col < self.gridSize; col++) {
var count = 1;
var currentType = self.grid[0][col] ? self.grid[0][col].candyType : -1;
for (var row = 1; row < self.gridSize; row++) {
var cellType = self.grid[row][col] ? self.grid[row][col].candyType : -1;
if (cellType === currentType && currentType !== -1) {
count++;
} else {
if (count >= 3) {
for (var i = row - count; i < row; i++) {
if (!visited[i][col]) {
matches.push({
row: i,
col: col
});
visited[i][col] = true;
}
}
}
count = 1;
currentType = cellType;
}
}
if (count >= 3) {
for (var i = self.gridSize - count; i < self.gridSize; i++) {
if (!visited[i][col]) {
matches.push({
row: i,
col: col
});
visited[i][col] = true;
}
}
}
}
if (matches.length > 0) {
self.removeMatches(matches);
return true;
}
return false;
};
self.removeMatches = function (matches) {
var points = 0;
for (var i = 0; i < matches.length; i++) {
var match = matches[i];
var candy = self.grid[match.row][match.col];
if (candy) {
points += 10;
// Check for special candy creation
var matchLength = self.getMatchLength(match.row, match.col, candy.candyType);
if (matchLength >= 5) {
// Create rainbow candy
candy.makeSpecial('rainbow');
continue;
} else if (matchLength >= 4) {
// Create bomb candy
candy.makeSpecial('bomb');
continue;
}
self.removeChild(candy);
self.grid[match.row][match.col] = null;
}
}
LK.setScore(LK.getScore() + points);
scoreText.setText(LK.getScore() + ' / ' + targetScore);
LK.getSound('match').play();
// Apply gravity and cascade
LK.setTimeout(function () {
self.applyGravity();
}, 100);
};
self.getMatchLength = function (row, col, candyType) {
var length = 1;
// Count horizontal
var left = col - 1;
while (left >= 0 && self.grid[row][left] && self.grid[row][left].candyType === candyType) {
length++;
left--;
}
var right = col + 1;
while (right < self.gridSize && self.grid[row][right] && self.grid[row][right].candyType === candyType) {
length++;
right++;
}
return length;
};
self.applyGravity = function () {
var moved = false;
for (var col = 0; col < self.gridSize; col++) {
for (var row = self.gridSize - 1; row >= 0; row--) {
if (!self.grid[row][col]) {
// Find candy above to fall down
for (var aboveRow = row - 1; aboveRow >= 0; aboveRow--) {
if (self.grid[aboveRow][col]) {
var candy = self.grid[aboveRow][col];
self.grid[row][col] = candy;
self.grid[aboveRow][col] = null;
candy.gridY = row;
candy.isAnimating = true;
moved = true;
tween(candy, {
y: row * self.cellSize
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
candy.isAnimating = false;
}
});
break;
}
}
}
}
}
if (moved) {
LK.getSound('cascade').play();
}
// Fill empty spaces and check for new matches
LK.setTimeout(function () {
self.fillGrid();
LK.setTimeout(function () {
if (!self.checkMatches()) {
isProcessingMatches = false;
}
}, 100);
}, 350);
};
self.isAdjacent = function (candy1, candy2) {
var dx = Math.abs(candy1.gridX - candy2.gridX);
var dy = Math.abs(candy1.gridY - candy2.gridY);
return dx === 1 && dy === 0 || dx === 0 && dy === 1;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x8B4513
});
/****
* Game Code
****/
var gameGrid;
var selectedCandy = null;
var movesLeft = 20;
var targetScore = 1000;
var currentLevel = 1;
var isProcessingMatches = false;
// UI Elements
var scoreText = new Text2('0 / 1000', {
size: 60,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
var movesText = new Text2('Moves: 20', {
size: 50,
fill: 0xFFFFFF
});
movesText.anchor.set(0, 0);
movesText.x = 50;
movesText.y = 100;
LK.gui.topLeft.addChild(movesText);
var levelText = new Text2('Level 1', {
size: 50,
fill: 0xFFFFFF
});
levelText.anchor.set(1, 0);
levelText.x = -50;
levelText.y = 100;
LK.gui.topRight.addChild(levelText);
// Initialize game grid
gameGrid = new GameGrid();
gameGrid.init();
// Center the grid and ensure it's in touchable area
var gridWidth = gameGrid.gridSize * gameGrid.cellSize;
var gridHeight = gameGrid.gridSize * gameGrid.cellSize;
gameGrid.x = (2048 - gridWidth) / 2;
gameGrid.y = (2732 - gridHeight) / 2;
// Ensure grid is not too close to top (avoid menu area)
if (gameGrid.y < 200) {
gameGrid.y = 200;
}
game.addChild(gameGrid);
function handleCandyClick(candy) {
if (isProcessingMatches || movesLeft <= 0) return;
if (!selectedCandy) {
selectedCandy = candy;
candy.setSelected(true);
} else if (selectedCandy === candy) {
selectedCandy.setSelected(false);
selectedCandy = null;
} else if (gameGrid.isAdjacent(selectedCandy, candy)) {
// Valid swap
selectedCandy.setSelected(false);
var oldGrid1 = {
x: selectedCandy.gridX,
y: selectedCandy.gridY
};
var oldGrid2 = {
x: candy.gridX,
y: candy.gridY
};
// Capture candy references before nullifying selectedCandy
var candy1Ref = selectedCandy;
var candy2Ref = candy;
if (gameGrid.swapCandies(selectedCandy, candy)) {
movesLeft--;
movesText.setText('Moves: ' + movesLeft);
isProcessingMatches = true;
// Check if swap creates matches, if not, swap back
LK.setTimeout(function () {
if (!gameGrid.checkMatches()) {
// Swap back using captured references
candy1Ref.gridX = oldGrid1.x;
candy1Ref.gridY = oldGrid1.y;
candy2Ref.gridX = oldGrid2.x;
candy2Ref.gridY = oldGrid2.y;
gameGrid.grid[candy1Ref.gridY][candy1Ref.gridX] = candy1Ref;
gameGrid.grid[candy2Ref.gridY][candy2Ref.gridX] = candy2Ref;
tween(candy1Ref, {
x: candy1Ref.gridX * gameGrid.cellSize,
y: candy1Ref.gridY * gameGrid.cellSize
}, {
duration: 200
});
tween(candy2Ref, {
x: candy2Ref.gridX * gameGrid.cellSize,
y: candy2Ref.gridY * gameGrid.cellSize
}, {
duration: 200
});
movesLeft++;
movesText.setText('Moves: ' + movesLeft);
isProcessingMatches = false;
}
}, 250);
}
selectedCandy = null;
} else {
selectedCandy.setSelected(false);
selectedCandy = candy;
candy.setSelected(true);
}
}
game.update = function () {
// Check win condition
if (LK.getScore() >= targetScore) {
currentLevel++;
targetScore += 500;
movesLeft = 20;
levelText.setText('Level ' + currentLevel);
scoreText.setText(LK.getScore() + ' / ' + targetScore);
movesText.setText('Moves: ' + movesLeft);
// Reset grid for next level
for (var row = 0; row < gameGrid.gridSize; row++) {
for (var col = 0; col < gameGrid.gridSize; col++) {
if (gameGrid.grid[row][col]) {
gameGrid.removeChild(gameGrid.grid[row][col]);
gameGrid.grid[row][col] = null;
}
}
}
gameGrid.fillGrid();
}
// Check lose condition
if (movesLeft <= 0 && LK.getScore() < targetScore && !isProcessingMatches) {
LK.showGameOver();
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Candy = Container.expand(function (type) {
var self = Container.call(this);
self.candyType = type || 0;
self.isSpecial = false;
self.specialType = 'normal'; // 'normal', 'bomb', 'rainbow'
self.gridX = 0;
self.gridY = 0;
self.isAnimating = false;
self.isSelected = false;
self.interactive = true;
var candyTypes = ['candy_red', 'candy_blue', 'candy_green', 'candy_yellow', 'candy_purple', 'candy_orange'];
var assetId = candyTypes[self.candyType] || candyTypes[0];
self.candyGraphics = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Set hit area for better touch detection
self.hitArea = new Rectangle(-60, -60, 120, 120);
self.makeSpecial = function (specialType) {
self.isSpecial = true;
self.specialType = specialType;
if (specialType === 'bomb') {
self.removeChild(self.candyGraphics);
self.candyGraphics = self.attachAsset('bomb_candy', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (specialType === 'rainbow') {
self.removeChild(self.candyGraphics);
self.candyGraphics = self.attachAsset('rainbow_candy', {
anchorX: 0.5,
anchorY: 0.5
});
}
};
self.setSelected = function (selected) {
self.isSelected = selected;
if (selected) {
self.candyGraphics.scaleX = 1.2;
self.candyGraphics.scaleY = 1.2;
} else {
self.candyGraphics.scaleX = 1.0;
self.candyGraphics.scaleY = 1.0;
}
};
self.down = function (x, y, obj) {
if (!self.isAnimating) {
handleCandyClick(self);
}
};
return self;
});
var GameGrid = Container.expand(function () {
var self = Container.call(this);
self.gridSize = 8;
self.cellSize = 120;
self.grid = [];
self.backgroundCells = [];
self.init = function () {
// Create background grid
for (var row = 0; row < self.gridSize; row++) {
for (var col = 0; col < self.gridSize; col++) {
var bgCell = self.attachAsset('grid_bg', {
anchorX: 0.5,
anchorY: 0.5
});
bgCell.x = col * self.cellSize;
bgCell.y = row * self.cellSize;
bgCell.alpha = 0.3;
self.backgroundCells.push(bgCell);
}
}
// Initialize grid array
for (var row = 0; row < self.gridSize; row++) {
self.grid[row] = [];
for (var col = 0; col < self.gridSize; col++) {
self.grid[row][col] = null;
}
}
// Fill grid with candies ensuring no initial matches
self.fillGrid();
};
self.fillGrid = function () {
for (var row = 0; row < self.gridSize; row++) {
for (var col = 0; col < self.gridSize; col++) {
if (!self.grid[row][col]) {
var candyType = self.getValidCandyType(row, col);
var candy = new Candy(candyType);
candy.gridX = col;
candy.gridY = row;
candy.x = col * self.cellSize;
candy.y = row * self.cellSize;
self.grid[row][col] = candy;
self.addChild(candy);
}
}
}
};
self.getValidCandyType = function (row, col) {
var invalidTypes = [];
// Check horizontal matches
if (col >= 2) {
var type1 = self.grid[row][col - 1] ? self.grid[row][col - 1].candyType : -1;
var type2 = self.grid[row][col - 2] ? self.grid[row][col - 2].candyType : -1;
if (type1 === type2 && type1 !== -1) {
invalidTypes.push(type1);
}
}
// Check vertical matches
if (row >= 2) {
var type1 = self.grid[row - 1][col] ? self.grid[row - 1][col].candyType : -1;
var type2 = self.grid[row - 2][col] ? self.grid[row - 2][col].candyType : -1;
if (type1 === type2 && type1 !== -1) {
invalidTypes.push(type1);
}
}
var validTypes = [];
for (var i = 0; i < 6; i++) {
if (invalidTypes.indexOf(i) === -1) {
validTypes.push(i);
}
}
return validTypes[Math.floor(Math.random() * validTypes.length)];
};
self.swapCandies = function (candy1, candy2) {
if (!candy1 || !candy2) return false;
var tempX = candy1.gridX;
var tempY = candy1.gridY;
candy1.gridX = candy2.gridX;
candy1.gridY = candy2.gridY;
candy2.gridX = tempX;
candy2.gridY = tempY;
self.grid[candy1.gridY][candy1.gridX] = candy1;
self.grid[candy2.gridY][candy2.gridX] = candy2;
// Animate swap
candy1.isAnimating = true;
candy2.isAnimating = true;
tween(candy1, {
x: candy1.gridX * self.cellSize,
y: candy1.gridY * self.cellSize
}, {
duration: 200,
onFinish: function onFinish() {
candy1.isAnimating = false;
}
});
tween(candy2, {
x: candy2.gridX * self.cellSize,
y: candy2.gridY * self.cellSize
}, {
duration: 200,
onFinish: function onFinish() {
candy2.isAnimating = false;
self.checkMatches();
}
});
return true;
};
self.checkMatches = function () {
var matches = [];
var visited = [];
// Initialize visited array
for (var row = 0; row < self.gridSize; row++) {
visited[row] = [];
for (var col = 0; col < self.gridSize; col++) {
visited[row][col] = false;
}
}
// Check horizontal matches
for (var row = 0; row < self.gridSize; row++) {
var count = 1;
var currentType = self.grid[row][0] ? self.grid[row][0].candyType : -1;
for (var col = 1; col < self.gridSize; col++) {
var cellType = self.grid[row][col] ? self.grid[row][col].candyType : -1;
if (cellType === currentType && currentType !== -1) {
count++;
} else {
if (count >= 3) {
for (var i = col - count; i < col; i++) {
if (!visited[row][i]) {
matches.push({
row: row,
col: i
});
visited[row][i] = true;
}
}
}
count = 1;
currentType = cellType;
}
}
if (count >= 3) {
for (var i = self.gridSize - count; i < self.gridSize; i++) {
if (!visited[row][i]) {
matches.push({
row: row,
col: i
});
visited[row][i] = true;
}
}
}
}
// Check vertical matches
for (var col = 0; col < self.gridSize; col++) {
var count = 1;
var currentType = self.grid[0][col] ? self.grid[0][col].candyType : -1;
for (var row = 1; row < self.gridSize; row++) {
var cellType = self.grid[row][col] ? self.grid[row][col].candyType : -1;
if (cellType === currentType && currentType !== -1) {
count++;
} else {
if (count >= 3) {
for (var i = row - count; i < row; i++) {
if (!visited[i][col]) {
matches.push({
row: i,
col: col
});
visited[i][col] = true;
}
}
}
count = 1;
currentType = cellType;
}
}
if (count >= 3) {
for (var i = self.gridSize - count; i < self.gridSize; i++) {
if (!visited[i][col]) {
matches.push({
row: i,
col: col
});
visited[i][col] = true;
}
}
}
}
if (matches.length > 0) {
self.removeMatches(matches);
return true;
}
return false;
};
self.removeMatches = function (matches) {
var points = 0;
for (var i = 0; i < matches.length; i++) {
var match = matches[i];
var candy = self.grid[match.row][match.col];
if (candy) {
points += 10;
// Check for special candy creation
var matchLength = self.getMatchLength(match.row, match.col, candy.candyType);
if (matchLength >= 5) {
// Create rainbow candy
candy.makeSpecial('rainbow');
continue;
} else if (matchLength >= 4) {
// Create bomb candy
candy.makeSpecial('bomb');
continue;
}
self.removeChild(candy);
self.grid[match.row][match.col] = null;
}
}
LK.setScore(LK.getScore() + points);
scoreText.setText(LK.getScore() + ' / ' + targetScore);
LK.getSound('match').play();
// Apply gravity and cascade
LK.setTimeout(function () {
self.applyGravity();
}, 100);
};
self.getMatchLength = function (row, col, candyType) {
var length = 1;
// Count horizontal
var left = col - 1;
while (left >= 0 && self.grid[row][left] && self.grid[row][left].candyType === candyType) {
length++;
left--;
}
var right = col + 1;
while (right < self.gridSize && self.grid[row][right] && self.grid[row][right].candyType === candyType) {
length++;
right++;
}
return length;
};
self.applyGravity = function () {
var moved = false;
for (var col = 0; col < self.gridSize; col++) {
for (var row = self.gridSize - 1; row >= 0; row--) {
if (!self.grid[row][col]) {
// Find candy above to fall down
for (var aboveRow = row - 1; aboveRow >= 0; aboveRow--) {
if (self.grid[aboveRow][col]) {
var candy = self.grid[aboveRow][col];
self.grid[row][col] = candy;
self.grid[aboveRow][col] = null;
candy.gridY = row;
candy.isAnimating = true;
moved = true;
tween(candy, {
y: row * self.cellSize
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
candy.isAnimating = false;
}
});
break;
}
}
}
}
}
if (moved) {
LK.getSound('cascade').play();
}
// Fill empty spaces and check for new matches
LK.setTimeout(function () {
self.fillGrid();
LK.setTimeout(function () {
if (!self.checkMatches()) {
isProcessingMatches = false;
}
}, 100);
}, 350);
};
self.isAdjacent = function (candy1, candy2) {
var dx = Math.abs(candy1.gridX - candy2.gridX);
var dy = Math.abs(candy1.gridY - candy2.gridY);
return dx === 1 && dy === 0 || dx === 0 && dy === 1;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x8B4513
});
/****
* Game Code
****/
var gameGrid;
var selectedCandy = null;
var movesLeft = 20;
var targetScore = 1000;
var currentLevel = 1;
var isProcessingMatches = false;
// UI Elements
var scoreText = new Text2('0 / 1000', {
size: 60,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
var movesText = new Text2('Moves: 20', {
size: 50,
fill: 0xFFFFFF
});
movesText.anchor.set(0, 0);
movesText.x = 50;
movesText.y = 100;
LK.gui.topLeft.addChild(movesText);
var levelText = new Text2('Level 1', {
size: 50,
fill: 0xFFFFFF
});
levelText.anchor.set(1, 0);
levelText.x = -50;
levelText.y = 100;
LK.gui.topRight.addChild(levelText);
// Initialize game grid
gameGrid = new GameGrid();
gameGrid.init();
// Center the grid and ensure it's in touchable area
var gridWidth = gameGrid.gridSize * gameGrid.cellSize;
var gridHeight = gameGrid.gridSize * gameGrid.cellSize;
gameGrid.x = (2048 - gridWidth) / 2;
gameGrid.y = (2732 - gridHeight) / 2;
// Ensure grid is not too close to top (avoid menu area)
if (gameGrid.y < 200) {
gameGrid.y = 200;
}
game.addChild(gameGrid);
function handleCandyClick(candy) {
if (isProcessingMatches || movesLeft <= 0) return;
if (!selectedCandy) {
selectedCandy = candy;
candy.setSelected(true);
} else if (selectedCandy === candy) {
selectedCandy.setSelected(false);
selectedCandy = null;
} else if (gameGrid.isAdjacent(selectedCandy, candy)) {
// Valid swap
selectedCandy.setSelected(false);
var oldGrid1 = {
x: selectedCandy.gridX,
y: selectedCandy.gridY
};
var oldGrid2 = {
x: candy.gridX,
y: candy.gridY
};
// Capture candy references before nullifying selectedCandy
var candy1Ref = selectedCandy;
var candy2Ref = candy;
if (gameGrid.swapCandies(selectedCandy, candy)) {
movesLeft--;
movesText.setText('Moves: ' + movesLeft);
isProcessingMatches = true;
// Check if swap creates matches, if not, swap back
LK.setTimeout(function () {
if (!gameGrid.checkMatches()) {
// Swap back using captured references
candy1Ref.gridX = oldGrid1.x;
candy1Ref.gridY = oldGrid1.y;
candy2Ref.gridX = oldGrid2.x;
candy2Ref.gridY = oldGrid2.y;
gameGrid.grid[candy1Ref.gridY][candy1Ref.gridX] = candy1Ref;
gameGrid.grid[candy2Ref.gridY][candy2Ref.gridX] = candy2Ref;
tween(candy1Ref, {
x: candy1Ref.gridX * gameGrid.cellSize,
y: candy1Ref.gridY * gameGrid.cellSize
}, {
duration: 200
});
tween(candy2Ref, {
x: candy2Ref.gridX * gameGrid.cellSize,
y: candy2Ref.gridY * gameGrid.cellSize
}, {
duration: 200
});
movesLeft++;
movesText.setText('Moves: ' + movesLeft);
isProcessingMatches = false;
}
}, 250);
}
selectedCandy = null;
} else {
selectedCandy.setSelected(false);
selectedCandy = candy;
candy.setSelected(true);
}
}
game.update = function () {
// Check win condition
if (LK.getScore() >= targetScore) {
currentLevel++;
targetScore += 500;
movesLeft = 20;
levelText.setText('Level ' + currentLevel);
scoreText.setText(LK.getScore() + ' / ' + targetScore);
movesText.setText('Moves: ' + movesLeft);
// Reset grid for next level
for (var row = 0; row < gameGrid.gridSize; row++) {
for (var col = 0; col < gameGrid.gridSize; col++) {
if (gameGrid.grid[row][col]) {
gameGrid.removeChild(gameGrid.grid[row][col]);
gameGrid.grid[row][col] = null;
}
}
}
gameGrid.fillGrid();
}
// Check lose condition
if (movesLeft <= 0 && LK.getScore() < targetScore && !isProcessingMatches) {
LK.showGameOver();
}
};