diff --git a/index.html b/index.html
index 823ae6e..663b52b 100644
--- a/index.html
+++ b/index.html
@@ -16,6 +16,8 @@
+
+
diff --git a/script.js b/script.js
index bb67325..fa0cf30 100644
--- a/script.js
+++ b/script.js
@@ -10,6 +10,9 @@ const DIRT_COLOR = '#8B4513';
const STONE_COLOR = '#A9A9A9';
const GRASS_COLOR = '#7CFC00';
const WOOD_COLOR = '#8B5A2B';
+const SEED_COLOR = '#654321';
+const FLOWER_COLORS = ['#FF0000', '#FFFF00', '#FF00FF', '#FFA500', '#FFFFFF', '#00FFFF'];
+const LEAF_COLOR = '#228B22';
// Element types
const EMPTY = 0;
@@ -20,6 +23,11 @@ const DIRT = 4;
const STONE = 5;
const GRASS = 6;
const WOOD = 7;
+const SEED = 8;
+const GRASS_BLADE = 9;
+const FLOWER = 10;
+const TREE_SEED = 11;
+const LEAF = 12;
// Global variables
let canvas, ctx;
@@ -35,6 +43,7 @@ let worldOffsetY = 0;
let worldOffsetXBeforeDrag = 0;
let worldOffsetYBeforeDrag = 0;
let chunks = new Map(); // Map to store chunks with key "x,y"
+let metadata = new Map(); // Map to store metadata for pixels
let debugMode = false;
// Initialize the simulation
@@ -53,6 +62,8 @@ window.onload = function() {
document.getElementById('stone-btn').addEventListener('click', () => setTool(STONE));
document.getElementById('grass-btn').addEventListener('click', () => setTool(GRASS));
document.getElementById('wood-btn').addEventListener('click', () => setTool(WOOD));
+ document.getElementById('seed-btn').addEventListener('click', () => setTool(SEED));
+ document.getElementById('tree-seed-btn').addEventListener('click', () => setTool(TREE_SEED));
document.getElementById('eraser-btn').addEventListener('click', () => setTool(EMPTY));
// Navigation controls
@@ -101,6 +112,10 @@ function setTool(tool) {
document.getElementById('grass-btn').classList.add('active');
} else if (tool === WOOD) {
document.getElementById('wood-btn').classList.add('active');
+ } else if (tool === SEED) {
+ document.getElementById('seed-btn').classList.add('active');
+ } else if (tool === TREE_SEED) {
+ document.getElementById('tree-seed-btn').classList.add('active');
} else if (tool === EMPTY) {
document.getElementById('eraser-btn').classList.add('active');
}
@@ -174,7 +189,22 @@ function draw(x, y) {
// Draw a small brush (3x3)
for (let dy = -1; dy <= 1; dy++) {
for (let dx = -1; dx <= 1; dx++) {
- setPixel(worldX + dx, worldY + dy, currentTool);
+ const pixelX = worldX + dx;
+ const pixelY = worldY + dy;
+
+ setPixel(pixelX, pixelY, currentTool);
+
+ // Add metadata for special types
+ if (currentTool === SEED) {
+ setMetadata(pixelX, pixelY, { type: 'regular' });
+ } else if (currentTool === FLOWER) {
+ setMetadata(pixelX, pixelY, {
+ type: 'flower',
+ color: FLOWER_COLORS[Math.floor(Math.random() * FLOWER_COLORS.length)],
+ age: 0,
+ height: 1
+ });
+ }
}
}
}
@@ -301,6 +331,31 @@ function getPixel(worldX, worldY) {
return chunk[index];
}
+// Metadata functions to store additional information about pixels
+function setMetadata(worldX, worldY, data) {
+ const key = `${worldX},${worldY}`;
+ metadata.set(key, data);
+}
+
+function getMetadata(worldX, worldY) {
+ const key = `${worldX},${worldY}`;
+ return metadata.get(key);
+}
+
+function removeMetadata(worldX, worldY) {
+ const key = `${worldX},${worldY}`;
+ metadata.delete(key);
+}
+
+// Move metadata when a pixel moves
+function moveMetadata(fromX, fromY, toX, toY) {
+ const data = getMetadata(fromX, fromY);
+ if (data) {
+ setMetadata(toX, toY, data);
+ removeMetadata(fromX, fromY);
+ }
+}
+
function simulationLoop(timestamp) {
// Calculate FPS
const deltaTime = timestamp - lastFrameTime;
@@ -337,7 +392,7 @@ function updatePhysics() {
const index = y * CHUNK_SIZE + x;
const type = chunk[index];
- if (type === EMPTY || type === STONE || type === WOOD) continue;
+ if (type === EMPTY || type === STONE || type === WOOD || type === LEAF) continue;
const worldX = chunkX * CHUNK_SIZE + x;
const worldY = chunkY * CHUNK_SIZE + y;
@@ -350,6 +405,14 @@ function updatePhysics() {
updateDirt(worldX, worldY);
} else if (type === GRASS) {
updateGrass(worldX, worldY);
+ } else if (type === SEED) {
+ updateSeed(worldX, worldY);
+ } else if (type === GRASS_BLADE) {
+ updateGrassBlade(worldX, worldY);
+ } else if (type === FLOWER) {
+ updateFlower(worldX, worldY);
+ } else if (type === TREE_SEED) {
+ updateTreeSeed(worldX, worldY);
}
}
}
@@ -403,6 +466,21 @@ function updateDirt(x, y) {
if (getPixel(x, y - 1) === EMPTY && Math.random() < 0.001) {
setPixel(x, y, GRASS);
}
+
+ // Dirt can randomly spawn seeds if exposed to air above
+ if (getPixel(x, y - 1) === EMPTY) {
+ // Spawn different types of seeds with different probabilities
+ const seedRoll = Math.random();
+ if (seedRoll < 0.0002) { // Grass blade seed (most common)
+ setPixel(x, y - 1, SEED);
+ } else if (seedRoll < 0.00025) { // Flower seed (less common)
+ setPixel(x, y - 1, SEED);
+ // Mark this seed as a flower seed (will be handled in updateSeed)
+ setMetadata(x, y - 1, { type: 'flower' });
+ } else if (seedRoll < 0.00026) { // Tree seed (rare)
+ setPixel(x, y - 1, TREE_SEED);
+ }
+ }
}
function updateGrass(x, y) {
@@ -443,6 +521,197 @@ function updateGrass(x, y) {
}
}
+// New functions for plant growth
+function updateSeed(x, y) {
+ // Seeds fall like sand
+ if (getPixel(x, y + 1) === EMPTY) {
+ setPixel(x, y, EMPTY);
+ setPixel(x, y + 1, SEED);
+ moveMetadata(x, y, x, y + 1);
+ }
+ else if (getPixel(x - 1, y + 1) === EMPTY) {
+ setPixel(x, y, EMPTY);
+ setPixel(x - 1, y + 1, SEED);
+ moveMetadata(x, y, x - 1, y + 1);
+ }
+ else if (getPixel(x + 1, y + 1) === EMPTY) {
+ setPixel(x, y, EMPTY);
+ setPixel(x + 1, y + 1, SEED);
+ moveMetadata(x, y, x + 1, y + 1);
+ }
+ // Seeds can float on water
+ else if (getPixel(x, y + 1) === WATER) {
+ // Just float, don't do anything
+ }
+ // If seed is on dirt or grass, it can germinate
+ else if (getPixel(x, y + 1) === DIRT || getPixel(x, y + 1) === GRASS) {
+ const metadata = getMetadata(x, y);
+
+ // Check if this is a flower seed
+ if (metadata && metadata.type === 'flower') {
+ setPixel(x, y, FLOWER);
+ // Set a random flower color
+ setMetadata(x, y, {
+ type: 'flower',
+ color: FLOWER_COLORS[Math.floor(Math.random() * FLOWER_COLORS.length)],
+ age: 0,
+ height: 1
+ });
+ } else {
+ // Regular seed becomes a grass blade
+ setPixel(x, y, GRASS_BLADE);
+ setMetadata(x, y, { age: 0, height: 1 });
+ }
+ }
+}
+
+function updateGrassBlade(x, y) {
+ // Grass blades are static once grown
+ const metadata = getMetadata(x, y);
+
+ if (!metadata) {
+ setMetadata(x, y, { age: 0, height: 1 });
+ return;
+ }
+
+ // Increment age
+ metadata.age++;
+ setMetadata(x, y, metadata);
+
+ // Grass blades can grow taller up to a limit
+ if (metadata.age % 200 === 0 && metadata.height < 3 && getPixel(x, y - 1) === EMPTY) {
+ setPixel(x, y - 1, GRASS_BLADE);
+ setMetadata(x, y - 1, { age: 0, height: metadata.height + 1 });
+ metadata.isTop = false;
+ setMetadata(x, y, metadata);
+ }
+
+ // Grass blades die if covered by something other than another grass blade
+ if (getPixel(x, y - 1) !== EMPTY && getPixel(x, y - 1) !== GRASS_BLADE && Math.random() < 0.01) {
+ setPixel(x, y, EMPTY);
+ removeMetadata(x, y);
+ }
+}
+
+function updateFlower(x, y) {
+ // Flowers are similar to grass blades but with a colored top
+ const metadata = getMetadata(x, y);
+
+ if (!metadata) {
+ setMetadata(x, y, {
+ type: 'flower',
+ color: FLOWER_COLORS[Math.floor(Math.random() * FLOWER_COLORS.length)],
+ age: 0,
+ height: 1
+ });
+ return;
+ }
+
+ // Increment age
+ metadata.age++;
+ setMetadata(x, y, metadata);
+
+ // Flowers can grow taller up to a limit
+ if (metadata.age % 300 === 0 && metadata.height < 4 && getPixel(x, y - 1) === EMPTY) {
+ // If this is the top of the flower, make it a stem and put a new flower on top
+ setPixel(x, y - 1, FLOWER);
+ setMetadata(x, y - 1, {
+ type: 'flower',
+ color: metadata.color,
+ age: 0,
+ height: metadata.height + 1,
+ isTop: true
+ });
+ metadata.isTop = false;
+ setMetadata(x, y, metadata);
+ }
+
+ // Flowers die if covered
+ if (getPixel(x, y - 1) !== EMPTY && getPixel(x, y - 1) !== FLOWER && Math.random() < 0.01) {
+ setPixel(x, y, EMPTY);
+ removeMetadata(x, y);
+ }
+
+ // Flowers can drop seeds occasionally
+ if (metadata.isTop && Math.random() < 0.0001) {
+ const directions = [-1, 1];
+ const dir = directions[Math.floor(Math.random() * directions.length)];
+ if (getPixel(x + dir, y) === EMPTY) {
+ setPixel(x + dir, y, SEED);
+ setMetadata(x + dir, y, { type: 'flower' });
+ }
+ }
+}
+
+function updateTreeSeed(x, y) {
+ // Tree seeds fall like other seeds
+ if (getPixel(x, y + 1) === EMPTY) {
+ setPixel(x, y, EMPTY);
+ setPixel(x, y + 1, TREE_SEED);
+ moveMetadata(x, y, x, y + 1);
+ }
+ else if (getPixel(x - 1, y + 1) === EMPTY) {
+ setPixel(x, y, EMPTY);
+ setPixel(x - 1, y + 1, TREE_SEED);
+ moveMetadata(x, y, x - 1, y + 1);
+ }
+ else if (getPixel(x + 1, y + 1) === EMPTY) {
+ setPixel(x, y, EMPTY);
+ setPixel(x + 1, y + 1, TREE_SEED);
+ moveMetadata(x, y, x + 1, y + 1);
+ }
+ // Seeds can float on water
+ else if (getPixel(x, y + 1) === WATER) {
+ // Just float, don't do anything
+ }
+ // If seed is on dirt or grass, it can grow into a tree
+ else if (getPixel(x, y + 1) === DIRT || getPixel(x, y + 1) === GRASS) {
+ // Start growing a tree
+ growTree(x, y);
+ }
+}
+
+function growTree(x, y) {
+ // Replace the seed with the trunk
+ setPixel(x, y, WOOD);
+
+ // Determine tree height (5-8 blocks)
+ const treeHeight = 5 + Math.floor(Math.random() * 4);
+
+ // Grow the trunk upward
+ for (let i = 1; i < treeHeight; i++) {
+ if (getPixel(x, y - i) === EMPTY) {
+ setPixel(x, y - i, WOOD);
+ } else {
+ break; // Stop if we hit something
+ }
+ }
+
+ // Add leaves at the top
+ addLeaves(x, y - treeHeight + 1, 2 + Math.floor(Math.random() * 2));
+}
+
+function addLeaves(x, y, radius) {
+ // Add a cluster of leaves around the point
+ for (let dy = -radius; dy <= radius; dy++) {
+ for (let dx = -radius; dx <= radius; dx++) {
+ // Skip the exact center (trunk position)
+ if (dx === 0 && dy === 0) continue;
+
+ // Make it more circular by checking distance
+ const distance = Math.sqrt(dx*dx + dy*dy);
+ if (distance <= radius) {
+ // Random chance to place a leaf based on distance from center
+ if (Math.random() < (1 - distance/radius/1.2)) {
+ if (getPixel(x + dx, y + dy) === EMPTY) {
+ setPixel(x + dx, y + dy, LEAF);
+ }
+ }
+ }
+ }
+ }
+}
+
function updateWater(x, y) {
// Try to move down
if (getPixel(x, y + 1) === EMPTY) {
@@ -569,6 +838,18 @@ function render() {
ctx.fillStyle = GRASS_COLOR;
} else if (type === WOOD) {
ctx.fillStyle = WOOD_COLOR;
+ } else if (type === SEED) {
+ ctx.fillStyle = SEED_COLOR;
+ } else if (type === GRASS_BLADE) {
+ ctx.fillStyle = GRASS_COLOR;
+ } else if (type === FLOWER) {
+ // Get flower color from metadata or use a default
+ const metadata = getMetadata(chunkX * CHUNK_SIZE + x, chunkY * CHUNK_SIZE + y);
+ ctx.fillStyle = metadata && metadata.color ? metadata.color : FLOWER_COLORS[0];
+ } else if (type === TREE_SEED) {
+ ctx.fillStyle = SEED_COLOR;
+ } else if (type === LEAF) {
+ ctx.fillStyle = LEAF_COLOR;
}
// Draw the pixel