feat: Add various seeds that grow on dirt, including grass blades, flowers, and trees
This commit is contained in:
parent
6ad564eea0
commit
d8e868aad8
@ -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
285
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
|
||||
|
Loading…
x
Reference in New Issue
Block a user