/****
* 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
};