feat: Add fire simulation with color variation and flammable material restrictions

This commit is contained in:
Kacper Kostka (aider) 2025-04-04 11:42:17 +02:00
parent 0788b3067d
commit d6d874ab99
2 changed files with 130 additions and 12 deletions

View File

@ -18,6 +18,7 @@
<button id="wood-btn">Wood</button>
<button id="seed-btn">Seed</button>
<button id="tree-seed-btn">Tree Seed</button>
<button id="fire-btn">Fire</button>
<button id="eraser-btn">Eraser</button>
</div>
<div class="navigation">

141
script.js
View File

@ -13,6 +13,7 @@ const WOOD_COLOR = '#8B5A2B';
const SEED_COLOR = '#654321';
const FLOWER_COLORS = ['#FF0000', '#FFFF00', '#FF00FF', '#FFA500', '#FFFFFF', '#00FFFF'];
const LEAF_COLOR = '#228B22';
const FIRE_COLORS = ['#FF0000', '#FF3300', '#FF6600', '#FF9900', '#FFCC00', '#FFFF00'];
// Element types
const EMPTY = 0;
@ -28,6 +29,10 @@ const GRASS_BLADE = 9;
const FLOWER = 10;
const TREE_SEED = 11;
const LEAF = 12;
const FIRE = 13;
// Flammable materials
const FLAMMABLE_MATERIALS = [GRASS, WOOD, SEED, GRASS_BLADE, FLOWER, TREE_SEED, LEAF];
// Global variables
let canvas, ctx;
@ -45,6 +50,7 @@ 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;
let fireUpdateCounter = 0;
// Initialize the simulation
window.onload = function() {
@ -64,6 +70,7 @@ window.onload = function() {
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('fire-btn').addEventListener('click', () => setTool(FIRE));
document.getElementById('eraser-btn').addEventListener('click', () => setTool(EMPTY));
// Navigation controls
@ -116,6 +123,8 @@ function setTool(tool) {
document.getElementById('seed-btn').classList.add('active');
} else if (tool === TREE_SEED) {
document.getElementById('tree-seed-btn').classList.add('active');
} else if (tool === FIRE) {
document.getElementById('fire-btn').classList.add('active');
} else if (tool === EMPTY) {
document.getElementById('eraser-btn').classList.add('active');
}
@ -192,18 +201,30 @@ function draw(x, y) {
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
});
// Special handling for fire - only set fire to flammable materials
if (currentTool === FIRE) {
const currentPixel = getPixel(pixelX, pixelY);
if (FLAMMABLE_MATERIALS.includes(currentPixel)) {
setPixel(pixelX, pixelY, FIRE);
setMetadata(pixelX, pixelY, {
lifetime: 100 + Math.floor(Math.random() * 100),
colorIndex: Math.floor(Math.random() * FIRE_COLORS.length)
});
}
} else {
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
});
}
}
}
}
@ -377,6 +398,9 @@ function updatePhysics() {
// Get visible chunks
const visibleChunks = getVisibleChunks();
// Increment fire update counter
fireUpdateCounter++;
// Process each visible chunk
for (const { chunkX, chunkY } of visibleChunks) {
const chunk = getOrCreateChunk(chunkX, chunkY);
@ -413,6 +437,8 @@ function updatePhysics() {
updateFlower(worldX, worldY);
} else if (type === TREE_SEED) {
updateTreeSeed(worldX, worldY);
} else if (type === FIRE) {
updateFire(worldX, worldY);
}
}
}
@ -793,6 +819,92 @@ function updateWater(x, y) {
moved = true;
}
}
// Water extinguishes 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) {
if (getPixel(x + dir.dx, y + dir.dy) === FIRE) {
setPixel(x + dir.dx, y + dir.dy, EMPTY);
removeMetadata(x + dir.dx, y + dir.dy);
}
}
}
// Add fire update function
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 getVisibleChunks() {
@ -889,6 +1001,11 @@ function render() {
ctx.fillStyle = SEED_COLOR;
} else if (type === LEAF) {
ctx.fillStyle = LEAF_COLOR;
} else if (type === FIRE) {
// Get fire color from metadata
const metadata = getMetadata(chunkX * CHUNK_SIZE + x, chunkY * CHUNK_SIZE + y);
const colorIndex = metadata ? metadata.colorIndex : 0;
ctx.fillStyle = FIRE_COLORS[colorIndex];
}
// Draw the pixel