/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Ship class for mergeable ships var Ship = Container.expand(function () { var self = Container.call(this); // Level of the ship (1-6) self.level = 1; self.gridX = 0; self.gridY = 0; self.isDragging = false; // Attach ship asset var shipSprite = self.attachAsset('ship' + self.level, { anchorX: 0.5, anchorY: 0.5 }); // Update ship sprite to match level self.setLevel = function (level) { self.level = level; self.removeChild(shipSprite); var newSprite = self.attachAsset('ship' + level, { anchorX: 0.5, anchorY: 0.5 }); shipSprite = newSprite; }; // Animate merge self.animateMerge = function () { tween(self, { scaleX: 1.2, scaleY: 1.2 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.easeIn }); } }); }; // Animate spawn self.animateSpawn = function () { self.scaleX = 0.2; self.scaleY = 0.2; tween(self, { scaleX: 1, scaleY: 1 }, { duration: 180, easing: tween.easeOut }); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x0e1621 }); /**** * Game Code ****/ // --- Game Constants --- // Pirate ship assets for different levels (1-6) // Sea background image (replace 'sea_bg_id' with your actual asset id) var GRID_SIZE = 5; var CELL_SIZE = 230; var GRID_MARGIN = 30; var BOARD_WIDTH = GRID_SIZE * CELL_SIZE; var BOARD_HEIGHT = GRID_SIZE * CELL_SIZE; var BOARD_X = Math.floor((2048 - BOARD_WIDTH) / 2); var BOARD_Y = Math.floor((2732 - BOARD_HEIGHT) / 2) + 60; // leave top margin for GUI // --- Game State --- var grid = []; // 2D array [y][x] of Ship or null var ships = []; // All ship objects var draggingShip = null; var dragStartGrid = null; var dragOffsetX = 0; var dragOffsetY = 0; var isMerging = false; var score = 0; var scoreTxt = null; // --- GUI --- scoreTxt = new Text2('0', { size: 120, fill: 0xFFE066 }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // --- Sea Background --- var seaBg = LK.getAsset('sea_bg', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); game.addChildAt(seaBg, 0); // Add as the very first child so it's behind everything // --- Board Background --- for (var y = 0; y < GRID_SIZE; y++) { for (var x = 0; x < GRID_SIZE; x++) { var cell = LK.getAsset('cell', { anchorX: 0.5, anchorY: 0.5, x: BOARD_X + x * CELL_SIZE + CELL_SIZE / 2, y: BOARD_Y + y * CELL_SIZE + CELL_SIZE / 2 }); game.addChild(cell); } } // --- Grid Initialization --- for (var y = 0; y < GRID_SIZE; y++) { grid[y] = []; for (var x = 0; x < GRID_SIZE; x++) { grid[y][x] = null; } } // --- Helper Functions --- function getCellPos(x, y) { return { x: BOARD_X + x * CELL_SIZE + CELL_SIZE / 2, y: BOARD_Y + y * CELL_SIZE + CELL_SIZE / 2 }; } function isInsideGrid(x, y) { return x >= 0 && x < GRID_SIZE && y >= 0 && y < GRID_SIZE; } function getGridFromPos(px, py) { var gx = Math.floor((px - BOARD_X) / CELL_SIZE); var gy = Math.floor((py - BOARD_Y) / CELL_SIZE); if (isInsideGrid(gx, gy)) { return { x: gx, y: gy }; } return null; } function spawnRandomShip() { // Find all empty cells var empties = []; for (var y = 0; y < GRID_SIZE; y++) { for (var x = 0; x < GRID_SIZE; x++) { if (!grid[y][x]) empties.push({ x: x, y: y }); } } if (empties.length === 0) return false; var idx = Math.floor(Math.random() * empties.length); var cell = empties[idx]; var level = Math.random() < 0.85 ? 1 : 2; // 85% level 1, 15% level 2 var ship = new Ship(); ship.setLevel(level); ship.gridX = cell.x; ship.gridY = cell.y; var pos = getCellPos(cell.x, cell.y); ship.x = pos.x; ship.y = pos.y; ship.animateSpawn(); grid[cell.y][cell.x] = ship; ships.push(ship); game.addChild(ship); return true; } function canMerge(shipA, shipB) { return shipA && shipB && shipA.level === shipB.level && shipA !== shipB; } // Find all connected ships of the same level (DFS) function findConnectedSameLevelShips(startShip) { var visited = {}; var stack = [startShip]; var result = []; while (stack.length > 0) { var ship = stack.pop(); if (!ship) continue; // Defensive: skip null/undefined ships var key = ship.gridX + "," + ship.gridY; if (visited[key]) continue; visited[key] = true; result.push(ship); // Check 4 neighbors var dirs = [[0, 1], [1, 0], [-1, 0], [0, -1]]; for (var d = 0; d < dirs.length; d++) { var nx = ship.gridX + dirs[d][0]; var ny = ship.gridY + dirs[d][1]; if (isInsideGrid(nx, ny)) { var neighbor = grid[ny][nx]; if (neighbor && neighbor.level === startShip.level && !visited[nx + "," + ny]) { stack.push(neighbor); } } } } return result; } // Merge 3 or more ships of the same level into one higher-level ship function mergeThreeOrMoreShips(shipsToMerge) { if (shipsToMerge.length < 3) return; // Pick the first ship as the merge target var target = shipsToMerge[0]; var newLevel = target.level + 1; if (newLevel > 6) newLevel = 6; target.setLevel(newLevel); target.animateMerge(); // Remove the other ships for (var i = 1; i < shipsToMerge.length; i++) { var ship = shipsToMerge[i]; grid[ship.gridY][ship.gridX] = null; var idx = ships.indexOf(ship); if (idx !== -1) ships.splice(idx, 1); ship.destroy(); } // Update grid for target grid[target.gridY][target.gridX] = target; // If merged to max level, sink surrounding ships and give bonus if (newLevel === 6) { var dirs = [[0, 1], [1, 0], [-1, 0], [0, -1], [1, 1], [-1, 1], [1, -1], [-1, -1]]; var sunk = 0; for (var d = 0; d < dirs.length; d++) { var nx = target.gridX + dirs[d][0]; var ny = target.gridY + dirs[d][1]; if (isInsideGrid(nx, ny) && grid[ny][nx]) { var s = grid[ny][nx]; grid[ny][nx] = null; var idx2 = ships.indexOf(s); if (idx2 !== -1) ships.splice(idx2, 1); s.destroy(); sunk++; } } // Bonus points for sinking var bonus = 1000 * sunk; score += bonus; } // Update score var addScore = Math.pow(2, newLevel + 1) * 10 * shipsToMerge.length; score += addScore; scoreTxt.setText(score); } function hasMovesAvailable() { // If any empty cell or any mergeable neighbor, return true for (var y = 0; y < GRID_SIZE; y++) { for (var x = 0; x < GRID_SIZE; x++) { var ship = grid[y][x]; if (!ship) return true; // Check neighbors var dirs = [[0, 1], [1, 0], [-1, 0], [0, -1]]; for (var d = 0; d < dirs.length; d++) { var nx = x + dirs[d][0]; var ny = y + dirs[d][1]; if (isInsideGrid(nx, ny) && grid[ny][nx] && grid[ny][nx].level === ship.level) { return true; } } } } return false; } // --- Initial Ships --- // No ships on the board at game start // After initial spawn, check for any 3+ merges and resolve them // (No need, board is empty) // --- Drag & Drop Logic --- game.down = function (x, y, obj) { if (isMerging) return; // Find ship at this position var gridPos = getGridFromPos(x, y); if (!gridPos) return; var ship = grid[gridPos.y][gridPos.x]; if (!ship) return; draggingShip = ship; dragStartGrid = { x: ship.gridX, y: ship.gridY }; dragOffsetX = x - ship.x; dragOffsetY = y - ship.y; ship.isDragging = true; // Bring to front game.removeChild(ship); game.addChild(ship); // Animate tween(ship, { scaleX: 1.15, scaleY: 1.15 }, { duration: 100, easing: tween.easeOut }); }; game.move = function (x, y, obj) { if (!draggingShip || isMerging) return; // Move ship with finger draggingShip.x = x - dragOffsetX; draggingShip.y = y - dragOffsetY; }; game.up = function (x, y, obj) { if (!draggingShip || isMerging) return; // Snap to nearest cell var gridPos = getGridFromPos(draggingShip.x, draggingShip.y); if (!gridPos) { // Out of bounds, return to original var orig = getCellPos(dragStartGrid.x, dragStartGrid.y); tween(draggingShip, { x: orig.x, y: orig.y, scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.easeIn }); draggingShip.isDragging = false; draggingShip = null; return; } var targetShip = grid[gridPos.y][gridPos.x]; if (targetShip && canMerge(draggingShip, targetShip)) { // Merge! isMerging = true; var pos = getCellPos(gridPos.x, gridPos.y); tween(draggingShip, { x: pos.x, y: pos.y }, { duration: 100, easing: tween.linear, onFinish: function onFinish() { mergeShips(draggingShip, targetShip); // Move merged ship to cell targetShip.gridX = gridPos.x; targetShip.gridY = gridPos.y; grid[dragStartGrid.y][dragStartGrid.x] = null; grid[gridPos.y][gridPos.x] = targetShip; isMerging = false; draggingShip = null; // After merge, check for 3+ merge at the merged cell var connected = findConnectedSameLevelShips(targetShip); while (connected.length >= 3) { mergeThreeOrMoreShips(connected); // After merge, check again at the same cell for new possible merges (chain reaction) connected = findConnectedSameLevelShips(grid[targetShip.gridY][targetShip.gridX]); } // After merge, spawn new ship if (!spawnRandomShip()) { // No space, check for moves if (!hasMovesAvailable()) { LK.showGameOver(); } } } }); } else if (!targetShip) { // Move to empty cell var pos = getCellPos(gridPos.x, gridPos.y); tween(draggingShip, { x: pos.x, y: pos.y, scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.easeIn, onFinish: function onFinish() { // Update grid grid[dragStartGrid.y][dragStartGrid.x] = null; grid[gridPos.y][gridPos.x] = draggingShip; draggingShip.gridX = gridPos.x; draggingShip.gridY = gridPos.y; draggingShip.isDragging = false; draggingShip = null; // After move, check for 3+ merge at the new cell var connected = findConnectedSameLevelShips(draggingShip); while (connected.length >= 3) { mergeThreeOrMoreShips(connected); // After merge, check again at the same cell for new possible merges (chain reaction) connected = findConnectedSameLevelShips(grid[draggingShip.gridY][draggingShip.gridX]); } // After move, spawn new ship if (!spawnRandomShip()) { if (!hasMovesAvailable()) { LK.showGameOver(); } } } }); } else { // Occupied, can't merge, return to original var orig = getCellPos(dragStartGrid.x, dragStartGrid.y); tween(draggingShip, { x: orig.x, y: orig.y, scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.easeIn }); draggingShip.isDragging = false; draggingShip = null; } }; // --- Bottom Bar for Ship Selection --- var unlockedLevels = [1]; // Always unlocked at start var bottomBarShips = []; var bottomBarY = 2732 - 180; var bottomBarHeight = 180; var bottomBarPadding = 40; var bottomBarX = 0; var bottomBarWidth = 2048; var bottomBarContainer = new Container(); game.addChild(bottomBarContainer); function updateBottomBar() { // Remove old for (var i = 0; i < bottomBarShips.length; i++) { bottomBarShips[i].destroy(); } bottomBarShips = []; bottomBarContainer.removeChildren(); // Always show up to 3 lowest unlocked levels in the bar var barLevels = unlockedLevels.slice(0, 3); var n = barLevels.length; var spacing = (bottomBarWidth - 2 * bottomBarPadding) / Math.max(1, n); for (var i = 0; i < n; i++) { var level = barLevels[i]; var ship = new Ship(); ship.setLevel(level); ship.x = bottomBarPadding + spacing * i + spacing / 2; ship.y = bottomBarY + bottomBarHeight / 2; ship.scaleX = 0.8; ship.scaleY = 0.8; ship.isBottomBar = true; ship.bottomBarIndex = i; bottomBarContainer.addChild(ship); bottomBarShips.push(ship); } } updateBottomBar(); // --- Drag & Drop from Bottom Bar --- var draggingFromBar = null; var draggingBarShip = null; var draggingBarLevel = null; var draggingBarSprite = null; game.down = function (x, y, obj) { if (isMerging) return; // Check if pressed on a bottom bar ship for (var i = 0; i < bottomBarShips.length; i++) { var ship = bottomBarShips[i]; var dx = x - ship.x; var dy = y - ship.y; var r = 90; if (dx * dx + dy * dy < r * r) { // Start dragging a new ship from bar draggingFromBar = true; draggingBarLevel = ship.level; draggingBarSprite = new Ship(); draggingBarSprite.setLevel(ship.level); draggingBarSprite.x = x; draggingBarSprite.y = y; draggingBarSprite.scaleX = 1.1; draggingBarSprite.scaleY = 1.1; draggingBarSprite.isDragging = true; game.addChild(draggingBarSprite); return; } } // Otherwise, normal board drag var gridPos = getGridFromPos(x, y); if (!gridPos) return; var ship = grid[gridPos.y][gridPos.x]; if (!ship) return; // Prevent moving ships already placed on the board return; }; game.move = function (x, y, obj) { if (isMerging) return; if (draggingFromBar && draggingBarSprite) { draggingBarSprite.x = x; draggingBarSprite.y = y; return; } if (!draggingShip) return; draggingShip.x = x - dragOffsetX; draggingShip.y = y - dragOffsetY; }; game.up = function (x, y, obj) { if (isMerging) return; if (draggingFromBar && draggingBarSprite) { // Try to place on board var gridPos = getGridFromPos(x, y); if (gridPos && !grid[gridPos.y][gridPos.x]) { // Place new ship var ship = draggingBarSprite; ship.isDragging = false; ship.gridX = gridPos.x; ship.gridY = gridPos.y; var pos = getCellPos(gridPos.x, gridPos.y); tween(ship, { x: pos.x, y: pos.y, scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.easeIn, onFinish: function onFinish() { grid[gridPos.y][gridPos.x] = ship; ships.push(ship); // After place, check for 3+ merge at the new cell var connected = findConnectedSameLevelShips(ship); while (connected.length >= 3) { mergeThreeOrMoreShips(connected); // After merge, check again at the same cell for new possible merges (chain reaction) connected = findConnectedSameLevelShips(grid[ship.gridY][ship.gridX]); } // Replace the used ship in the bottom bar with a new random unlocked ship (if more than 1 unlocked) if (unlockedLevels.length > 1) { // Find the index of the used ship in the bar var usedBarIndex = -1; for (var i = 0; i < bottomBarShips.length; i++) { if (bottomBarShips[i].level === draggingBarLevel) { usedBarIndex = i; break; } } if (usedBarIndex !== -1) { // Pick a random unlocked level (not the one just used, if possible) var possibleLevels = unlockedLevels.slice(); if (possibleLevels.length > 1) { var idx = possibleLevels.indexOf(draggingBarLevel); if (idx !== -1) possibleLevels.splice(idx, 1); } var newLevel = possibleLevels[Math.floor(Math.random() * possibleLevels.length)]; // Replace the ship in the bar bottomBarShips[usedBarIndex].destroy(); var shipBar = new Ship(); shipBar.setLevel(newLevel); var n = bottomBarShips.length; var spacing = (bottomBarWidth - 2 * bottomBarPadding) / Math.max(1, n); shipBar.x = bottomBarPadding + spacing * usedBarIndex + spacing / 2; shipBar.y = bottomBarY + bottomBarHeight / 2; shipBar.scaleX = 0.8; shipBar.scaleY = 0.8; shipBar.isBottomBar = true; shipBar.bottomBarIndex = usedBarIndex; bottomBarContainer.addChild(shipBar); bottomBarShips[usedBarIndex] = shipBar; } } // After move, spawn new ship (if desired, or not) if (!hasMovesAvailable()) { LK.showGameOver(); } } }); } else { // Not placed, destroy draggingBarSprite.destroy(); } draggingFromBar = false; draggingBarSprite = null; draggingBarLevel = null; return; } if (!draggingShip) return; // Snap to nearest cell var gridPos = getGridFromPos(draggingShip.x, draggingShip.y); if (!gridPos) { // Out of bounds, return to original var orig = getCellPos(dragStartGrid.x, dragStartGrid.y); tween(draggingShip, { x: orig.x, y: orig.y, scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.easeIn }); draggingShip.isDragging = false; draggingShip = null; return; } var targetShip = grid[gridPos.y][gridPos.x]; if (targetShip && canMerge(draggingShip, targetShip)) { // Merge! isMerging = true; var pos = getCellPos(gridPos.x, gridPos.y); tween(draggingShip, { x: pos.x, y: pos.y }, { duration: 100, easing: tween.linear, onFinish: function onFinish() { mergeShips(draggingShip, targetShip); // Move merged ship to cell targetShip.gridX = gridPos.x; targetShip.gridY = gridPos.y; grid[dragStartGrid.y][dragStartGrid.x] = null; grid[gridPos.y][gridPos.x] = targetShip; isMerging = false; draggingShip = null; // After merge, check for 3+ merge at the merged cell var connected = findConnectedSameLevelShips(targetShip); while (connected.length >= 3) { mergeThreeOrMoreShips(connected); // After merge, check again at the same cell for new possible merges (chain reaction) connected = findConnectedSameLevelShips(grid[targetShip.gridY][targetShip.gridX]); } // After merge, spawn new ship if (!spawnRandomShip()) { // No space, check for moves if (!hasMovesAvailable()) { LK.showGameOver(); } } } }); } else if (!targetShip) { // Move to empty cell var pos = getCellPos(gridPos.x, gridPos.y); tween(draggingShip, { x: pos.x, y: pos.y, scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.easeIn, onFinish: function onFinish() { // Update grid grid[dragStartGrid.y][dragStartGrid.x] = null; grid[gridPos.y][gridPos.x] = draggingShip; draggingShip.gridX = gridPos.x; draggingShip.gridY = gridPos.y; draggingShip.isDragging = false; draggingShip = null; // After move, check for 3+ merge at the new cell var connected = findConnectedSameLevelShips(draggingShip); while (connected.length >= 3) { mergeThreeOrMoreShips(connected); // After merge, check again at the same cell for new possible merges (chain reaction) connected = findConnectedSameLevelShips(grid[draggingShip.gridY][draggingShip.gridX]); } // After move, spawn new ship if (!spawnRandomShip()) { if (!hasMovesAvailable()) { LK.showGameOver(); } } } }); } else { // Occupied, can't merge, return to original var orig = getCellPos(dragStartGrid.x, dragStartGrid.y); tween(draggingShip, { x: orig.x, y: orig.y, scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.easeIn }); draggingShip.isDragging = false; draggingShip = null; } }; // --- Game Update (not used for logic, but could be for future animations) --- game.update = function () { // No per-frame logic needed for MVP }; // --- Unlock new ship levels when merged --- var _old_mergeThreeOrMoreShips = mergeThreeOrMoreShips; mergeThreeOrMoreShips = function mergeThreeOrMoreShips(shipsToMerge) { if (shipsToMerge.length < 3) return; var oldLevel = shipsToMerge[0].level; _old_mergeThreeOrMoreShips(shipsToMerge); var newLevel = oldLevel + 1; if (newLevel > 6) newLevel = 6; if (unlockedLevels.indexOf(newLevel) === -1) { unlockedLevels.push(newLevel); unlockedLevels.sort(function (a, b) { return a - b; }); updateBottomBar(); } }; // Merge two ships of the same level into one higher-level ship at the target's position function mergeShips(shipA, shipB) { if (!canMerge(shipA, shipB)) return; // Find all connected same-level ships (including shipA and shipB) var connected = findConnectedSameLevelShips(shipB); // If not enough to merge, fallback to just merging two if (connected.length < 2) connected = [shipA, shipB]; // Always merge into shipB (the target cell) var newLevel = shipB.level + 1; if (newLevel > 6) newLevel = 6; shipB.setLevel(newLevel); shipB.animateMerge(); // Remove all other ships in connected except shipB for (var i = 0; i < connected.length; i++) { var s = connected[i]; if (s !== shipB) { grid[s.gridY][s.gridX] = null; var idx = ships.indexOf(s); if (idx !== -1) ships.splice(idx, 1); s.destroy(); } } // Update grid for shipB grid[shipB.gridY][shipB.gridX] = shipB; // Update score for this merge var addScore = Math.pow(2, newLevel + 1) * 10 * connected.length; score += addScore; scoreTxt.setText(score); }
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Ship class for mergeable ships
var Ship = Container.expand(function () {
var self = Container.call(this);
// Level of the ship (1-6)
self.level = 1;
self.gridX = 0;
self.gridY = 0;
self.isDragging = false;
// Attach ship asset
var shipSprite = self.attachAsset('ship' + self.level, {
anchorX: 0.5,
anchorY: 0.5
});
// Update ship sprite to match level
self.setLevel = function (level) {
self.level = level;
self.removeChild(shipSprite);
var newSprite = self.attachAsset('ship' + level, {
anchorX: 0.5,
anchorY: 0.5
});
shipSprite = newSprite;
};
// Animate merge
self.animateMerge = function () {
tween(self, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 120,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.easeIn
});
}
});
};
// Animate spawn
self.animateSpawn = function () {
self.scaleX = 0.2;
self.scaleY = 0.2;
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 180,
easing: tween.easeOut
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x0e1621
});
/****
* Game Code
****/
// --- Game Constants ---
// Pirate ship assets for different levels (1-6)
// Sea background image (replace 'sea_bg_id' with your actual asset id)
var GRID_SIZE = 5;
var CELL_SIZE = 230;
var GRID_MARGIN = 30;
var BOARD_WIDTH = GRID_SIZE * CELL_SIZE;
var BOARD_HEIGHT = GRID_SIZE * CELL_SIZE;
var BOARD_X = Math.floor((2048 - BOARD_WIDTH) / 2);
var BOARD_Y = Math.floor((2732 - BOARD_HEIGHT) / 2) + 60; // leave top margin for GUI
// --- Game State ---
var grid = []; // 2D array [y][x] of Ship or null
var ships = []; // All ship objects
var draggingShip = null;
var dragStartGrid = null;
var dragOffsetX = 0;
var dragOffsetY = 0;
var isMerging = false;
var score = 0;
var scoreTxt = null;
// --- GUI ---
scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFE066
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// --- Sea Background ---
var seaBg = LK.getAsset('sea_bg', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChildAt(seaBg, 0); // Add as the very first child so it's behind everything
// --- Board Background ---
for (var y = 0; y < GRID_SIZE; y++) {
for (var x = 0; x < GRID_SIZE; x++) {
var cell = LK.getAsset('cell', {
anchorX: 0.5,
anchorY: 0.5,
x: BOARD_X + x * CELL_SIZE + CELL_SIZE / 2,
y: BOARD_Y + y * CELL_SIZE + CELL_SIZE / 2
});
game.addChild(cell);
}
}
// --- Grid Initialization ---
for (var y = 0; y < GRID_SIZE; y++) {
grid[y] = [];
for (var x = 0; x < GRID_SIZE; x++) {
grid[y][x] = null;
}
}
// --- Helper Functions ---
function getCellPos(x, y) {
return {
x: BOARD_X + x * CELL_SIZE + CELL_SIZE / 2,
y: BOARD_Y + y * CELL_SIZE + CELL_SIZE / 2
};
}
function isInsideGrid(x, y) {
return x >= 0 && x < GRID_SIZE && y >= 0 && y < GRID_SIZE;
}
function getGridFromPos(px, py) {
var gx = Math.floor((px - BOARD_X) / CELL_SIZE);
var gy = Math.floor((py - BOARD_Y) / CELL_SIZE);
if (isInsideGrid(gx, gy)) {
return {
x: gx,
y: gy
};
}
return null;
}
function spawnRandomShip() {
// Find all empty cells
var empties = [];
for (var y = 0; y < GRID_SIZE; y++) {
for (var x = 0; x < GRID_SIZE; x++) {
if (!grid[y][x]) empties.push({
x: x,
y: y
});
}
}
if (empties.length === 0) return false;
var idx = Math.floor(Math.random() * empties.length);
var cell = empties[idx];
var level = Math.random() < 0.85 ? 1 : 2; // 85% level 1, 15% level 2
var ship = new Ship();
ship.setLevel(level);
ship.gridX = cell.x;
ship.gridY = cell.y;
var pos = getCellPos(cell.x, cell.y);
ship.x = pos.x;
ship.y = pos.y;
ship.animateSpawn();
grid[cell.y][cell.x] = ship;
ships.push(ship);
game.addChild(ship);
return true;
}
function canMerge(shipA, shipB) {
return shipA && shipB && shipA.level === shipB.level && shipA !== shipB;
}
// Find all connected ships of the same level (DFS)
function findConnectedSameLevelShips(startShip) {
var visited = {};
var stack = [startShip];
var result = [];
while (stack.length > 0) {
var ship = stack.pop();
if (!ship) continue; // Defensive: skip null/undefined ships
var key = ship.gridX + "," + ship.gridY;
if (visited[key]) continue;
visited[key] = true;
result.push(ship);
// Check 4 neighbors
var dirs = [[0, 1], [1, 0], [-1, 0], [0, -1]];
for (var d = 0; d < dirs.length; d++) {
var nx = ship.gridX + dirs[d][0];
var ny = ship.gridY + dirs[d][1];
if (isInsideGrid(nx, ny)) {
var neighbor = grid[ny][nx];
if (neighbor && neighbor.level === startShip.level && !visited[nx + "," + ny]) {
stack.push(neighbor);
}
}
}
}
return result;
}
// Merge 3 or more ships of the same level into one higher-level ship
function mergeThreeOrMoreShips(shipsToMerge) {
if (shipsToMerge.length < 3) return;
// Pick the first ship as the merge target
var target = shipsToMerge[0];
var newLevel = target.level + 1;
if (newLevel > 6) newLevel = 6;
target.setLevel(newLevel);
target.animateMerge();
// Remove the other ships
for (var i = 1; i < shipsToMerge.length; i++) {
var ship = shipsToMerge[i];
grid[ship.gridY][ship.gridX] = null;
var idx = ships.indexOf(ship);
if (idx !== -1) ships.splice(idx, 1);
ship.destroy();
}
// Update grid for target
grid[target.gridY][target.gridX] = target;
// If merged to max level, sink surrounding ships and give bonus
if (newLevel === 6) {
var dirs = [[0, 1], [1, 0], [-1, 0], [0, -1], [1, 1], [-1, 1], [1, -1], [-1, -1]];
var sunk = 0;
for (var d = 0; d < dirs.length; d++) {
var nx = target.gridX + dirs[d][0];
var ny = target.gridY + dirs[d][1];
if (isInsideGrid(nx, ny) && grid[ny][nx]) {
var s = grid[ny][nx];
grid[ny][nx] = null;
var idx2 = ships.indexOf(s);
if (idx2 !== -1) ships.splice(idx2, 1);
s.destroy();
sunk++;
}
}
// Bonus points for sinking
var bonus = 1000 * sunk;
score += bonus;
}
// Update score
var addScore = Math.pow(2, newLevel + 1) * 10 * shipsToMerge.length;
score += addScore;
scoreTxt.setText(score);
}
function hasMovesAvailable() {
// If any empty cell or any mergeable neighbor, return true
for (var y = 0; y < GRID_SIZE; y++) {
for (var x = 0; x < GRID_SIZE; x++) {
var ship = grid[y][x];
if (!ship) return true;
// Check neighbors
var dirs = [[0, 1], [1, 0], [-1, 0], [0, -1]];
for (var d = 0; d < dirs.length; d++) {
var nx = x + dirs[d][0];
var ny = y + dirs[d][1];
if (isInsideGrid(nx, ny) && grid[ny][nx] && grid[ny][nx].level === ship.level) {
return true;
}
}
}
}
return false;
}
// --- Initial Ships ---
// No ships on the board at game start
// After initial spawn, check for any 3+ merges and resolve them
// (No need, board is empty)
// --- Drag & Drop Logic ---
game.down = function (x, y, obj) {
if (isMerging) return;
// Find ship at this position
var gridPos = getGridFromPos(x, y);
if (!gridPos) return;
var ship = grid[gridPos.y][gridPos.x];
if (!ship) return;
draggingShip = ship;
dragStartGrid = {
x: ship.gridX,
y: ship.gridY
};
dragOffsetX = x - ship.x;
dragOffsetY = y - ship.y;
ship.isDragging = true;
// Bring to front
game.removeChild(ship);
game.addChild(ship);
// Animate
tween(ship, {
scaleX: 1.15,
scaleY: 1.15
}, {
duration: 100,
easing: tween.easeOut
});
};
game.move = function (x, y, obj) {
if (!draggingShip || isMerging) return;
// Move ship with finger
draggingShip.x = x - dragOffsetX;
draggingShip.y = y - dragOffsetY;
};
game.up = function (x, y, obj) {
if (!draggingShip || isMerging) return;
// Snap to nearest cell
var gridPos = getGridFromPos(draggingShip.x, draggingShip.y);
if (!gridPos) {
// Out of bounds, return to original
var orig = getCellPos(dragStartGrid.x, dragStartGrid.y);
tween(draggingShip, {
x: orig.x,
y: orig.y,
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.easeIn
});
draggingShip.isDragging = false;
draggingShip = null;
return;
}
var targetShip = grid[gridPos.y][gridPos.x];
if (targetShip && canMerge(draggingShip, targetShip)) {
// Merge!
isMerging = true;
var pos = getCellPos(gridPos.x, gridPos.y);
tween(draggingShip, {
x: pos.x,
y: pos.y
}, {
duration: 100,
easing: tween.linear,
onFinish: function onFinish() {
mergeShips(draggingShip, targetShip);
// Move merged ship to cell
targetShip.gridX = gridPos.x;
targetShip.gridY = gridPos.y;
grid[dragStartGrid.y][dragStartGrid.x] = null;
grid[gridPos.y][gridPos.x] = targetShip;
isMerging = false;
draggingShip = null;
// After merge, check for 3+ merge at the merged cell
var connected = findConnectedSameLevelShips(targetShip);
while (connected.length >= 3) {
mergeThreeOrMoreShips(connected);
// After merge, check again at the same cell for new possible merges (chain reaction)
connected = findConnectedSameLevelShips(grid[targetShip.gridY][targetShip.gridX]);
}
// After merge, spawn new ship
if (!spawnRandomShip()) {
// No space, check for moves
if (!hasMovesAvailable()) {
LK.showGameOver();
}
}
}
});
} else if (!targetShip) {
// Move to empty cell
var pos = getCellPos(gridPos.x, gridPos.y);
tween(draggingShip, {
x: pos.x,
y: pos.y,
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.easeIn,
onFinish: function onFinish() {
// Update grid
grid[dragStartGrid.y][dragStartGrid.x] = null;
grid[gridPos.y][gridPos.x] = draggingShip;
draggingShip.gridX = gridPos.x;
draggingShip.gridY = gridPos.y;
draggingShip.isDragging = false;
draggingShip = null;
// After move, check for 3+ merge at the new cell
var connected = findConnectedSameLevelShips(draggingShip);
while (connected.length >= 3) {
mergeThreeOrMoreShips(connected);
// After merge, check again at the same cell for new possible merges (chain reaction)
connected = findConnectedSameLevelShips(grid[draggingShip.gridY][draggingShip.gridX]);
}
// After move, spawn new ship
if (!spawnRandomShip()) {
if (!hasMovesAvailable()) {
LK.showGameOver();
}
}
}
});
} else {
// Occupied, can't merge, return to original
var orig = getCellPos(dragStartGrid.x, dragStartGrid.y);
tween(draggingShip, {
x: orig.x,
y: orig.y,
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.easeIn
});
draggingShip.isDragging = false;
draggingShip = null;
}
};
// --- Bottom Bar for Ship Selection ---
var unlockedLevels = [1]; // Always unlocked at start
var bottomBarShips = [];
var bottomBarY = 2732 - 180;
var bottomBarHeight = 180;
var bottomBarPadding = 40;
var bottomBarX = 0;
var bottomBarWidth = 2048;
var bottomBarContainer = new Container();
game.addChild(bottomBarContainer);
function updateBottomBar() {
// Remove old
for (var i = 0; i < bottomBarShips.length; i++) {
bottomBarShips[i].destroy();
}
bottomBarShips = [];
bottomBarContainer.removeChildren();
// Always show up to 3 lowest unlocked levels in the bar
var barLevels = unlockedLevels.slice(0, 3);
var n = barLevels.length;
var spacing = (bottomBarWidth - 2 * bottomBarPadding) / Math.max(1, n);
for (var i = 0; i < n; i++) {
var level = barLevels[i];
var ship = new Ship();
ship.setLevel(level);
ship.x = bottomBarPadding + spacing * i + spacing / 2;
ship.y = bottomBarY + bottomBarHeight / 2;
ship.scaleX = 0.8;
ship.scaleY = 0.8;
ship.isBottomBar = true;
ship.bottomBarIndex = i;
bottomBarContainer.addChild(ship);
bottomBarShips.push(ship);
}
}
updateBottomBar();
// --- Drag & Drop from Bottom Bar ---
var draggingFromBar = null;
var draggingBarShip = null;
var draggingBarLevel = null;
var draggingBarSprite = null;
game.down = function (x, y, obj) {
if (isMerging) return;
// Check if pressed on a bottom bar ship
for (var i = 0; i < bottomBarShips.length; i++) {
var ship = bottomBarShips[i];
var dx = x - ship.x;
var dy = y - ship.y;
var r = 90;
if (dx * dx + dy * dy < r * r) {
// Start dragging a new ship from bar
draggingFromBar = true;
draggingBarLevel = ship.level;
draggingBarSprite = new Ship();
draggingBarSprite.setLevel(ship.level);
draggingBarSprite.x = x;
draggingBarSprite.y = y;
draggingBarSprite.scaleX = 1.1;
draggingBarSprite.scaleY = 1.1;
draggingBarSprite.isDragging = true;
game.addChild(draggingBarSprite);
return;
}
}
// Otherwise, normal board drag
var gridPos = getGridFromPos(x, y);
if (!gridPos) return;
var ship = grid[gridPos.y][gridPos.x];
if (!ship) return;
// Prevent moving ships already placed on the board
return;
};
game.move = function (x, y, obj) {
if (isMerging) return;
if (draggingFromBar && draggingBarSprite) {
draggingBarSprite.x = x;
draggingBarSprite.y = y;
return;
}
if (!draggingShip) return;
draggingShip.x = x - dragOffsetX;
draggingShip.y = y - dragOffsetY;
};
game.up = function (x, y, obj) {
if (isMerging) return;
if (draggingFromBar && draggingBarSprite) {
// Try to place on board
var gridPos = getGridFromPos(x, y);
if (gridPos && !grid[gridPos.y][gridPos.x]) {
// Place new ship
var ship = draggingBarSprite;
ship.isDragging = false;
ship.gridX = gridPos.x;
ship.gridY = gridPos.y;
var pos = getCellPos(gridPos.x, gridPos.y);
tween(ship, {
x: pos.x,
y: pos.y,
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.easeIn,
onFinish: function onFinish() {
grid[gridPos.y][gridPos.x] = ship;
ships.push(ship);
// After place, check for 3+ merge at the new cell
var connected = findConnectedSameLevelShips(ship);
while (connected.length >= 3) {
mergeThreeOrMoreShips(connected);
// After merge, check again at the same cell for new possible merges (chain reaction)
connected = findConnectedSameLevelShips(grid[ship.gridY][ship.gridX]);
}
// Replace the used ship in the bottom bar with a new random unlocked ship (if more than 1 unlocked)
if (unlockedLevels.length > 1) {
// Find the index of the used ship in the bar
var usedBarIndex = -1;
for (var i = 0; i < bottomBarShips.length; i++) {
if (bottomBarShips[i].level === draggingBarLevel) {
usedBarIndex = i;
break;
}
}
if (usedBarIndex !== -1) {
// Pick a random unlocked level (not the one just used, if possible)
var possibleLevels = unlockedLevels.slice();
if (possibleLevels.length > 1) {
var idx = possibleLevels.indexOf(draggingBarLevel);
if (idx !== -1) possibleLevels.splice(idx, 1);
}
var newLevel = possibleLevels[Math.floor(Math.random() * possibleLevels.length)];
// Replace the ship in the bar
bottomBarShips[usedBarIndex].destroy();
var shipBar = new Ship();
shipBar.setLevel(newLevel);
var n = bottomBarShips.length;
var spacing = (bottomBarWidth - 2 * bottomBarPadding) / Math.max(1, n);
shipBar.x = bottomBarPadding + spacing * usedBarIndex + spacing / 2;
shipBar.y = bottomBarY + bottomBarHeight / 2;
shipBar.scaleX = 0.8;
shipBar.scaleY = 0.8;
shipBar.isBottomBar = true;
shipBar.bottomBarIndex = usedBarIndex;
bottomBarContainer.addChild(shipBar);
bottomBarShips[usedBarIndex] = shipBar;
}
}
// After move, spawn new ship (if desired, or not)
if (!hasMovesAvailable()) {
LK.showGameOver();
}
}
});
} else {
// Not placed, destroy
draggingBarSprite.destroy();
}
draggingFromBar = false;
draggingBarSprite = null;
draggingBarLevel = null;
return;
}
if (!draggingShip) return;
// Snap to nearest cell
var gridPos = getGridFromPos(draggingShip.x, draggingShip.y);
if (!gridPos) {
// Out of bounds, return to original
var orig = getCellPos(dragStartGrid.x, dragStartGrid.y);
tween(draggingShip, {
x: orig.x,
y: orig.y,
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.easeIn
});
draggingShip.isDragging = false;
draggingShip = null;
return;
}
var targetShip = grid[gridPos.y][gridPos.x];
if (targetShip && canMerge(draggingShip, targetShip)) {
// Merge!
isMerging = true;
var pos = getCellPos(gridPos.x, gridPos.y);
tween(draggingShip, {
x: pos.x,
y: pos.y
}, {
duration: 100,
easing: tween.linear,
onFinish: function onFinish() {
mergeShips(draggingShip, targetShip);
// Move merged ship to cell
targetShip.gridX = gridPos.x;
targetShip.gridY = gridPos.y;
grid[dragStartGrid.y][dragStartGrid.x] = null;
grid[gridPos.y][gridPos.x] = targetShip;
isMerging = false;
draggingShip = null;
// After merge, check for 3+ merge at the merged cell
var connected = findConnectedSameLevelShips(targetShip);
while (connected.length >= 3) {
mergeThreeOrMoreShips(connected);
// After merge, check again at the same cell for new possible merges (chain reaction)
connected = findConnectedSameLevelShips(grid[targetShip.gridY][targetShip.gridX]);
}
// After merge, spawn new ship
if (!spawnRandomShip()) {
// No space, check for moves
if (!hasMovesAvailable()) {
LK.showGameOver();
}
}
}
});
} else if (!targetShip) {
// Move to empty cell
var pos = getCellPos(gridPos.x, gridPos.y);
tween(draggingShip, {
x: pos.x,
y: pos.y,
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.easeIn,
onFinish: function onFinish() {
// Update grid
grid[dragStartGrid.y][dragStartGrid.x] = null;
grid[gridPos.y][gridPos.x] = draggingShip;
draggingShip.gridX = gridPos.x;
draggingShip.gridY = gridPos.y;
draggingShip.isDragging = false;
draggingShip = null;
// After move, check for 3+ merge at the new cell
var connected = findConnectedSameLevelShips(draggingShip);
while (connected.length >= 3) {
mergeThreeOrMoreShips(connected);
// After merge, check again at the same cell for new possible merges (chain reaction)
connected = findConnectedSameLevelShips(grid[draggingShip.gridY][draggingShip.gridX]);
}
// After move, spawn new ship
if (!spawnRandomShip()) {
if (!hasMovesAvailable()) {
LK.showGameOver();
}
}
}
});
} else {
// Occupied, can't merge, return to original
var orig = getCellPos(dragStartGrid.x, dragStartGrid.y);
tween(draggingShip, {
x: orig.x,
y: orig.y,
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.easeIn
});
draggingShip.isDragging = false;
draggingShip = null;
}
};
// --- Game Update (not used for logic, but could be for future animations) ---
game.update = function () {
// No per-frame logic needed for MVP
};
// --- Unlock new ship levels when merged ---
var _old_mergeThreeOrMoreShips = mergeThreeOrMoreShips;
mergeThreeOrMoreShips = function mergeThreeOrMoreShips(shipsToMerge) {
if (shipsToMerge.length < 3) return;
var oldLevel = shipsToMerge[0].level;
_old_mergeThreeOrMoreShips(shipsToMerge);
var newLevel = oldLevel + 1;
if (newLevel > 6) newLevel = 6;
if (unlockedLevels.indexOf(newLevel) === -1) {
unlockedLevels.push(newLevel);
unlockedLevels.sort(function (a, b) {
return a - b;
});
updateBottomBar();
}
};
// Merge two ships of the same level into one higher-level ship at the target's position
function mergeShips(shipA, shipB) {
if (!canMerge(shipA, shipB)) return;
// Find all connected same-level ships (including shipA and shipB)
var connected = findConnectedSameLevelShips(shipB);
// If not enough to merge, fallback to just merging two
if (connected.length < 2) connected = [shipA, shipB];
// Always merge into shipB (the target cell)
var newLevel = shipB.level + 1;
if (newLevel > 6) newLevel = 6;
shipB.setLevel(newLevel);
shipB.animateMerge();
// Remove all other ships in connected except shipB
for (var i = 0; i < connected.length; i++) {
var s = connected[i];
if (s !== shipB) {
grid[s.gridY][s.gridX] = null;
var idx = ships.indexOf(s);
if (idx !== -1) ships.splice(idx, 1);
s.destroy();
}
}
// Update grid for shipB
grid[shipB.gridY][shipB.gridX] = shipB;
// Update score for this merge
var addScore = Math.pow(2, newLevel + 1) * 10 * connected.length;
score += addScore;
scoreTxt.setText(score);
}
deniz arka plan. In-Game asset. 2d. High contrast. No shadows
3d piksar tarzı şeffaf kare. In-Game asset. 2d. High contrast. No shadows
3d pixar tarzı korsan gemisi beyaz renk. In-Game asset. 2d. High contrast. No shadows
beyaz bölgeler sarı olsun
beyaz kısımlar açık yeşil olsun
beyaz kısımlar mavi olsun
mavi kısımlar koyu mor olsun
mor kısımlar siyah olsun