/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // Block class: represents a single block in the world grid var Block = Container.expand(function () { var self = Container.call(this); self.blockType = 'grass'; // default, will be set on init self.gridX = 0; self.gridY = 0; // The block asset self.blockAsset = null; self.setType = function (type) { self.blockType = type; if (self.blockAsset) { self.removeChild(self.blockAsset); } self.blockAsset = self.attachAsset('block_' + type, { anchorX: 0.5, anchorY: 0.5 }); }; self.setGrid = function (x, y) { self.gridX = x; self.gridY = y; }; return self; }); // InventorySlot class: represents a selectable block type in the inventory var InventorySlot = Container.expand(function () { var self = Container.call(this); self.blockType = 'grass'; self.selected = false; // Slot background self.bg = self.attachAsset('inv_slot', { anchorX: 0.5, anchorY: 0.5 }); // Block icon self.icon = self.attachAsset('block_grass', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8 }); // Selection highlight self.selectHighlight = self.attachAsset('block_select', { anchorX: 0.5, anchorY: 0.5 }); self.selectHighlight.alpha = 0; self.setType = function (type) { self.blockType = type; self.removeChild(self.icon); self.icon = self.attachAsset('block_' + type, { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8 }); self.addChild(self.icon); }; self.setSelected = function (sel) { self.selected = sel; self.selectHighlight.alpha = sel ? 0.7 : 0; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x87ceeb // light blue sky }); /**** * Game Code ****/ // --- World/grid setup --- // Block types: grass, dirt, stone, water, wood, sand // Each block is a colored box shape, 120x120px // Selection highlight // Inventory slot background var BLOCK_SIZE = 120; var GRID_COLS = 16; // 16x16 visible grid (1920x1920), fits on iPad Pro with some margin var GRID_ROWS = 16; var WORLD_COLS = 32; // world is 32x32 blocks var WORLD_ROWS = 32; // The world data: 2D array of block types var world = []; // Try to load from storage, else generate new if (storage.world && storage.world.length === WORLD_ROWS) { for (var y = 0; y < WORLD_ROWS; y++) { world[y] = []; for (var x = 0; x < WORLD_COLS; x++) { world[y][x] = storage.world[y][x]; } } } else { // Generate a simple world: grass on top, dirt below, stone at bottom, some water and sand for (var y = 0; y < WORLD_ROWS; y++) { world[y] = []; for (var x = 0; x < WORLD_COLS; x++) { if (y === WORLD_ROWS - 1) { world[y][x] = 'stone'; } else if (y > WORLD_ROWS - 4) { world[y][x] = 'dirt'; } else if (y === WORLD_ROWS - 4) { world[y][x] = 'grass'; } else if (y > WORLD_ROWS - 8 && x > 5 && x < 10) { world[y][x] = 'water'; } else if (y > WORLD_ROWS - 8 && x > 20 && x < 25) { world[y][x] = 'sand'; } else { world[y][x] = 'air'; } } } } // The block types available to the player var blockTypes = ['grass', 'dirt', 'stone', 'water', 'wood', 'sand']; var blockTypeNames = { grass: 'Grass', dirt: 'Dirt', stone: 'Stone', water: 'Water', wood: 'Wood', sand: 'Sand' }; // The currently selected block type for placement var selectedBlockType = storage.selectedBlockType || 'grass'; // Camera offset (in grid units) // Center the camera on the world spawn (center of the world) var defaultCameraX = Math.floor((WORLD_COLS - GRID_COLS) / 2); var defaultCameraY = Math.floor((WORLD_ROWS - GRID_ROWS) / 2); var cameraX = typeof storage.cameraX === "number" ? storage.cameraX : defaultCameraX; var cameraY = typeof storage.cameraY === "number" ? storage.cameraY : defaultCameraY; // Clamp camera to world bounds function clampCamera() { if (cameraX < 0) cameraX = 0; if (cameraY < 0) cameraY = 0; if (cameraX > WORLD_COLS - GRID_COLS) cameraX = WORLD_COLS - GRID_COLS; if (cameraY > WORLD_ROWS - GRID_ROWS) cameraY = WORLD_ROWS - GRID_ROWS; } // --- Block rendering --- var blockLayer = new Container(); game.addChild(blockLayer); // 2D array of Block objects for visible grid var blockObjs = []; for (var y = 0; y < GRID_ROWS; y++) { blockObjs[y] = []; for (var x = 0; x < GRID_COLS; x++) { var block = new Block(); block.x = x * BLOCK_SIZE + BLOCK_SIZE / 2; block.y = y * BLOCK_SIZE + BLOCK_SIZE / 2 + 100; // leave space for inventory block.setGrid(x, y); blockLayer.addChild(block); blockObjs[y][x] = block; } } // Center the grid horizontally blockLayer.x = (2048 - GRID_COLS * BLOCK_SIZE) / 2; blockLayer.y = 0; // --- Inventory UI --- var inventoryBar = new Container(); LK.gui.bottom.addChild(inventoryBar); var invSlots = []; var invSlotMargin = 20; var invBarWidth = blockTypes.length * (130 + invSlotMargin) - invSlotMargin; for (var i = 0; i < blockTypes.length; i++) { var slot = new InventorySlot(); slot.setType(blockTypes[i]); slot.x = i * (130 + invSlotMargin) - invBarWidth / 2 + 2048 / 2; slot.y = -80; // above bottom edge inventoryBar.addChild(slot); invSlots.push(slot); // Touch/click to select block type (function (slot, i) { slot.down = function (x, y, obj) { selectedBlockType = slot.blockType; for (var j = 0; j < invSlots.length; j++) { invSlots[j].setSelected(j === i); } storage.selectedBlockType = selectedBlockType; }; })(slot, i); // Set initial selection if (blockTypes[i] === selectedBlockType) { slot.setSelected(true); } } // --- Instructions UI --- var instructions = new Text2("Tap/drag to place blocks\nDouble tap to remove\nUse arrows to scroll", { size: 60, fill: 0xFFFFFF }); instructions.anchor.set(0.5, 0); LK.gui.top.addChild(instructions); instructions.x = 2048 / 2; instructions.y = 120; // --- Mobile Control Buttons --- var controlsContainer = new Container(); LK.gui.bottomLeft.addChild(controlsContainer); // Create arrow buttons for camera movement var arrowButtons = []; var buttonSize = 120; var buttonMargin = 20; // Up arrow var upButton = LK.getAsset('inv_slot', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8 }); upButton.x = buttonSize + buttonMargin; upButton.y = -buttonSize * 3 - buttonMargin * 2; controlsContainer.addChild(upButton); // Down arrow var downButton = LK.getAsset('inv_slot', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8 }); downButton.x = buttonSize + buttonMargin; downButton.y = -buttonSize - buttonMargin; controlsContainer.addChild(downButton); // Left arrow var leftButton = LK.getAsset('inv_slot', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8 }); leftButton.x = buttonMargin; leftButton.y = -buttonSize * 2 - buttonMargin; controlsContainer.addChild(leftButton); // Right arrow var rightButton = LK.getAsset('inv_slot', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8 }); rightButton.x = buttonSize * 2 + buttonMargin * 2; rightButton.y = -buttonSize * 2 - buttonMargin; controlsContainer.addChild(rightButton); // Add text labels for arrows var upText = new Text2("↑", { size: 80, fill: 0xFFFFFF }); upText.anchor.set(0.5, 0.5); upText.x = upButton.x; upText.y = upButton.y; controlsContainer.addChild(upText); var downText = new Text2("↓", { size: 80, fill: 0xFFFFFF }); downText.anchor.set(0.5, 0.5); downText.x = downButton.x; downText.y = downButton.y; controlsContainer.addChild(downText); var leftText = new Text2("←", { size: 80, fill: 0xFFFFFF }); leftText.anchor.set(0.5, 0.5); leftText.x = leftButton.x; leftText.y = leftButton.y; controlsContainer.addChild(leftText); var rightText = new Text2("→", { size: 80, fill: 0xFFFFFF }); rightText.anchor.set(0.5, 0.5); rightText.x = rightButton.x; rightText.y = rightButton.y; controlsContainer.addChild(rightText); // Add touch handlers for camera movement upButton.down = function (x, y, obj) { cameraY -= 1; clampCamera(); updateBlockObjs(); storage.cameraY = cameraY; }; downButton.down = function (x, y, obj) { cameraY += 1; clampCamera(); updateBlockObjs(); storage.cameraY = cameraY; }; leftButton.down = function (x, y, obj) { cameraX -= 1; clampCamera(); updateBlockObjs(); storage.cameraX = cameraX; }; rightButton.down = function (x, y, obj) { cameraX += 1; clampCamera(); updateBlockObjs(); storage.cameraX = cameraX; }; arrowButtons.push(upButton, downButton, leftButton, rightButton); // --- World update/render --- function updateBlockObjs() { for (var y = 0; y < GRID_ROWS; y++) { for (var x = 0; x < GRID_COLS; x++) { var wx = x + cameraX; var wy = y + cameraY; var blockType = world[wy] && world[wy][wx] ? world[wy][wx] : 'air'; var block = blockObjs[y][x]; block.setGrid(wx, wy); block.visible = blockType !== 'air'; if (block.visible) { block.setType(blockType); } } } } updateBlockObjs(); // --- Touch/drag controls --- var dragMode = null; // 'place', 'remove', 'scroll' var lastTouchGrid = { x: -1, y: -1 }; var lastTouchTime = 0; var lastTouchPos = { x: 0, y: 0 }; var scrollStart = { x: 0, y: 0 }; var cameraStart = { x: 0, y: 0 }; // Convert game coordinates to grid coordinates function gameToGrid(x, y) { var gx = Math.floor((x - blockLayer.x) / BLOCK_SIZE); var gy = Math.floor((y - blockLayer.y - 100) / BLOCK_SIZE); return { x: gx, y: gy }; } // Place a block at grid (gx, gy) function placeBlock(gx, gy) { var wx = gx + cameraX; var wy = gy + cameraY; if (wx < 0 || wx >= WORLD_COLS || wy < 0 || wy >= WORLD_ROWS) return; if (world[wy][wx] === selectedBlockType) return; world[wy][wx] = selectedBlockType; updateBlockObjs(); } // Remove a block at grid (gx, gy) function removeBlock(gx, gy) { var wx = gx + cameraX; var wy = gy + cameraY; if (wx < 0 || wx >= WORLD_COLS || wy < 0 || wy >= WORLD_ROWS) return; if (world[wy][wx] === 'air') return; world[wy][wx] = 'air'; updateBlockObjs(); } // --- Main touch handlers --- game.down = function (x, y, obj) { // Check if touch is on inventory bar var guiY = y / 2732 * LK.gui.height; if (guiY > LK.gui.height - 200) { // Let inventory slots handle their own down return; } var grid = gameToGrid(x, y); lastTouchGrid.x = grid.x; lastTouchGrid.y = grid.y; lastTouchPos.x = x; lastTouchPos.y = y; scrollStart.x = x; scrollStart.y = y; cameraStart.x = cameraX; cameraStart.y = cameraY; // Double tap to remove var now = Date.now(); if (now - lastTouchTime < 350) { removeBlock(grid.x, grid.y); dragMode = 'remove'; } else { placeBlock(grid.x, grid.y); dragMode = 'place'; } lastTouchTime = now; }; game.move = function (x, y, obj) { if (dragMode === 'scroll') { // Drag to scroll var dx = x - scrollStart.x; var dy = y - scrollStart.y; cameraX = cameraStart.x - Math.round(dx / BLOCK_SIZE); cameraY = cameraStart.y - Math.round(dy / BLOCK_SIZE); clampCamera(); updateBlockObjs(); return; } var grid = gameToGrid(x, y); if (grid.x === lastTouchGrid.x && grid.y === lastTouchGrid.y) return; lastTouchGrid.x = grid.x; lastTouchGrid.y = grid.y; if (dragMode === 'place') { placeBlock(grid.x, grid.y); } else if (dragMode === 'remove') { removeBlock(grid.x, grid.y); } else { // If moved far, start scrolling var dist = Math.abs(x - scrollStart.x) + Math.abs(y - scrollStart.y); if (dist > 40) { dragMode = 'scroll'; } } }; game.up = function (x, y, obj) { dragMode = null; storage.cameraX = cameraX; storage.cameraY = cameraY; }; // --- Save selected block type and camera on exit --- LK.on('destroy', function () { storage.selectedBlockType = selectedBlockType; storage.cameraX = cameraX; storage.cameraY = cameraY; }); // --- Game update loop (not much needed) --- game.update = function () { // No per-frame logic needed for MVP };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Block class: represents a single block in the world grid
var Block = Container.expand(function () {
var self = Container.call(this);
self.blockType = 'grass'; // default, will be set on init
self.gridX = 0;
self.gridY = 0;
// The block asset
self.blockAsset = null;
self.setType = function (type) {
self.blockType = type;
if (self.blockAsset) {
self.removeChild(self.blockAsset);
}
self.blockAsset = self.attachAsset('block_' + type, {
anchorX: 0.5,
anchorY: 0.5
});
};
self.setGrid = function (x, y) {
self.gridX = x;
self.gridY = y;
};
return self;
});
// InventorySlot class: represents a selectable block type in the inventory
var InventorySlot = Container.expand(function () {
var self = Container.call(this);
self.blockType = 'grass';
self.selected = false;
// Slot background
self.bg = self.attachAsset('inv_slot', {
anchorX: 0.5,
anchorY: 0.5
});
// Block icon
self.icon = self.attachAsset('block_grass', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
// Selection highlight
self.selectHighlight = self.attachAsset('block_select', {
anchorX: 0.5,
anchorY: 0.5
});
self.selectHighlight.alpha = 0;
self.setType = function (type) {
self.blockType = type;
self.removeChild(self.icon);
self.icon = self.attachAsset('block_' + type, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
self.addChild(self.icon);
};
self.setSelected = function (sel) {
self.selected = sel;
self.selectHighlight.alpha = sel ? 0.7 : 0;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb // light blue sky
});
/****
* Game Code
****/
// --- World/grid setup ---
// Block types: grass, dirt, stone, water, wood, sand
// Each block is a colored box shape, 120x120px
// Selection highlight
// Inventory slot background
var BLOCK_SIZE = 120;
var GRID_COLS = 16; // 16x16 visible grid (1920x1920), fits on iPad Pro with some margin
var GRID_ROWS = 16;
var WORLD_COLS = 32; // world is 32x32 blocks
var WORLD_ROWS = 32;
// The world data: 2D array of block types
var world = [];
// Try to load from storage, else generate new
if (storage.world && storage.world.length === WORLD_ROWS) {
for (var y = 0; y < WORLD_ROWS; y++) {
world[y] = [];
for (var x = 0; x < WORLD_COLS; x++) {
world[y][x] = storage.world[y][x];
}
}
} else {
// Generate a simple world: grass on top, dirt below, stone at bottom, some water and sand
for (var y = 0; y < WORLD_ROWS; y++) {
world[y] = [];
for (var x = 0; x < WORLD_COLS; x++) {
if (y === WORLD_ROWS - 1) {
world[y][x] = 'stone';
} else if (y > WORLD_ROWS - 4) {
world[y][x] = 'dirt';
} else if (y === WORLD_ROWS - 4) {
world[y][x] = 'grass';
} else if (y > WORLD_ROWS - 8 && x > 5 && x < 10) {
world[y][x] = 'water';
} else if (y > WORLD_ROWS - 8 && x > 20 && x < 25) {
world[y][x] = 'sand';
} else {
world[y][x] = 'air';
}
}
}
}
// The block types available to the player
var blockTypes = ['grass', 'dirt', 'stone', 'water', 'wood', 'sand'];
var blockTypeNames = {
grass: 'Grass',
dirt: 'Dirt',
stone: 'Stone',
water: 'Water',
wood: 'Wood',
sand: 'Sand'
};
// The currently selected block type for placement
var selectedBlockType = storage.selectedBlockType || 'grass';
// Camera offset (in grid units)
// Center the camera on the world spawn (center of the world)
var defaultCameraX = Math.floor((WORLD_COLS - GRID_COLS) / 2);
var defaultCameraY = Math.floor((WORLD_ROWS - GRID_ROWS) / 2);
var cameraX = typeof storage.cameraX === "number" ? storage.cameraX : defaultCameraX;
var cameraY = typeof storage.cameraY === "number" ? storage.cameraY : defaultCameraY;
// Clamp camera to world bounds
function clampCamera() {
if (cameraX < 0) cameraX = 0;
if (cameraY < 0) cameraY = 0;
if (cameraX > WORLD_COLS - GRID_COLS) cameraX = WORLD_COLS - GRID_COLS;
if (cameraY > WORLD_ROWS - GRID_ROWS) cameraY = WORLD_ROWS - GRID_ROWS;
}
// --- Block rendering ---
var blockLayer = new Container();
game.addChild(blockLayer);
// 2D array of Block objects for visible grid
var blockObjs = [];
for (var y = 0; y < GRID_ROWS; y++) {
blockObjs[y] = [];
for (var x = 0; x < GRID_COLS; x++) {
var block = new Block();
block.x = x * BLOCK_SIZE + BLOCK_SIZE / 2;
block.y = y * BLOCK_SIZE + BLOCK_SIZE / 2 + 100; // leave space for inventory
block.setGrid(x, y);
blockLayer.addChild(block);
blockObjs[y][x] = block;
}
}
// Center the grid horizontally
blockLayer.x = (2048 - GRID_COLS * BLOCK_SIZE) / 2;
blockLayer.y = 0;
// --- Inventory UI ---
var inventoryBar = new Container();
LK.gui.bottom.addChild(inventoryBar);
var invSlots = [];
var invSlotMargin = 20;
var invBarWidth = blockTypes.length * (130 + invSlotMargin) - invSlotMargin;
for (var i = 0; i < blockTypes.length; i++) {
var slot = new InventorySlot();
slot.setType(blockTypes[i]);
slot.x = i * (130 + invSlotMargin) - invBarWidth / 2 + 2048 / 2;
slot.y = -80; // above bottom edge
inventoryBar.addChild(slot);
invSlots.push(slot);
// Touch/click to select block type
(function (slot, i) {
slot.down = function (x, y, obj) {
selectedBlockType = slot.blockType;
for (var j = 0; j < invSlots.length; j++) {
invSlots[j].setSelected(j === i);
}
storage.selectedBlockType = selectedBlockType;
};
})(slot, i);
// Set initial selection
if (blockTypes[i] === selectedBlockType) {
slot.setSelected(true);
}
}
// --- Instructions UI ---
var instructions = new Text2("Tap/drag to place blocks\nDouble tap to remove\nUse arrows to scroll", {
size: 60,
fill: 0xFFFFFF
});
instructions.anchor.set(0.5, 0);
LK.gui.top.addChild(instructions);
instructions.x = 2048 / 2;
instructions.y = 120;
// --- Mobile Control Buttons ---
var controlsContainer = new Container();
LK.gui.bottomLeft.addChild(controlsContainer);
// Create arrow buttons for camera movement
var arrowButtons = [];
var buttonSize = 120;
var buttonMargin = 20;
// Up arrow
var upButton = LK.getAsset('inv_slot', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
upButton.x = buttonSize + buttonMargin;
upButton.y = -buttonSize * 3 - buttonMargin * 2;
controlsContainer.addChild(upButton);
// Down arrow
var downButton = LK.getAsset('inv_slot', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
downButton.x = buttonSize + buttonMargin;
downButton.y = -buttonSize - buttonMargin;
controlsContainer.addChild(downButton);
// Left arrow
var leftButton = LK.getAsset('inv_slot', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
leftButton.x = buttonMargin;
leftButton.y = -buttonSize * 2 - buttonMargin;
controlsContainer.addChild(leftButton);
// Right arrow
var rightButton = LK.getAsset('inv_slot', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
rightButton.x = buttonSize * 2 + buttonMargin * 2;
rightButton.y = -buttonSize * 2 - buttonMargin;
controlsContainer.addChild(rightButton);
// Add text labels for arrows
var upText = new Text2("↑", {
size: 80,
fill: 0xFFFFFF
});
upText.anchor.set(0.5, 0.5);
upText.x = upButton.x;
upText.y = upButton.y;
controlsContainer.addChild(upText);
var downText = new Text2("↓", {
size: 80,
fill: 0xFFFFFF
});
downText.anchor.set(0.5, 0.5);
downText.x = downButton.x;
downText.y = downButton.y;
controlsContainer.addChild(downText);
var leftText = new Text2("←", {
size: 80,
fill: 0xFFFFFF
});
leftText.anchor.set(0.5, 0.5);
leftText.x = leftButton.x;
leftText.y = leftButton.y;
controlsContainer.addChild(leftText);
var rightText = new Text2("→", {
size: 80,
fill: 0xFFFFFF
});
rightText.anchor.set(0.5, 0.5);
rightText.x = rightButton.x;
rightText.y = rightButton.y;
controlsContainer.addChild(rightText);
// Add touch handlers for camera movement
upButton.down = function (x, y, obj) {
cameraY -= 1;
clampCamera();
updateBlockObjs();
storage.cameraY = cameraY;
};
downButton.down = function (x, y, obj) {
cameraY += 1;
clampCamera();
updateBlockObjs();
storage.cameraY = cameraY;
};
leftButton.down = function (x, y, obj) {
cameraX -= 1;
clampCamera();
updateBlockObjs();
storage.cameraX = cameraX;
};
rightButton.down = function (x, y, obj) {
cameraX += 1;
clampCamera();
updateBlockObjs();
storage.cameraX = cameraX;
};
arrowButtons.push(upButton, downButton, leftButton, rightButton);
// --- World update/render ---
function updateBlockObjs() {
for (var y = 0; y < GRID_ROWS; y++) {
for (var x = 0; x < GRID_COLS; x++) {
var wx = x + cameraX;
var wy = y + cameraY;
var blockType = world[wy] && world[wy][wx] ? world[wy][wx] : 'air';
var block = blockObjs[y][x];
block.setGrid(wx, wy);
block.visible = blockType !== 'air';
if (block.visible) {
block.setType(blockType);
}
}
}
}
updateBlockObjs();
// --- Touch/drag controls ---
var dragMode = null; // 'place', 'remove', 'scroll'
var lastTouchGrid = {
x: -1,
y: -1
};
var lastTouchTime = 0;
var lastTouchPos = {
x: 0,
y: 0
};
var scrollStart = {
x: 0,
y: 0
};
var cameraStart = {
x: 0,
y: 0
};
// Convert game coordinates to grid coordinates
function gameToGrid(x, y) {
var gx = Math.floor((x - blockLayer.x) / BLOCK_SIZE);
var gy = Math.floor((y - blockLayer.y - 100) / BLOCK_SIZE);
return {
x: gx,
y: gy
};
}
// Place a block at grid (gx, gy)
function placeBlock(gx, gy) {
var wx = gx + cameraX;
var wy = gy + cameraY;
if (wx < 0 || wx >= WORLD_COLS || wy < 0 || wy >= WORLD_ROWS) return;
if (world[wy][wx] === selectedBlockType) return;
world[wy][wx] = selectedBlockType;
updateBlockObjs();
}
// Remove a block at grid (gx, gy)
function removeBlock(gx, gy) {
var wx = gx + cameraX;
var wy = gy + cameraY;
if (wx < 0 || wx >= WORLD_COLS || wy < 0 || wy >= WORLD_ROWS) return;
if (world[wy][wx] === 'air') return;
world[wy][wx] = 'air';
updateBlockObjs();
}
// --- Main touch handlers ---
game.down = function (x, y, obj) {
// Check if touch is on inventory bar
var guiY = y / 2732 * LK.gui.height;
if (guiY > LK.gui.height - 200) {
// Let inventory slots handle their own down
return;
}
var grid = gameToGrid(x, y);
lastTouchGrid.x = grid.x;
lastTouchGrid.y = grid.y;
lastTouchPos.x = x;
lastTouchPos.y = y;
scrollStart.x = x;
scrollStart.y = y;
cameraStart.x = cameraX;
cameraStart.y = cameraY;
// Double tap to remove
var now = Date.now();
if (now - lastTouchTime < 350) {
removeBlock(grid.x, grid.y);
dragMode = 'remove';
} else {
placeBlock(grid.x, grid.y);
dragMode = 'place';
}
lastTouchTime = now;
};
game.move = function (x, y, obj) {
if (dragMode === 'scroll') {
// Drag to scroll
var dx = x - scrollStart.x;
var dy = y - scrollStart.y;
cameraX = cameraStart.x - Math.round(dx / BLOCK_SIZE);
cameraY = cameraStart.y - Math.round(dy / BLOCK_SIZE);
clampCamera();
updateBlockObjs();
return;
}
var grid = gameToGrid(x, y);
if (grid.x === lastTouchGrid.x && grid.y === lastTouchGrid.y) return;
lastTouchGrid.x = grid.x;
lastTouchGrid.y = grid.y;
if (dragMode === 'place') {
placeBlock(grid.x, grid.y);
} else if (dragMode === 'remove') {
removeBlock(grid.x, grid.y);
} else {
// If moved far, start scrolling
var dist = Math.abs(x - scrollStart.x) + Math.abs(y - scrollStart.y);
if (dist > 40) {
dragMode = 'scroll';
}
}
};
game.up = function (x, y, obj) {
dragMode = null;
storage.cameraX = cameraX;
storage.cameraY = cameraY;
};
// --- Save selected block type and camera on exit ---
LK.on('destroy', function () {
storage.selectedBlockType = selectedBlockType;
storage.cameraX = cameraX;
storage.cameraY = cameraY;
});
// --- Game update loop (not much needed) ---
game.update = function () {
// No per-frame logic needed for MVP
};