feat: Add various seeds that grow on dirt, including grass blades, flowers, and trees

This commit is contained in:
Kacper Kostka (aider) 2025-04-04 11:30:47 +02:00
parent 6ad564eea0
commit d8e868aad8
2 changed files with 285 additions and 2 deletions

View File

@ -16,6 +16,8 @@
<button id="stone-btn">Stone</button>
<button id="grass-btn">Grass</button>
<button id="wood-btn">Wood</button>
<button id="seed-btn">Seed</button>
<button id="tree-seed-btn">Tree Seed</button>
<button id="eraser-btn">Eraser</button>
</div>
<div class="navigation">

285
script.js
View File

@ -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