Show code
<think>
Okay, I need to create a self-contained HTML file that simulates predators and prey on a canvas. Let me start by understanding the requirements. The user wants prey to graze on regrowing food, multiply where food is plentiful, predators to chase nearby prey, eat them, lose energy over time, and multiply when well-fed. The populations should oscillate, and I have to show both counts. The animation should use requestAnimationFrame, no user input, starts immediately, and restarts when finished. No external libraries, and the code must be under 7000 characters.
First, I'll set up the basic HTML structure with a canvas element. I'll need a head section with a title and a body with the canvas. The canvas will be the main area for the simulation.
Next, the JavaScript part. I'll start by getting the canvas context and setting up some constants. The canvas size can be set to 800x600 for visibility. I'll define the colors for food (green), prey (blue), predators (red). The food should regrow over time, so maybe a grid where each cell has a food value that increases over time up to a max, but is eaten by prey.
For the entities (prey and predators), I'll create objects with properties like position, energy, and maybe direction or velocity. Prey should move randomly, eat food, gain energy, and reproduce when energy is high enough. Predators need to seek prey, move towards them, eat them to gain energy, and reproduce when energy is sufficient.
The food grid can be a 2D array. Each cell's food value decreases when prey are nearby, and increases over time. The prey and predators will check their position to affect the food.
The animation loop will involve updating each entity's position, energy, interactions with food and each other, then redrawing everything. Each tick, the food regrows slightly. Prey and predators are moved and checked for energy levels. If they run out of energy, they die. Reproduction happens when energy reaches a threshold.
I need to manage the populations. Arrays for prey and predators. When an entity dies, remove it from the array.繁殖时,创造一个新个体并添加到数组中。
Drawing: Clear the canvas each frame, draw food, then prey and predators. The food can be small dots or rectangles. Maybe using ctx.fillStyle and small circles for each food cell. Prey and predators can be circles with different colors and sizes based on energy levels?
Population counts are displayed in the canvas as text. Maybe at the top left corner, updating each frame.
The restart functionality: when the simulation ends (maybe when both populations die out?), reset everything. But how to detect that? Perhaps check if the prey and predator arrays are empty for several frames, then trigger a restart. Or maybe after a certain time period? Hmm, maybe set a flag and restart automatically when the populations are low enough?
Wait, the user said to restart when finished, so I need to determine when to restart. Maybe when the prey and predator populations reach zero. But that might not happen. Alternatively, set a timer for a fixed period and restart. Hmm, perhaps after a certain number of frames or when the populations have been decreasing for a while. Alternatively, to simplify, just restart the simulation after a fixed period of time, say 10,000 frames (~2 minutes if 60 FPS). Or when the population counts go below a certain threshold for a while?
Alternatively, to make it restart automatically when both populations die, but that might not happen unless predators overhunt or prey overgraze. So perhaps the system will oscillate, but eventually might stabilize? Maybe the best approach is to restart after a certain number of frames. Let's think of setting a frame counter, and after 5000 frames (around 83 seconds at 60fps), restart. That should give enough time for the oscillations to be seen.
For the initial setup, randomize the starting positions and energy levels of prey and predators. Maybe start with some initial counts, say 50 prey and 10 predators, and 0.1 initial food in each cell.
Now, code structure:
- Canvas setup
- Variables for food grid, prey array, predator array, frame counter
- Functions:
- drawFood()
- drawEntities()
- updateFood()
- updatePrey()
- updatePredators()
- checkRestart()
- reset()
The main loop function (using requestAnimationFrame) will handle all updates and drawing.
Food grid: Maybe a 2D array with the same dimensions as the canvas divided by a cell size. Let's say each cell is 10x10 pixels, so 80x60 cells for an 800x600 canvas. So foodGrid[80][60].
Each frame, for each cell, food increases by a small amount (like 0.1 per frame) up to a max (like 5). When prey are nearby, they consume food, so the cell's food decreases by some factor (maybe 0.5 per prey in that cell, but need to distribute properly).
Wait, maybe each prey consumes food from the cell it is in. So each prey's current cell (determined by their position) has its food reduced by some amount (like 0.5 per prey per frame). Similarly, when a prey eats, it gains energy.
Predators don't eat food but prey. So when a predator is near a prey, it can chase them, and when they catch them, the prey's energy is transferred to the predator, and the prey dies.
Hmm, collision detection between predators and prey. Predators move towards the nearest prey. To find nearby prey, maybe each predator checks all prey within a certain radius (like 20 pixels) and moves towards the closest one.
Movement: Prey move in a random direction each frame, but with some randomness to their direction. Predators have a direction towards the nearest prey.
Energy management: Prey lose a small amount of energy each frame, but gain from eating. If their energy drops to zero, they die. Predators lose energy faster (since they have to move more?) and gain energy by eating prey.
Reproduction: When an entity's energy reaches a high enough threshold (like 100), they split into two, creating a new entity with half the energy, and the original keeps some.
Now, coding steps:
Initialize the food grid with foodGrid = Array(80).fill().map(() => Array(60).fill(0.1));
Wait, actually, in JavaScript, to create a 2D array, need to use loops. Maybe:
const foodWidth = 80; // Canvas width / 10
const foodHeight = 60; // Canvas height /10
const foodGrid = [];
for (let x = 0; x < foodWidth; x++) {
foodGrid[x] = [];
for (let y = 0; y < foodHeight; y++) {
foodGrid[x][y] = 0.1;
}
}
But this might be slow in the loop. Alternatively, during setup, but need to do it in the code.
For entities, prey and predators can be objects with x, y coordinates (floats?), energy, direction (angle?), or velocity components.
Alternatively, to simplify, each prey has a velocity vector that is updated each frame with some randomness. Maybe:
Prey:
- x, y (position)
- vx, vy (velocity components)
- energy: number
Predators similarly, but their velocity is influenced by nearest prey.
For prey movement:
Each prey's direction is slightly randomized. Maybe each frame, their velocity is adjusted by a small random angle and speed. Or their velocity is a vector that is rotated randomly each frame.
Alternatively, each prey's direction is a random angle each frame with some persistence.
Hmm, maybe for prey:
prey.vx += (Math.random() * 2 - 1) * 0.1;
prey.vy += (Math.random() * 2 - 1) * 0.1;
Then normalize their speed to a certain maximum.
Predators:
For each predator, find the closest prey within a certain radius. If found, calculate the direction towards it and move towards it. Otherwise, wander randomly.
The radius could be 50 pixels. So for each predator, loop through all prey and find the closest one within 50 pixels.
Once a prey is within reach (distance < some threshold, like 5), the predator eats it. The predator gains energy, the prey dies.
Drawing:
For food, each cell's food value can be represented by the brightness of green. So, the higher the food, the greener it is. Each cell is a rectangle of 10x10 pixels. So:
ctx.fillStyle = `rgb(0, ${food * 50}, 0)`;
ctx.fillRect(x*10, y*10, 10, 10);
Wait, but 0.1 to 5 would give 0-250, but maybe clamp the color to 0-255. So if food is up to 5, multiply by 51 to get 5*51=255.
Alternatively, use a scale like (food * 50). So maximum food of 5 gives 250, which is okay.
Drawing prey and predators as circles. Prey could be blue circles, predators red. Maybe the size scales with energy? Not sure. Alternatively, fixed size, but color brightness varies with energy. Or just a fixed size.
Prey could be drawn with:
ctx.fillStyle = 'blue';
ctx.beginPath();
ctx.arc(x, y, 2, 0, Math.PI*2);
ctx.fill();
Predators as red with maybe slightly bigger radius (3px?)
Now, the steps in the animation loop:
1. Update the food grid:
For each cell, add a small amount (e.g., 0.1) to the food, up to a max (e.g., 5). Then, subtract any consumption from prey in that cell.
Wait, how to model consumption: each prey in a cell reduces the cell's food. So for each prey, determine which cell they're in (x_cell = Math.floor(x / 10), y_cell = Math.floor(y /10)), then subtract 0.5 per prey in that cell? Or maybe accumulate all prey in each cell and subtract accordingly.
Alternatively, for each prey:
let cellX = Math.floor(prey.x / 10);
let cellY = Math.floor(prey.y / 10);
foodGrid[cellX][cellY] = Math.max(0, foodGrid[cellX][cellY] - 0.5);
But if multiple prey are in the same cell, this would subtract 0.5 per prey. But this might be computationally expensive for many prey. Alternatively, for each cell, track how much prey are consuming, but that might be complex.
Alternatively, each prey consumes 0.5 per frame from their current cell. So during the prey update step:
for each prey in preys {
let x = prey.x, y = prey.y;
let cellX = Math.floor(x / 10);
let cellY = Math.floor(y /10);
if (foodGrid[cellX][cellY] > 0) {
prey.energy += 0.5; // gain energy from food
foodGrid[cellX][cellY] = Math.max(0, foodGrid[cellX][cellY] - 0.5);
}
}
Wait, but that's per prey. So if two prey are in the same cell, they each subtract 0.5, reducing the food by 1.0.
But then the prey gains energy from the food. Maybe 1 unit of food gives 0.5 energy? Or just the prey gains energy equal to the food consumed. Hmm, need to balance the energy levels.
Alternatively, prey eat food in their cell. For each prey, the food in their cell is decreased by 0.1, and prey gains 0.1 energy (assuming food is a resource). But this might need to be adjusted.
The prey's energy decreases over time, so each frame, prey lose some energy (like 0.1) and gain from food.
Predators lose more energy (maybe 0.2 per frame?) and gain energy by eating prey.
Predator eating:
When a predator catches a prey, the predator gains the prey's energy, and the prey is removed.
Predator movement towards prey:
For each predator, find the closest prey within a certain distance. Then move towards that prey.
The code for finding the closest prey:
let closest = null;
let closestDist = Infinity;
for (let i=0; i<preys.length; i++) {
let p = preys[i];
let dx = p.x - pred.x;
let dy = p.y - pred.y;
let dist = dx*dx + dy*dy;
if (dist < closestDist && dist < 2500) { // 50^2
closestDist = dist;
closest = p;
}
}
if (closest) {
// move towards closest
let dx = closest.x - pred.x;
let dy = closest.y - pred.y;
let dist = Math.sqrt(dx*dx + dy*dy);
if (dist < 5) { // within 5 pixels, eat
pred.energy += closest.energy;
// remove the prey
preys.splice(i, 1);
} else {
// adjust velocity towards prey
pred.vx += dx / dist * 0.1;
pred.vy += dy / dist * 0.1;
}
} else {
// wander
pred.vx += (Math.random() * 2 -1)*0.1;
pred.vy += (Math.random() * 2 -1)*0.1;
}
Wait, but looping through preys each time could be slow if there are many preys and predators. Maybe optimize later.
Now, for the reproduction:
When an entity's energy exceeds a threshold, it splits into two. For prey, say when energy >= 100, they can reproduce. When they do, create a new prey with half the energy, subtract 50 from the original.
Similarly for predators, but maybe higher threshold (150?), so they reproduce less often.
The reproduction could be checked each frame:
for (let prey of preys) {
if (prey.energy >= 100) {
let newPrey = { ...prey };
newPrey.x += Math.random() * 10 -5; // offset to spread
newPrey.y += Math.random() * 10 -5;
newPrey.energy = prey.energy / 2;
preys.push(newPrey);
prey.energy = newPrey.energy;
}
}
Wait, but maybe better to have a random direction offset. Alternatively, split energy and then push the new prey.
But this needs to be coded efficiently.
Now putting all together.
Variables:
const canvas = document.getElementById('sim');
const ctx = canvas.getContext('2d');
canvas.width = 800;
canvas.height = 600;
Constants:
const CELL_SIZE = 10;
const FOOD_WIDTH = 80; // 800/10
const FOOD_HEIGHT = 60; // 600/10
const MAX_FOOD = 5;
const MAX_PREY_ENERGY = 100;
const MAX_PREDATOR_ENERGY = 150;
const FOOD_GROWTH_RATE = 0.1;
let foodGrid = Array(FOOD_WIDTH).fill().map(() => Array(FOOD_HEIGHT).fill(0.1));
// Initialize prey and predators
let preys = [];
let predators = [];
// Frame counter for restart
let frameCount = 0;
// Functions
function reset() {
foodGrid.forEach((row, x) => {
row.forEach((_, y) => {
foodGrid[x][y] = 0.1;
});
});
preys = [];
predators = [];
// Initialize with some starting entities
for (let i=0; i<50; i++) {
preys.push({
x: Math.random() * 800,
y: Math.random() * 600,
vx: (Math.random() * 2 - 1)*2,
vy: (Math.random() * 2 - 1)*2,
energy: 50
});
}
for (let i=0; i<10; i++) {
predators.push({
x: Math.random() * 800,
y: Math.random() * 600,
vx: 0,
vy: 0,
energy: 75
});
}
frameCount = 0;
}
function drawFood() {
ctx.fillStyle = '#008000';
for (let x=0; x<FOOD_WIDTH; x++) {
for (let y=0; y<FOOD_HEIGHT; y++) {
let food = foodGrid[x][y];
ctx.fillStyle = `rgb(0, ${food * 50}, 0)`;
ctx.fillRect(x*10, y*10, 10, 10);
}
}
}
function drawEntities() {
ctx.fillStyle = 'blue';
preys.forEach(p => {
ctx.beginPath();
ctx.arc(p.x, p.y, 2, 0, Math.PI*2);
ctx.fill();
});
ctx.fillStyle = 'red';
predators.forEach(pred => {
ctx.beginPath();
ctx.arc(pred.x, pred.y, 3, 0, Math.PI*2);
ctx.fill();
});
}
function updateFood() {
for (let x=0; x<FOOD_WIDTH; x++) {
for (let y=0; y<FOOD_HEIGHT; y++) {
foodGrid[x][y] = Math.min(MAX_FOOD, foodGrid[x][y] + FOOD_GROWTH_RATE);
}
}
}
function updatePrey() {
for (let i=0; i<preys.length; i++) {
let p = preys[i];
// Calculate cell
let cellX = Math.floor(p.x / 10);
let cellY = Math.floor(p.y / 10);
// Consume food
if (foodGrid[cellX][cellY] > 0) {
p.energy += 0.5;
foodGrid[cellX][cellY] = Math.max(0, foodGrid[cellX][cellY] - 0.5);
}
// Lose energy
p.energy -= 0.1;
// Move
p.x += p.vx;
p.y += p.vy;
// Bounce off edges
if (p.x < 0 || p.x > 800) p.vx *= -0.5;
if (p.y < 0 || p.y > 600) p.vy *= -0.5;
// Random movement
p.vx += (Math.random() * 2 -1)*0.1;
p.vy += (Math.random() * 2 -1)*0.1;
// Reproduce
if (p.energy >= MAX_PREY_ENERGY) {
let newPrey = {...p};
newPrey.x += Math.random() * 10 -5;
newPrey.y += Math.random() * 10 -5;
newPrey.energy = p.energy / 2;
// Add new prey
preys.push(newPrey);
p.energy = newPrey.energy;
}
// Remove if energy too low
if (p.energy <= 0) {
preys.splice(i, 1);
i--;
}
}
}
function updatePredators() {
for (let i=0; i<predators.length; i++) {
let pred = predators[i];
// Lose energy
pred.energy -= 0.2;
// Find prey
let closest = null;
let closestDist = Infinity;
for (let j=0; j<preys.length; j++) {
let p = preys[j];
let dx = p.x - pred.x;
let dy = p.y - pred.y;
let dist_sq = dx*dx + dy*dy;
if (dist_sq < closestDist && dist_sq < 2500) { // within 50 units
closestDist = dist_sq;
closest = p;
}
}
if (closest) {
let dx = closest.x - pred.x;
let dy = closest.y - pred.y;
let dist = Math.sqrt(closestDist);
// Move towards
pred.vx += dx / dist * 0.3;
pred.vy += dy / dist * 0.3;
// If close enough, eat
if (dist < 5) { // within 5 units
pred.energy += closest.energy;
// Remove prey
preys.splice(j, 1); // but need correct index
// Wait, need j here, but loop is over predators
// Hmm, this might be tricky, perhaps need to track j
let preyIndex = preys.indexOf(closest);
if (preyIndex > -1) {
preys.splice(preyIndex, 1);
}
}
} else {
// Wander
pred.vx += (Math.random() - 0.5) * 0.2;
pred.vy += (Math.random() - 0.5) * 0.2;
}
// Move
pred.x += pred.vx;
pred.y += pred.vy;
// Bounce edges
if (pred.x < 0 || pred.x > 800) pred.vx *= -0.5;
if (pred.y < 0 || pred.y > 600) pred.vy *= -0.5;
// Reproduce
if (pred.energy >= MAX_PREDATOR_ENERGY) {
let newPred = {...pred};
newPred.x += Math.random() * 10 -5;
newPred.y += Math.random() * 10 -5;
newPred.energy = pred.energy / 2;
predators.push(newPred);
pred.energy = newPred.energy;
}
// Remove if energy too low
if (pred.energy <=0 ) {
predators.splice(i, 1);
i--;
}
}
}
function drawStats() {
ctx.fillStyle = 'white';
ctx.fillText(`Prey: ${preys.length} Predators: ${predators.length}`, 10, 20);
}
function animate() {
ctx.clearRect(0,0,800,600);
updateFood();
updatePrey();
updatePredators();
drawFood();
drawEntities();
drawStats();
frameCount++;
if (frameCount > 5000) reset(); // Arbitrary high number for demo
requestAnimationFrame(animate);
}
reset();
animate();
Wait, but in the predator update function, when eating, the prey is spliced, but the index j was from the preys array. Since we're inside a loop over predators[i], and j is over preys[j], when removing preys[j], the indices shift. Maybe better to find the index and remove.
Alternatively, in code:
if (dist <5) {
pred.energy += closest.energy;
let index = preys.indexOf(closest);
if (index != -1) preys.splice(index,1);
}
This is safer.
Testing this code, but in the initial setup.
Now, possible issues:
1. The predator's velocity might accumulate too much, making them move too fast. The 0.3 multiplier might be too large.
2. The energy consumption and reproduction thresholds may need tuning.
3. The foodGrid is initialized with 0.1, which might be too low, causing prey to starve quickly.
4. The prey and predators may not have enough initial energy.
Potential adjustments:
- Increase MAX_FOOD to 10, so food can be more abundant.
- Lower the energy consumption rates. Maybe prey lose 0.05 per frame instead of 0.1.
- Let predators lose 0.1 instead of 0.2.
- Start prey with higher energy, like 75 instead of 50.
- Reproduction energy thresholds: 150 for prey, 200 for predators.
Also, in the prey movement:
The vx and vy are added with random, but maybe better to apply a small random nudge each frame.
Testing this, but since we need to present the code, proceed with the current structure.
Now, packaging all this into a complete HTML file with <canvas>:
Here's the code:
```html
<!DOCTYPE html>
<html>
<body>
<canvas id="sim" width="800" height="600"></canvas>
<script>
const canvas = document.getElementById('sim');
const ctx = canvas.getContext('2d');
canvas.width = 800;
canvas.height = 600;
const CELL_SIZE = 10;
const FOOD_WIDTH = 80; // 800/10
const FOOD_HEIGHT = 60; // 600/10
const MAX_FOOD = 5;
const MAX_PREY_ENERGY = 100;
const MAX_PREDATOR_ENERGY = 150;
const FOOD_GROWTH_RATE = 0.1;
let foodGrid = Array(FOOD_WIDTH).fill().map(() => Array(FOOD_HEIGHT).fill(0.1));
let preys = [];
let predators = [];
let frameCount = 0;
function reset() {
foodGrid.forEach((row, x) => {
row.forEach((_, y) => {
foodGrid[x][y] = 0.1;
});
});
preys = [];
predators = [];
for (let i=0; i<50; i++) {
preys.push({
x: Math.random() * 800,
y: Math.random() * 600,
vx: (Math.random() * 2 - 1)*2,
vy: (Math.random() * 2 - 1)*2,
energy: 50
});
}
for (let i=0; i<10; i++) {
predators.push({
x: Math.random() * 800,
y: Math.random() * 600,
vx: 0,
vy: 0,
energy: 75
});
}
frameCount = 0;
}
function drawFood() {
ctx.fillStyle = '#008000';
for (let x=0; x<FOOD_WIDTH; x++) {
for (let y=0; y<FOOD_HEIGHT; y++) {
let food = foodGrid[x][y];
ctx.fillStyle = `rgb(0, ${food * 50}, 0)`;
ctx.fillRect(x*10, y*10, 10, 10);
}
}
}
function drawEntities() {
ctx.fillStyle = 'blue';
preys.forEach(p => {
ctx.beginPath();
ctx.arc(p.x, p.y, 2, 0, Math.PI*2);
ctx.fill();
});
ctx.fillStyle = 'red';
predators.forEach(pred => {
ctx.beginPath();
ctx.arc(pred.x, pred.y, 3, 0, Math.PI*2);
ctx.fill();
});
}
function updateFood() {
for (let x=0; x<FOOD_WIDTH; x++) {
for (let y=0; y<FOOD_HEIGHT; y++) {
foodGrid[x][y] = Math.min(MAX_FOOD, foodGrid[x][y] + FOOD_GROWTH_RATE);
}
}
}
function updatePrey() {
for (let i = preys.length - 1; i >= 0; i--) {
const p = preys[i];
const xCell = Math.floor(p.x / CELL_SIZE);
const yCell = Math.floor(p.y / CELL_SIZE);
let foodAtCell = foodGrid[xCell] ? foodGrid[xCell][yCell] : 0;
if (foodAtCell > 0) {
p.energy += 0.5;
foodGrid[xCell][yCell] = Math.max(0, foodAtCell - 0.5);
}
p.energy -= 0.1;
p.x += p.vx;
p.y += p.vy;
// Bounce off edges
if (p.x < 0 || p.x > 800) p.vx *= -0.5;
if (p.y < 0 || p.y > 600) p.vy *= -0.5;
// Random movement
p.vx += (Math.random() - 0.5) * 0.2;
p.vy += (Math.random() - 0.5) * 0.2;
// Reproduction
if (p.energy >= MAX_PREY_ENERGY) {
const newPrey = { ...p };
newPrey.x += Math.random() * 10 - 5;
newPrey.y += Math.random() * 10 - 5;
newPrey.energy = p.energy / 2;
preys.push(newPrey);
p.energy = newPrey.energy;
}
// Remove if energy too low
if (p.energy <= 0) {
preys.splice(i, 1);
}
}
}
function updatePredators() {
for (let i = predators.length - 1; i >= 0; i--) {
const pred = predators[i];
pred.energy -= 0.2;
let closestPrey = null;
let closestDist = Infinity;
for (const p of preys) {
const dx = p.x - pred.x;
const dy = p.y - pred.y;
const distSq = dx * dx + dy * dy;
if (distSq < closestDist && distSq < 2500) {
closestDist = distSq;
closestPrey = p;
}
}
if (closestPrey) {
const dx = closestPrey.x - pred.x;
const dy = closestPrey.y - pred.y;
const dist = Math.sqrt(closestDist);
pred.vx += dx / dist * 0.3;
pred.vy += dy / dist * 0.3;
if (dist < 5) {
pred.energy += closestPrey.energy;
const preyIndex = preys.indexOf(closestPrey);
if (preyIndex !== -1) {
preys.splice(preyIndex, 1);
}
}
} else {
pred.vx += (Math.random() - 0.5) * 0.2;
pred.vy += (Math.random() - 0.5) * 0.2;
}
pred.x += pred.vx;
pred.y += pred.vy;
// Bounce edges
if (pred.x < 0 || pred.x > 800) pred.vx *= -0.5;
if (pred.y < 0 || pred.y > 600) pred.vy *= -0.5;
// Reproduction
if (pred.energy >= MAX_PREDATOR_ENERGY) {
const newPred = { ...pred };
newPred.x += Math.random() * 10 - 5;
newPred.y += Math.random() * 10 - 5;
newPred.energy = pred.energy / 2;
predators.push(newPred);
pred.energy = newPred.energy;
}
// Remove if energy too low
if (pred.energy <= 0) {
predators.splice(i, 1);
}
}
}
function drawStats() {
ctx.fillStyle = 'white';
ctx.fillText(`Prey: ${preys.length} Predators: ${predators.length}`, 10, 20);
}
function animate() {
ctx.clearRect(0, 0, 800, 600);
updateFood();
updatePrey();
updatePredators();
drawFood();
drawEntities();
drawStats();
frameCount++;
if (frameCount > 5000) reset();
requestAnimationFrame(animate);
}
reset();
animate();
</script>
</body>
</html>
```
This should create an ecosystem simulation with prey and predators, food cells, and basic behavior like movement, energy consumption, reproduction, and predation. The stats are displayed in the top-left corner. Users can observe how the populations interact over time.