/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // BlockSprite class var BlockSprite = Container.expand(function () { var self = Container.call(this); self.blockType = BLOCK_AIR; self.xIndex = 0; self.yIndex = 0; self.asset = null; self.setBlock = function (type) { self.blockType = type; if (self.asset) { self.removeChild(self.asset); self.asset = null; } if (type !== BLOCK_AIR) { self.asset = self.attachAsset('block_' + type, { width: BLOCK_SIZE, height: BLOCK_SIZE, color: BLOCK_COLORS[type], shape: 'box', anchorX: 0, anchorY: 0 }); } }; return self; }); // Minimal Player class definition to fix 'Player is not defined' var Player = Container.expand(function () { var self = Container.call(this); self.xIndex = playerX; self.yIndex = playerY; // Attach player asset self.asset = self.attachAsset('player', { width: BLOCK_SIZE, height: BLOCK_SIZE, anchorX: 0, anchorY: 0 }); // Dummy update method for compatibility self.update = function () { self.x = self.xIndex * BLOCK_SIZE - cameraX; self.y = self.yIndex * BLOCK_SIZE - cameraY; }; // Dummy move method for compatibility self.move = function (dx, dy) { self.xIndex += dx; self.yIndex += dy; }; // Dummy jump method for compatibility self.jump = function () {}; // Dummy applyGravity method for compatibility self.applyGravity = function () {}; // Attack method: damages any zombie adjacent to player self.attack = function () { // Player attacks all zombies in adjacent blocks (including diagonals) for (var i = zombies.length - 1; i >= 0; i--) { var z = zombies[i]; var dx = Math.abs(z.xIndex - self.xIndex); var dy = Math.abs(z.yIndex - self.yIndex); if (dx <= 1 && dy <= 1) { // Remove zombie from game and array if (z.destroy) z.destroy(); zombies.splice(i, 1); } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x87ceeb // sky blue }); /**** * Game Code ****/ // Block types // Generate world var BLOCK_AIR = 0; var BLOCK_DIRT = 1; var BLOCK_GRASS = 2; var BLOCK_STONE = 3; var BLOCK_WOOD = 4; var BLOCK_LEAF = 5; var BLOCK_CRAFTING = 6; var BLOCK_CHEST = 7; var BLOCK_SAND = 8; // Block colors var BLOCK_COLORS = {}; BLOCK_COLORS[BLOCK_AIR] = 0x000000; // invisible BLOCK_COLORS[BLOCK_DIRT] = 0x8B5A2B; BLOCK_COLORS[BLOCK_GRASS] = 0x4CAF50; BLOCK_COLORS[BLOCK_STONE] = 0x888888; BLOCK_COLORS[BLOCK_WOOD] = 0xA0522D; BLOCK_COLORS[BLOCK_LEAF] = 0x228B22; BLOCK_COLORS[BLOCK_CRAFTING] = 0xFFD700; BLOCK_COLORS[BLOCK_CHEST] = 0xC2B280; BLOCK_COLORS[BLOCK_SAND] = 0xF4E2B2; // Block size var BLOCK_SIZE = 100; // World size (in blocks) var WORLD_WIDTH = 20; var WORLD_HEIGHT = 24; // Inventory size var INVENTORY_SIZE = 6; // Simple recipes: [ [inputBlock, count], ... ] => [outputBlock, count] var RECIPES = [{ inputs: [[BLOCK_WOOD, 2]], output: [BLOCK_CRAFTING, 1] }, { inputs: [[BLOCK_STONE, 3]], output: [BLOCK_CHEST, 1] }, { inputs: [[BLOCK_SAND, 2]], output: [BLOCK_GLASS = 9, 1] } // glass is not in world, but can be crafted ]; // Add glass to block colors BLOCK_COLORS[BLOCK_GLASS] = 0xE0FFFF; // Block names var BLOCK_NAMES = {}; BLOCK_NAMES[BLOCK_AIR] = "Air"; BLOCK_NAMES[BLOCK_DIRT] = "Dirt"; BLOCK_NAMES[BLOCK_GRASS] = "Grass"; BLOCK_NAMES[BLOCK_STONE] = "Stone"; BLOCK_NAMES[BLOCK_WOOD] = "Wood"; BLOCK_NAMES[BLOCK_LEAF] = "Leaf"; BLOCK_NAMES[BLOCK_CRAFTING] = "Crafting Table"; BLOCK_NAMES[BLOCK_CHEST] = "Chest"; BLOCK_NAMES[BLOCK_SAND] = "Sand"; BLOCK_NAMES[BLOCK_GLASS] = "Glass"; // Block can be placed var BLOCK_PLACEABLE = {}; BLOCK_PLACEABLE[BLOCK_DIRT] = true; BLOCK_PLACEABLE[BLOCK_GRASS] = true; BLOCK_PLACEABLE[BLOCK_STONE] = true; BLOCK_PLACEABLE[BLOCK_WOOD] = true; BLOCK_PLACEABLE[BLOCK_LEAF] = true; BLOCK_PLACEABLE[BLOCK_CRAFTING] = true; BLOCK_PLACEABLE[BLOCK_CHEST] = true; BLOCK_PLACEABLE[BLOCK_SAND] = true; BLOCK_PLACEABLE[BLOCK_GLASS] = true; // Block can be mined var BLOCK_MINEABLE = {}; BLOCK_MINEABLE[BLOCK_DIRT] = true; BLOCK_MINEABLE[BLOCK_GRASS] = true; BLOCK_MINEABLE[BLOCK_STONE] = true; BLOCK_MINEABLE[BLOCK_WOOD] = true; BLOCK_MINEABLE[BLOCK_LEAF] = true; BLOCK_MINEABLE[BLOCK_CRAFTING] = true; BLOCK_MINEABLE[BLOCK_CHEST] = true; BLOCK_MINEABLE[BLOCK_SAND] = true; BLOCK_MINEABLE[BLOCK_GLASS] = true; // Block drops (what you get when you mine it) var BLOCK_DROPS = {}; BLOCK_DROPS[BLOCK_GRASS] = BLOCK_DIRT; BLOCK_DROPS[BLOCK_DIRT] = BLOCK_DIRT; BLOCK_DROPS[BLOCK_STONE] = BLOCK_STONE; BLOCK_DROPS[BLOCK_WOOD] = BLOCK_WOOD; BLOCK_DROPS[BLOCK_LEAF] = BLOCK_LEAF; BLOCK_DROPS[BLOCK_CRAFTING] = BLOCK_CRAFTING; BLOCK_DROPS[BLOCK_CHEST] = BLOCK_CHEST; BLOCK_DROPS[BLOCK_SAND] = BLOCK_SAND; BLOCK_DROPS[BLOCK_GLASS] = BLOCK_GLASS; // World data: 2D array [x][y] = block type var world = []; // Block sprites: 2D array [x][y] = BlockSprite var blockSprites = []; // Zombies array var zombies = []; // Inventory: array of {type, count} var inventory = []; for (var i = 0; i < INVENTORY_SIZE; i++) inventory.push({ type: BLOCK_AIR, count: 0 }); // Selected inventory slot var selectedSlot = 0; // Player position (in block coordinates) var playerX = Math.floor(WORLD_WIDTH / 2); var playerY = 6; // Player direction (1=right, -1=left) var playerDir = 1; // Is mining/placing var isMining = false; var isPlacing = false; // Drag state var dragStartX = null; var dragStartY = null; // Camera offset (in px) var cameraX = 0; var cameraY = 0; // Day-night cycle var dayTicks = 0; var DAY_LENGTH = 1800; // 30 seconds // UI elements var inventoryNodes = []; var selectedRect = null; var blockNameText = null; var craftingPanel = null; var craftingButtons = []; var showCrafting = false; function generateWorld() { for (var x = 0; x < WORLD_WIDTH; x++) { world[x] = []; blockSprites[x] = []; for (var y = 0; y < WORLD_HEIGHT; y++) { var type = BLOCK_AIR; if (y > 18) { type = BLOCK_STONE; } else if (y > 14) { type = BLOCK_DIRT; } else if (y == 14) { type = BLOCK_GRASS; } else if (y == 13 && Math.random() < 0.2) { type = BLOCK_WOOD; } else if (y == 12 && world[x] && // check world[x] is defined world[x][13] === BLOCK_WOOD && Math.random() < 0.7) { type = BLOCK_LEAF; } else if (y == 14 && Math.random() < 0.1) { type = BLOCK_SAND; } world[x][y] = type; var block = new BlockSprite(); block.xIndex = x; block.yIndex = y; block.x = x * BLOCK_SIZE; block.y = y * BLOCK_SIZE; block.setBlock(type); blockSprites[x][y] = block; game.addChild(block); } } } // Place player var player = new Player(); game.addChild(player); // Camera function updateCamera() { // Center on player, clamp to world cameraX = player.xIndex * BLOCK_SIZE + BLOCK_SIZE / 2 - 2048 / 2; cameraY = player.yIndex * BLOCK_SIZE + BLOCK_SIZE - 1200; if (cameraX < 0) cameraX = 0; if (cameraY < 0) cameraY = 0; var maxX = WORLD_WIDTH * BLOCK_SIZE - 2048; var maxY = WORLD_HEIGHT * BLOCK_SIZE - 2732; if (cameraX > maxX) cameraX = maxX; if (cameraY > maxY) cameraY = maxY; // Move all blocks for (var x = 0; x < WORLD_WIDTH; x++) { for (var y = 0; y < WORLD_HEIGHT; y++) { blockSprites[x][y].x = x * BLOCK_SIZE - cameraX; blockSprites[x][y].y = y * BLOCK_SIZE - cameraY; } } // Player's update will set its own position if (player && player.update) player.update(); } // Inventory UI function updateInventoryUI() { for (var i = 0; i < INVENTORY_SIZE; i++) { var node = inventoryNodes[i]; var inv = inventory[i]; if (inv.type !== BLOCK_AIR) { node.visible = true; node.asset.width = 90; node.asset.height = 90; node.asset.color = BLOCK_COLORS[inv.type]; node.countText.setText(inv.count > 1 ? inv.count : ""); } else { node.visible = false; } if (i === selectedSlot) { selectedRect.x = node.x - 5; selectedRect.y = node.y - 5; selectedRect.visible = true; } } } // Show block name function showBlockName(type) { if (!blockNameText) return; blockNameText.setText(BLOCK_NAMES[type] || ""); blockNameText.visible = true; LK.setTimeout(function () { blockNameText.visible = false; }, 1000); } // Crafting UI function showCraftingPanel() { if (craftingPanel) craftingPanel.visible = true; showCrafting = true; // Update buttons for (var i = 0; i < RECIPES.length; i++) { var btn = craftingButtons[i]; var canCraft = true; for (var j = 0; j < RECIPES[i].inputs.length; j++) { var input = RECIPES[i].inputs[j]; var have = 0; for (var k = 0; k < INVENTORY_SIZE; k++) { if (inventory[k].type === input[0]) have += inventory[k].count; } if (have < input[1]) canCraft = false; } btn.alpha = canCraft ? 1 : 0.5; btn.interactive = canCraft; } } function hideCraftingPanel() { if (craftingPanel) craftingPanel.visible = false; showCrafting = false; } // Add to inventory function addToInventory(type, count) { // Try to stack for (var i = 0; i < INVENTORY_SIZE; i++) { if (inventory[i].type === type && inventory[i].count < 99) { var add = Math.min(count, 99 - inventory[i].count); inventory[i].count += add; count -= add; if (count <= 0) return true; } } // Try to add to empty slot for (var i = 0; i < INVENTORY_SIZE; i++) { if (inventory[i].type === BLOCK_AIR) { inventory[i].type = type; inventory[i].count = count; return true; } } return false; } // Remove from inventory function removeFromInventory(type, count) { for (var i = 0; i < INVENTORY_SIZE; i++) { if (inventory[i].type === type && inventory[i].count > 0) { var take = Math.min(count, inventory[i].count); inventory[i].count -= take; count -= take; if (inventory[i].count === 0) inventory[i].type = BLOCK_AIR; if (count <= 0) return true; } } return false; } // Mining/placing logic function mineBlock(x, y) { if (x < 0 || x >= WORLD_WIDTH || y < 0 || y >= WORLD_HEIGHT) return; if (!world[x]) return; var type = world[x][y]; if (type === BLOCK_AIR) return; if (!BLOCK_MINEABLE[type]) return; // Remove block world[x][y] = BLOCK_AIR; blockSprites[x][y].setBlock(BLOCK_AIR); // Drop item var drop = BLOCK_DROPS[type]; if (drop) addToInventory(drop, 1); updateInventoryUI(); showBlockName(type); } function placeBlock(x, y) { if (x < 0 || x >= WORLD_WIDTH || y < 0 || y >= WORLD_HEIGHT) return; if (world[x][y] !== BLOCK_AIR) return; var inv = inventory[selectedSlot]; if (inv.type === BLOCK_AIR) return; if (!BLOCK_PLACEABLE[inv.type]) return; // Place block world[x][y] = inv.type; blockSprites[x][y].setBlock(inv.type); removeFromInventory(inv.type, 1); updateInventoryUI(); showBlockName(inv.type); } // Touch/mouse events game.down = function (x, y, obj) { if (showCrafting) return; dragStartX = x; dragStartY = y; // Convert to world block var wx = Math.floor((x + cameraX) / BLOCK_SIZE); var wy = Math.floor((y + cameraY) / BLOCK_SIZE); // If tap on player, open crafting if standing on crafting table if (wx === playerX && wy === playerY - 1 && world[playerX][playerY - 1] === BLOCK_CRAFTING) { showCraftingPanel(); return; } // Mouse/touch controls for moving player: tap left/right block to move if (wx === player.xIndex - 1 && wy === player.yIndex) { movePlayer(-1, 0); return; } if (wx === player.xIndex + 1 && wy === player.yIndex) { movePlayer(1, 0); return; } if (wx === player.xIndex && wy === player.yIndex - 1) { movePlayer(0, -1); return; } // If tap on zombie: attack! var attackedZombie = false; for (var i = zombies.length - 1; i >= 0; i--) { var z = zombies[i]; if (z.xIndex === wx && z.yIndex === wy) { if (player && player.attack) player.attack(); attackedZombie = true; break; } } // If not attacking a zombie, mine/place as usual if (!attackedZombie && world[wx] && world[wx][wy] !== undefined) { if (world[wx][wy] !== BLOCK_AIR) { mineBlock(wx, wy); isMining = true; } else { placeBlock(wx, wy); isPlacing = true; } } }; game.up = function (x, y, obj) { isMining = false; isPlacing = false; dragStartX = null; dragStartY = null; }; game.move = function (x, y, obj) { if (showCrafting) return; if (dragStartX === null) return; var wx = Math.floor((x + cameraX) / BLOCK_SIZE); var wy = Math.floor((y + cameraY) / BLOCK_SIZE); if (isMining) { mineBlock(wx, wy); } else if (isPlacing) { placeBlock(wx, wy); } }; // Inventory tap function onInventoryTap(i) { selectedSlot = i; updateInventoryUI(); } // Crafting tap function onCraftingTap(i) { var recipe = RECIPES[i]; // Remove inputs for (var j = 0; j < recipe.inputs.length; j++) { removeFromInventory(recipe.inputs[j][0], recipe.inputs[j][1]); } // Add output addToInventory(recipe.output[0], recipe.output[1]); updateInventoryUI(); showCraftingPanel(); } // Close crafting function onCloseCrafting() { hideCraftingPanel(); } // Move player left/right/jump function movePlayer(dx, dy) { if (!player) return; if (dx !== 0) { player.move(dx, 0); } if (dy !== 0) { // Only allow jump if moving up if (dy < 0) player.jump(); } updateCamera(); } // Gravity function applyGravity() { if (!player) return; player.applyGravity(); updateCamera(); } // Day-night cycle function updateDayNight() { dayTicks++; if (dayTicks >= DAY_LENGTH) dayTicks = 0; // Fade to dark at night var t = dayTicks / DAY_LENGTH * 2 * Math.PI; var night = (Math.cos(t) + 1) / 2; var overlay = 0x000000; var alpha = 0.2 + 0.5 * night; if (!game.nightOverlay) { game.nightOverlay = LK.getAsset('nightOverlay', { width: 2048, height: 2732, color: overlay, shape: 'box', anchorX: 0, anchorY: 0 }); game.nightOverlay.alpha = alpha; game.addChild(game.nightOverlay); } game.nightOverlay.alpha = alpha; game.nightOverlay.zIndex = 9999; } // GUI function setupGUI() { // Inventory bar for (var i = 0; i < INVENTORY_SIZE; i++) { var node = new Container(); node.x = 300 + i * 130; node.y = 2632; node.asset = node.attachAsset('inv_' + i, { width: 90, height: 90, color: 0x222222, shape: 'box', anchorX: 0, anchorY: 0 }); node.countText = new Text2("", { size: 40, fill: "#fff" }); node.countText.x = 60; node.countText.y = 50; node.addChild(node.countText); node.interactive = true; (function (idx) { node.down = function (x, y, obj) { onInventoryTap(idx); }; })(i); LK.gui.bottom.addChild(node); inventoryNodes.push(node); } // Selected rect selectedRect = LK.getAsset('selectedRect', { width: 100, height: 100, color: 0xffff00, shape: 'box', anchorX: 0, anchorY: 0 }); selectedRect.alpha = 0.3; LK.gui.bottom.addChild(selectedRect); // Block name text blockNameText = new Text2("", { size: 60, fill: "#fff" }); blockNameText.x = 1024; blockNameText.y = 100; blockNameText.anchor.set(0.5, 0); LK.gui.top.addChild(blockNameText); // Crafting panel craftingPanel = new Container(); craftingPanel.x = 400; craftingPanel.y = 800; var panelBg = craftingPanel.attachAsset('craftingBg', { width: 1200, height: 900, color: 0x333333, shape: 'box', anchorX: 0, anchorY: 0 }); var title = new Text2("Crafting", { size: 80, fill: "#fff" }); title.x = 600; title.y = 60; title.anchor.set(0.5, 0); craftingPanel.addChild(title); // Recipe buttons for (var i = 0; i < RECIPES.length; i++) { var btn = new Container(); btn.x = 100; btn.y = 180 + i * 200; btn.bg = btn.attachAsset('craftBtn' + i, { width: 1000, height: 160, color: 0x555555, shape: 'box', anchorX: 0, anchorY: 0 }); var txt = new Text2("Craft " + BLOCK_NAMES[RECIPES[i].output[0]], { size: 50, fill: "#fff" }); txt.x = 500; txt.y = 40; txt.anchor.set(0.5, 0); btn.addChild(txt); btn.interactive = true; (function (idx) { btn.down = function (x, y, obj) { onCraftingTap(idx); }; })(i); craftingPanel.addChild(btn); craftingButtons.push(btn); } // Close button var closeBtn = new Container(); closeBtn.x = 1050; closeBtn.y = 60; closeBtn.bg = closeBtn.attachAsset('closeBtn', { width: 100, height: 100, color: 0xaa2222, shape: 'box', anchorX: 0, anchorY: 0 }); var closeTxt = new Text2("X", { size: 70, fill: "#fff" }); closeTxt.x = 50; closeTxt.y = 15; closeTxt.anchor.set(0.5, 0); closeBtn.addChild(closeTxt); closeBtn.interactive = true; closeBtn.down = function (x, y, obj) { onCloseCrafting(); }; craftingPanel.addChild(closeBtn); craftingPanel.visible = false; LK.gui.center.addChild(craftingPanel); } // Controls function setupControls() { // Left button var leftBtn = new Container(); leftBtn.x = 100; leftBtn.y = 2500; leftBtn.bg = leftBtn.attachAsset('leftBtn', { width: 120, height: 120, color: 0x444444, shape: 'box', anchorX: 0, anchorY: 0 }); var ltxt = new Text2("<", { size: 80, fill: "#fff" }); ltxt.x = 60; ltxt.y = 20; ltxt.anchor.set(0.5, 0); leftBtn.addChild(ltxt); leftBtn.interactive = true; leftBtn.down = function (x, y, obj) { movePlayer(-1, 0); }; LK.gui.bottomLeft.addChild(leftBtn); // Right button var rightBtn = new Container(); rightBtn.x = 350; rightBtn.y = 2500; rightBtn.bg = rightBtn.attachAsset('rightBtn', { width: 120, height: 120, color: 0x444444, shape: 'box', anchorX: 0, anchorY: 0 }); var rtxt = new Text2(">", { size: 80, fill: "#fff" }); rtxt.x = 60; rtxt.y = 20; rtxt.anchor.set(0.5, 0); rightBtn.addChild(rtxt); rightBtn.interactive = true; rightBtn.down = function (x, y, obj) { movePlayer(1, 0); }; LK.gui.bottomLeft.addChild(rightBtn); // Jump button var jumpBtn = new Container(); jumpBtn.x = 1800; jumpBtn.y = 2500; jumpBtn.bg = jumpBtn.attachAsset('jumpBtn', { width: 120, height: 120, color: 0x444444, shape: 'box', anchorX: 0, anchorY: 0 }); var jtxt = new Text2("^", { size: 80, fill: "#fff" }); jtxt.x = 60; jtxt.y = 20; jtxt.anchor.set(0.5, 0); jumpBtn.addChild(jtxt); jumpBtn.interactive = true; jumpBtn.down = function (x, y, obj) { movePlayer(0, -1); }; LK.gui.bottomRight.addChild(jumpBtn); } // Main update game.update = function () { // Gravity applyGravity(); // Zombies update for (var i = 0; i < zombies.length; i++) { var z = zombies[i]; // Save last position for event logic z.lastXIndex = z.xIndex; z.lastYIndex = z.yIndex; var wasTouching = z.lastWasTouchingPlayer; // AI and gravity z.ai(); z.applyGravity(); if (z.update) z.update(); // Check collision with player (only trigger on first frame of contact) var touching = z.isTouchingPlayer(); if (!wasTouching && touching) { // Flash screen red and show game over LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); } z.lastWasTouchingPlayer = touching; } // Camera updateCamera(); // Day-night updateDayNight(); }; // Init generateWorld(); if (player && player.update) player.update(); updateCamera(); setupGUI(); setupControls(); updateInventoryUI();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// BlockSprite class
var BlockSprite = Container.expand(function () {
var self = Container.call(this);
self.blockType = BLOCK_AIR;
self.xIndex = 0;
self.yIndex = 0;
self.asset = null;
self.setBlock = function (type) {
self.blockType = type;
if (self.asset) {
self.removeChild(self.asset);
self.asset = null;
}
if (type !== BLOCK_AIR) {
self.asset = self.attachAsset('block_' + type, {
width: BLOCK_SIZE,
height: BLOCK_SIZE,
color: BLOCK_COLORS[type],
shape: 'box',
anchorX: 0,
anchorY: 0
});
}
};
return self;
});
// Minimal Player class definition to fix 'Player is not defined'
var Player = Container.expand(function () {
var self = Container.call(this);
self.xIndex = playerX;
self.yIndex = playerY;
// Attach player asset
self.asset = self.attachAsset('player', {
width: BLOCK_SIZE,
height: BLOCK_SIZE,
anchorX: 0,
anchorY: 0
});
// Dummy update method for compatibility
self.update = function () {
self.x = self.xIndex * BLOCK_SIZE - cameraX;
self.y = self.yIndex * BLOCK_SIZE - cameraY;
};
// Dummy move method for compatibility
self.move = function (dx, dy) {
self.xIndex += dx;
self.yIndex += dy;
};
// Dummy jump method for compatibility
self.jump = function () {};
// Dummy applyGravity method for compatibility
self.applyGravity = function () {};
// Attack method: damages any zombie adjacent to player
self.attack = function () {
// Player attacks all zombies in adjacent blocks (including diagonals)
for (var i = zombies.length - 1; i >= 0; i--) {
var z = zombies[i];
var dx = Math.abs(z.xIndex - self.xIndex);
var dy = Math.abs(z.yIndex - self.yIndex);
if (dx <= 1 && dy <= 1) {
// Remove zombie from game and array
if (z.destroy) z.destroy();
zombies.splice(i, 1);
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb // sky blue
});
/****
* Game Code
****/
// Block types
// Generate world
var BLOCK_AIR = 0;
var BLOCK_DIRT = 1;
var BLOCK_GRASS = 2;
var BLOCK_STONE = 3;
var BLOCK_WOOD = 4;
var BLOCK_LEAF = 5;
var BLOCK_CRAFTING = 6;
var BLOCK_CHEST = 7;
var BLOCK_SAND = 8;
// Block colors
var BLOCK_COLORS = {};
BLOCK_COLORS[BLOCK_AIR] = 0x000000; // invisible
BLOCK_COLORS[BLOCK_DIRT] = 0x8B5A2B;
BLOCK_COLORS[BLOCK_GRASS] = 0x4CAF50;
BLOCK_COLORS[BLOCK_STONE] = 0x888888;
BLOCK_COLORS[BLOCK_WOOD] = 0xA0522D;
BLOCK_COLORS[BLOCK_LEAF] = 0x228B22;
BLOCK_COLORS[BLOCK_CRAFTING] = 0xFFD700;
BLOCK_COLORS[BLOCK_CHEST] = 0xC2B280;
BLOCK_COLORS[BLOCK_SAND] = 0xF4E2B2;
// Block size
var BLOCK_SIZE = 100;
// World size (in blocks)
var WORLD_WIDTH = 20;
var WORLD_HEIGHT = 24;
// Inventory size
var INVENTORY_SIZE = 6;
// Simple recipes: [ [inputBlock, count], ... ] => [outputBlock, count]
var RECIPES = [{
inputs: [[BLOCK_WOOD, 2]],
output: [BLOCK_CRAFTING, 1]
}, {
inputs: [[BLOCK_STONE, 3]],
output: [BLOCK_CHEST, 1]
}, {
inputs: [[BLOCK_SAND, 2]],
output: [BLOCK_GLASS = 9, 1]
} // glass is not in world, but can be crafted
];
// Add glass to block colors
BLOCK_COLORS[BLOCK_GLASS] = 0xE0FFFF;
// Block names
var BLOCK_NAMES = {};
BLOCK_NAMES[BLOCK_AIR] = "Air";
BLOCK_NAMES[BLOCK_DIRT] = "Dirt";
BLOCK_NAMES[BLOCK_GRASS] = "Grass";
BLOCK_NAMES[BLOCK_STONE] = "Stone";
BLOCK_NAMES[BLOCK_WOOD] = "Wood";
BLOCK_NAMES[BLOCK_LEAF] = "Leaf";
BLOCK_NAMES[BLOCK_CRAFTING] = "Crafting Table";
BLOCK_NAMES[BLOCK_CHEST] = "Chest";
BLOCK_NAMES[BLOCK_SAND] = "Sand";
BLOCK_NAMES[BLOCK_GLASS] = "Glass";
// Block can be placed
var BLOCK_PLACEABLE = {};
BLOCK_PLACEABLE[BLOCK_DIRT] = true;
BLOCK_PLACEABLE[BLOCK_GRASS] = true;
BLOCK_PLACEABLE[BLOCK_STONE] = true;
BLOCK_PLACEABLE[BLOCK_WOOD] = true;
BLOCK_PLACEABLE[BLOCK_LEAF] = true;
BLOCK_PLACEABLE[BLOCK_CRAFTING] = true;
BLOCK_PLACEABLE[BLOCK_CHEST] = true;
BLOCK_PLACEABLE[BLOCK_SAND] = true;
BLOCK_PLACEABLE[BLOCK_GLASS] = true;
// Block can be mined
var BLOCK_MINEABLE = {};
BLOCK_MINEABLE[BLOCK_DIRT] = true;
BLOCK_MINEABLE[BLOCK_GRASS] = true;
BLOCK_MINEABLE[BLOCK_STONE] = true;
BLOCK_MINEABLE[BLOCK_WOOD] = true;
BLOCK_MINEABLE[BLOCK_LEAF] = true;
BLOCK_MINEABLE[BLOCK_CRAFTING] = true;
BLOCK_MINEABLE[BLOCK_CHEST] = true;
BLOCK_MINEABLE[BLOCK_SAND] = true;
BLOCK_MINEABLE[BLOCK_GLASS] = true;
// Block drops (what you get when you mine it)
var BLOCK_DROPS = {};
BLOCK_DROPS[BLOCK_GRASS] = BLOCK_DIRT;
BLOCK_DROPS[BLOCK_DIRT] = BLOCK_DIRT;
BLOCK_DROPS[BLOCK_STONE] = BLOCK_STONE;
BLOCK_DROPS[BLOCK_WOOD] = BLOCK_WOOD;
BLOCK_DROPS[BLOCK_LEAF] = BLOCK_LEAF;
BLOCK_DROPS[BLOCK_CRAFTING] = BLOCK_CRAFTING;
BLOCK_DROPS[BLOCK_CHEST] = BLOCK_CHEST;
BLOCK_DROPS[BLOCK_SAND] = BLOCK_SAND;
BLOCK_DROPS[BLOCK_GLASS] = BLOCK_GLASS;
// World data: 2D array [x][y] = block type
var world = [];
// Block sprites: 2D array [x][y] = BlockSprite
var blockSprites = [];
// Zombies array
var zombies = [];
// Inventory: array of {type, count}
var inventory = [];
for (var i = 0; i < INVENTORY_SIZE; i++) inventory.push({
type: BLOCK_AIR,
count: 0
});
// Selected inventory slot
var selectedSlot = 0;
// Player position (in block coordinates)
var playerX = Math.floor(WORLD_WIDTH / 2);
var playerY = 6;
// Player direction (1=right, -1=left)
var playerDir = 1;
// Is mining/placing
var isMining = false;
var isPlacing = false;
// Drag state
var dragStartX = null;
var dragStartY = null;
// Camera offset (in px)
var cameraX = 0;
var cameraY = 0;
// Day-night cycle
var dayTicks = 0;
var DAY_LENGTH = 1800; // 30 seconds
// UI elements
var inventoryNodes = [];
var selectedRect = null;
var blockNameText = null;
var craftingPanel = null;
var craftingButtons = [];
var showCrafting = false;
function generateWorld() {
for (var x = 0; x < WORLD_WIDTH; x++) {
world[x] = [];
blockSprites[x] = [];
for (var y = 0; y < WORLD_HEIGHT; y++) {
var type = BLOCK_AIR;
if (y > 18) {
type = BLOCK_STONE;
} else if (y > 14) {
type = BLOCK_DIRT;
} else if (y == 14) {
type = BLOCK_GRASS;
} else if (y == 13 && Math.random() < 0.2) {
type = BLOCK_WOOD;
} else if (y == 12 && world[x] &&
// check world[x] is defined
world[x][13] === BLOCK_WOOD && Math.random() < 0.7) {
type = BLOCK_LEAF;
} else if (y == 14 && Math.random() < 0.1) {
type = BLOCK_SAND;
}
world[x][y] = type;
var block = new BlockSprite();
block.xIndex = x;
block.yIndex = y;
block.x = x * BLOCK_SIZE;
block.y = y * BLOCK_SIZE;
block.setBlock(type);
blockSprites[x][y] = block;
game.addChild(block);
}
}
}
// Place player
var player = new Player();
game.addChild(player);
// Camera
function updateCamera() {
// Center on player, clamp to world
cameraX = player.xIndex * BLOCK_SIZE + BLOCK_SIZE / 2 - 2048 / 2;
cameraY = player.yIndex * BLOCK_SIZE + BLOCK_SIZE - 1200;
if (cameraX < 0) cameraX = 0;
if (cameraY < 0) cameraY = 0;
var maxX = WORLD_WIDTH * BLOCK_SIZE - 2048;
var maxY = WORLD_HEIGHT * BLOCK_SIZE - 2732;
if (cameraX > maxX) cameraX = maxX;
if (cameraY > maxY) cameraY = maxY;
// Move all blocks
for (var x = 0; x < WORLD_WIDTH; x++) {
for (var y = 0; y < WORLD_HEIGHT; y++) {
blockSprites[x][y].x = x * BLOCK_SIZE - cameraX;
blockSprites[x][y].y = y * BLOCK_SIZE - cameraY;
}
}
// Player's update will set its own position
if (player && player.update) player.update();
}
// Inventory UI
function updateInventoryUI() {
for (var i = 0; i < INVENTORY_SIZE; i++) {
var node = inventoryNodes[i];
var inv = inventory[i];
if (inv.type !== BLOCK_AIR) {
node.visible = true;
node.asset.width = 90;
node.asset.height = 90;
node.asset.color = BLOCK_COLORS[inv.type];
node.countText.setText(inv.count > 1 ? inv.count : "");
} else {
node.visible = false;
}
if (i === selectedSlot) {
selectedRect.x = node.x - 5;
selectedRect.y = node.y - 5;
selectedRect.visible = true;
}
}
}
// Show block name
function showBlockName(type) {
if (!blockNameText) return;
blockNameText.setText(BLOCK_NAMES[type] || "");
blockNameText.visible = true;
LK.setTimeout(function () {
blockNameText.visible = false;
}, 1000);
}
// Crafting UI
function showCraftingPanel() {
if (craftingPanel) craftingPanel.visible = true;
showCrafting = true;
// Update buttons
for (var i = 0; i < RECIPES.length; i++) {
var btn = craftingButtons[i];
var canCraft = true;
for (var j = 0; j < RECIPES[i].inputs.length; j++) {
var input = RECIPES[i].inputs[j];
var have = 0;
for (var k = 0; k < INVENTORY_SIZE; k++) {
if (inventory[k].type === input[0]) have += inventory[k].count;
}
if (have < input[1]) canCraft = false;
}
btn.alpha = canCraft ? 1 : 0.5;
btn.interactive = canCraft;
}
}
function hideCraftingPanel() {
if (craftingPanel) craftingPanel.visible = false;
showCrafting = false;
}
// Add to inventory
function addToInventory(type, count) {
// Try to stack
for (var i = 0; i < INVENTORY_SIZE; i++) {
if (inventory[i].type === type && inventory[i].count < 99) {
var add = Math.min(count, 99 - inventory[i].count);
inventory[i].count += add;
count -= add;
if (count <= 0) return true;
}
}
// Try to add to empty slot
for (var i = 0; i < INVENTORY_SIZE; i++) {
if (inventory[i].type === BLOCK_AIR) {
inventory[i].type = type;
inventory[i].count = count;
return true;
}
}
return false;
}
// Remove from inventory
function removeFromInventory(type, count) {
for (var i = 0; i < INVENTORY_SIZE; i++) {
if (inventory[i].type === type && inventory[i].count > 0) {
var take = Math.min(count, inventory[i].count);
inventory[i].count -= take;
count -= take;
if (inventory[i].count === 0) inventory[i].type = BLOCK_AIR;
if (count <= 0) return true;
}
}
return false;
}
// Mining/placing logic
function mineBlock(x, y) {
if (x < 0 || x >= WORLD_WIDTH || y < 0 || y >= WORLD_HEIGHT) return;
if (!world[x]) return;
var type = world[x][y];
if (type === BLOCK_AIR) return;
if (!BLOCK_MINEABLE[type]) return;
// Remove block
world[x][y] = BLOCK_AIR;
blockSprites[x][y].setBlock(BLOCK_AIR);
// Drop item
var drop = BLOCK_DROPS[type];
if (drop) addToInventory(drop, 1);
updateInventoryUI();
showBlockName(type);
}
function placeBlock(x, y) {
if (x < 0 || x >= WORLD_WIDTH || y < 0 || y >= WORLD_HEIGHT) return;
if (world[x][y] !== BLOCK_AIR) return;
var inv = inventory[selectedSlot];
if (inv.type === BLOCK_AIR) return;
if (!BLOCK_PLACEABLE[inv.type]) return;
// Place block
world[x][y] = inv.type;
blockSprites[x][y].setBlock(inv.type);
removeFromInventory(inv.type, 1);
updateInventoryUI();
showBlockName(inv.type);
}
// Touch/mouse events
game.down = function (x, y, obj) {
if (showCrafting) return;
dragStartX = x;
dragStartY = y;
// Convert to world block
var wx = Math.floor((x + cameraX) / BLOCK_SIZE);
var wy = Math.floor((y + cameraY) / BLOCK_SIZE);
// If tap on player, open crafting if standing on crafting table
if (wx === playerX && wy === playerY - 1 && world[playerX][playerY - 1] === BLOCK_CRAFTING) {
showCraftingPanel();
return;
}
// Mouse/touch controls for moving player: tap left/right block to move
if (wx === player.xIndex - 1 && wy === player.yIndex) {
movePlayer(-1, 0);
return;
}
if (wx === player.xIndex + 1 && wy === player.yIndex) {
movePlayer(1, 0);
return;
}
if (wx === player.xIndex && wy === player.yIndex - 1) {
movePlayer(0, -1);
return;
}
// If tap on zombie: attack!
var attackedZombie = false;
for (var i = zombies.length - 1; i >= 0; i--) {
var z = zombies[i];
if (z.xIndex === wx && z.yIndex === wy) {
if (player && player.attack) player.attack();
attackedZombie = true;
break;
}
}
// If not attacking a zombie, mine/place as usual
if (!attackedZombie && world[wx] && world[wx][wy] !== undefined) {
if (world[wx][wy] !== BLOCK_AIR) {
mineBlock(wx, wy);
isMining = true;
} else {
placeBlock(wx, wy);
isPlacing = true;
}
}
};
game.up = function (x, y, obj) {
isMining = false;
isPlacing = false;
dragStartX = null;
dragStartY = null;
};
game.move = function (x, y, obj) {
if (showCrafting) return;
if (dragStartX === null) return;
var wx = Math.floor((x + cameraX) / BLOCK_SIZE);
var wy = Math.floor((y + cameraY) / BLOCK_SIZE);
if (isMining) {
mineBlock(wx, wy);
} else if (isPlacing) {
placeBlock(wx, wy);
}
};
// Inventory tap
function onInventoryTap(i) {
selectedSlot = i;
updateInventoryUI();
}
// Crafting tap
function onCraftingTap(i) {
var recipe = RECIPES[i];
// Remove inputs
for (var j = 0; j < recipe.inputs.length; j++) {
removeFromInventory(recipe.inputs[j][0], recipe.inputs[j][1]);
}
// Add output
addToInventory(recipe.output[0], recipe.output[1]);
updateInventoryUI();
showCraftingPanel();
}
// Close crafting
function onCloseCrafting() {
hideCraftingPanel();
}
// Move player left/right/jump
function movePlayer(dx, dy) {
if (!player) return;
if (dx !== 0) {
player.move(dx, 0);
}
if (dy !== 0) {
// Only allow jump if moving up
if (dy < 0) player.jump();
}
updateCamera();
}
// Gravity
function applyGravity() {
if (!player) return;
player.applyGravity();
updateCamera();
}
// Day-night cycle
function updateDayNight() {
dayTicks++;
if (dayTicks >= DAY_LENGTH) dayTicks = 0;
// Fade to dark at night
var t = dayTicks / DAY_LENGTH * 2 * Math.PI;
var night = (Math.cos(t) + 1) / 2;
var overlay = 0x000000;
var alpha = 0.2 + 0.5 * night;
if (!game.nightOverlay) {
game.nightOverlay = LK.getAsset('nightOverlay', {
width: 2048,
height: 2732,
color: overlay,
shape: 'box',
anchorX: 0,
anchorY: 0
});
game.nightOverlay.alpha = alpha;
game.addChild(game.nightOverlay);
}
game.nightOverlay.alpha = alpha;
game.nightOverlay.zIndex = 9999;
}
// GUI
function setupGUI() {
// Inventory bar
for (var i = 0; i < INVENTORY_SIZE; i++) {
var node = new Container();
node.x = 300 + i * 130;
node.y = 2632;
node.asset = node.attachAsset('inv_' + i, {
width: 90,
height: 90,
color: 0x222222,
shape: 'box',
anchorX: 0,
anchorY: 0
});
node.countText = new Text2("", {
size: 40,
fill: "#fff"
});
node.countText.x = 60;
node.countText.y = 50;
node.addChild(node.countText);
node.interactive = true;
(function (idx) {
node.down = function (x, y, obj) {
onInventoryTap(idx);
};
})(i);
LK.gui.bottom.addChild(node);
inventoryNodes.push(node);
}
// Selected rect
selectedRect = LK.getAsset('selectedRect', {
width: 100,
height: 100,
color: 0xffff00,
shape: 'box',
anchorX: 0,
anchorY: 0
});
selectedRect.alpha = 0.3;
LK.gui.bottom.addChild(selectedRect);
// Block name text
blockNameText = new Text2("", {
size: 60,
fill: "#fff"
});
blockNameText.x = 1024;
blockNameText.y = 100;
blockNameText.anchor.set(0.5, 0);
LK.gui.top.addChild(blockNameText);
// Crafting panel
craftingPanel = new Container();
craftingPanel.x = 400;
craftingPanel.y = 800;
var panelBg = craftingPanel.attachAsset('craftingBg', {
width: 1200,
height: 900,
color: 0x333333,
shape: 'box',
anchorX: 0,
anchorY: 0
});
var title = new Text2("Crafting", {
size: 80,
fill: "#fff"
});
title.x = 600;
title.y = 60;
title.anchor.set(0.5, 0);
craftingPanel.addChild(title);
// Recipe buttons
for (var i = 0; i < RECIPES.length; i++) {
var btn = new Container();
btn.x = 100;
btn.y = 180 + i * 200;
btn.bg = btn.attachAsset('craftBtn' + i, {
width: 1000,
height: 160,
color: 0x555555,
shape: 'box',
anchorX: 0,
anchorY: 0
});
var txt = new Text2("Craft " + BLOCK_NAMES[RECIPES[i].output[0]], {
size: 50,
fill: "#fff"
});
txt.x = 500;
txt.y = 40;
txt.anchor.set(0.5, 0);
btn.addChild(txt);
btn.interactive = true;
(function (idx) {
btn.down = function (x, y, obj) {
onCraftingTap(idx);
};
})(i);
craftingPanel.addChild(btn);
craftingButtons.push(btn);
}
// Close button
var closeBtn = new Container();
closeBtn.x = 1050;
closeBtn.y = 60;
closeBtn.bg = closeBtn.attachAsset('closeBtn', {
width: 100,
height: 100,
color: 0xaa2222,
shape: 'box',
anchorX: 0,
anchorY: 0
});
var closeTxt = new Text2("X", {
size: 70,
fill: "#fff"
});
closeTxt.x = 50;
closeTxt.y = 15;
closeTxt.anchor.set(0.5, 0);
closeBtn.addChild(closeTxt);
closeBtn.interactive = true;
closeBtn.down = function (x, y, obj) {
onCloseCrafting();
};
craftingPanel.addChild(closeBtn);
craftingPanel.visible = false;
LK.gui.center.addChild(craftingPanel);
}
// Controls
function setupControls() {
// Left button
var leftBtn = new Container();
leftBtn.x = 100;
leftBtn.y = 2500;
leftBtn.bg = leftBtn.attachAsset('leftBtn', {
width: 120,
height: 120,
color: 0x444444,
shape: 'box',
anchorX: 0,
anchorY: 0
});
var ltxt = new Text2("<", {
size: 80,
fill: "#fff"
});
ltxt.x = 60;
ltxt.y = 20;
ltxt.anchor.set(0.5, 0);
leftBtn.addChild(ltxt);
leftBtn.interactive = true;
leftBtn.down = function (x, y, obj) {
movePlayer(-1, 0);
};
LK.gui.bottomLeft.addChild(leftBtn);
// Right button
var rightBtn = new Container();
rightBtn.x = 350;
rightBtn.y = 2500;
rightBtn.bg = rightBtn.attachAsset('rightBtn', {
width: 120,
height: 120,
color: 0x444444,
shape: 'box',
anchorX: 0,
anchorY: 0
});
var rtxt = new Text2(">", {
size: 80,
fill: "#fff"
});
rtxt.x = 60;
rtxt.y = 20;
rtxt.anchor.set(0.5, 0);
rightBtn.addChild(rtxt);
rightBtn.interactive = true;
rightBtn.down = function (x, y, obj) {
movePlayer(1, 0);
};
LK.gui.bottomLeft.addChild(rightBtn);
// Jump button
var jumpBtn = new Container();
jumpBtn.x = 1800;
jumpBtn.y = 2500;
jumpBtn.bg = jumpBtn.attachAsset('jumpBtn', {
width: 120,
height: 120,
color: 0x444444,
shape: 'box',
anchorX: 0,
anchorY: 0
});
var jtxt = new Text2("^", {
size: 80,
fill: "#fff"
});
jtxt.x = 60;
jtxt.y = 20;
jtxt.anchor.set(0.5, 0);
jumpBtn.addChild(jtxt);
jumpBtn.interactive = true;
jumpBtn.down = function (x, y, obj) {
movePlayer(0, -1);
};
LK.gui.bottomRight.addChild(jumpBtn);
}
// Main update
game.update = function () {
// Gravity
applyGravity();
// Zombies update
for (var i = 0; i < zombies.length; i++) {
var z = zombies[i];
// Save last position for event logic
z.lastXIndex = z.xIndex;
z.lastYIndex = z.yIndex;
var wasTouching = z.lastWasTouchingPlayer;
// AI and gravity
z.ai();
z.applyGravity();
if (z.update) z.update();
// Check collision with player (only trigger on first frame of contact)
var touching = z.isTouchingPlayer();
if (!wasTouching && touching) {
// Flash screen red and show game over
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
}
z.lastWasTouchingPlayer = touching;
}
// Camera
updateCamera();
// Day-night
updateDayNight();
};
// Init
generateWorld();
if (player && player.update) player.update();
updateCamera();
setupGUI();
setupControls();
updateInventoryUI();