/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { highScore: 0, currentLevel: 1 }); /**** * Classes ****/ var Boiler = Container.expand(function () { var self = Container.call(this); // Boiler background self.boilerBg = self.attachAsset('boiler', { anchorX: 0.5, anchorY: 0.5 }); // Current pressure and max pressure self.currentPressure = 0; self.maxPressure = 100; // Ability costs self.hammerCost = 0; // Removed Steam Hammer self.turboCost = 0; // Removed Turbo-Charger // Ability availability self.canUseHammer = false; // Removed Steam Hammer self.canUseTurbo = false; // Removed Turbo-Charger // Initialize buttons as disabled // Removed Steam Hammer and Turbo-Charger button interactions return self; }); var GameBoard = Container.expand(function () { var self = Container.call(this); self.gridSize = 8; self.cellSize = 160; self.padding = 10; self.boardSize = (self.cellSize + self.padding) * self.gridSize; // Metrics for positioning self.boardStartX = (2048 - self.boardSize) / 2; self.boardStartY = (2732 - self.boardSize) / 2; // Game state self.selectedGear = null; self.grid = []; self.gearTypes = ['copper', 'steel', 'brass']; // Board background var boardBg = self.attachAsset('board_bg', { anchorX: 0.5, anchorY: 0.5, x: self.boardSize / 2, y: self.boardSize / 2 }); // Initialize grid cells for (var row = 0; row < self.gridSize; row++) { self.grid[row] = []; for (var col = 0; col < self.gridSize; col++) { // Create cell background var cellBg = self.attachAsset('cell_bg', { anchorX: 0.5, anchorY: 0.5, x: col * (self.cellSize + self.padding) + self.cellSize / 2, y: row * (self.cellSize + self.padding) + self.cellSize / 2 }); // Initialize with null (empty cell) self.grid[row][col] = null; } } // Fill the board with gears self.fillBoard = function () { for (var row = 0; row < self.gridSize; row++) { for (var col = 0; col < self.gridSize; col++) { do { self.createRandomGear(row, col); } while (self.findMatches().length > 0); } } // Add some rusty bolts as obstacles var numBolts = Math.min(Math.floor(currentLevel / 2) + 2, 10); for (var i = 0; i < numBolts; i++) { var randomRow = Math.floor(Math.random() * self.gridSize); var randomCol = Math.floor(Math.random() * self.gridSize); if (self.grid[randomRow][randomCol]) { self.removeGearAt(randomRow, randomCol); } self.createRustyBolt(randomRow, randomCol); } // Check for initial matches and resolve them self.checkAndResolveMatches(); }; self.createRandomGear = function (row, col) { if (self.grid[row][col]) { self.removeGearAt(row, col); } var randomType = self.gearTypes[Math.floor(Math.random() * self.gearTypes.length)]; var gear = new Gear(randomType); gear.row = row; gear.col = col; gear.x = col * (self.cellSize + self.padding) + self.cellSize / 2; gear.y = row * (self.cellSize + self.padding) + self.cellSize / 2; self.grid[row][col] = gear; self.addChild(gear); return gear; }; self.createRustyBolt = function (row, col) { if (self.grid[row][col]) { self.removeGearAt(row, col); } var bolt = new RustyBolt(); bolt.row = row; bolt.col = col; bolt.x = col * (self.cellSize + self.padding) + self.cellSize / 2; bolt.y = row * (self.cellSize + self.padding) + self.cellSize / 2; self.grid[row][col] = bolt; self.addChild(bolt); return bolt; }; self.removeGearAt = function (row, col) { if (self.grid[row][col]) { self.removeChild(self.grid[row][col]); self.grid[row][col] = null; } }; self.handleGearClick = function (gear) { if (!gear || gear instanceof RustyBolt) { return; } if (self.selectedGear === null) { // Select the gear self.selectedGear = gear; gear.select(); } else if (self.selectedGear === gear) { // Deselect the gear self.selectedGear.deselect(); self.selectedGear = null; } else { // Check if the gears are adjacent var row1 = self.selectedGear.row; var col1 = self.selectedGear.col; var row2 = gear.row; var col2 = gear.col; if (self.areGearsAdjacent(row1, col1, row2, col2)) { // Swap gears self.swapGears(row1, col1, row2, col2); // Deselect after swapping self.selectedGear.deselect(); self.selectedGear = null; } else { // Not adjacent, select the new gear instead self.selectedGear.deselect(); self.selectedGear = gear; gear.select(); } } }; self.areGearsAdjacent = function (row1, col1, row2, col2) { return Math.abs(row1 - row2) === 1 && col1 === col2 || Math.abs(col1 - col2) === 1 && row1 === row2; }; self.swapGears = function (row1, col1, row2, col2) { var gear1 = self.grid[row1][col1]; var gear2 = self.grid[row2][col2]; if (!gear1 || !gear2 || gear1 instanceof RustyBolt || gear2 instanceof RustyBolt) { return false; } // Update grid references self.grid[row1][col1] = gear2; self.grid[row2][col2] = gear1; // Update gear positions var tempRow = gear1.row; var tempCol = gear1.col; gear1.row = gear2.row; gear1.col = gear2.col; gear2.row = tempRow; gear2.col = tempCol; // Animate the swapping tween(gear1, { x: col2 * (self.cellSize + self.padding) + self.cellSize / 2, y: row2 * (self.cellSize + self.padding) + self.cellSize / 2 }, { duration: 300, easing: tween.easeOut }); tween(gear2, { x: col1 * (self.cellSize + self.padding) + self.cellSize / 2, y: row1 * (self.cellSize + self.padding) + self.cellSize / 2 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { // After swapping, check for matches var foundMatches = self.checkAndResolveMatches(); // If no matches, swap back if (!foundMatches) { // Update grid references back self.grid[row1][col1] = gear1; self.grid[row2][col2] = gear2; // Update gear data gear1.row = row1; gear1.col = col1; gear2.row = row2; gear2.col = col2; // Animate swapping back tween(gear1, { x: col1 * (self.cellSize + self.padding) + self.cellSize / 2, y: row1 * (self.cellSize + self.padding) + self.cellSize / 2 }, { duration: 300, easing: tween.easeOut }); tween(gear2, { x: col2 * (self.cellSize + self.padding) + self.cellSize / 2, y: row2 * (self.cellSize + self.padding) + self.cellSize / 2 }, { duration: 300, easing: tween.easeOut }); } } }); return true; }; self.checkAndResolveMatches = function () { var matches = self.findMatches(); if (matches.length > 0) { self.resolveMatches(matches); return true; } return false; }; self.findMatches = function () { var matches = []; // Check horizontal matches for (var row = 0; row < self.gridSize; row++) { var currentType = null; var count = 0; var startCol = 0; for (var col = 0; col < self.gridSize; col++) { var gear = self.grid[row][col]; if (gear && gear instanceof Gear) { if (currentType === null) { currentType = gear.type; count = 1; startCol = col; } else if (gear.type === currentType) { count++; } else { if (count >= 3) { matches.push({ row: row, startCol: startCol, endCol: col - 1, direction: 'horizontal', type: currentType, count: count }); } currentType = gear.type; count = 1; startCol = col; } } else { if (count >= 3) { matches.push({ row: row, startCol: startCol, endCol: col - 1, direction: 'horizontal', type: currentType, count: count }); } currentType = null; count = 0; } } // Check at the end of row if (count >= 3) { matches.push({ row: row, startCol: startCol, endCol: self.gridSize - 1, direction: 'horizontal', type: currentType, count: count }); } } // Check vertical matches for (var col = 0; col < self.gridSize; col++) { var currentType = null; var count = 0; var startRow = 0; for (var row = 0; row < self.gridSize; row++) { var gear = self.grid[row][col]; if (gear && gear instanceof Gear) { if (currentType === null) { currentType = gear.type; count = 1; startRow = row; } else if (gear.type === currentType) { count++; } else { if (count >= 3) { matches.push({ col: col, startRow: startRow, endRow: row - 1, direction: 'vertical', type: currentType, count: count }); } currentType = gear.type; count = 1; startRow = row; } } else { if (count >= 3) { matches.push({ col: col, startRow: startRow, endRow: row - 1, direction: 'vertical', type: currentType, count: count }); } currentType = null; count = 0; } } // Check at the end of column if (count >= 3) { matches.push({ col: col, startRow: startRow, endRow: self.gridSize - 1, direction: 'vertical', type: currentType, count: count }); } } return matches; }; self.resolveMatches = function (matches) { var gearsToRemove = []; var pointsGained = 0; var steamGained = 0; // Mark all gears to be removed for (var i = 0; i < matches.length; i++) { var match = matches[i]; var matchCount = 0; if (match.direction === 'horizontal') { for (var col = match.startCol; col <= match.endCol; col++) { if (self.grid[match.row][col] && self.grid[match.row][col] instanceof Gear) { gearsToRemove.push({ row: match.row, col: col }); matchCount++; } } } else { // vertical for (var row = match.startRow; row <= match.endRow; row++) { if (self.grid[row][match.col] && self.grid[row][match.col] instanceof Gear) { gearsToRemove.push({ row: row, col: match.col }); matchCount++; } } } // Calculate points and steam for this match var matchPoints = matchCount * 10; // Basic points var matchSteam = 0; // Special bonuses based on match length if (matchCount >= 5) { matchPoints *= 2; // Double points for 5+ matches matchSteam = 25; // Big steam boost } else if (matchCount >= 4) { matchPoints += 20; // Bonus for 4 matches matchSteam = 15; // Good steam boost } else { matchSteam = 10; // Basic steam boost } // Type bonuses if (match.type === 'brass') { matchPoints += 5; // Bonus for brass gears matchSteam += 5; // Extra steam for brass } else if (match.type === 'steel') { matchPoints += 3; // Small bonus for steel } pointsGained += matchPoints; steamGained += matchSteam; } // Remove duplicate entries var uniqueRemoves = []; for (var i = 0; i < gearsToRemove.length; i++) { var alreadyIncluded = false; for (var j = 0; j < uniqueRemoves.length; j++) { if (gearsToRemove[i].row === uniqueRemoves[j].row && gearsToRemove[i].col === uniqueRemoves[j].col) { alreadyIncluded = true; break; } } if (!alreadyIncluded) { uniqueRemoves.push(gearsToRemove[i]); } } // Play match sound LK.getSound('gear_match').play(); // Add to score score += pointsGained; scoreTxt.setText(score); // Define increasePressure function function increasePressure(amount) { boiler.currentPressure += amount; if (boiler.currentPressure > boiler.maxPressure) { boiler.currentPressure = boiler.maxPressure; } } // Add to steam pressure increasePressure(steamGained); // Animate and remove matched gears for (var i = 0; i < uniqueRemoves.length; i++) { var pos = uniqueRemoves[i]; var gear = self.grid[pos.row][pos.col]; if (gear && gear instanceof Gear) { gear.matched(); self.grid[pos.row][pos.col] = null; } } // Wait for animations to complete before dropping gears LK.setTimeout(function () { self.dropGears(); LK.setTimeout(function () { self.fillEmptyCells(); LK.setTimeout(function () { // Check for more matches after filling self.checkAndResolveMatches(); }, 500); }, 500); }, 300); return uniqueRemoves.length; }; self.dropGears = function () { // For each column, check from bottom to top for (var col = 0; col < self.gridSize; col++) { var emptySpaces = 0; // Start from bottom and count empty spaces for (var row = self.gridSize - 1; row >= 0; row--) { var cell = self.grid[row][col]; if (!cell) { // Empty cell emptySpaces++; } else if (cell instanceof RustyBolt) { // Rusty bolts don't drop emptySpaces = 0; } else if (emptySpaces > 0) { // Move gear down var newRow = row + emptySpaces; self.grid[newRow][col] = cell; self.grid[row][col] = null; // Update gear data cell.row = newRow; // Animate the drop tween(cell, { y: newRow * (self.cellSize + self.padding) + self.cellSize / 2 }, { duration: 300, easing: tween.bounceOut }); } } } }; self.fillEmptyCells = function () { for (var col = 0; col < self.gridSize; col++) { for (var row = 0; row < self.gridSize; row++) { if (!self.grid[row][col]) { // Create new gear from top (with animation) var gear = self.createRandomGear(row, col); // Start position above the board gear.y = -150; // Animate dropping in tween(gear, { y: row * (self.cellSize + self.padding) + self.cellSize / 2 }, { duration: 300, easing: tween.easeIn }); } } } }; return self; }); var Gear = Container.expand(function (type) { var self = Container.call(this); self.type = type || 'copper'; // Default to copper if no type specified var color; switch (self.type) { case 'copper': color = 'gear_copper'; break; case 'steel': color = 'gear_steel'; break; case 'brass': color = 'gear_brass'; break; default: color = 'gear_copper'; } self.gearGraphic = self.attachAsset(color, { anchorX: 0.5, anchorY: 0.5, scaleX: 0.9, scaleY: 0.9 }); // Add teeth to make it look more like a gear self.teethCount = 8; self.radius = self.gearGraphic.width / 2; // Apply a small rotation animation to make gears feel mechanical self.rotationSpeed = Math.random() * 0.01 + 0.005; if (Math.random() > 0.5) { self.rotationSpeed *= -1; } // Random direction self.update = function () { self.gearGraphic.rotation += self.rotationSpeed; }; self.select = function () { tween(self.gearGraphic, { scaleX: 1.1, scaleY: 1.1 }, { duration: 200, easing: tween.easeOut }); LK.getSound('gear_select').play(); }; self.deselect = function () { tween(self.gearGraphic, { scaleX: 0.9, scaleY: 0.9 }, { duration: 200, easing: tween.easeOut }); }; self.matched = function () { tween(self, { alpha: 0 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { if (self.parent) { self.parent.removeChild(self); } } }); }; self.down = function (x, y, obj) { gameBoard.handleGearClick(self); }; return self; }); var RustyBolt = Container.expand(function () { var self = Container.call(this); self.boltGraphic = self.attachAsset('rusty_bolt', { anchorX: 0.5, anchorY: 0.5 }); self.breakBolt = function () { tween(self, { alpha: 0 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { if (self.parent) { self.parent.removeChild(self); } } }); }; self.down = function (x, y, obj) { // Rusty bolts cannot be selected }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2C3E50 // Dark blue-gray background }); /**** * Game Code ****/ // Game variables var score = 0; var currentLevel = storage.currentLevel || 1; var targetScore = currentLevel * 1000; // Score needed to complete the level var gameBoard; var boiler; // UI elements var scoreTxt = new Text2('0', { size: 80, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); scoreTxt.y = 50; var levelTxt = new Text2('Level: ' + currentLevel, { size: 60, fill: 0xFFFFFF }); levelTxt.anchor.set(0, 0); LK.gui.topLeft.addChild(levelTxt); levelTxt.x = 120; // Leave space for menu icon levelTxt.y = 50; var targetTxt = new Text2('Target: ' + targetScore, { size: 60, fill: 0xFFFFFF }); targetTxt.anchor.set(1, 0); LK.gui.topRight.addChild(targetTxt); targetTxt.y = 50; // Initialize game board gameBoard = new GameBoard(); game.addChild(gameBoard); gameBoard.x = (2048 - gameBoard.boardSize) / 2; gameBoard.y = (2732 - gameBoard.boardSize) / 2; // Initialize boiler (steam pressure meter) boiler = new Boiler(); game.addChild(boiler); boiler.x = 2048 / 2; boiler.y = 2732 - 200; // Fill the board with initial gears gameBoard.fillBoard(); // Play background music LK.playMusic('steampunk_bgm'); // Main game update loop game.update = function () { // Check if level is complete if (score >= targetScore) { // Level complete logic currentLevel++; storage.currentLevel = currentLevel; // Update high score if (score > storage.highScore) { storage.highScore = score; } // Show win screen LK.showYouWin(); } }; // Global input handling game.down = function (x, y, obj) { // Handle any global click events if needed }; game.move = function (x, y, obj) { // Handle any global move events if needed }; game.up = function (x, y, obj) { // Handle any global release events if needed };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0,
currentLevel: 1
});
/****
* Classes
****/
var Boiler = Container.expand(function () {
var self = Container.call(this);
// Boiler background
self.boilerBg = self.attachAsset('boiler', {
anchorX: 0.5,
anchorY: 0.5
});
// Current pressure and max pressure
self.currentPressure = 0;
self.maxPressure = 100;
// Ability costs
self.hammerCost = 0; // Removed Steam Hammer
self.turboCost = 0; // Removed Turbo-Charger
// Ability availability
self.canUseHammer = false; // Removed Steam Hammer
self.canUseTurbo = false; // Removed Turbo-Charger
// Initialize buttons as disabled
// Removed Steam Hammer and Turbo-Charger button interactions
return self;
});
var GameBoard = Container.expand(function () {
var self = Container.call(this);
self.gridSize = 8;
self.cellSize = 160;
self.padding = 10;
self.boardSize = (self.cellSize + self.padding) * self.gridSize;
// Metrics for positioning
self.boardStartX = (2048 - self.boardSize) / 2;
self.boardStartY = (2732 - self.boardSize) / 2;
// Game state
self.selectedGear = null;
self.grid = [];
self.gearTypes = ['copper', 'steel', 'brass'];
// Board background
var boardBg = self.attachAsset('board_bg', {
anchorX: 0.5,
anchorY: 0.5,
x: self.boardSize / 2,
y: self.boardSize / 2
});
// Initialize grid cells
for (var row = 0; row < self.gridSize; row++) {
self.grid[row] = [];
for (var col = 0; col < self.gridSize; col++) {
// Create cell background
var cellBg = self.attachAsset('cell_bg', {
anchorX: 0.5,
anchorY: 0.5,
x: col * (self.cellSize + self.padding) + self.cellSize / 2,
y: row * (self.cellSize + self.padding) + self.cellSize / 2
});
// Initialize with null (empty cell)
self.grid[row][col] = null;
}
}
// Fill the board with gears
self.fillBoard = function () {
for (var row = 0; row < self.gridSize; row++) {
for (var col = 0; col < self.gridSize; col++) {
do {
self.createRandomGear(row, col);
} while (self.findMatches().length > 0);
}
}
// Add some rusty bolts as obstacles
var numBolts = Math.min(Math.floor(currentLevel / 2) + 2, 10);
for (var i = 0; i < numBolts; i++) {
var randomRow = Math.floor(Math.random() * self.gridSize);
var randomCol = Math.floor(Math.random() * self.gridSize);
if (self.grid[randomRow][randomCol]) {
self.removeGearAt(randomRow, randomCol);
}
self.createRustyBolt(randomRow, randomCol);
}
// Check for initial matches and resolve them
self.checkAndResolveMatches();
};
self.createRandomGear = function (row, col) {
if (self.grid[row][col]) {
self.removeGearAt(row, col);
}
var randomType = self.gearTypes[Math.floor(Math.random() * self.gearTypes.length)];
var gear = new Gear(randomType);
gear.row = row;
gear.col = col;
gear.x = col * (self.cellSize + self.padding) + self.cellSize / 2;
gear.y = row * (self.cellSize + self.padding) + self.cellSize / 2;
self.grid[row][col] = gear;
self.addChild(gear);
return gear;
};
self.createRustyBolt = function (row, col) {
if (self.grid[row][col]) {
self.removeGearAt(row, col);
}
var bolt = new RustyBolt();
bolt.row = row;
bolt.col = col;
bolt.x = col * (self.cellSize + self.padding) + self.cellSize / 2;
bolt.y = row * (self.cellSize + self.padding) + self.cellSize / 2;
self.grid[row][col] = bolt;
self.addChild(bolt);
return bolt;
};
self.removeGearAt = function (row, col) {
if (self.grid[row][col]) {
self.removeChild(self.grid[row][col]);
self.grid[row][col] = null;
}
};
self.handleGearClick = function (gear) {
if (!gear || gear instanceof RustyBolt) {
return;
}
if (self.selectedGear === null) {
// Select the gear
self.selectedGear = gear;
gear.select();
} else if (self.selectedGear === gear) {
// Deselect the gear
self.selectedGear.deselect();
self.selectedGear = null;
} else {
// Check if the gears are adjacent
var row1 = self.selectedGear.row;
var col1 = self.selectedGear.col;
var row2 = gear.row;
var col2 = gear.col;
if (self.areGearsAdjacent(row1, col1, row2, col2)) {
// Swap gears
self.swapGears(row1, col1, row2, col2);
// Deselect after swapping
self.selectedGear.deselect();
self.selectedGear = null;
} else {
// Not adjacent, select the new gear instead
self.selectedGear.deselect();
self.selectedGear = gear;
gear.select();
}
}
};
self.areGearsAdjacent = function (row1, col1, row2, col2) {
return Math.abs(row1 - row2) === 1 && col1 === col2 || Math.abs(col1 - col2) === 1 && row1 === row2;
};
self.swapGears = function (row1, col1, row2, col2) {
var gear1 = self.grid[row1][col1];
var gear2 = self.grid[row2][col2];
if (!gear1 || !gear2 || gear1 instanceof RustyBolt || gear2 instanceof RustyBolt) {
return false;
}
// Update grid references
self.grid[row1][col1] = gear2;
self.grid[row2][col2] = gear1;
// Update gear positions
var tempRow = gear1.row;
var tempCol = gear1.col;
gear1.row = gear2.row;
gear1.col = gear2.col;
gear2.row = tempRow;
gear2.col = tempCol;
// Animate the swapping
tween(gear1, {
x: col2 * (self.cellSize + self.padding) + self.cellSize / 2,
y: row2 * (self.cellSize + self.padding) + self.cellSize / 2
}, {
duration: 300,
easing: tween.easeOut
});
tween(gear2, {
x: col1 * (self.cellSize + self.padding) + self.cellSize / 2,
y: row1 * (self.cellSize + self.padding) + self.cellSize / 2
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
// After swapping, check for matches
var foundMatches = self.checkAndResolveMatches();
// If no matches, swap back
if (!foundMatches) {
// Update grid references back
self.grid[row1][col1] = gear1;
self.grid[row2][col2] = gear2;
// Update gear data
gear1.row = row1;
gear1.col = col1;
gear2.row = row2;
gear2.col = col2;
// Animate swapping back
tween(gear1, {
x: col1 * (self.cellSize + self.padding) + self.cellSize / 2,
y: row1 * (self.cellSize + self.padding) + self.cellSize / 2
}, {
duration: 300,
easing: tween.easeOut
});
tween(gear2, {
x: col2 * (self.cellSize + self.padding) + self.cellSize / 2,
y: row2 * (self.cellSize + self.padding) + self.cellSize / 2
}, {
duration: 300,
easing: tween.easeOut
});
}
}
});
return true;
};
self.checkAndResolveMatches = function () {
var matches = self.findMatches();
if (matches.length > 0) {
self.resolveMatches(matches);
return true;
}
return false;
};
self.findMatches = function () {
var matches = [];
// Check horizontal matches
for (var row = 0; row < self.gridSize; row++) {
var currentType = null;
var count = 0;
var startCol = 0;
for (var col = 0; col < self.gridSize; col++) {
var gear = self.grid[row][col];
if (gear && gear instanceof Gear) {
if (currentType === null) {
currentType = gear.type;
count = 1;
startCol = col;
} else if (gear.type === currentType) {
count++;
} else {
if (count >= 3) {
matches.push({
row: row,
startCol: startCol,
endCol: col - 1,
direction: 'horizontal',
type: currentType,
count: count
});
}
currentType = gear.type;
count = 1;
startCol = col;
}
} else {
if (count >= 3) {
matches.push({
row: row,
startCol: startCol,
endCol: col - 1,
direction: 'horizontal',
type: currentType,
count: count
});
}
currentType = null;
count = 0;
}
}
// Check at the end of row
if (count >= 3) {
matches.push({
row: row,
startCol: startCol,
endCol: self.gridSize - 1,
direction: 'horizontal',
type: currentType,
count: count
});
}
}
// Check vertical matches
for (var col = 0; col < self.gridSize; col++) {
var currentType = null;
var count = 0;
var startRow = 0;
for (var row = 0; row < self.gridSize; row++) {
var gear = self.grid[row][col];
if (gear && gear instanceof Gear) {
if (currentType === null) {
currentType = gear.type;
count = 1;
startRow = row;
} else if (gear.type === currentType) {
count++;
} else {
if (count >= 3) {
matches.push({
col: col,
startRow: startRow,
endRow: row - 1,
direction: 'vertical',
type: currentType,
count: count
});
}
currentType = gear.type;
count = 1;
startRow = row;
}
} else {
if (count >= 3) {
matches.push({
col: col,
startRow: startRow,
endRow: row - 1,
direction: 'vertical',
type: currentType,
count: count
});
}
currentType = null;
count = 0;
}
}
// Check at the end of column
if (count >= 3) {
matches.push({
col: col,
startRow: startRow,
endRow: self.gridSize - 1,
direction: 'vertical',
type: currentType,
count: count
});
}
}
return matches;
};
self.resolveMatches = function (matches) {
var gearsToRemove = [];
var pointsGained = 0;
var steamGained = 0;
// Mark all gears to be removed
for (var i = 0; i < matches.length; i++) {
var match = matches[i];
var matchCount = 0;
if (match.direction === 'horizontal') {
for (var col = match.startCol; col <= match.endCol; col++) {
if (self.grid[match.row][col] && self.grid[match.row][col] instanceof Gear) {
gearsToRemove.push({
row: match.row,
col: col
});
matchCount++;
}
}
} else {
// vertical
for (var row = match.startRow; row <= match.endRow; row++) {
if (self.grid[row][match.col] && self.grid[row][match.col] instanceof Gear) {
gearsToRemove.push({
row: row,
col: match.col
});
matchCount++;
}
}
}
// Calculate points and steam for this match
var matchPoints = matchCount * 10; // Basic points
var matchSteam = 0;
// Special bonuses based on match length
if (matchCount >= 5) {
matchPoints *= 2; // Double points for 5+ matches
matchSteam = 25; // Big steam boost
} else if (matchCount >= 4) {
matchPoints += 20; // Bonus for 4 matches
matchSteam = 15; // Good steam boost
} else {
matchSteam = 10; // Basic steam boost
}
// Type bonuses
if (match.type === 'brass') {
matchPoints += 5; // Bonus for brass gears
matchSteam += 5; // Extra steam for brass
} else if (match.type === 'steel') {
matchPoints += 3; // Small bonus for steel
}
pointsGained += matchPoints;
steamGained += matchSteam;
}
// Remove duplicate entries
var uniqueRemoves = [];
for (var i = 0; i < gearsToRemove.length; i++) {
var alreadyIncluded = false;
for (var j = 0; j < uniqueRemoves.length; j++) {
if (gearsToRemove[i].row === uniqueRemoves[j].row && gearsToRemove[i].col === uniqueRemoves[j].col) {
alreadyIncluded = true;
break;
}
}
if (!alreadyIncluded) {
uniqueRemoves.push(gearsToRemove[i]);
}
}
// Play match sound
LK.getSound('gear_match').play();
// Add to score
score += pointsGained;
scoreTxt.setText(score);
// Define increasePressure function
function increasePressure(amount) {
boiler.currentPressure += amount;
if (boiler.currentPressure > boiler.maxPressure) {
boiler.currentPressure = boiler.maxPressure;
}
}
// Add to steam pressure
increasePressure(steamGained);
// Animate and remove matched gears
for (var i = 0; i < uniqueRemoves.length; i++) {
var pos = uniqueRemoves[i];
var gear = self.grid[pos.row][pos.col];
if (gear && gear instanceof Gear) {
gear.matched();
self.grid[pos.row][pos.col] = null;
}
}
// Wait for animations to complete before dropping gears
LK.setTimeout(function () {
self.dropGears();
LK.setTimeout(function () {
self.fillEmptyCells();
LK.setTimeout(function () {
// Check for more matches after filling
self.checkAndResolveMatches();
}, 500);
}, 500);
}, 300);
return uniqueRemoves.length;
};
self.dropGears = function () {
// For each column, check from bottom to top
for (var col = 0; col < self.gridSize; col++) {
var emptySpaces = 0;
// Start from bottom and count empty spaces
for (var row = self.gridSize - 1; row >= 0; row--) {
var cell = self.grid[row][col];
if (!cell) {
// Empty cell
emptySpaces++;
} else if (cell instanceof RustyBolt) {
// Rusty bolts don't drop
emptySpaces = 0;
} else if (emptySpaces > 0) {
// Move gear down
var newRow = row + emptySpaces;
self.grid[newRow][col] = cell;
self.grid[row][col] = null;
// Update gear data
cell.row = newRow;
// Animate the drop
tween(cell, {
y: newRow * (self.cellSize + self.padding) + self.cellSize / 2
}, {
duration: 300,
easing: tween.bounceOut
});
}
}
}
};
self.fillEmptyCells = function () {
for (var col = 0; col < self.gridSize; col++) {
for (var row = 0; row < self.gridSize; row++) {
if (!self.grid[row][col]) {
// Create new gear from top (with animation)
var gear = self.createRandomGear(row, col);
// Start position above the board
gear.y = -150;
// Animate dropping in
tween(gear, {
y: row * (self.cellSize + self.padding) + self.cellSize / 2
}, {
duration: 300,
easing: tween.easeIn
});
}
}
}
};
return self;
});
var Gear = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'copper'; // Default to copper if no type specified
var color;
switch (self.type) {
case 'copper':
color = 'gear_copper';
break;
case 'steel':
color = 'gear_steel';
break;
case 'brass':
color = 'gear_brass';
break;
default:
color = 'gear_copper';
}
self.gearGraphic = self.attachAsset(color, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.9,
scaleY: 0.9
});
// Add teeth to make it look more like a gear
self.teethCount = 8;
self.radius = self.gearGraphic.width / 2;
// Apply a small rotation animation to make gears feel mechanical
self.rotationSpeed = Math.random() * 0.01 + 0.005;
if (Math.random() > 0.5) {
self.rotationSpeed *= -1;
} // Random direction
self.update = function () {
self.gearGraphic.rotation += self.rotationSpeed;
};
self.select = function () {
tween(self.gearGraphic, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 200,
easing: tween.easeOut
});
LK.getSound('gear_select').play();
};
self.deselect = function () {
tween(self.gearGraphic, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 200,
easing: tween.easeOut
});
};
self.matched = function () {
tween(self, {
alpha: 0
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
if (self.parent) {
self.parent.removeChild(self);
}
}
});
};
self.down = function (x, y, obj) {
gameBoard.handleGearClick(self);
};
return self;
});
var RustyBolt = Container.expand(function () {
var self = Container.call(this);
self.boltGraphic = self.attachAsset('rusty_bolt', {
anchorX: 0.5,
anchorY: 0.5
});
self.breakBolt = function () {
tween(self, {
alpha: 0
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
if (self.parent) {
self.parent.removeChild(self);
}
}
});
};
self.down = function (x, y, obj) {
// Rusty bolts cannot be selected
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2C3E50 // Dark blue-gray background
});
/****
* Game Code
****/
// Game variables
var score = 0;
var currentLevel = storage.currentLevel || 1;
var targetScore = currentLevel * 1000; // Score needed to complete the level
var gameBoard;
var boiler;
// UI elements
var scoreTxt = new Text2('0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
scoreTxt.y = 50;
var levelTxt = new Text2('Level: ' + currentLevel, {
size: 60,
fill: 0xFFFFFF
});
levelTxt.anchor.set(0, 0);
LK.gui.topLeft.addChild(levelTxt);
levelTxt.x = 120; // Leave space for menu icon
levelTxt.y = 50;
var targetTxt = new Text2('Target: ' + targetScore, {
size: 60,
fill: 0xFFFFFF
});
targetTxt.anchor.set(1, 0);
LK.gui.topRight.addChild(targetTxt);
targetTxt.y = 50;
// Initialize game board
gameBoard = new GameBoard();
game.addChild(gameBoard);
gameBoard.x = (2048 - gameBoard.boardSize) / 2;
gameBoard.y = (2732 - gameBoard.boardSize) / 2;
// Initialize boiler (steam pressure meter)
boiler = new Boiler();
game.addChild(boiler);
boiler.x = 2048 / 2;
boiler.y = 2732 - 200;
// Fill the board with initial gears
gameBoard.fillBoard();
// Play background music
LK.playMusic('steampunk_bgm');
// Main game update loop
game.update = function () {
// Check if level is complete
if (score >= targetScore) {
// Level complete logic
currentLevel++;
storage.currentLevel = currentLevel;
// Update high score
if (score > storage.highScore) {
storage.highScore = score;
}
// Show win screen
LK.showYouWin();
}
};
// Global input handling
game.down = function (x, y, obj) {
// Handle any global click events if needed
};
game.move = function (x, y, obj) {
// Handle any global move events if needed
};
game.up = function (x, y, obj) {
// Handle any global release events if needed
};
латунная шестерня в стиле hand-paint. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
медная шестерня в стиле hand-paint. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
стальная шестерня в стиле hand-paint. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
ржавый болт в стиле hand-paint. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
котел в стиле hand-paint. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A hand-painted, steampunk-style illustration of billowing steam and smoke, rich with warm copper tones and industrial textures. The vapor swirls dynamically around intricate brass gears and pipes, glowing with subtle orange highlights as if lit by flickering gaslight. The painting style should mimic traditional concept art with visible brush strokes, soft edges, and a slightly weathered, vintage feel. The background features hints of a dimly lit factory, with atmospheric perspective fading into deep browns and blues. The steam itself appears thick, almost liquid, with a mix of transparency and volumetric density, evoking a sense of pressurized heat. Artstation trending, fantasy illustration, painterly style, warm lighting. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
фон для прогресс бара. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows