refactor: Spread game code into modular files for better maintainability
This commit reorganizes the game's source code into multiple files within a `js/` directory, creating a more modular and maintainable structure. The changes include:
- Created separate files for different game components:
- `constants.js`: Game constants and element types
- `world.js`: World management functions
- `terrain.js`: Terrain generation logic
- `physics.js`: Physics simulation
- `render.js`: Rendering functions
- `input.js`: Input handling
- `main.js`: Main game initialization and loop
- Element-specific files in `js/elements/`:
- `basic.js`: Sand, water, dirt behaviors
- `plants.js`: Grass, seeds, flowers
- `trees.js`: Tree growth and leaf generation
- `fire.js`: Fire and lava behaviors
- Updated `index.html` to load modules in the correct order
- Removed the monolithic `script.js`
The modular approach improves code readability, makes future extensions easier, and separates concerns more effectively.
This commit is contained in:
130
js/elements/basic.js
Normal file
130
js/elements/basic.js
Normal file
@@ -0,0 +1,130 @@
|
||||
// Basic element behaviors (sand, water, dirt)
|
||||
function updateSand(x, y) {
|
||||
// Try to move down
|
||||
if (getPixel(x, y + 1) === EMPTY) {
|
||||
setPixel(x, y, EMPTY);
|
||||
setPixel(x, y + 1, SAND);
|
||||
}
|
||||
// Try to move down-left or down-right
|
||||
else if (getPixel(x - 1, y + 1) === EMPTY) {
|
||||
setPixel(x, y, EMPTY);
|
||||
setPixel(x - 1, y + 1, SAND);
|
||||
}
|
||||
else if (getPixel(x + 1, y + 1) === EMPTY) {
|
||||
setPixel(x, y, EMPTY);
|
||||
setPixel(x + 1, y + 1, SAND);
|
||||
}
|
||||
// Sand can displace water
|
||||
else if (getPixel(x, y + 1) === WATER) {
|
||||
setPixel(x, y, WATER);
|
||||
setPixel(x, y + 1, SAND);
|
||||
}
|
||||
}
|
||||
|
||||
function updateWater(x, y) {
|
||||
// Try to move down
|
||||
if (getPixel(x, y + 1) === EMPTY) {
|
||||
setPixel(x, y, EMPTY);
|
||||
setPixel(x, y + 1, WATER);
|
||||
}
|
||||
// Try to move down-left or down-right
|
||||
else if (getPixel(x - 1, y + 1) === EMPTY) {
|
||||
setPixel(x, y, EMPTY);
|
||||
setPixel(x - 1, y + 1, WATER);
|
||||
}
|
||||
else if (getPixel(x + 1, y + 1) === EMPTY) {
|
||||
setPixel(x, y, EMPTY);
|
||||
setPixel(x + 1, y + 1, WATER);
|
||||
}
|
||||
// Try to spread horizontally
|
||||
else {
|
||||
let moved = false;
|
||||
|
||||
// Randomly choose direction first
|
||||
const goLeft = Math.random() > 0.5;
|
||||
|
||||
if (goLeft && getPixel(x - 1, y) === EMPTY) {
|
||||
setPixel(x, y, EMPTY);
|
||||
setPixel(x - 1, y, WATER);
|
||||
moved = true;
|
||||
} else if (!goLeft && getPixel(x + 1, y) === EMPTY) {
|
||||
setPixel(x, y, EMPTY);
|
||||
setPixel(x + 1, y, WATER);
|
||||
moved = true;
|
||||
}
|
||||
// Try the other direction if first failed
|
||||
else if (!goLeft && getPixel(x - 1, y) === EMPTY) {
|
||||
setPixel(x, y, EMPTY);
|
||||
setPixel(x - 1, y, WATER);
|
||||
moved = true;
|
||||
} else if (goLeft && getPixel(x + 1, y) === EMPTY) {
|
||||
setPixel(x, y, EMPTY);
|
||||
setPixel(x + 1, y, WATER);
|
||||
moved = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Water extinguishes fire and turns lava into stone
|
||||
const directions = [
|
||||
{dx: -1, dy: 0}, {dx: 1, dy: 0},
|
||||
{dx: 0, dy: -1}, {dx: 0, dy: 1},
|
||||
{dx: -1, dy: -1}, {dx: 1, dy: -1},
|
||||
{dx: -1, dy: 1}, {dx: 1, dy: 1}
|
||||
];
|
||||
|
||||
for (const dir of directions) {
|
||||
if (getPixel(x + dir.dx, y + dir.dy) === FIRE) {
|
||||
setPixel(x + dir.dx, y + dir.dy, EMPTY);
|
||||
removeMetadata(x + dir.dx, y + dir.dy);
|
||||
} else if (getPixel(x + dir.dx, y + dir.dy) === LAVA) {
|
||||
// Water turns lava into stone
|
||||
setPixel(x + dir.dx, y + dir.dy, STONE);
|
||||
removeMetadata(x + dir.dx, y + dir.dy);
|
||||
// Water is consumed in the process
|
||||
setPixel(x, y, EMPTY);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateDirt(x, y) {
|
||||
// Try to move down
|
||||
if (getPixel(x, y + 1) === EMPTY) {
|
||||
setPixel(x, y, EMPTY);
|
||||
setPixel(x, y + 1, DIRT);
|
||||
}
|
||||
// Try to move down-left or down-right
|
||||
else if (getPixel(x - 1, y + 1) === EMPTY) {
|
||||
setPixel(x, y, EMPTY);
|
||||
setPixel(x - 1, y + 1, DIRT);
|
||||
}
|
||||
else if (getPixel(x + 1, y + 1) === EMPTY) {
|
||||
setPixel(x, y, EMPTY);
|
||||
setPixel(x + 1, y + 1, DIRT);
|
||||
}
|
||||
// Dirt can displace water
|
||||
else if (getPixel(x, y + 1) === WATER) {
|
||||
setPixel(x, y, WATER);
|
||||
setPixel(x, y + 1, DIRT);
|
||||
}
|
||||
|
||||
// Dirt can turn into grass if exposed to air above
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
166
js/elements/fire.js
Normal file
166
js/elements/fire.js
Normal file
@@ -0,0 +1,166 @@
|
||||
// Fire and lava element behaviors
|
||||
let fireUpdateCounter = 0;
|
||||
|
||||
function updateFire(x, y) {
|
||||
const metadata = getMetadata(x, y);
|
||||
|
||||
if (!metadata) {
|
||||
// Initialize metadata if it doesn't exist
|
||||
setMetadata(x, y, {
|
||||
lifetime: 100 + Math.floor(Math.random() * 100),
|
||||
colorIndex: Math.floor(Math.random() * FIRE_COLORS.length)
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Decrease lifetime
|
||||
metadata.lifetime--;
|
||||
|
||||
// Randomly change color for flickering effect
|
||||
if (Math.random() < 0.2) {
|
||||
metadata.colorIndex = Math.floor(Math.random() * FIRE_COLORS.length);
|
||||
}
|
||||
|
||||
// Update metadata
|
||||
setMetadata(x, y, metadata);
|
||||
|
||||
// Fire rises upward occasionally
|
||||
if (Math.random() < 0.3 && getPixel(x, y - 1) === EMPTY) {
|
||||
setPixel(x, y, EMPTY);
|
||||
setPixel(x, y - 1, FIRE);
|
||||
moveMetadata(x, y, x, y - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fire can also move slightly to the sides
|
||||
if (Math.random() < 0.1) {
|
||||
const direction = Math.random() > 0.5 ? 1 : -1;
|
||||
if (getPixel(x + direction, y - 1) === EMPTY) {
|
||||
setPixel(x, y, EMPTY);
|
||||
setPixel(x + direction, y - 1, FIRE);
|
||||
moveMetadata(x, y, x + direction, y - 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Fire spreads to nearby flammable materials (less frequently to reduce performance impact)
|
||||
if (fireUpdateCounter % 3 === 0 && Math.random() < 0.3) {
|
||||
const directions = [
|
||||
{dx: -1, dy: 0}, {dx: 1, dy: 0},
|
||||
{dx: 0, dy: -1}, {dx: 0, dy: 1},
|
||||
{dx: -1, dy: -1}, {dx: 1, dy: -1},
|
||||
{dx: -1, dy: 1}, {dx: 1, dy: 1}
|
||||
];
|
||||
|
||||
const dir = directions[Math.floor(Math.random() * directions.length)];
|
||||
const nearbyType = getPixel(x + dir.dx, y + dir.dy);
|
||||
|
||||
if (FLAMMABLE_MATERIALS.includes(nearbyType)) {
|
||||
setPixel(x + dir.dx, y + dir.dy, FIRE);
|
||||
setMetadata(x + dir.dx, y + dir.dy, {
|
||||
lifetime: 100 + Math.floor(Math.random() * 100),
|
||||
colorIndex: Math.floor(Math.random() * FIRE_COLORS.length)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Fire burns out after its lifetime
|
||||
if (metadata.lifetime <= 0) {
|
||||
setPixel(x, y, EMPTY);
|
||||
removeMetadata(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
function updateLava(x, y) {
|
||||
const metadata = getMetadata(x, y);
|
||||
|
||||
if (!metadata) {
|
||||
// Initialize metadata if it doesn't exist
|
||||
setMetadata(x, y, {
|
||||
colorIndex: Math.floor(Math.random() * LAVA_COLORS.length)
|
||||
});
|
||||
} else {
|
||||
// Randomly change color for flowing effect
|
||||
if (Math.random() < 0.1) {
|
||||
metadata.colorIndex = Math.floor(Math.random() * LAVA_COLORS.length);
|
||||
setMetadata(x, y, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
// Lava moves slower than water
|
||||
if (Math.random() < 0.7) {
|
||||
// Try to move down
|
||||
if (getPixel(x, y + 1) === EMPTY) {
|
||||
setPixel(x, y, EMPTY);
|
||||
setPixel(x, y + 1, LAVA);
|
||||
moveMetadata(x, y, x, y + 1);
|
||||
}
|
||||
// Try to move down-left or down-right
|
||||
else if (getPixel(x - 1, y + 1) === EMPTY) {
|
||||
setPixel(x, y, EMPTY);
|
||||
setPixel(x - 1, y + 1, LAVA);
|
||||
moveMetadata(x, y, x - 1, y + 1);
|
||||
}
|
||||
else if (getPixel(x + 1, y + 1) === EMPTY) {
|
||||
setPixel(x, y, EMPTY);
|
||||
setPixel(x + 1, y + 1, LAVA);
|
||||
moveMetadata(x, y, x + 1, y + 1);
|
||||
}
|
||||
// Try to spread horizontally (slower than water)
|
||||
else if (Math.random() < 0.3) {
|
||||
// Randomly choose direction first
|
||||
const goLeft = Math.random() > 0.5;
|
||||
|
||||
if (goLeft && getPixel(x - 1, y) === EMPTY) {
|
||||
setPixel(x, y, EMPTY);
|
||||
setPixel(x - 1, y, LAVA);
|
||||
moveMetadata(x, y, x - 1, y);
|
||||
} else if (!goLeft && getPixel(x + 1, y) === EMPTY) {
|
||||
setPixel(x, y, EMPTY);
|
||||
setPixel(x + 1, y, LAVA);
|
||||
moveMetadata(x, y, x + 1, y);
|
||||
}
|
||||
// Try the other direction if first failed
|
||||
else if (!goLeft && getPixel(x - 1, y) === EMPTY) {
|
||||
setPixel(x, y, EMPTY);
|
||||
setPixel(x - 1, y, LAVA);
|
||||
moveMetadata(x, y, x - 1, y);
|
||||
} else if (goLeft && getPixel(x + 1, y) === EMPTY) {
|
||||
setPixel(x, y, EMPTY);
|
||||
setPixel(x + 1, y, LAVA);
|
||||
moveMetadata(x, y, x + 1, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Lava sets nearby flammable materials on fire
|
||||
const directions = [
|
||||
{dx: -1, dy: 0}, {dx: 1, dy: 0},
|
||||
{dx: 0, dy: -1}, {dx: 0, dy: 1},
|
||||
{dx: -1, dy: -1}, {dx: 1, dy: -1},
|
||||
{dx: -1, dy: 1}, {dx: 1, dy: 1}
|
||||
];
|
||||
|
||||
for (const dir of directions) {
|
||||
const nearbyType = getPixel(x + dir.dx, y + dir.dy);
|
||||
|
||||
// Set flammable materials on fire
|
||||
if (FLAMMABLE_MATERIALS.includes(nearbyType)) {
|
||||
setPixel(x + dir.dx, y + dir.dy, FIRE);
|
||||
setMetadata(x + dir.dx, y + dir.dy, {
|
||||
lifetime: 100 + Math.floor(Math.random() * 100),
|
||||
colorIndex: Math.floor(Math.random() * FIRE_COLORS.length)
|
||||
});
|
||||
}
|
||||
|
||||
// Lava can melt sand into glass (stone)
|
||||
else if (nearbyType === SAND && Math.random() < 0.05) {
|
||||
setPixel(x + dir.dx, y + dir.dy, STONE);
|
||||
}
|
||||
|
||||
// Lava can burn dirt
|
||||
else if (nearbyType === DIRT && Math.random() < 0.02) {
|
||||
setPixel(x + dir.dx, y + dir.dy, EMPTY);
|
||||
}
|
||||
}
|
||||
}
|
||||
159
js/elements/plants.js
Normal file
159
js/elements/plants.js
Normal file
@@ -0,0 +1,159 @@
|
||||
// Plant element behaviors (grass, seeds, trees)
|
||||
function updateGrass(x, y) {
|
||||
// Grass behaves like dirt for physics
|
||||
if (getPixel(x, y + 1) === EMPTY) {
|
||||
setPixel(x, y, EMPTY);
|
||||
setPixel(x, y + 1, GRASS);
|
||||
}
|
||||
else if (getPixel(x - 1, y + 1) === EMPTY) {
|
||||
setPixel(x, y, EMPTY);
|
||||
setPixel(x - 1, y + 1, GRASS);
|
||||
}
|
||||
else if (getPixel(x + 1, y + 1) === EMPTY) {
|
||||
setPixel(x, y, EMPTY);
|
||||
setPixel(x + 1, y + 1, GRASS);
|
||||
}
|
||||
else if (getPixel(x, y + 1) === WATER) {
|
||||
setPixel(x, y, WATER);
|
||||
setPixel(x, y + 1, GRASS);
|
||||
}
|
||||
|
||||
// Grass can spread to nearby dirt
|
||||
if (Math.random() < 0.0005) {
|
||||
const directions = [
|
||||
{dx: -1, dy: 0}, {dx: 1, dy: 0},
|
||||
{dx: 0, dy: -1}, {dx: 0, dy: 1}
|
||||
];
|
||||
|
||||
const dir = directions[Math.floor(Math.random() * directions.length)];
|
||||
if (getPixel(x + dir.dx, y + dir.dy) === DIRT) {
|
||||
setPixel(x + dir.dx, y + dir.dy, GRASS);
|
||||
}
|
||||
}
|
||||
|
||||
// Grass dies if covered (no air above)
|
||||
if (getPixel(x, y - 1) !== EMPTY && Math.random() < 0.05) {
|
||||
setPixel(x, y, DIRT);
|
||||
}
|
||||
}
|
||||
|
||||
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 (2x bigger)
|
||||
if (metadata.age % 300 === 0 && metadata.height < 8 && 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' });
|
||||
}
|
||||
}
|
||||
}
|
||||
108
js/elements/trees.js
Normal file
108
js/elements/trees.js
Normal file
@@ -0,0 +1,108 @@
|
||||
// Tree element behaviors
|
||||
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 (50-80 blocks, 10x bigger)
|
||||
const treeHeight = 50 + Math.floor(Math.random() * 31);
|
||||
|
||||
// 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 (10x bigger radius)
|
||||
addLeaves(x, y - treeHeight + 1, 20 + Math.floor(Math.random() * 10));
|
||||
|
||||
// Add some branches
|
||||
addBranches(x, y, treeHeight);
|
||||
}
|
||||
|
||||
function addBranches(x, y, treeHeight) {
|
||||
// Add 2-4 branches at different heights
|
||||
const numBranches = 2 + Math.floor(Math.random() * 3);
|
||||
|
||||
for (let i = 0; i < numBranches; i++) {
|
||||
// Position branch at different heights along the trunk
|
||||
const branchY = y - Math.floor(treeHeight * (0.3 + 0.4 * i / numBranches));
|
||||
|
||||
// Choose left or right direction
|
||||
const direction = Math.random() > 0.5 ? 1 : -1;
|
||||
|
||||
// Branch length (10-15 blocks)
|
||||
const branchLength = 10 + Math.floor(Math.random() * 6);
|
||||
|
||||
// Create the branch
|
||||
for (let j = 1; j <= branchLength; j++) {
|
||||
// Branch goes out horizontally with some upward angle
|
||||
const branchX = x + (j * direction);
|
||||
const upwardAngle = Math.floor(j * 0.3);
|
||||
|
||||
if (getPixel(branchX, branchY - upwardAngle) === EMPTY) {
|
||||
setPixel(branchX, branchY - upwardAngle, WOOD);
|
||||
} else {
|
||||
break; // Stop if we hit something
|
||||
}
|
||||
|
||||
// Add small leaf clusters at the end of branches
|
||||
if (j === branchLength) {
|
||||
addLeaves(branchX, branchY - upwardAngle, 8 + Math.floor(Math.random() * 4));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
// More dense leaves for larger trees
|
||||
const density = radius > 10 ? 0.8 : 0.6;
|
||||
if (Math.random() < (1 - distance/radius/density)) {
|
||||
if (getPixel(x + dx, y + dy) === EMPTY) {
|
||||
setPixel(x + dx, y + dy, LEAF);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user